flog_flow 0.4.6 copy "flog_flow: ^0.4.6" to clipboard
flog_flow: ^0.4.6 copied to clipboard

Graba el flujo de navegación real del usuario (pantallas, taps, red, logs y errores) y lo exporta en un archivo cifrado .flowx que se reproduce en un visor web.

FlogFlow

FlogFlow

Observabilidad para Flutter — Logs + Session Replay, en un solo SDK.

🌐 flog-flow.com — crea tu cuenta y obtén el projectToken de tu proyecto.


¿Qué es FlogFlow? #

FlogFlow te da dos herramientas de observabilidad para tu app Flutter con una sola dependencia. Las usas por separado o juntas:

  • Logs — logs estructurados (niveles, atributos, stack trace y hasta un screenshot del momento del fallo) que llegan a tu dashboard en la nube. Ligero: no captura pantallas ni gestos, solo envía los logs que tú emites.
  • Session Replay — graba el recorrido real del usuario (pantallas como screenshots, taps, scrolls, navegaciones, red, logs y errores) y lo reproduces paso a paso en un visor web. Puede subirse a tu dashboard en la nube o quedarse 100 % local en un archivo cifrado .flowx.
   [Pantalla]  ──"User clicks 'Sign in'"──▶  [Pantalla]  ──▶  [Pantalla]
      •tap                                       •tap            ⚠ error

Instalas el paquete, pegas el token de tu proyecto (o ni eso, en modo local) y los datos empiezan a llegar. Sin agentes, sin configuración complicada. Todo lo costoso es opt-in, así que no afecta el rendimiento salvo que tú lo actives.


Las 3 formas de integrarlo #

¿De dónde sale el projectToken? Entra a flog-flow.com, crea tu cuenta (con Google) y un proyecto: ahí obtienes tu token flw_…, que pegas en el SDK. El modo local (C) no necesita cuenta ni token.

Elige según lo que necesites. Todas parten de instalar el paquete:

# pubspec.yaml
dependencies:
  flog_flow: ^0.4.6
flutter pub get
import 'package:flog_flow/flog_flow.dart';

A · Solo Logs — lo más simple para empezar #

Un init al arrancar y una línea por log. Ideal si solo quieres ver qué pasa en producción sin grabar sesiones.

await FlogLogs.init(
  projectToken: 'flw_xxxxxxxxxxxxxxxx',   // te lo da tu dashboard
  service: 'mi-app',
  env: 'prod',
  version: '1.4.2',
);

FlogLogs.info('sesión iniciada', logname: 'auth', attributes: {'userId': 'u_123'});
FlogLogs.warn('token por expirar', logname: 'auth');
FlogLogs.error('pago rechazado', logname: 'payments', error: e, stackTrace: st, screenshot: true);

→ Los ves en la pestaña Logs del dashboard: filtros por nivel y logname, búsqueda, stacktrace, atributos y screenshot.

Pestaña Logs del dashboard: niveles, logname, versión y mensaje de cada log

B · Session Replay en la nube — graba y sube con un token #

Envuelve tu MaterialApp y arranca con FlogCloud.start. Cada sesión se sube sola (al pasar a segundo plano, al cerrar o ante un crash) y aparece en tu dashboard, lista para reproducir.

MaterialApp(
  navigatorObservers: [FlogFlow.instance.navigatorObserver],   // 1) observa la navegación
  builder: (context, child) => FlogFlogScope(child: child!),   // 2) captura taps + screenshots
  // ...tus rutas / home
);

// 3) una sola llamada: graba + sube automáticamente
await FlogCloud.start(
  projectToken: 'flw_xxxxxxxxxxxxxxxx',
  encryptionPassphrase: 'CLAVE-DEL-PROYECTO',   // el archivo viaja siempre cifrado
  appName: 'MiApp',
  appVersion: '1.4.2',
);

→ Las sesiones aparecen en el dashboard y se abren con un clic, sin descargar nada.

Visor de sesiones: mapa del recorrido con pantallas, taps numerados y navegación

