Flutter
evolve_logs is the Flutter SDK of the Observer family. Flutter is a wrapper, not a port: the Dart side holds no queue and no transport. It captures errors, spans and logs in Dart and forwards them over the platform channel to the native Observer client — Android: io.e-volv:logs-android (see the Android page), iOS: EvolveLogs (see the iOS page) — which owns the disk-backed queue, the retry schedule and the lifecycle-driven flush. One transport per device: a native crash is caught even when the Dart engine is gone, and Dart events survive process death in the native queue.
Install
A federated plugin, four packages:
dependencies: evolve_logs: ^0.1.0
evolve_logs is the app-facing API (error capture, trace context, Dio/package:http helpers); evolve_logs_platform_interface holds the channel method map and the default method-channel implementation; evolve_logs_android and evolve_logs_ios are the endorsed implementations bridging to the native clients. Adding evolve_logs is enough — pub resolves the rest.
The native clients are not on Maven Central / CocoaPods yet, so until they are, apply them from source: in android/app/build.gradle, implementation project(':logs-android') and implementation project(':evolve_logs_android') (minSdk 21); in the Podfile, pod 'EvolveLogs', path: '../packages/logs-swift' and pod 'evolve_logs_ios', path: '../packages/logs-flutter/evolve_logs_ios', then pod install (iOS 12+ deployment target). The Dart package covers the channel side once the native SDK is on the classpath — the plugin registers itself.
Initialise
import 'package:evolve_logs/evolve_logs.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await EvolveLogs.initialize(
key: 'evk_pub_…', // public key — see "Key kinds"
appId: 'com.acme.app', // Android application id / iOS bundle id
environment: 'production',
release: '1.2.3',
);
runApp(const MyApp());
}
Future<void> checkout() async {
EvolveLogs.info('order created', attrs: {'orderId': 'o_1', 'total': 42.5});
try {
await charge();
} catch (e, st) {
EvolveLogs.exception(e, st); // exception.type / message / stack
}
// A span: ends failed when fn throws, completed otherwise.
final rows = await EvolveLogs.span(
'db.query',
() => db.query('SELECT …'),
attrs: {'table': 'orders'},
);
}initialize installs the error handlers (FlutterError.onError, PlatformDispatcher.onError), both chaining to any handler you had installed before. Errors raised inside a zone you guard yourself reach the SDK too when you run the app body in runZonedGuarded:
runZonedGuarded(() => runApp(const MyApp()), (error, stack) {
EvolveLogs.exception(error, stack);
});The Dart stack is preserved as a string (attrs.exception.stack) across the channel, so error groups show real frames — upload your symbols (below) so release builds deobfuscate.
Key kinds
EvolveLogs.initialize requires an evk_pub_… public key and an app id, and refuses server keys (prefix check, ArgumentError) — a server key has no app-id allowlist and no per-install quota, so it must never ship in a binary. The native client checks again; a mobile SDK never ships a server-key path. The app id is sent by the native transport as x-evolve-app-id and checked against the key’s allowlist; the per-key and per-install quotas are what bound abuse of an extracted key.
Trace propagation
EvolveLogs.traceparent() returns the W3C header of the ambient trace — Dart keeps trace context in a Zone, so it flows across await. Native HTTP clients (OkHttp, URLSession) are intercepted in the native client; the channel keeps the native side synced via startSpan / endSpan / setTraceparent. For requests that stay inside the Dart engine:
// Dio final dio = Dio()..interceptors.add(EvolveLogsDioInterceptor()); // package:http final client = EvolveLogsHttpClient();
Both inject traceparent only inside a trace and never overwrite an existing header. Both live in optional entrypoints (package:evolve_logs/dio.dart, package:evolve_logs/http.dart) — add dio / http to your own pubspec and import only what you use. Queue hops, e.g. a background job started from a traced request:
// producer: send EvolveLogs.traceparent() with the job
// consumer:
EvolveLogs.runWithTraceparent(job.traceparent, () async {
EvolveLogs.info('job received', attrs: {'jobId': job.id});
await EvolveLogs.span('queue.work', () => handle(job));
});Release builds and symbols
Ship symbols for every release or Observer shows obfuscated frames:
# Android and iOS, one directory per ABI/platform flutter build apk --release --obfuscate --split-debug-info=symbols flutter build ipa --release --obfuscate --split-debug-info=symbols # Upload them in the release pipeline (server key, from CI): npx @evolve/logs-cli upload-artifacts --key evk_… --url https://api.e-volv.io \ --release 1.2.3 --platform flutter --type dart-symbols \ symbols/app.android-arm64.symbols
--type dart-symbols is the Flutter --split-debug-info bundle; proguard and dsym cover the native Android/iOS stacks of the same app.
Limitations
Zone-bound trace context. It flows across await inside the zone but not into other isolates; pass traceparent() explicitly across isolates and use runWithTraceparent on the far side. Native crashes, ANRs and lifecycle flush belong to the native clients; this package only bridges them. Dropped/retry accounting lives natively: EvolveLogs.droppedCount() asks the native client, and the Dart side holds no buffer of its own. No web/desktop support — there is no native client for those platforms and the architecture deliberately gives Dart no transport. Trace/fatal log levels have no Dart API (debug/info/warn/error are the surface).
Status
Built to the pinned wire contract but not yet verified: no Dart/Flutter toolchain was available where this was written, so run flutter pub get && dart analyze per package before trusting it. The Kotlin/Swift bridges are written against the native clients’ interfaces and need a first integration build on a machine with the Android SDK and Xcode. The conformance runner lives at evolve_logs/tool/conformance_runner.dart and is pending emulator/device CI.
Package reference: packages/logs-flutter. Back to SDK overview.