e-volv
Docs menu

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:

yaml
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

dart
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:

dart
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:

dart
// 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:

dart
// 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:

text
# 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.