⚠️ ¿No ves las llamadas a tu backend en la sesión? #

La captura de red NO viene activada por defecto (privacidad y rendimiento) y necesita DOS cosas a la vez — con solo una, no se graba nada:

// 1) el interceptor en tu cliente Dio:
dio.interceptors.add(FlogDioInterceptor());

// 2) el toggle en el config de FlogCloud.start:
config: const CaptureConfig(captureNetwork: true),

Desde 0.4.1 la consola avisa en debug si el interceptor recibe tráfico con el toggle apagado. Si usas el paquete http en vez de Dio, el interceptor no aplica: registra las peticiones clave con FlogFlow.instance.recordEvent(type: 'network', …).

C · Modo local — gratis, sin cuenta ni token #

El mismo wiring de MaterialApp, pero con FlogFlow.instance.start. Exportas un archivo cifrado .flowx y lo abres tú mismo en el visor web. Perfecto para probar la librería o depurar sin contratar nada.

MaterialApp(
  navigatorObservers: [FlogFlow.instance.navigatorObserver],
  builder: (context, child) => FlogFlogScope(child: child!),
);

await FlogFlow.instance.start(
  appName: 'MiApp',
  config: const CaptureConfig(encryptionPassphrase: 'CAMBIA-ESTA-CLAVE'),
);

// cuando quieras el archivo:
final file = await FlogFlow.instance.export();   // .flowx cifrado
await FlogFlow.instance.stop();

→ Sube el .flowx a el visor web, escribe la clave y reproduce la sesión. No se instala nada.

Visor web: arrastra tu archivo .flowx, escribe tu clave y reproduce la sesión

Los tres modos conviven. Lo más común es Logs (A) + Session Replay (B) juntos, compartiendo el mismo token de proyecto.


Dónde va cada cosa (evita los tropiezos comunes) #

Estas cuatro reglas resuelven casi todos los problemas de integración que nos han reportado:

1. Llama a start DESPUÉS de runApp, sin bloquear el arranque. FlogCloud.start ya no espera a las subidas pendientes (se drenan en segundo plano), pero igual lo correcto es arrancarlo tras el primer frame:

void main() {
  runApp(const MyApp());
  WidgetsBinding.instance.addPostFrameCallback((_) {
    FlogCloud.start(
      projectToken: 'flw_…',
      encryptionPassphrase: const String.fromEnvironment('FLOG_KEY'),
      appName: 'MiApp',
      initialRouteName: '/splash',  // nombre de la primera pantalla
    );
  });
}

2. Un observer POR CADA Navigator. Flutter no permite compartir un NavigatorObserver entre dos Navigators (falla con una aserción del framework que no menciona a flog_flow). Si tu app tiene más de uno — el caso típico: un MaterialApp anidado dentro de un GetMaterialApp, o Navigators propios — crea uno nuevo por cada uno:

GetMaterialApp(navigatorObservers: [FlogFlow.instance.newNavigatorObserver()]);
// y en el MaterialApp interno:
MaterialApp(navigatorObservers: [FlogFlow.instance.newNavigatorObserver()]);

Con un solo Navigator, FlogFlow.instance.navigatorObserver sigue siendo válido. Ojo con GetX: Get.context apunta al navigator del GetMaterialApp, no al del MaterialApp que tengas debajo.

3. Un solo FlogFlogScope en todo el árbol (el más externo, normalmente en el builder: del app raíz). Dos scopes anidados se pisan: los screenshots salen del RepaintBoundary equivocado sin ningún error. Desde 0.4.0 la consola avisa en debug si montas más de uno.

4. No compiles la passphrase en el código. Esa clave descifra las sesiones — y con replay en producción, las sesiones son screenshots de usuarios reales. Pásala por entorno y obténla de tu backend/remote config en release:

flutter run --dart-define=FLOG_KEY=una-clave-larga-y-secreta
encryptionPassphrase: const String.fromEnvironment('FLOG_KEY'),

Rutas sin nombre (MaterialPageRoute anónimos): el grafo sale sin nombres de pantalla. Dales nombre con RouteSettings(name: …) o define un resolver global:

