voo_logging 0.4.9
voo_logging: ^0.4.9 copied to clipboard
Zero-config Flutter logging with pretty console output, persistent storage, DevTools integration, and Dio interceptor. Just call VooLogger.info() and it works.
import 'package:flutter/material.dart';
import 'package:voo_logging/voo_logging.dart';
import 'package:dio/dio.dart';
import 'package:voo_toast/voo_toast.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// VooLogger now works with ZERO configuration!
// Just call logging methods directly - it auto-initializes with smart defaults.
//
// For explicit control, use:
// await VooLogger.ensureInitialized(config: LoggingConfig.production());
// Optional: Initialize VooToast for toast notifications
try {
VooToastController.instance;
} catch (_) {
VooToastController.init();
}
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'VooLogging Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo, brightness: Brightness.light),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo, brightness: Brightness.dark),
useMaterial3: true,
),
home: const VooToastOverlay(child: LoggingDemoScreen()),
);
}
}
class LoggingDemoScreen extends StatefulWidget {
const LoggingDemoScreen({super.key});
@override
State<LoggingDemoScreen> createState() => _LoggingDemoScreenState();
}
class _LoggingDemoScreenState extends State<LoggingDemoScreen> {
late final Dio dio;
List<LogEntry> recentLogs = [];
LogStatistics? stats;
bool toastEnabled = false;
int _selectedTab = 0;
// Config options
String _selectedPreset = 'default';
bool _enablePrettyLogs = true;
bool _showEmojis = true;
bool _showTimestamp = true;
bool _showBorders = true;
bool _showMetadata = true;
LogLevel _minimumLevel = LogLevel.verbose;
@override
void initState() {
super.initState();
_setupDio();
_listenToLogs();
_loadStats();
}
void _setupDio() {
dio = Dio();
final interceptor = VooDioInterceptor();
dio.interceptors.add(InterceptorsWrapper(onRequest: interceptor.onRequest, onResponse: interceptor.onResponse, onError: interceptor.onError));
}
void _listenToLogs() {
VooLogger.instance.stream.listen((log) {
setState(() {
recentLogs.insert(0, log);
if (recentLogs.length > 50) recentLogs.removeLast();
});
_loadStats();
});
}
Future<void> _loadStats() async {
final newStats = await VooLogger.instance.getStatistics();
setState(() => stats = newStats);
}
Future<void> _clearLogs() async {
await VooLogger.instance.clearLogs();
setState(() => recentLogs.clear());
_loadStats();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('VooLogging Demo'),
centerTitle: true,
actions: [
IconButton(
icon: Icon(toastEnabled ? Icons.notifications_active : Icons.notifications_off_outlined),
onPressed: () => setState(() => toastEnabled = !toastEnabled),
tooltip: 'Toggle toast notifications',
),
IconButton(icon: const Icon(Icons.delete_outline), onPressed: _clearLogs, tooltip: 'Clear logs'),
],
),
body: Column(
children: [
// Tab bar
Container(
color: colorScheme.surfaceContainerHighest,
child: Row(
children: [
_buildTab('Quick Log', 0, Icons.flash_on),
_buildTab('Categories', 1, Icons.category),
_buildTab('Network', 2, Icons.wifi),
_buildTab('Config', 3, Icons.tune),
_buildTab('Stats', 4, Icons.analytics),
],
),
),
// Tab content
Expanded(
child: IndexedStack(index: _selectedTab, children: [_buildQuickLogTab(), _buildCategoriesTab(), _buildNetworkTab(), _buildConfigTab(), _buildStatsTab()]),
),
// Log stream
_buildLogStream(),
],
),
);
}
Widget _buildTab(String label, int index, IconData icon) {
final isSelected = _selectedTab == index;
final colorScheme = Theme.of(context).colorScheme;
return Expanded(
child: InkWell(
onTap: () => setState(() => _selectedTab = index),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: isSelected ? colorScheme.primary : Colors.transparent, width: 2)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 20, color: isSelected ? colorScheme.primary : colorScheme.onSurfaceVariant),
const SizedBox(height: 4),
Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
color: isSelected ? colorScheme.primary : colorScheme.onSurfaceVariant,
),
),
],
),
),
),
);
}
Widget _buildQuickLogTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle('Log Levels'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildLogButton('Verbose', Colors.grey, () => VooLogger.verbose('Verbose trace message')),
_buildLogButton('Debug', Colors.blueGrey, () => VooLogger.debug('Debug information')),
_buildLogButton('Info', Colors.blue, () => VooLogger.info('Info message', shouldNotify: toastEnabled)),
_buildLogButton('Warning', Colors.orange, () => VooLogger.warning('Warning alert', shouldNotify: toastEnabled)),
_buildLogButton('Error', Colors.red, () => VooLogger.error('Error occurred', error: Exception('Sample error'), shouldNotify: toastEnabled)),
_buildLogButton('Fatal', Colors.red.shade900, () => VooLogger.fatal('Fatal crash!', error: Exception('Critical failure'), shouldNotify: toastEnabled)),
],
),
const SizedBox(height: 24),
_buildSectionTitle('Quick Actions'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
FilledButton.icon(onPressed: _logWithMetadata, icon: const Icon(Icons.data_object, size: 18), label: const Text('Log with Metadata')),
FilledButton.tonalIcon(onPressed: _logMultiple, icon: const Icon(Icons.burst_mode, size: 18), label: const Text('Log 10 Messages')),
],
),
const SizedBox(height: 24),
_buildCodeExample('Zero-Config Usage', '''// Just use it - auto-initializes!
VooLogger.info('Hello world');
// With metadata
VooLogger.info('User action',
category: 'Analytics',
tag: 'button_click',
metadata: {'screen': 'home'},
);'''),
],
),
);
}
Widget _buildCategoriesTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle('Log by Category'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildCategoryButton('Auth', Icons.lock, Colors.purple, () {
VooLogger.info('User logged in', category: 'Auth', tag: 'login', metadata: {'method': 'email'});
}),
_buildCategoryButton('Network', Icons.cloud, Colors.blue, () {
VooLogger.info('API request completed', category: 'Network', tag: 'api', metadata: {'endpoint': '/users'});
}),
_buildCategoryButton('Analytics', Icons.analytics, Colors.green, () {
VooLogger.info('Event tracked', category: 'Analytics', tag: 'event', metadata: {'name': 'page_view'});
}),
_buildCategoryButton('Payment', Icons.payment, Colors.orange, () {
VooLogger.info('Payment processed', category: 'Payment', tag: 'transaction', metadata: {'amount': 99.99});
}),
_buildCategoryButton('System', Icons.settings, Colors.grey, () {
VooLogger.debug('System check', category: 'System', tag: 'health');
}),
_buildCategoryButton('Error', Icons.error, Colors.red, () {
VooLogger.error('Operation failed', category: 'Error', tag: 'failure', error: Exception('Database error'));
}),
],
),
const SizedBox(height: 24),
_buildCodeExample('Structured Logging', '''VooLogger.info(
'User completed purchase',
category: 'Payment',
tag: 'checkout_complete',
metadata: {
'orderId': 'ORD-123',
'amount': 99.99,
'currency': 'USD',
'items': 3,
},
);'''),
],
),
);
}
Widget _buildNetworkTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle('Network Logging with Dio'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
FilledButton.icon(
onPressed: () => _makeRequest('https://jsonplaceholder.typicode.com/posts/1'),
icon: const Icon(Icons.download, size: 18),
label: const Text('GET Request'),
),
FilledButton.icon(
onPressed: () => _makeRequest('https://jsonplaceholder.typicode.com/posts', isPost: true),
icon: const Icon(Icons.upload, size: 18),
label: const Text('POST Request'),
),
FilledButton.tonalIcon(onPressed: () => _makeRequest('https://httpstat.us/404'), icon: const Icon(Icons.error_outline, size: 18), label: const Text('404 Error')),
FilledButton.tonalIcon(onPressed: () => _makeRequest('https://httpstat.us/500'), icon: const Icon(Icons.dangerous, size: 18), label: const Text('500 Error')),
],
),
const SizedBox(height: 24),
_buildCodeExample('Dio Integration', '''final dio = Dio();
final interceptor = VooDioInterceptor();
dio.interceptors.add(InterceptorsWrapper(
onRequest: interceptor.onRequest,
onResponse: interceptor.onResponse,
onError: interceptor.onError,
));
// All requests are now logged automatically!
await dio.get('https://api.example.com/data');'''),
],
),
);
}
Widget _buildConfigTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle('Configuration Presets'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildPresetButton('Default', 'default', Colors.blue),
_buildPresetButton('Development', 'development', Colors.green),
_buildPresetButton('Production', 'production', Colors.orange),
_buildPresetButton('Minimal', 'minimal', Colors.grey),
],
),
const SizedBox(height: 24),
_buildSectionTitle('Format Options'),
const SizedBox(height: 8),
_buildToggleOption('Pretty Logs', _enablePrettyLogs, (v) {
setState(() => _enablePrettyLogs = v);
_applyConfig();
}),
_buildToggleOption('Show Emojis', _showEmojis, (v) {
setState(() => _showEmojis = v);
_applyConfig();
}),
_buildToggleOption('Show Timestamp', _showTimestamp, (v) {
setState(() => _showTimestamp = v);
_applyConfig();
}),
_buildToggleOption('Show Borders', _showBorders, (v) {
setState(() => _showBorders = v);
_applyConfig();
}),
_buildToggleOption('Show Metadata', _showMetadata, (v) {
setState(() => _showMetadata = v);
_applyConfig();
}),
const SizedBox(height: 16),
_buildSectionTitle('Minimum Log Level'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: LogLevel.values.map((level) {
final isSelected = _minimumLevel == level;
return FilterChip(
label: Text(level.name),
selected: isSelected,
onSelected: (_) {
setState(() => _minimumLevel = level);
_applyConfig();
},
selectedColor: _getLevelColor(level.name).withAlpha(50),
checkmarkColor: _getLevelColor(level.name),
);
}).toList(),
),
const SizedBox(height: 24),
_buildSectionTitle('Test Current Config'),
const SizedBox(height: 8),
FilledButton.icon(onPressed: _logAllLevels, icon: const Icon(Icons.play_arrow), label: const Text('Log All Levels')),
const SizedBox(height: 24),
_buildCodeExample('Current Config Code', _getConfigCode()),
],
),
);
}
Widget _buildPresetButton(String label, String preset, Color color) {
final isSelected = _selectedPreset == preset;
return FilterChip(
label: Text(label),
selected: isSelected,
onSelected: (_) => _applyPreset(preset),
selectedColor: color.withAlpha(50),
checkmarkColor: color,
avatar: isSelected ? Icon(Icons.check, size: 18, color: color) : null,
);
}
Widget _buildToggleOption(String label, bool value, ValueChanged<bool> onChanged) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label),
Switch(value: value, onChanged: onChanged),
],
),
);
}
void _applyPreset(String preset) {
setState(() {
_selectedPreset = preset;
switch (preset) {
case 'development':
_enablePrettyLogs = true;
_showEmojis = true;
_showTimestamp = true;
_showBorders = true;
_showMetadata = true;
_minimumLevel = LogLevel.verbose;
case 'production':
_enablePrettyLogs = false;
_showEmojis = false;
_showTimestamp = true;
_showBorders = false;
_showMetadata = false;
_minimumLevel = LogLevel.warning;
case 'minimal':
_enablePrettyLogs = false;
_showEmojis = false;
_showTimestamp = false;
_showBorders = false;
_showMetadata = false;
_minimumLevel = LogLevel.info;
default:
_enablePrettyLogs = true;
_showEmojis = true;
_showTimestamp = true;
_showBorders = true;
_showMetadata = true;
_minimumLevel = LogLevel.verbose;
}
});
_applyConfig();
}
Future<void> _applyConfig() async {
final config = LoggingConfig(
enablePrettyLogs: _enablePrettyLogs,
showEmojis: _showEmojis,
showTimestamp: _showTimestamp,
showBorders: _showBorders,
showMetadata: _showMetadata,
minimumLevel: _minimumLevel,
);
await VooLogger.initialize(config: config);
}
void _logAllLevels() {
VooLogger.verbose('This is a verbose message - detailed tracing info');
VooLogger.debug('This is a debug message - debugging information');
VooLogger.info('This is an info message - general information');
VooLogger.warning('This is a warning message - something to watch');
VooLogger.error('This is an error message', error: Exception('Sample error'));
VooLogger.fatal('This is a fatal message', error: Exception('Critical failure'));
}
String _getConfigCode() {
final buffer = StringBuffer();
buffer.writeln('LoggingConfig(');
buffer.writeln(' enablePrettyLogs: $_enablePrettyLogs,');
buffer.writeln(' showEmojis: $_showEmojis,');
buffer.writeln(' showTimestamp: $_showTimestamp,');
buffer.writeln(' showBorders: $_showBorders,');
buffer.writeln(' showMetadata: $_showMetadata,');
buffer.writeln(' minimumLevel: LogLevel.${_minimumLevel.name},');
buffer.writeln(')');
return buffer.toString();
}
Widget _buildStatsTab() {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle('Log Statistics'),
const SizedBox(height: 8),
if (stats != null) ...[
_buildStatCard('Total Logs', stats!.totalLogs.toString(), Icons.list_alt, Colors.blue),
const SizedBox(height: 12),
_buildSectionTitle('By Level'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: stats!.levelCounts.entries.map((e) {
return _buildStatChip(e.key, e.value, _getLevelColor(e.key));
}).toList(),
),
if (stats!.categoryCounts.isNotEmpty) ...[
const SizedBox(height: 16),
_buildSectionTitle('By Category'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: stats!.categoryCounts.entries.map((e) {
return _buildStatChip(e.key, e.value, Colors.indigo);
}).toList(),
),
],
] else
const Center(child: CircularProgressIndicator()),
const SizedBox(height: 24),
_buildSectionTitle('Configuration'),
const SizedBox(height: 8),
_buildConfigInfo(),
],
),
);
}
Widget _buildLogStream() {
final colorScheme = Theme.of(context).colorScheme;
return Container(
height: 200,
decoration: BoxDecoration(
color: colorScheme.surfaceContainerLowest,
border: Border(top: BorderSide(color: colorScheme.outlineVariant)),
),
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
color: colorScheme.surfaceContainerHighest,
child: Row(
children: [
Icon(Icons.terminal, size: 16, color: colorScheme.onSurfaceVariant),
const SizedBox(width: 8),
Text(
'Log Stream',
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: colorScheme.onSurfaceVariant),
),
const Spacer(),
Text('${recentLogs.length} logs', style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant)),
],
),
),
Expanded(
child: recentLogs.isEmpty
? Center(
child: Text('No logs yet', style: TextStyle(color: colorScheme.onSurfaceVariant)),
)
: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 8),
itemCount: recentLogs.length,
itemBuilder: (context, index) {
final log = recentLogs[index];
return _buildLogEntry(log);
},
),
),
],
),
);
}
Widget _buildLogEntry(LogEntry log) {
final color = _getLevelColor(log.level.name);
final time = '${log.timestamp.hour.toString().padLeft(2, '0')}:${log.timestamp.minute.toString().padLeft(2, '0')}:${log.timestamp.second.toString().padLeft(2, '0')}';
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
time,
style: TextStyle(fontSize: 11, fontFamily: 'monospace', color: Colors.grey.shade600),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(color: color.withAlpha(30), borderRadius: BorderRadius.circular(4)),
child: Text(
log.level.name.toUpperCase().substring(0, 3),
style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: color),
),
),
const SizedBox(width: 8),
if (log.category != null) ...[Text('[${log.category}]', style: TextStyle(fontSize: 11, color: Colors.grey.shade600)), const SizedBox(width: 4)],
Expanded(
child: Text(log.message, style: const TextStyle(fontSize: 12), overflow: TextOverflow.ellipsis),
),
],
),
);
}
Widget _buildSectionTitle(String title) {
return Text(title, style: Theme.of(context).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600));
}
Widget _buildLogButton(String label, Color color, VoidCallback onPressed) {
return FilledButton(
onPressed: onPressed,
style: FilledButton.styleFrom(backgroundColor: color),
child: Text(label),
);
}
Widget _buildCategoryButton(String label, IconData icon, Color color, VoidCallback onPressed) {
return FilledButton.icon(
onPressed: onPressed,
style: FilledButton.styleFrom(backgroundColor: color),
icon: Icon(icon, size: 18),
label: Text(label),
);
}
Widget _buildStatCard(String title, String value, IconData icon, Color color) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Icon(icon, size: 32, color: color),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.bodySmall),
Text(value, style: Theme.of(context).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.bold)),
],
),
],
),
),
);
}
Widget _buildStatChip(String label, int count, Color color) {
return Chip(
avatar: CircleAvatar(
backgroundColor: color,
radius: 10,
child: Text('$count', style: const TextStyle(fontSize: 10, color: Colors.white)),
),
label: Text(label),
);
}
Widget _buildConfigInfo() {
final config = VooLogger.config;
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildConfigRow('Min Level', config.minimumLevel.name),
_buildConfigRow('Pretty Logs', config.enablePrettyLogs.toString()),
_buildConfigRow('Max Logs', config.maxLogs?.toString() ?? 'Unlimited'),
_buildConfigRow('Retention Days', config.retentionDays?.toString() ?? 'Forever'),
_buildConfigRow('Auto Cleanup', config.autoCleanup.toString()),
],
),
),
);
}
Widget _buildConfigRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.w500)),
Text(value, style: TextStyle(color: Theme.of(context).colorScheme.primary)),
],
),
);
}
Widget _buildCodeExample(String title, String code) {
final colorScheme = Theme.of(context).colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle(title),
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(8)),
child: Text(
code,
style: TextStyle(fontFamily: 'monospace', fontSize: 12, color: colorScheme.onSurfaceVariant),
),
),
],
);
}
Color _getLevelColor(String level) {
switch (level.toLowerCase()) {
case 'verbose':
return Colors.grey;
case 'debug':
return Colors.blueGrey;
case 'info':
return Colors.blue;
case 'warning':
return Colors.orange;
case 'error':
return Colors.red;
case 'fatal':
return Colors.red.shade900;
default:
return Colors.grey;
}
}
void _logWithMetadata() {
VooLogger.info(
'User interaction logged',
category: 'Analytics',
tag: 'user_action',
metadata: {'screen': 'demo', 'action': 'button_click', 'timestamp': DateTime.now().toIso8601String(), 'sessionId': 'demo-session-123'},
shouldNotify: toastEnabled,
);
}
Future<void> _logMultiple() async {
for (var i = 1; i <= 10; i++) {
await VooLogger.info('Batch log #$i', category: 'Batch', tag: 'test');
await Future.delayed(const Duration(milliseconds: 50));
}
}
Future<void> _makeRequest(String url, {bool isPost = false}) async {
try {
if (isPost) {
await dio.post(url, data: {'title': 'Test', 'body': 'Content'});
} else {
await dio.get(url);
}
VooLogger.info('Request completed: $url', category: 'Network', shouldNotify: toastEnabled);
} catch (e) {
VooLogger.error('Request failed: $url', category: 'Network', error: e, shouldNotify: toastEnabled);
}
}
}