api_network_logger 2.0.0 copy "api_network_logger: ^2.0.0" to clipboard
api_network_logger: ^2.0.0 copied to clipboard

A production-ready, publishable Flutter package that transparently logs and stores API calls, offline events, and navigation routes.

api_network_logger #

pub package License: MIT Automated Tests

A premium, production-ready, publishable Flutter package designed to transparently capture, log, compress, and inspect your application's network traffic, offline events, system telemetry, and navigation route transitions into a secure on-device SQLite database.

It includes a draggable, glassmorphic visual debugging console that is default off and fully tree-shaken from release builds (kDebugMode gated) to protect production performance and bundle sizes.


📖 Table of Contents #

  1. Key Features & Architectural Mechanics
  2. 60-Second Setup Guide
  3. Adapters & Connectivity Table
  4. Premium Features (Tiers 1 & 2)
  5. Pre-Storage PII Security & Redaction Engine
  6. On-Device Storage, Retention Policy, & Indexing
  7. Programmatic Database Query APIs
  8. Log Export & Remote Upload Mechanics
  9. Premium Glassmorphic Developer Console Overlay
  10. Comprehensive Edge Case & Troubleshooting Catalog
  11. Internal Developers Guide
  12. License

🛠 Key Features & Architectural Mechanics #

api_network_logger was designed by senior mobile architects specifically to solve developer telemetry needs without introducing common performance regressions. Here are the core mechanics under the hood:

  • Concurrency-Safe SQLite Queue: SQLite is single-writer. In standard multi-threaded network environments (like loading a screen that triggers 5 parallel HTTP calls), rapid simultaneous inserts can lead to database locks or corrupted/dropped rows. We utilize a sequential asynchronous lock queue in Dart to serialize database transactions, protecting your app's thread.
  • Non-Destructive Stream Buffering: Streamed HTTP responses (especially in the http package) can only be read once. Our HttpApiLoggerClient safely buffers incoming bytes, extracts metadata, and re-emits a cloned stream so your application code never breaks or crashes on consumer pipes.
  • Smart Payload Compression: To preserve user storage, any request/response body exceeding 10 KB is zlib-compressed prior to storage. SQLite handles the raw byte arrays dynamically as BLOB fields, unpacking them on the fly only during inspection.
  • Binary, Form-Data, & Media Exclusion: Standard string logging on binary outputs (like image downloads or PDF views) causes UTF-8 parsing crashes and database bloat. This package scans incoming content-types. Binary streams (image/*, video/*, application/pdf, multipart/form-data) are bypassed and logged as highly compressed diagnostic summaries (e.g. [Binary Content: image/png - 2.4 MB]).

⚡ 60-Second Setup Guide #

1. Add Dependency #

Add api_network_logger to your pubspec.yaml:

dependencies:
  api_network_logger: ^1.1.0

2. Initialize (Idempotent) #

Initialize the controller once in your main thread:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Initialize with zero-config defaults (Overlay is DEFAULT OFF)
  await ApiLogger.instance.init(
    config: const ApiLoggerConfig(
      showOverlay: true, // Optional: Set to true if you want the visual debug floating button to be active
    ),
  );

  runApp(const MyApp());
}

3. Add the Glassmorphic Visual Console Overlay #

Wrap your child in ApiLoggerOverlay inside MaterialApp.builder to mount the draggable visual debugger console overlay:

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Tractal App',
      navigatorKey: ApiLogger.instance.navigatorKey, // Bind navigatorKey
      theme: ThemeData(useMaterial3: true, primaryColor: const Color(0xFF2CAC5C)),
      // Mount the debugger overlay seamlessly (shows only if showOverlay: true is set or enableOverlay: true is passed!)
      builder: (context, child) => ApiLoggerOverlay(child: child!),
      navigatorObservers: [ApiLoggerNavigatorObserver()],
      home: const HomeScreen(),
    );
  }
}

🔌 Adapters & Connectivity Table #

The package natively provides generic APIs (logApi, logNavigation, logSystemTrace, logBreadcrumb) so you can connect any state management framework or network library without adding bloated third-party dependencies to the core package.

Framework / Package Connection Adapter How to Connect
Dio REST Client DioApiLoggerInterceptor dio.interceptors.add(DioApiLoggerInterceptor());
Http REST Client HttpApiLoggerClient final client = HttpApiLoggerClient();
Retrofit Client Supports Dio Out-of-box Inherits DioApiLoggerInterceptor from underlying Dio instance.
Chopper REST Client ChopperApiLoggerInterceptor Copy/Paste the custom Chopper Interceptor (see below)
flutter_bloc ApiLoggerBlocObserver Copy/Paste the custom Bloc Observer (see below)
Riverpod ApiLoggerProviderObserver Copy/Paste the custom Provider Observer (see below)
graphql_flutter ApiLoggerGraphQLHandler Connect custom links using GraphQL Link chains (see below)
WebSockets WebSocketApiLoggerChannel Wrap socket events using our generic message loggers (see below)
Pickers ApiLoggerPicker ApiLoggerPicker.logImagePicker(file); / logFilePicker(res);
share_plus ApiLoggerShare ApiLoggerShare.logShareText(text); / logShareFiles(files);
permission_handler ApiLoggerPermission ApiLoggerPermission.logPermissionRequest(permission, status);
geolocator ApiLoggerLocation ApiLoggerLocation.logLocationFetch(optInCoords: true);
Deep Links AppLinksLogger AppLinksLogger.instance.start();
workmanager ApiLoggerWorkmanager ApiLoggerWorkmanager.logTaskStart(uniqueName, taskName);
App Lifecycle AppLifecycleLogger AppLifecycleLogger.instance.start();
User Heatmap HeatmapListener builder: (context, child) => ApiLoggerOverlay(child: HeatmapListener(child: child!)),
connectivity_plus ConnectivityLogger ConnectivityLogger.instance.start(); (automatically logs offline events)
package_info_plus SystemTelemetryLogger Automatically logs static app name and build metadata on session launch.
device_info_plus SystemTelemetryLogger Automatically logs model and manufacturer specifications on session launch.
system_info2 SystemTelemetryLogger Automatically logs CPU physical cores and RAM memory profiles on errors/crashes.
battery_plus SystemTelemetryLogger Automatically logs battery levels and charging states on errors/crashes.

🚀 Zero-Dependency Adapter Code Blocks (Copy-Paste Ready) #

A. flutter_bloc (Observer breadcrumbs)

Log all state transitions and events as contextual breadcrumbs automatically:

class ApiLoggerBlocObserver extends BlocObserver {
  @override
  void onEvent(Bloc bloc, Object? event) {
    super.onEvent(bloc, event);
    ApiLogger.instance.logSystemTrace('bloc_event', {
      'bloc': bloc.runtimeType.toString(),
      'event': event.toString(),
    });
  }

  @override
  void onTransition(Bloc bloc, Transition transition) {
    super.onTransition(bloc, transition);
    ApiLogger.instance.logSystemTrace('bloc_transition', {
      'bloc': bloc.runtimeType.toString(),
      'currentState': transition.currentState.toString(),
      'nextState': transition.nextState.toString(),
    });
  }
}

B. Riverpod (ProviderObserver)

class ApiLoggerProviderObserver extends ProviderObserver {
  @override
  void didUpdateProvider(ProviderBase provider, Object? oldValue, Object? newValue, ProviderContainer container) {
    ApiLogger.instance.logSystemTrace('riverpod_update', {
      'provider': provider.name ?? provider.runtimeType.toString(),
      'newValue': newValue.toString(),
    });
  }
}
class ApiLoggerGraphQLHandler extends Link {
  @override
  Stream<Response> request(Request request, [NextLink? forward]) {
    final stopwatch = Stopwatch()..start();
    return forward!(request).map((response) {
      stopwatch.stop();
      ApiLogger.instance.logApi(
        ApiLogEntry(
          id: DateTime.now().microsecondsSinceEpoch.toString(),
          method: 'GRAPHQL',
          url: request.operation.operationName ?? 'GraphQL Query',
          requestHeaders: {},
          requestBody: request.variables.toString(),
          statusCode: 200,
          responseBody: response.response.toString(),
          durationMs: stopwatch.elapsedMilliseconds,
          timestamp: DateTime.now(),
        ),
      );
      return response;
    });
  }
}

D. WebSockets Frame Logger

class WebSocketApiLoggerChannel {
  static void logFrameIn(String channel, dynamic frame) {
    ApiLogger.instance.logSystemTrace('ws_frame_in', {
      'channel': channel,
      'direction': 'INCOMING',
      'payload': frame.toString(),
    });
  }

  static void logFrameOut(String channel, dynamic frame) {
    ApiLogger.instance.logSystemTrace('ws_frame_out', {
      'channel': channel,
      'direction': 'OUTGOING',
      'payload': frame.toString(),
    });
  }
}

E. File / Image Pickers Logger

class ApiLoggerPicker {
  static void logImagePicker(XFile? file) {
    if (file == null) {
      ApiLogger.instance.logSystemTrace('picker', {'type': 'image', 'status': 'cancelled'});
      return;
    }
    ApiLogger.instance.logSystemTrace('picker', {
      'type': 'image',
      'status': 'selected',
      'file_name': file.name,
      'path': file.path,
    });
  }

  static void logFilePicker(FilePickerResult? result) {
    if (result == null) {
      ApiLogger.instance.logSystemTrace('picker', {'type': 'file', 'status': 'cancelled'});
      return;
    }
    final files = result.files.map((f) => {
      'name': f.name,
      'size': f.size,
      'extension': f.extension,
    }).toList();
    ApiLogger.instance.logSystemTrace('picker', {
      'type': 'file',
      'status': 'selected',
      'count': files.length,
      'files': files,
    });
  }
}

F. Share Event Logger

class ApiLoggerShare {
  static void logShareText(String text, {String? subject}) {
    final preview = text.length > 100 ? '${text.substring(0, 100)}...' : text;
    ApiLogger.instance.logSystemTrace('share', {
      'type': 'text',
      'length': text.length,
      'preview': preview,
      'subject': subject,
      'timestamp': DateTime.now().toIso8601String(),
    });
  }

  static void logShareFiles(List<XFile> files, {String? text, String? subject}) {
    final filesMeta = files.map((f) => {
      'name': f.name,
      'mime_type': f.mimeType,
    }).toList();
    ApiLogger.instance.logSystemTrace('share', {
      'type': 'files',
      'count': files.length,
      'files': filesMeta,
      'text': text != null && text.length > 100 ? '${text.substring(0, 100)}...' : text,
      'subject': subject,
      'timestamp': DateTime.now().toIso8601String(),
    });
  }
}

G. Geolocator & Location Logger

class ApiLoggerLocation {
  static Future<void> logLocationFetch({bool optInCoords = false}) async {
    try {
      final permission = await Geolocator.checkPermission();
      if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
        ApiLogger.instance.logSystemTrace('location', {
          'status': 'denied_permission',
          'permission': permission.toString().split('.').last,
          'timestamp': DateTime.now().toIso8601String(),
        });
        return;
      }

      final position = await Geolocator.getCurrentPosition();
      ApiLogger.instance.logSystemTrace('location', {
        'status': 'success',
        'permission': permission.toString().split('.').last,
        'coordinates': optInCoords ? {'lat': position.latitude, 'lng': position.longitude} : 'opted_out',
        'accuracy': position.accuracy,
        'timestamp': DateTime.now().toIso8601String(),
      });
    } catch (e) {
      ApiLogger.instance.logSystemTrace('location', {
        'status': 'failed',
        'error': e.toString(),
        'timestamp': DateTime.now().toIso8601String(),
      });
    }
  }
}

H. Permission Request Logger

class ApiLoggerPermission {
  static void logPermissionRequest(Permission permission, PermissionStatus status) {
    ApiLogger.instance.logSystemTrace('permission', {
      'permission': permission.toString().split('.').last,
      'status': status.toString().split('.').last,
      'timestamp': DateTime.now().toIso8601String(),
    });
  }
}

I. Background Task (workmanager) Logger

class ApiLoggerWorkmanager {
  static void logTaskStart(String uniqueName, String taskName) {
    ApiLogger.instance.logSystemTrace('workmanager', {
      'status': 'started',
      'unique_name': uniqueName,
      'task_name': taskName,
      'timestamp': DateTime.now().toIso8601String(),
    });
  }

  static void logTaskSuccess(String uniqueName, String taskName) {
    ApiLogger.instance.logSystemTrace('workmanager', {
      'status': 'success',
      'unique_name': uniqueName,
      'task_name': taskName,
      'timestamp': DateTime.now().toIso8601String(),
    });
  }

  static void logTaskFailure(String uniqueName, String taskName, String error) {
    ApiLogger.instance.logSystemTrace('workmanager', {
      'status': 'failed',
      'unique_name': uniqueName,
      'task_name': taskName,
      'error': error,
      'timestamp': DateTime.now().toIso8601String(),
    });
  }
}

J. App Lifecycle Logger

class AppLifecycleLogger extends WidgetsBindingObserver {
  AppLifecycleLogger._();
  static final AppLifecycleLogger instance = AppLifecycleLogger._();

  void start() {
    WidgetsBinding.instance.addObserver(this);
  }

  void stop() {
    WidgetsBinding.instance.removeObserver(this);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    ApiLogger.instance.logSystemTrace('lifecycle', {
      'state': state.toString().split('.').last,
      'timestamp': DateTime.now().toIso8601String(),
    });
  }
}

K. User Heatmap (Translucent Listener)

To trace tap locations proportionally, simply wrap your root layout with our translucent pointer event listener:

class HeatmapListener extends StatelessWidget {
  final Widget child;
  const HeatmapListener({required this.child, super.key});

  @override
  Widget build(BuildContext context) {
    return Listener(
      behavior: HitTestBehavior.translucent,
      onPointerDown: (event) {
        try {
          ApiLogger.instance.logHeatmapEvent(
            x: event.position.dx,
            y: event.position.dy,
            screenWidth: MediaQuery.of(context).size.width,
            screenHeight: MediaQuery.of(context).size.height,
            route: ApiLogger.instance.currentRoute,
            timestamp: DateTime.now(),
          );
        } catch (e) {
          debugPrint('[HeatmapListener] Failed to log tap event: $e');
        }
      },
      child: child,
    );
  }
}

Mount it seamlessly alongside the console overlay inside MaterialApp.builder:

MaterialApp(
  navigatorKey: ApiLogger.instance.navigatorKey,
  builder: (context, child) => ApiLoggerOverlay(
    child: HeatmapListener(child: child!),
  ),
  ...
)

âš™ī¸ Premium Features (Tiers 1 & 2) #

đŸĨ‡ Tier 1 - High Impact Developer Tools #

1. Breadcrumb Trail on Error/Crash

Attach a contextual chronological timeline of the last 15 actions leading up to an exception. Perfect for translating "Crash caught" into "App crashed after user navigated to /checkout ➔ post_order API returned 500 ➔ retry clicked."

// Retreive an aggregated breadcrumbs list on demand!
final List<Map<String, dynamic>> breadcrumbs = await ApiLogger.instance.getBreadcrumbTrail(limit: 15);

2. cURL Command Export

Tapping an API log card inside the developer console automatically generates a formatted, ready-to-run cURL command:

// Native implementation inside ApiLogEntry:
final String curlString = logEntry.toCurlCommand();
// Output: curl -X POST -H "Content-Type: application/json" -d '{"item_id": 1}' "https://api.mycorp.com/v1/order"

3. Global Uncaught Error Capture

Integrates with Flutter exception boundaries to log snapshot crashes alongside active RAM/battery specs:

FlutterError.onError = (details) async {
  final telemetry = await SystemTelemetryLogger.instance.getFullTelemetry(); // Captured in example main.dart
  telemetry['error_details'] = {
    'exception': details.exceptionAsString(),
    'stack': details.stack?.toString(),
  };
  await ApiLogger.instance.logSystemTrace('error_crash', telemetry);
};

đŸĨˆ Tier 2 - Advanced Differentiators #

1. Real-time System Trace Logging

Includes a gorgeous third System Trace tab alongside API Logs and Route Transitions in our visual console viewer, housing session logs, hardware snapshots, and uncaught crash traces.

  • Gathers static hardware specifications (CPU Cores, RAM specs) once per session on initialization to save device cycles.
  • Fetches dynamic snapshots (Battery level, Free RAM) solely during initialization or on actual errors/crashes to keep background metrics extremely lightweight!

🔒 Pre-Storage PII Security & Redaction Engine #

Plaintext authentication credentials, access tokens, and passwords should never be stored on user device memory due to risks in rooted/jailbroken devices.

api_network_logger implements a strict Pre-Storage Redactor. It parses your headers and recursive JSON structures, replacing matching credentials with [REDACTED] before any database serialization or disk writes occur:

Configurable Redaction Keywords #

You can extend or modify the default case-insensitive redaction lists in your ApiLoggerConfig:

ApiLogger.instance.init(
  config: const ApiLoggerConfig(
    // Case-insensitive header keys
    redactedHeaders: ['Authorization', 'Cookie', 'X-Auth-Token', 'My-Custom-Token'],
    // Case-insensitive JSON keys (walks nested objects and lists recursively)
    redactedFields: ['password', 'token', 'secret', 'client_secret', 'api_key', 'accessToken', 'refreshToken', 'cvv'],
  ),
);

đŸŽ›ī¸ API Reference & Functions Table #

The central ApiLogger singleton provides a fully loaded interface to ingest, query, and sweep telemetry structures:

Function Signature / Parameter Variations Return Type Description
init ({ApiLoggerConfig? config}) Future<void> Initializes the singleton, registers default retention policies, and opens SQLite connections.
logApi (ApiLogEntry entry) Future<void> Ingests and sanitizes an API request/response. Evaluates recursive redactions and zlib body compression.
logNavigation ({String? fromRoute, required String toRoute, Map<String, dynamic>? arguments}) Future<void> Records route transition details and arguments in history, dynamically updating the currentRoute getter.
logSystemTrace (String eventType, Map<String, dynamic> data) Future<void> Records structured telemetry entries (such as crashes, background tasks, geolocation permission checks, battery, RAM profiles).
logHeatmapEvent ({required double x, required double y, required double screenWidth, required double screenHeight, required String route, required DateTime timestamp}) Future<void> Stores absolute screen coordinates and layout aspect boundaries to compute proportional visual heatmap plots.
getApiLogs ({DateTime? from, DateTime? to, String? method, List<int>? statusCodes, String? searchQuery, int? minDurationMs, bool? hasError, int? limit, int? offset}) Future<List<ApiLogEntry>> Parameters-driven query for REST logs. Full-text search queries cover URLs, stringified bodies, and exceptions.
getNavigationLogs ({DateTime? from, DateTime? to, int? limit, int? offset}) Future<List<NavigationLog>> Queries logged page routing transitions chronologically.
getSystemTraces ({DateTime? from, DateTime? to, int? limit, int? offset}) Future<List<SystemTraceLog>> Queries chronological structured system diagnostics, lifecycles, and uncaught crash snapshots.
getHeatmapEvents ({String? route, DateTime? from, DateTime? to, int? limit, int? offset}) Future<List<HeatmapEvent>> Queries pointer-down coordinate events. Supports filtering by destination page route.
getBreadcrumbTrail ({int limit = 15}) Future<List<Map<String, dynamic>>> Aggregates and merges API, navigation, and system logs into a single chronological timeline list.
cleanup () Future<void> Manually triggers vacuum purges and file cleanups matching the standard log retention presets.
clearAll () Future<void> Drops all database tables and clears SQLite log histories safely.
clearHeatmapEvents ({DateTime? before, String? route}) Future<void> Deletes specific user tap coordinate logs based on time ranges and/or screen routes.

🔍 Programmatic Database Query APIs #

You can build your own dedicated telemetry views or manually extract entries using our robust, parameterized query methods:

final logs = await ApiLogger.instance.getApiLogs(
  from: DateTime.now().subtract(const Duration(hours: 24)),
  to: DateTime.now(),
  method: 'POST',                  // Optional: Case-insensitive method filters
  statusCodes: [401, 403, 500],    // Optional: Exact response code matching
  searchQuery: 'payments',         // Optional: Full-text search (scans URLs, bodies, and exception errors)
  minDurationMs: 150,              // Optional: Filter laggy responses
  hasError: true,                  // Optional: Filter error/exceptions paths only
  limit: 50,                       // Optional: Paginate results
  offset: 0,                       // Optional: Paginate results
);

â„šī¸ Internal Developers Guide #

For engineers wishing to modify the database schema, lock-queue concurrency design, payload compression pipeline, or write bespoke storage engines, please consult the authoritative DEVELOPER.md document in the repository root.


📄 License #

Distributed under the MIT License. See LICENSE for more information.


đŸ“Ļ More Packages by the Author #

Expand your Flutter toolkit with these high-performance, developer-friendly solutions:

Package Badges Description
page_navigation_transition pub package Seamlessly customize page route transitions with beautiful fluid curves and durations.
photo_opener_view pub package Premium gesture-driven interactive photo viewer with fluid swipe-to-dismiss and dragging.
smart_review_prompter pub package Intelligent, high-conversion in-app store review dialog prompter to skyrocket app ratings.

👨‍đŸ’ģ Author #

Satish Parmar

GitHub    Portfolio

🌐 Portfolio


⭐ If you like this package, please give it a star on GitHub! ⭐


Copyright (c) 2026 Tractal Solutions Private Limited.

2
likes
160
points
151
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A production-ready, publishable Flutter package that transparently logs and stores API calls, offline events, and navigation routes.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

dio, flutter, http, meta, path, path_provider, sqflite

More

Packages that depend on api_network_logger