FlogFlow.instance.screenNameResolver =
    (route) => route.settings.name ?? route.runtimeType.toString();

5. La red NO se captura sola (es opt-in por privacidad y rendimiento). Necesitas las dos cosas — el interceptor en tu cliente Dio Y el toggle en el config:

dio.interceptors.add(FlogDioInterceptor());

FlogCloud.start(
  // …,
  config: const CaptureConfig(captureNetwork: true),
);

Con solo una de las dos no se graba nada (desde 0.4.1 la consola avisa en debug si el interceptor recibe tráfico con el toggle apagado). Si tu app usa el paquete http en vez de Dio, el interceptor no aplica: registra las peticiones clave a mano con FlogFlow.instance.recordEvent(type: 'network', …).

6. Logs ↔ sesión se vinculan solos… si ambos corren. Cuando FlogLogs y la grabación (FlogCloud.start) están activos en la misma app, cada log lleva el id de la sesión en curso y el dashboard permite saltar de un log a su sesión y de una sesión a sus logs. Si solo usas FlogLogs (sin replay), los logs llegan igual pero sin sesión que enlazar.



Referencia detallada #

Plataformas #

El grabador usa dart:io + path_provider, así que corre en Android, iOS, macOS, Windows y Linux. Flutter web no es destino de grabación (la visualización se hace en el visor web publicado, aparte).


Uso rápido de Session Replay (3 pasos) #

import 'package:flog_flow/flog_flow.dart';

MaterialApp(
  // 1) observa la navegación
  navigatorObservers: [FlogFlow.instance.navigatorObserver],
  // 2) envuelve la app para capturar taps + screenshots
  builder: (context, child) => FlogFlogScope(child: child!),
  // ...tus rutas / home
);

// 3) controla la grabación (el export siempre va cifrado → define una clave)
await FlogFlow.instance.start(
  appName: 'MiApp',
  config: const CaptureConfig(encryptionPassphrase: 'CAMBIA-ESTA-CLAVE'),
);

// (opcional) etiqueta la acción que dispara una transición, antes de navegar:
FlogFlow.instance.tag('User clicks "Sign in"');
Navigator.pushNamed(context, '/password');

// cuando quieras el archivo cifrado (.flowx):
final file = await FlogFlow.instance.export();
await FlogFlow.instance.stop();

Funciona igual con go_router (pasa FlogFlow.instance.navigatorObserver a GoRouter(observers: [...])) y con GetX (GetMaterialApp reenvía los navigatorObservers). Si no usas tag(...), la flecha usa el nombre de la ruta.


Integración completa (ejemplo) #

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

    // captura print/debugPrint (gated por captureLogs)
    FlogLogCapture.installDebugPrintCapture();

    runApp(const MyApp());

    // arranca la grabación tras el primer frame
    WidgetsBinding.instance.addPostFrameCallback((_) {
      FlogFlow.instance.start(
        appName: 'MiApp',
        appVersion: '1.4.2',
        build: '142',
        userId: 'u_123',                 // opcional; cuidado con PII
        config: const CaptureConfig(
          captureNetwork: true,          // registra HTTP (necesita el interceptor)
          captureLogs: true,             // registra print/debugPrint
          identifyTapTargets: true,      // etiqueta el widget tocado
          encryptionPassphrase: 'CAMBIA-ESTA-CLAVE', // export cifrado .flowx
        ),
      );
    });
  }, (error, stack) {
    FlogFlow.instance.recordError(error, stackTrace: stack, context: 'zona global');
  },
    zoneSpecification: FlogLogCapture.zoneSpec(),   // captura print() crudo
  );
}

// en tu cliente Dio:
dio.interceptors.add(FlogDioInterceptor());

// captura también los errores de widgets:
FlutterError.onError = (d) =>
    FlogFlow.instance.recordError(d.exception, stackTrace: d.stack, context: 'widget');

Referencia de CaptureConfig #

