flex_logger 1.0.4
flex_logger: ^1.0.4 copied to clipboard
A flexible logging package for Flutter applications with support for multiple logging strategies, observers, and integrations.
import 'package:flex_logger/flex_logger.dart';
import 'package:flutter/material.dart';
// ---------------------------------------------------------------------------
// Custom log type – demonstrates extending FlexLog.
// ---------------------------------------------------------------------------
/// A custom log type carrying HTTP request metadata.
final class NetworkLog extends FlexLog {
NetworkLog({
required String url,
required int statusCode,
String? description,
}) : super(
'${description ?? 'HTTP'} $url [$statusCode]',
level: statusCode >= 400 ? FlexLogLevel.error : FlexLogLevel.info,
tag: 'HTTP',
);
}
// ---------------------------------------------------------------------------
// Custom observer – captures logs into an in-memory list so the UI can
// display them. Demonstrates implementing FlexObserver without any
// external dependency.
// ---------------------------------------------------------------------------
/// An observer that stores formatted log entries for display in the UI.
final class InMemoryObserver extends FlexObserver {
InMemoryObserver({super.filter});
final List<LogEntry> entries = [];
/// Callback invoked whenever a new entry is added.
void Function()? onUpdate;
@override
Future<void> onLog(FlexLog log) async {
entries.add(LogEntry(log.formattedTitle, log.message ?? '', log.level));
onUpdate?.call();
}
@override
Future<void> onException(FlexLog log) async {
entries.add(
LogEntry(
log.formattedTitle,
'${log.message}\n${log.error}',
log.level,
),
);
onUpdate?.call();
}
@override
Future<void> onError(FlexLog log) async {
entries.add(
LogEntry(
log.formattedTitle,
'${log.message}\n${log.error}',
log.level,
),
);
onUpdate?.call();
}
void clear() {
entries.clear();
onUpdate?.call();
}
}
/// Holds a single log line for display purposes.
final class LogEntry {
const LogEntry(this.title, this.message, this.level);
final String title;
final String message;
final FlexLogLevel level;
Color get color => switch (level) {
FlexLogLevel.debug => const Color(0xFF00BCD4),
FlexLogLevel.info => const Color(0xFF2196F3),
FlexLogLevel.success => const Color(0xFF4CAF50),
FlexLogLevel.warning => const Color(0xFFFF9800),
FlexLogLevel.error => const Color(0xFFF44336),
FlexLogLevel.critical => const Color(0xFFD32F2F),
};
}
// ---------------------------------------------------------------------------
// Custom provider – wires the observer into FlexLogger.
// ---------------------------------------------------------------------------
/// Provider that exposes [InMemoryObserver] to FlexLogger.
final class InMemoryLoggerProvider implements LoggerProvider {
InMemoryLoggerProvider({LogFilter filter = const AcceptAllFilter()}) : _observer = InMemoryObserver(filter: filter);
final InMemoryObserver _observer;
InMemoryObserver get observer => _observer;
@override
String get providerId => 'InMemoryLoggerProvider';
@override
Future<void> initialize() async {}
@override
FlexObserver? createObserver() => _observer;
@override
Future<void> dispose() async => _observer.clear();
}
// ---------------------------------------------------------------------------
// App entry point
// ---------------------------------------------------------------------------
final InMemoryLoggerProvider _provider = InMemoryLoggerProvider();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
FlexLogger.instance.configure(providers: [_provider]);
await FlexLogger.instance.initialize();
FlexLogger.instance.info('FlexLogger core example started');
FlexLogger.instance.debug('All log levels and custom log types are supported');
runApp(const MyApp());
}
// ---------------------------------------------------------------------------
// UI
// ---------------------------------------------------------------------------
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlexLogger Core Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const LoggerDemoPage(),
);
}
}
class LoggerDemoPage extends StatefulWidget {
const LoggerDemoPage({super.key});
@override
State<LoggerDemoPage> createState() => _LoggerDemoPageState();
}
class _LoggerDemoPageState extends State<LoggerDemoPage> {
late final InMemoryObserver _observer;
@override
void initState() {
super.initState();
_observer = _provider.observer;
_observer.onUpdate = () {
if (mounted) setState(() {});
};
}
@override
void dispose() {
_observer.onUpdate = null;
FlexLogger.instance.dispose();
super.dispose();
}
void _logAll() {
FlexLogger.instance.debug('Debug – detailed diagnostic information');
FlexLogger.instance.info('Info – general application event');
FlexLogger.instance.success('Success – operation completed');
FlexLogger.instance.warning('Warning – something might be wrong');
FlexLogger.instance.error('Error – something went wrong');
FlexLogger.instance.critical('Critical – immediate action required');
}
void _logCustom() {
FlexLogger.instance.logCustom(
NetworkLog(
url: 'https://api.example.com/data',
statusCode: 200,
description: 'GET',
),
);
FlexLogger.instance.logCustom(
NetworkLog(
url: 'https://api.example.com/missing',
statusCode: 404,
description: 'GET',
),
);
}
void _logException() {
try {
throw Exception('Something went wrong in the app');
} catch (e, st) {
FlexLogger.instance.error('Caught exception', e, st);
}
}
@override
Widget build(BuildContext context) {
final entries = List<LogEntry>.from(_observer.entries.reversed);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('FlexLogger Core Example'),
actions: [
IconButton(
icon: const Icon(Icons.delete_outline),
tooltip: 'Clear logs',
onPressed: () => setState(_observer.clear),
),
],
),
body: Column(
children: [
// Button panel
Padding(
padding: const EdgeInsets.all(12),
child: Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.center,
children: [
_LogButton(
label: 'All levels',
color: Colors.indigo,
onPressed: _logAll,
),
_LogButton(
label: 'Debug',
color: const Color(0xFF00BCD4),
onPressed: () => FlexLogger.instance.debug('Debug message'),
),
_LogButton(
label: 'Info',
color: const Color(0xFF2196F3),
onPressed: () => FlexLogger.instance.info('Info message'),
),
_LogButton(
label: 'Success',
color: const Color(0xFF4CAF50),
onPressed: () => FlexLogger.instance.success('Success message'),
),
_LogButton(
label: 'Warning',
color: const Color(0xFFFF9800),
onPressed: () => FlexLogger.instance.warning('Warning message'),
),
_LogButton(
label: 'Error',
color: const Color(0xFFF44336),
foreground: Colors.white,
onPressed: () => FlexLogger.instance.error('Error message'),
),
_LogButton(
label: 'Critical',
color: const Color(0xFFD32F2F),
foreground: Colors.white,
onPressed: () => FlexLogger.instance.critical('Critical message'),
),
_LogButton(
label: 'Custom (NetworkLog)',
color: Colors.teal,
foreground: Colors.white,
onPressed: _logCustom,
),
_LogButton(
label: 'Exception',
color: const Color(0xFFF44336),
foreground: Colors.white,
onPressed: _logException,
),
],
),
),
const Divider(height: 1),
// Log list
Expanded(
child: entries.isEmpty
? const Center(
child: Text(
'Press a button to generate logs',
style: TextStyle(color: Colors.grey),
),
)
: ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: entries.length,
separatorBuilder: (_, __) => const SizedBox(height: 4),
itemBuilder: (context, index) {
final entry = entries[index];
return _LogTile(entry: entry);
},
),
),
],
),
);
}
}
// ---------------------------------------------------------------------------
// Reusable widgets
// ---------------------------------------------------------------------------
class _LogButton extends StatelessWidget {
const _LogButton({
required this.label,
required this.color,
required this.onPressed,
this.foreground,
});
final String label;
final Color color;
final Color? foreground;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: color,
foregroundColor: foreground ?? Colors.black87,
),
child: Text(label),
);
}
}
class _LogTile extends StatelessWidget {
const _LogTile({required this.entry});
final LogEntry entry;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: entry.color.withAlpha(25),
border: Border(left: BorderSide(color: entry.color, width: 3)),
borderRadius: const BorderRadius.only(
topRight: Radius.circular(4),
bottomRight: Radius.circular(4),
),
),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
entry.title,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.bold,
color: entry.color,
),
),
const SizedBox(height: 2),
Text(entry.message, style: const TextStyle(fontSize: 13)),
],
),
);
}
}