Smart Grow Logs Plugin for Flutter

Smart Grow Logs for Flutter sends encrypted logs from your app to Smart Grow Logs, with optional automatic capture of uncaught errors.

What This Plugin Is For

Use this plugin if you want to:

  • send debug, info, warn, error and fatal logs from Flutter
  • attach metadata such as user, session or business context
  • capture uncaught Flutter errors automatically
  • capture async errors launched in a guarded zone
  • capture failures from secondary isolates when you wire them explicitly

Before You Start

You need:

  1. A Smart Grow Logs project.
  2. An API key.
  3. Your ingestion URL, for example https://logs-api.smart-grow.app/.

Installation

Add the dependency:

dependencies:
  smart_grow_logs_plugin: ^2.0.1

Then run:

flutter pub get

Quick Start

This is the recommended startup pattern for most Flutter apps:

import 'package:flutter/widgets.dart';
import 'package:smart_grow_logs_plugin/smart_grow_logs_plugin.dart';

Future<void> main() async {
  SmartGrowLogs.runWithAutoCapture<void>(() async {
    WidgetsFlutterBinding.ensureInitialized();

    await SmartGrowLogs.initialize(
      apiKey: 'sgl_your_api_key',
      baseUrl: 'https://logs-api.smart-grow.app/',
      autoCaptureEnabled: true,
      androidAnrWatchdogEnabled: true,
      androidAnrTimeoutMs: 5000,
    );

    runApp(const MyApp());
  });
}

Why this exact order:

  • WidgetsFlutterBinding.ensureInitialized() and runApp() must run in the same zone in debug mode.
  • runWithAutoCapture() ensures uncaught async errors in that zone are reported.
  • initialize() must be called once before sending logs.

Common Use Cases

1. Send a Simple Log

await SmartGrowLogs.instance.info('User opened settings');

2. Send a Log With Metadata

await SmartGrowLogs.instance.warn(
  'Payment retry scheduled',
  metadata: '{"orderId":"ORD-123","retry":2}',
  userIdentifier: 'user@example.com',
  sessionId: 'session-abc-123',
);

3. Capture a Caught Exception

try {
  await repository.sync();
} catch (error, stackTrace) {
  await SmartGrowLogs.instance.errorWithException(
    'Sync failed',
    error,
    stackTrace: stackTrace,
    metadata: '{"feature":"sync"}',
  );
}

4. Send a Full Custom Log

final response = await SmartGrowLogs.instance.sendLog(
  LogOptions(
    level: LogLevel.error,
    message: 'Checkout failed',
    stackTrace: StackTrace.current.toString(),
    metadata: '{"cartId":"CART-42","step":"payment"}',
    userIdentifier: 'user@example.com',
    sessionId: 'session-abc-123',
  ),
);

if (!response.success) {
  debugPrint('Log failed: ${response.error}');
}

Auto-Capture

When autoCaptureEnabled is true, the plugin installs these mechanisms automatically:

  • FlutterError.onError for framework and rendering errors
  • PlatformDispatcher.instance.onError for uncaught main-isolate runtime errors
  • runWithAutoCapture() for async errors inside the guarded zone

What Is Captured Today

All auto-captured errors are sent at fatal level.

  • unhandled Flutter framework errors
  • unhandled runtime errors on the main isolate
  • unhandled async errors inside runWithAutoCapture()
  • secondary-isolate errors when you wire them explicitly
  • Android main-thread ANR watchdog when enabled
  • iOS uncaught NSException reported on the next app launch

What Is Not Captured Automatically Yet

  • native crash signals
  • every possible failure from child isolates unless you attach them explicitly

Disable Auto-Capture

Disable it if your app already owns global error handling and you want to forward failures manually:

await SmartGrowLogs.initialize(
  apiKey: 'sgl_your_api_key',
  baseUrl: 'https://logs-api.smart-grow.app/',
  autoCaptureEnabled: false,
);

Secondary Isolates

If your app uses Isolate.spawn, create one listener on the main isolate and pass its sendPort to child isolates.

final isolateErrorPort = SmartGrowLogs.createIsolateErrorListener(
  mechanism: 'MyBackgroundIsolate',
);

await Isolate.spawn<SendPort>(
  backgroundEntryPoint,
  isolateErrorPort.sendPort,
  onError: isolateErrorPort.sendPort,
  errorsAreFatal: true,
);

void backgroundEntryPoint(SendPort errorPort) {
  SmartGrowLogs.attachCurrentIsolateToAutoCapture(errorPort);
  throw StateError('Background isolate failure');
}

When that listener is no longer needed:

isolateErrorPort.close();

API Essentials

Main entry points:

  • SmartGrowLogs.initialize(...)
  • SmartGrowLogs.instance
  • SmartGrowLogs.runWithAutoCapture(...)
  • SmartGrowLogs.createIsolateErrorListener(...)
  • SmartGrowLogs.attachCurrentIsolateToAutoCapture(...)

Main send methods:

  • logDebug(...)
  • info(...)
  • warn(...)
  • error(...)
  • errorWithException(...)
  • fatal(...)
  • sendLog(LogOptions(...))

Response object:

  • success: whether the log was accepted locally for delivery
  • logId: server log id when successful
  • error: failure message when not successful

Performance Recommendation

Logging does network work. If you do not need to wait for the result, prefer non-blocking usage:

unawaited(SmartGrowLogs.instance.info('Screen opened'));

Use await only when you need to inspect success, logId or error.

Troubleshooting

I get a zone error when starting the app

Make sure WidgetsFlutterBinding.ensureInitialized() and runApp() are both inside SmartGrowLogs.runWithAutoCapture(...).

My child isolate errors are not showing up

Make sure you:

  1. create the listener on the main isolate
  2. pass its sendPort to Isolate.spawn
  3. call attachCurrentIsolateToAutoCapture(sendPort) inside the child isolate

iOS crashes appear on the next launch

That is expected for uncaught NSException. The plugin persists a crash marker and retries delivery on the next successful startup.

Requirements

  • Flutter 3.3.0+
  • Dart 3.0.0+
  • iOS 15.0+
  • Android API 26+

License

Proprietary - Smart Dev Agency