Todo lo que pueda pesar es un toggle. Los valores por defecto priorizan rendimiento y privacidad (lo costoso viene apagado).

Opción Tipo Default Qué hace
captureOnNavigation bool true Captura un screenshot al entrar a cada pantalla.
captureOnTap bool true Refresca la imagen de la pantalla tras un tap.
recordTaps bool true Registra taps/scrolls (si false, solo navegación).
recordScroll bool true Registra los scrolls (como flechas).
captureOnScroll bool false Re-captura la pantalla en cada scroll (pesado; riesgo OOM).
framePerInteraction bool false Guarda un screenshot por cada tap, congelado en el instante de la interacción (más disco).
scrollSlop double 18.0 Arrastre (dp) para clasificar scroll vs tap.
pixelRatio double 0.6 Escala del screenshot (↓ = más liviano).
jpegQuality int 45 Calidad JPEG (0–100).
maxLongestSide int 900 Tope del lado mayor (px); acota memoria.
minCaptureInterval Duration 350ms Debounce entre capturas.
navigationSettleDelay Duration 300ms Espera tras navegar antes de capturar (deja terminar la transición).
maxScreens int 400 Tope duro de pantallas por sesión.
captureNetwork bool false Registra peticiones HTTP (interceptor de Dio).
captureNetworkBodies bool false Incluye query + preview del body (puede traer PII).
captureLogs bool false Registra print/debugPrint.
recordBreadcrumbs bool true Habilita breadcrumb()/log() manuales.
identifyTapTargets bool false Guarda el texto del widget bajo el tap.
recordOnErrorOnly bool false Solo conserva/exporta si hubo un error.
collectSessionMetadata bool true Recoge locale, versión de SO, etc.
redactSensitive bool true Aplica los RedactedZone antes de capturar.
encryptionPassphrase String? null Clave para cifrar el export. Obligatoria: el export siempre va cifrado.
deleteAfterExport bool true Borra los datos del disco tras exportar.
cleanupOnStart bool true Borra sesiones viejas al iniciar.

Referencia de la API #

FlogFlow.instance #

Miembro Descripción
start({appName, config?, initialRouteName?, appVersion?, build?, userId?, metadata?}) Inicia una grabación.
stop() Cierra la grabación y la deja guardada.
export({outFile?, passphrase?, deleteAfterExport?}) → Future<File> Genera el archivo cifrado .flowx.
exportIfError({...}) → Future<File?> Exporta solo si hubo error; si no, descarta.
discard() Borra la sesión actual del disco.
clear() Borra todo lo de la librería en disco (actual + viejas).
tag(String label) Etiqueta la próxima transición de navegación.
recordError(error, {stackTrace?, context?}) Registra un error (mensaje + stack + screenshot).
breadcrumb(message, {data?}) Anota un breadcrumb en la línea de tiempo.
log(message, {level}) Registra un log manual.
recordEvent({type, message, level?, data?}) Agrega un evento genérico.
enableAutoUpload({upload, onAppClose?, onBackground?, onError?}) Sube el flujo al ir a background / cerrar / crashear (solo si hay contenido nuevo).
recoverPendingUploads({upload, passphrase}) → Future<int> Reenvía sesiones pendientes al arrancar.
currentSizeBytes() → Future<int> Tamaño en disco de la sesión actual.
navigatorObserver El NavigatorObserver a registrar.
isRecording / hadError Estado de la grabación.
onErrorRecorded Callback que se dispara al registrar un error.

Widgets y helpers #

API Descripción
FlogFlogScope(child:) Envuelve la app (captura taps + screenshots).
FlogDioInterceptor() Interceptor de Dio para registrar la red.
FlogLogCapture.installDebugPrintCapture() Captura debugPrint.
FlogLogCapture.zoneSpec() ZoneSpecification para capturar print().
RedactedZone(child:) Tapa contenido sensible en los screenshots.
FlogHttpUpload(url:, headers?, fields?) Subida HTTP lista para enableAutoUpload.

Guías por funcionalidad #

