initialize static method
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.');
}