initialize static method

Future<void> initialize({
  1. required InitializationParams params,
})

Boots the SDK: initialises the native layer, optionally installs automatic error handlers / HTTP overrides / jank monitoring per InitializationParams.instrumentations, and registers the global tracer provider (routing spans through the native bridge).

Implementation

static Future<void> initialize({required InitializationParams params}) async {
  // Startup milestones stamped onto the native cold-start `app.start` span.
  // Both fire well before the first frame, so the span is still open on every
  // platform; native drops anything that arrives late.
  //
  // Deliberately two milestones rather than a bracket around initialize: the
  // elapsed time between them is dominated by the platform-channel call
  // waiting for a busy main thread (the native handler itself runs in ~1ms),
  // so framing it as "SDK init duration" would misattribute platform
  // contention to this SDK.
  //
  // `flutter.dart_entry` assumes initialize() is the first statement in
  // main(); app work before it makes the marker late and inflates what looks
  // like engine-boot time.
  final dartEntryMs = DateTime.now().millisecondsSinceEpoch;

  _initializationParams = params;
  configureSdkLogging(params.logLevel);
  _log.info(
    'Initializing vuTelemetry Flutter SDK for app "${params.appName}" '
    '(${params.buildType}) with log level ${params.logLevel.name}.',
  );

  HttpInstrumentationSettings.update(
    captureRequestHeaders: params.captureRequestHeaders,
    captureResponseHeaders: params.captureResponseHeaders,
    peerServiceByHost: params.peerServiceByHost,
  );
  _log.fine(
    'Updated HTTP instrumentation settings '
    '(requestHeaders=${params.captureRequestHeaders.length}, '
    'responseHeaders=${params.captureResponseHeaders.length}, '
    'peerServiceOverrides=${params.peerServiceByHost.length}).',
  );

  // Initialize the Native OpenTelemetry SDK
  await OpentelemetryPlatform.instance.initialize(params);
  _log.info('Native telemetry layer initialized.');

  // Build mode, so a debug startup number is never compared blind against a
  // release one. Written twice on purpose: the global set covers every span
  // created from now on, while app.start needs it written directly — native
  // appends globals when a span starts, and app.start started at process
  // creation, before any Dart ran.
  final dartMode =
      kReleaseMode
          ? 'release'
          : kProfileMode
          ? 'profile'
          : 'debug';
  unawaited(
    OpentelemetryPlatform.instance
        .setGlobalAttributes({'dart.mode': dartMode})
        .catchError((_) {}),
  );
  unawaited(
    OpentelemetryPlatform.instance
        .recordAppStartAttribute('dart.mode', dartMode)
        .catchError((_) {}),
  );

  final gates = params.instrumentations;
  _log.info(
    'Auto-instrumentation: errors=${gates.errors}, http=${gates.http}, '
    'jankDetection=${gates.jankDetection}.',
  );

  if (gates.errors) {
    final platformOriginalOnError = PlatformDispatcher.instance.onError;

    // Set the error handler to capture errors
    PlatformDispatcher.instance.onError = (e, st) {
      _log.warning(
        'Captured platform error; recording telemetry span and flushing.',
        e,
        st,
      );
      recordFlutterErrorSpan(
        category: FlutterErrorCategory.platform,
        error: e,
        stackTrace: st,
      );
      // The isolate may die after this handler: push the error span (and
      // anything else buffered) through to the native disk buffer now.
      unawaited(flushSpans());

      return platformOriginalOnError?.call(e, st) ?? false;
    };

    FlutterError.onError = (FlutterErrorDetails details) async {
      FlutterError.dumpErrorToConsole(details);
      _log.warning(
        'Captured Flutter UI error; recording telemetry span and flushing.',
        details.exception,
        details.stack,
      );

      recordFlutterErrorSpan(
        category: FlutterErrorCategory.ui,
        error: details.exception,
        stackTrace: details.stack ?? StackTrace.current,
      );

      // The isolate may die after this handler: make the error span durable.
      await flushSpans();
    };
  } else {
    _log.fine('Skipping automatic Flutter and platform error handlers.');
  }

  // Route Dart-collected spans through the native layer, which owns the
  // single telemetry egress (OTLP export + OTel disk-buffered persistence).
  // Dart remains the collector; the native side is the only exit door.
  // Spans are pushed eagerly at onEnd over a background-TaskQueue channel;
  // native micro-batches them and owns durability (500ms disk batcher +
  // crash-time drain).
  final bridgeProcessor = NativeBridgeSpanProcessor();
  _bridgeProcessor = bridgeProcessor;

  // The resource (incl. service.name) is owned by the native layer and applied
  // to bridged spans there, so the Dart-side resource is intentionally empty.
  final provider = TracerProviderBase(
    processors: [ScreenNameSpanProcessor(), bridgeProcessor],
    resource: Resource([]),
  );

  registerGlobalTracerProvider(provider);
  _log.info('Registered global tracer provider and native bridge processor.');

  // Sweep the pipeline to disk when the app leaves the foreground — the
  // cheapest cover for background kills and swipe-away terminations.
  WidgetsBinding.instance.addObserver(_BridgeFlushLifecycleObserver());

  if (gates.http) {
    TrackedHttpOverrides.install(
      connectionFactory: params.httpConnectionFactory,
    );
    _log.info('Installed TrackedHttpOverrides for dart:io HTTP.');
  } else {
    _log.fine(
      'HTTP auto-instrumentation disabled; not installing overrides.',
    );
  }

  if (gates.jankDetection) {
    startFrameMonitoring(
      globalTracerProvider.getTracer(
        InstrumentationScopes.frames,
        version: pluginVersion,
      ),
    );
    _log.info('Started frame jank monitoring.');
  } else {
    _log.fine('Jank detection disabled; not starting frame monitoring.');
  }

  VuAction.enableRecording();
  VuErrorApi.enableRecording();
  _log.fine('Enabled custom action and custom error recording APIs.');

  // Close the native app.start span at first frame. The native SDK creates
  // this span at process launch but keeps it open until Time-To-Full-Display
  // is marked — a signal it derives from UIKit view-controller / CADisplayLink
  // events that never fire for Flutter's engine-rendered UI. Marking it from
  // Dart's first frame is what makes the app.start event get emitted (instead
  // of only closing via the native 10s fallback timeout).
  //
  // Rasterized, not post-frame: addPostFrameCallback fires when the frame has
  // been built and handed to the rasterizer, ~21ms (one frame at 60Hz, measured
  // on Android) before it is actually on screen. On iOS this timestamp *is* the
  // app.start end, so the earlier signal under-reports time-to-initial-display.
  //
  // The event timestamp is FramePhase.rasterFinishWallTime, not
  // DateTime.now() of this callback.
  //
  // Only armed ahead of the first frame. Registered after that frame has
  // already rasterized (a late `initialize()` call), the callback would be
  // satisfied by whatever frame reports *next* — any animation or rebuild
  // within the timeout below — and stamp flutter.first_frame with that
  // later frame's timing instead of falling back to now.
  Completer<FrameTiming?>? firstTiming;
  void Function(List<FrameTiming>)? onFirstTimings;
  if (!WidgetsBinding.instance.firstFrameRasterized) {
    final completer = Completer<FrameTiming?>();
    firstTiming = completer;
    onFirstTimings = (timings) {
      if (timings.isEmpty || completer.isCompleted) {
        return;
      }
      completer.complete(timings.first);
    };
    WidgetsBinding.instance.addTimingsCallback(onFirstTimings);
  }

  _isInitialised = true;

  // Stamped here rather than batched to first frame: on Android the app.start
  // span closes at the first committed frame, so a deferred flush would arrive
  // after the span it targets had already ended.
  final platform = OpentelemetryPlatform.instance;
  await platform
      .recordAppStartEvent('flutter.dart_entry', dartEntryMs)
      .catchError((_) {});
  await platform
      .recordAppStartEvent(
        'flutter.native_bridge.ready',
        DateTime.now().millisecondsSinceEpoch,
      )
      .catchError((_) {});

  // Baseline for stampFlutterFirstFrame's drop-detection diff, captured here
  // rather than on the frame-rasterized path: a pre-write read there was a
  // full platform-channel round trip inserted ahead of the flutter.first_frame
  // write, spending the very headroom this measurement exists to protect (and
  // making the write's own drop more likely). droppedAppStartWrites is
  // cumulative since process start, and nothing else writes to app.start
  // between this point and that write, so this value is still a valid
  // "before" reading.
  final droppedBaseline = await platform.droppedAppStartWrites().catchError(
    (_) => 0,
  );

  final timingsCallback = onFirstTimings;
  unawaited(
    WidgetsBinding.instance.waitUntilFirstFrameRasterized.then((_) async {
      final timing =
          firstTiming == null
              ? null
              : await firstTiming.future.timeout(
                const Duration(seconds: 2),
                onTimeout: () => null,
              );
      await stampFlutterFirstFrame(timing, droppedBefore: droppedBaseline);
      if (timingsCallback != null) {
        WidgetsBinding.instance.removeTimingsCallback(timingsCallback);
      }
    }),
  );

  _log.info('vuTelemetry Flutter SDK initialization completed.');
}