El navigatorObserver detecta push/pop/replace y crea un nodo por pantalla. Etiqueta la acción que dispara la navegación con tag(...) antes de navegar:

FlogFlow.instance.tag('Confirmar pedido');
Navigator.pushNamed(context, '/checkout');

Errores #

try {
  await checkout();
} catch (e, st) {
  FlogFlow.instance.recordError(e, stackTrace: st, context: 'checkout');
  rethrow;
}

Guarda mensaje + stack trace + un screenshot del estado. En el visor, las pantallas con error muestran un chip rojo ⚠ y puedes ver el stack completo. Funciona aunque recordTaps esté apagado.

FlogFlow.instance.breadcrumb('carrito actualizado', data: {'items': 3});
FlogFlow.instance.log('token por expirar', level: 'warn');

Para captura automática de logs:

FlogLogCapture.installDebugPrintCapture();                 // debugPrint + framework
runZonedGuarded(body, onError, zoneSpecification: FlogLogCapture.zoneSpec()); // print()
// y en el config: captureLogs: true

Captura de red (Dio) #

dio.interceptors.add(FlogDioInterceptor());
// config: captureNetwork: true  (opcional captureNetworkBodies: true)

Registra método host/path → status (ms). Por defecto NO guarda headers ni bodies para no filtrar tokens/PII; captureNetworkBodies agrega el query y un preview truncado del body.

Grabar solo si hay error #

CaptureConfig(recordOnErrorOnly: true);

FlogFlow.instance.onErrorRecorded = () async {
  final f = await FlogFlow.instance.exportIfError(); // null si no hubo error
  if (f != null) await subirAlServidor(f);
};

Grabas todo, pero el flujo solo se conserva si ocurrió un error; si no, se descarta. Ideal para dejarlo encendido sin acumular sesiones sanas.

Redacción de datos sensibles #

RedactedZone(child: Text(card.number));       // no aparece en las capturas
RedactedZone(child: TextField(obscureText: true));

El área se tapa de negro en el screenshot (la UI real no cambia). Requiere redactSensitive: true (por defecto).

Widget tocado #

Con identifyTapTargets: true, cada tap guarda el texto del widget bajo el dedo (ej. Add to bag) en target. Tiene un pequeño costo por tap (hit-test).

Metadata de sesión #

await FlogFlow.instance.start(
  appName: 'MiApp', appVersion: '1.4.2', build: '142', userId: 'u_123',
  metadata: {'flavor': 'prod'},
);

Lo demás (locale, versión de SO, tamaño de pantalla) se recoge solo.


Cifrado del export #

El export siempre va cifrado. Define una clave con encryptionPassphrase (o pásala a export()); el archivo .flowx que se genera solo se puede abrir en el visor introduciendo esa misma clave.

await FlogFlow.instance.export();                    // usa la clave del config
await FlogFlow.instance.export(passphrase: 'otra');  // o una clave por llamada

Guarda la clave en secreto y compártela solo con quien deba ver las grabaciones. Si no defines ninguna clave, export() lanza un error (no hay salida sin cifrar).

El contenedor está versionado: los exports nuevos usan v2 (PBKDF2 con 600k iteraciones, recomendación OWASP) y el visor sigue abriendo los .flowx antiguos (v1, 100k). Evita compilar la clave en el binario en producción: obténla de tu backend o de remote config.


Borrado de datos #

Por defecto (deleteAfterExport: true, cleanupOnStart: true): los datos de la sesión se borran del disco tras exportar, y al iniciar una nueva grabación se limpia cualquier sesión anterior — nada persiste entre usos. El archivo exportado vive fuera de la carpeta de la sesión, así que sobrevive al borrado. FlogFlow.instance.clear() hace un borrado manual total.

En móvil, el "al cerrar la app" no llega de forma confiable cuando el SO mata el proceso; por eso el borrado se ancla a exportar y a iniciar.


Modo SaaS: conecta con la plataforma solo con un token #

Con el token del proyecto (lo generas en flog-flow.com) no necesitas exportar ni mover archivos: todo se sube automáticamente y ves el historial de sesiones y estadísticas en el dashboard.

