api_network_logger 2.0.0
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 #
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 #
- Key Features & Architectural Mechanics
- 60-Second Setup Guide
- Adapters & Connectivity Table
- Premium Features (Tiers 1 & 2)
- Pre-Storage PII Security & Redaction Engine
- On-Device Storage, Retention Policy, & Indexing
- Programmatic Database Query APIs
- Log Export & Remote Upload Mechanics
- Premium Glassmorphic Developer Console Overlay
- Comprehensive Edge Case & Troubleshooting Catalog
- Internal Developers Guide
- 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
httppackage) can only be read once. OurHttpApiLoggerClientsafely 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
BLOBfields, 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(),
});
}
}
C. GraphQL Link Handler
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:
đ¨âđģ Author #
Satish Parmar
â If you like this package, please give it a star on GitHub! â
Copyright (c) 2026 Tractal Solutions Private Limited.