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:
- A Smart Grow Logs project.
- An API key.
- Your ingestion URL, for example
https://logs-api.smart-grow.app/.
Installation
Add the dependency:
dependencies:
smart_grow_logs_plugin: ^3.1.1
Requires a Smart Grow Logs server >= 5.2.0.
Then run:
flutter pub get
Quick Start
This is the recommended startup pattern for most Flutter apps:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SmartGrowLogs.initialize(
apiKey: 'sgl_your_api_key',
baseUrl: 'https://logs-api.smart-grow.app/',
);
runApp(const MyApp());
}
Desde Flutter 3.3 (el mínimo de este plugin) PlatformDispatcher.instance.onError captura los errores asíncronos no controlados de la zona raíz, así que no hace falta envolver main en una zona. runWithAutoCapture() sigue disponible para quien necesite una zona propia.
Common Use Cases
1. Send a Simple Log
await SmartGrowLogs.instance.info('User opened settings');
2. Send a Log With Metadata
metadata acepta un String (JSON o texto plano, tal cual) o cualquier otro objeto, que se serializa con jsonEncode (por ejemplo un Map):
await SmartGrowLogs.instance.warn(
'Payment retry scheduled',
metadata: {'orderId': 'ORD-123', 'retry': 2},
userIdentifier: 'user@example.com',
sessionId: 'session-abc-123',
);
3. Default User and Session
setUser/setSession fijan un userIdentifier/sessionId por defecto para todos los logs
(manuales y auto-capturados, incluidos los errores auto-capturados) que no pasen uno explícito en
la llamada. Un valor explícito en la llamada siempre gana; null borra el defecto. Se resuelven
antes de beforeSend, que puede verlos y modificarlos o quitarlos.
SmartGrowLogs.instance.setUser('user@example.com');
SmartGrowLogs.instance.setSession('session-456');
await SmartGrowLogs.instance.info('User opened profile'); // lleva userIdentifier y sessionId por defecto
await SmartGrowLogs.instance.warn(
'Impersonating support view',
userIdentifier: 'support-agent-1', // gana el explícito
);
// En logout:
SmartGrowLogs.instance.setUser(null);
SmartGrowLogs.instance.setSession(null);
4. Capture a Caught Exception
try {
await repository.sync();
} catch (error, stackTrace) {
await SmartGrowLogs.instance.errorWithException(
'Sync failed',
error,
stackTrace: stackTrace,
metadata: '{"feature":"sync"}',
);
}
5. 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}');
}
Cola offline y flush
Desde 3.0.0 todo log (manual o auto-capturado) se encola en la cola cifrada del SDK nativo y el core la entrega de forma asíncrona. Esto implica:
LogResponse.logIdes siemprenull: el id del servidor ya no se conoce de forma síncrona en la llamada.- Que
sendLog/info/error/... devuelvansuccess: truesignifica "aceptado en la cola local cifrada", no "entregado". - Llama a
flush()para drenar la cola de inmediato y esperar (del lado nativo) hasta un timeout, por ejemplo antes de que la app pase a segundo plano:
final drained = await SmartGrowLogs.instance.flush(timeoutMs: 5000);
Devuelve si no quedó nada pendiente.
Sobre v2: contra un servidor ≥ 5.3.0 el core negocia automáticamente el sobre v2 (deflate + AES-GCM + base64) para la cola; contra 5.2.x sigue usando el sobre v1. No requiere ningún cambio en el código del SDK: la negociación es transparente.
minLevel
Pasa minLevel a initialize para descartar en el core nativo, antes de tocar disco, los eventos por debajo de un umbral:
await SmartGrowLogs.initialize(
apiKey: 'sgl_your_api_key',
baseUrl: 'https://logs-api.smart-grow.app/',
minLevel: LogLevel.info,
);
Default: LogLevel.debug (no filtra nada).
beforeSend
beforeSend corre en Dart, una vez por evento, en todos los caminos de envío: llamadas manuales (info, error, sendLog, ...) y los cuatro mecanismos de auto-captura (FlutterError.onError, PlatformDispatcher.onError, runWithAutoCapture, errores de isolate). Devuelve el evento modificado o sin modificar, o null para descartarlo antes de que llegue a la cola nativa:
await SmartGrowLogs.initialize(
apiKey: 'sgl_your_api_key',
baseUrl: 'https://logs-api.smart-grow.app/',
beforeSend: (options) {
if (options.message.contains('password')) return null;
return options.copyWith(message: options.message);
},
);
Si beforeSend lanza una excepción, se atrapa y el evento se envía sin modificar. Nunca debe hacer trabajo de red ni bloquear.
Nota: los crashes nativos capturados directamente por los SDKs nativos de Android/iOS (excepción JVM no controlada, NSException, ANR, MetricKit) no pasan por este beforeSend de Dart; solo lo hacen los eventos generados desde Dart.
Nota: si llamas a initialize más de una vez, las opciones nativas (minLevel, androidAnrWatchdogEnabled/androidAnrTimeoutMs, extraCaPem) se fijan en la primera llamada y no se reenvían al canal en llamadas posteriores; beforeSend y autoCaptureEnabled sí se actualizan en cada llamada.
Auto-Capture
When autoCaptureEnabled is true, the plugin installs these mechanisms automatically:
FlutterError.onErrorfor framework and rendering errorsPlatformDispatcher.instance.onErrorfor uncaught main-isolate runtime errorsrunWithAutoCapture()for async errors inside the guarded zone
Cobertura de auto-captura
All auto-captured errors from Dart mechanisms are sent at fatal level, and every one of them (Dart and native) carries autoCaptured: true.
| Fallo | Dart | Android 8-10 | Android 11 | Android 12+ | iOS 14+ |
|---|---|---|---|---|---|
| Error de framework / async no controlado | Sí | — | — | — | — |
| Error en aislado secundario | Con createIsolateErrorListener |
— | — | — | — |
Excepción JVM / NSException no controlada |
— | Sí | Sí | Sí | Sí |
| ANR / hang del hilo principal | — | Watchdog opt-in | Sí (sistema) + watchdog | Sí (sistema) + watchdog | Sí (MetricKit) |
| Crash nativo (engine, plugins, Rust) | — | No | Razón y señal | Razón y señal | Sí (MetricKit, sin simbolicar) |
| Muerte por memoria | — | No | Sí | Sí | Sí (MetricKit) |
Crash antes de runApp |
— | Sí (instalación temprana del plugin) | Sí | Sí | Sí |
Instalación temprana
El plugin instala los handlers nativos de auto-captura al registrarse en el engine de Flutter, antes de que se ejecute main(). Esto es lo que permite cubrir crashes previos a runApp en la tabla de arriba.
Se puede desactivar:
- Android:
<meta-data android:name="app.smart_grow.logs.AUTO_CAPTURE_EARLY" android:value="false"/>en el manifest. - iOS: clave
SmartGrowLogsAutoCaptureEarlycon valorNOenInfo.plist.
Lo que sigue sin cubrirse son los crashes que ocurren en Application.onCreate (Android) o AppDelegate (iOS) antes de que el engine registre este plugin. Para ese caso extremo, el integrador puede llamar directamente a installCrashMarkersEarly (Android) o installAutoCaptureEarly (iOS) desde su propio código nativo, lo antes posible en el arranque.
Throttle
Los eventos automáticos pasan por el mismo throttle del core nativo que los logs manuales: si un mismo tipo de fallo se repite en un bucle, el SDK limita cuántos envía en lugar de saturar la red o el backend.
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.instanceSmartGrowLogs.instance.setUser(identifier)/SmartGrowLogs.instance.setSession(id): defectos deuserIdentifier/sessionIdpara todos los logsSmartGrowLogs.instance.flush({timeoutMs})SmartGrowLogs.runWithAutoCapture(...)SmartGrowLogs.createIsolateErrorListener(...)SmartGrowLogs.attachCurrentIsolateToAutoCapture(...)
Main send methods (todos aceptan metadata como String o cualquier objeto serializable con jsonEncode, más userIdentifier y sessionId):
debug(...)(logDebug(...)queda como alias@Deprecated)info(...)warn(...)error(...)errorWithException(...)fatal(...)sendLog(LogOptions(...))
Response object:
success: whether the log was accepted into the local encrypted queuelogId: alwaysnullsince 3.0.0 (delivery is asynchronous, handled by the native core's queue)error: failure message when not successful
Cada log lleva clientTimestamp (UTC, RFC 3339) marcado por Dart en el momento del evento.
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:
- create the listener on the main isolate
- pass its
sendPorttoIsolate.spawn - 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.
La UI se congelaba al enviar logs en iOS (2.0.x)
Resuelto en 2.1.0: el plugin envía desde una cola en segundo plano en lugar de bloquear el hilo principal.
Requirements
- Flutter 3.3.0+
- Dart 3.0.0+
- iOS 15.0+
- Android API 26+
Este plugin cubre Android e iOS. Flutter Web: usa @smart-dev-agency/smart-grow-logs (el paquete npm) directamente.
License
Proprietary - Smart Dev Agency