await FlogCloud.start(
  projectToken: 'flw_xxxxxxxxxxxxxxxx',      // te lo da la plataforma
  encryptionPassphrase: 'CLAVE-DEL-PROYECTO', // el archivo viaja siempre cifrado
  appName: 'MiApp',
  appVersion: '1.4.2',
  // endpoint: 'https://api.midominio.com',   // si usas un despliegue propio
);

Esa única llamada reemplaza a start() y deja configurado: la recuperación de sesiones pendientes al arrancar, la grabación con los ajustes correctos y el envío automático (background / cierre / crash) autenticado con tu token. Cada subida adjunta un resumen sin cifrar (conteos y metadata; nunca imágenes ni contenido) para que la plataforma indexe estadísticas sin descifrar el archivo.

El modo local (export manual + visor, sin token) sigue funcionando igual: es la forma de probar la librería sin contratar nada.


Modo Logs: solo logs, sin grabación #

Si no necesitas la grabación completa de sesiones, FlogLogs es la opción ligera: envía logs estructurados a la plataforma con el mismo token. Independiente de FlogCloud/FlogFlow (pueden convivir o usarse solo).

await FlogLogs.init(
  projectToken: 'flw_xxxxxxxxxxxxxxxx',
  service: 'mi-app',          // logname por defecto
  env: 'prod',
  version: '1.4.2',
  captureCrashes: true,       // emergency automático en cada crash (default)
);

FlogLogs.info('sesión iniciada', attributes: {'userId': 'u_123'});
FlogLogs.warn('token por expirar', logname: 'auth');
FlogLogs.error('pago rechazado', error: e, stackTrace: st,
    screenshot: true, attributes: {'orderId': 'o_9', 'amount': 25.5});
FlogLogs.emergency('estado irrecuperable');

Niveles: emergency · error · warn · info · debug.

  • error/emergency llevan stacktrace siempre (el que pases, o el actual).
  • emergency se emite solo en cada crash (errores de Flutter + async no capturados) y se envía de inmediato, sin esperar al lote.
  • screenshot: true adjunta una captura (útil en errores). Requiere la app envuelta en FlogFlogScope; respeta los RedactedZone.
  • Atributos dinámicos por log, más globales con FlogLogs.setGlobalAttribute('userId', id) (ej. tras el login).
  • Batching: lotes cada 10 s o 50 logs; flush al ir a background; lo que no se pudo enviar se guarda en disco y se reenvía en el próximo arranque.
  • Cada log lleva contexto automático: plataforma, versión de SO y locale.

En el dashboard, la pestaña Logs permite filtrar por status/logname, buscar, ver el stacktrace, los atributos y el screenshot de cada log.

Detalle de un log: resumen, JSON, contexto, stacktrace y salto a su sesión


Envío automático a un servidor #

Sube el archivo .flowx cifrado a tu servidor automáticamente:

  • al pasar a segundo plano (paused) — el disparador confiable en móvil, y cubre también el cierre forzado: para matar la app desde el switcher, el usuario primero la mandó a background;
  • al cierre limpio (detached, casi nunca llega en móvil — red adicional);
  • ante un crash de Dart (recordError).

Solo se re-sube si hay contenido nuevo desde la última subida exitosa. Para kills del SO o crashes nativos (donde no corre código Dart), lo pendiente se reintenta al próximo arranque con recoverPendingUploads.

El nombre del archivo lleva la fecha y el motivo de la subida:

flow_<sessionId>_<yyyy-MM-dd_HH-mm-ss>_<motivo>.flowx
       │           │                     └ background | close | crash | recovered | manual
       │           └ fecha del evento (en 'recovered', la fecha de inicio de la sesión original)
       └ id de la sesión
final uploader = FlogHttpUpload(
  url: 'https://api.tuservidor.com/flows',
  headers: {'Authorization': 'Bearer $token'},
  fields: {'app': 'MiApp'},                 // campos extra opcionales
);

