flutter_log_interceptor 0.1.0
flutter_log_interceptor: ^0.1.0 copied to clipboard
The smart, zero-setup logging layer for Flutter. Beautiful colored logs, HTTP interception, in-app overlay, auto-silent in release — all from one line: FLog.init()
example/lib/main.dart
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_log_interceptor/flutter_log_interceptor.dart';
import 'package:http/http.dart' as http;
void main() {
FLog.init(
theme: FLogTheme.minimal,
minLevel: FLogLevel.debug,
autoLogErrors: true,
persistLogs: true,
maxStoredLogs: 500,
boxedLogs: true,
showFloatingButton: true,
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FLog Demo',
navigatorObservers: [FLog.navigatorObserver],
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: Stack(
children: [
const HomePage(),
FLogOverlay(),
],
),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with FLogMixin {
final Dio _dio = Dio();
late final FLogHttpClient _httpClient;
@override
void initState() {
super.initState();
_dio.interceptors.add(FLogDioInterceptor());
_httpClient = FLogHttpClient(http.Client());
logI('HomePage initialised');
}
@override
void dispose() {
_httpClient.close();
super.dispose();
}
Future<void> _runDemoLogs() async {
logD('Button tapped — running demo logs');
FLog.d('Debug message', tag: 'Demo');
FLog.i('Info message', tag: 'Demo');
FLog.w('Warning — something to watch', tag: 'Demo');
FLog.i('Order placed', tag: 'OrderService', data: {
'orderId': 'ORD_991',
'amount': 2999,
'success': true,
});
try {
throw Exception('Simulated payment timeout');
} catch (e, st) {
FLog.e('Payment gateway timeout',
tag: 'CartService', error: e, stackTrace: st);
}
try {
throw Exception('Simulated crash');
} catch (e, st) {
FLog.f('Unhandled exception', tag: 'App', error: e, stackTrace: st);
}
}
Future<void> _runLongLogs() async {
// Long message — over 120 chars, should show expand arrow
FLog.i(
'User session validated successfully after token refresh. '
'Access token expires in 3600 seconds. Refresh token expires in 30 days. '
'User roles: [admin, editor, viewer]. Last login: 2024-01-15T09:32:11Z.',
tag: 'AuthService',
);
// Warning with rich data map
FLog.w('Cache miss — falling back to network', tag: 'CacheManager', data: {
'key': 'user_profile_v2_usr_00912',
'strategy': 'stale-while-revalidate',
'ttl': 300,
'miss_reason': 'expired',
'fallback_url': 'https://api.example.com/v2/users/00912/profile',
'retry_count': 3,
'last_cached_at': '2024-01-15T08:00:00Z',
'cache_size_kb': 1024,
});
// Warning with deep nested data
FLog.w('Sync conflict — remote config out of date',
tag: 'SyncService',
data: {
'userId': 'usr_00912',
'conflictField': 'notification_preferences',
'localValue': {'push': true, 'email': false, 'sms': true},
'remoteValue': {'push': false, 'email': true, 'sms': true},
'resolution': 'pending_manual_review',
'attemptedAt': '2024-01-15T09:35:22.441Z',
});
// Error with error object only (no data param on FLog.e)
FLog.e(
'Failed to sync user preferences with remote config',
tag: 'SyncService',
error: StateError(
'Remote config returned 409 Conflict — concurrent write detected'),
);
// Fatal with a real stack trace
try {
_simulateDeepCrash();
} catch (e, st) {
FLog.f(
'Unrecoverable state — payment pipeline corrupted. '
'Transaction rolled back. Customer support notified.',
tag: 'PaymentPipeline',
error: e,
stackTrace: st,
);
}
}
void _simulateDeepCrash() {
_levelOne();
}
void _levelOne() => _levelTwo();
void _levelTwo() => _levelThree();
void _levelThree() => throw Exception(
'NullPointerException: paymentIntent was null after 3 retries. '
'Stripe SDK returned error code: card_declined. '
'Decline reason: insufficient_funds.',
);
Future<void> _runDioRequest() async {
try {
await _dio.get<dynamic>('https://jsonplaceholder.typicode.com/todos/1');
} catch (_) {
// errors are already logged by FLogDioInterceptor
}
}
Future<void> _runHttpRequest() async {
try {
await _httpClient
.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
} catch (_) {
// errors are already logged by FLogHttpClient
}
}
void _exportLogs() {
final json = FLog.exportAsJson();
logI('Exported ${FLog.history.length} log records', tag: 'Export');
debugPrint(json);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('flutter_log_interceptor demo'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Tap the buttons below and watch your terminal\n'
'for coloured log output.',
style: TextStyle(fontSize: 15),
),
const SizedBox(height: 24),
_DemoButton(
label: 'Open log overlay',
onPressed: FLogOverlay.show,
),
const SizedBox(height: 12),
_DemoButton(
label: 'Run demo logs (D/I/W/E/F)',
onPressed: _runDemoLogs,
),
const SizedBox(height: 12),
_DemoButton(
label: 'Run long logs (expandable)',
onPressed: _runLongLogs,
),
const SizedBox(height: 12),
_DemoButton(
label: 'Dio GET request',
onPressed: _runDioRequest,
),
const SizedBox(height: 12),
_DemoButton(
label: 'http.Client GET request',
onPressed: _runHttpRequest,
),
const SizedBox(height: 12),
_DemoButton(
label: 'Export logs (print to console)',
onPressed: _exportLogs,
),
const SizedBox(height: 12),
_DemoButton(
label: 'Clear history',
onPressed: () {
FLog.clearHistory();
logI('History cleared', tag: 'Demo');
},
),
const SizedBox(height: 24),
ValueListenableBuilder<int>(
valueListenable: FLog.recordCount,
builder: (_, __, ___) => Text(
'History: ${FLog.history.length} records',
style: const TextStyle(color: Colors.grey),
),
),
],
),
),
);
}
}
class _DemoButton extends StatelessWidget {
final String label;
final VoidCallback onPressed;
const _DemoButton({required this.label, required this.onPressed});
@override
Widget build(BuildContext context) => ElevatedButton(
onPressed: onPressed,
child: Text(label),
);
}