// 1) al arrancar, ANTES de start(): reenvía lo pendiente de sesiones anteriores
await FlogFlow.instance.recoverPendingUploads(
  upload: uploader,
  passphrase: 'CAMBIA-ESTA-CLAVE',
);

// 2) inicia la grabación (misma clave; conserva pendientes que no subieron)
await FlogFlow.instance.start(
  appName: 'MiApp',
  config: const CaptureConfig(
    encryptionPassphrase: 'CAMBIA-ESTA-CLAVE',
    cleanupOnStart: false,     // no borres lo que aún no se ha subido
  ),
);

// 3) activa el envío automático (segundo plano + cierre + crash de Dart)
FlogFlow.instance.enableAutoUpload(upload: uploader);
// opcional: apaga disparadores individuales
// FlogFlow.instance.enableAutoUpload(upload: uploader, onBackground: false);

// 4) captura los crashes de Dart
FlutterError.onError = (d) =>
    FlogFlow.instance.recordError(d.exception, stackTrace: d.stack);
runZonedGuarded(() => runApp(MyApp()),
    (e, st) => FlogFlow.instance.recordError(e, stackTrace: st));
  • Sin duplicados inútiles: si la app entra y sale de background varias veces sin actividad nueva, no se re-sube. Si el usuario siguió usando la app, la siguiente pasada a background sube la sesión completa actualizada (quédate en el servidor con el archivo de fecha más reciente por sesión).
  • Tu propia subida: si necesitas otra autenticación/formato, pasa una función Future<bool> Function(File) en vez de FlogHttpUpload.
  • Usa la misma clave en el config y en recoverPendingUploads, y cleanupOnStart: false para que los envíos fallidos se reintenten en el siguiente arranque.

Rendimiento #

Grabar no debería notarse: las capturas son diferidas y se toman en momentos de inactividad. Si en algún dispositivo lo sientes pesado, ajusta el CaptureConfig:

  • baja pixelRatio (p. ej. 0.5);
  • sube minCaptureInterval;
  • pon captureOnTap: false (captura solo al navegar) o recordScroll: false;
  • apaga las features que no uses (captureNetwork, captureLogs, framePerInteraction…).

Visor web #

El visor está publicado en línea. Abre flog-flow.com, sube tu archivo .flowx e introduce la clave con la que se grabó. No necesitas instalar nada.

Es una app con barra lateral y 4 vistas:

  • Flujo — el mapa del recorrido; clic en una pantalla abre el detalle de cada interacción (con su miniatura, fecha y el widget tocado) y los errores con su stack trace.
  • Reproducciónreplay paso a paso con play/pausa, velocidad y una línea de tiempo de todos los eventos (navegación, tap, scroll, red, log, breadcrumb, error).
  • Eventos — lista cronológica filtrable por tipo.
  • Sesión — información del dispositivo/app y conteos.

Notas: escribe comentarios por pantalla y descárgalos para compartir.


Limitaciones conocidas #

  • Platform views (Google Maps, WebView, cámara, vídeo) suelen salir en blanco al rasterizar: es una restricción de la plataforma, no del paquete.
  • En iOS, los screenshots vía RepaintBoundary excluyen contenido protegido por DRM.
  • Con framePerInteraction, el frame de cada tap se congela en el instante de la interacción (la codificación va diferida). Solo en ráfagas muy largas (más de ~4 taps seguidos sin pausa) los excedentes caen a la captura diferida clásica, que puede salir con un estado posterior de la pantalla.
  • El grabador no corre en Flutter web (usa dart:io); en web va solo el visor.

Licencia #

MIT.

9
likes
0
points
713
downloads

Publisher

verified publisheralecodeando.dev

Weekly Downloads

Graba el flujo de navegación real del usuario (pantallas, taps, red, logs y errores) y lo exporta en un archivo cifrado .flowx que se reproduce en un visor web.

Homepage

Topics

#debugging #session-replay #monitoring #analytics

License

unknown (license)

Dependencies

archive, cryptography, dio, flutter, image, path, path_provider

More

Packages that depend on flog_flow