27 App Logging
I’d like to group related functionalities in the same class. Can we group these loggers in a one class that can be imported and use in other places in app? Is it a good idea to do so?
Absolutely! Grouping loggers in a centralized class is a great idea and follows good software engineering practices. It provides consistency, easier maintenance, and better organization - exactly the kind of simple, practical solution you prefer.
Here’s how to create a centralized logging class:
27.1 AppLogger Class
// lib/utils/app_logger.dart
import 'package:logging/logging.dart';
import 'package:flutter/foundation.dart';
import 'dart:developer' as developer;
class AppLogger {
// Private constructor to prevent instantiation
._();
AppLogger
// Logger instances - organized by functionality
static final auth = Logger('MyApp.Auth');
static final network = Logger('MyApp.Network');
static final database = Logger('MyApp.Database');
static final ui = Logger('MyApp.UI');
static final navigation = Logger('MyApp.Navigation');
static final storage = Logger('MyApp.Storage');
static final payment = Logger('MyApp.Payment');
static final analytics = Logger('MyApp.Analytics');
// Root logger for app-wide messages
static final app = Logger('MyApp');
/// Initialize logging configuration
static void initialize() {
// Set log level based on build mode
.root.level = kDebugMode ? Level.ALL : Level.WARNING;
Logger
// Set up log listener
.root.onRecord.listen(_handleLogRecord);
Logger
.info('AppLogger initialized');
app}
/// Handle log records - customize output format and routing
static void _handleLogRecord(LogRecord record) {
if (kDebugMode) {
// Use dart:developer log for better DevTools integration
.log(
developer.message,
record: record.time,
time: record.level.value,
level: record.loggerName,
name: record.error,
error: record.stackTrace,
stackTrace
);} else {
// In production, route to crash reporting, analytics, etc.
_handleProductionLog(record);}
}
/// Handle production logging (customize as needed)
static void _handleProductionLog(LogRecord record) {
// Example: Send to crash reporting service
// FirebaseCrashlytics.instance.log('${record.level.name}: ${record.message}');
// Example: Send severe errors to monitoring service
if (record.level >= Level.SEVERE) {
// Sentry.captureException(record.error ?? record.message);
}
}
/// Convenience method to set log level for specific functionality
static void setLogLevel(String loggerName, Level level) {
.level = level;
Logger(loggerName)}
/// Convenience method to disable all logging
static void disableLogging() {
.root.level = Level.OFF;
Logger}
}
27.2 Usage in Your App
1. Initialize in main.dart:
// lib/main.dart
import 'package:flutter/material.dart';
import 'utils/app_logger.dart';
void main() {
.initialize();
AppLogger
runApp(MyApp());}
2. Use in your widgets and services:
// lib/pages/home_page.dart
import 'package:flutter/material.dart';
import '../utils/app_logger.dart';
class HomePage extends StatefulWidget {
@override
=> _HomePageState();
_HomePageState createState() }
class _HomePageState extends State<HomePage> {
@override
void initState() {
super.initState();
.ui.info('HomePage initialized');
AppLogger
_loadData();}
Future<void> _loadData() async {
try {
.network.info('Starting data fetch');
AppLogger
final data = await ApiService.fetchUserData();
.network.info('Data fetched successfully', data.length);
AppLogger.ui.fine('UI updated with new data');
AppLogger
} catch (e, stackTrace) {
.network.severe('Failed to fetch data', e, stackTrace);
AppLogger
// Show user-friendly error
if (mounted) {
.of(context).showSnackBar(
ScaffoldMessenger: Text('Failed to load data')),
SnackBar(content
);}
}
}
}
3. Use in services:
// lib/services/auth_service.dart
import '../utils/app_logger.dart';
class AuthService {
static Future<User?> signIn(String email, String password) async {
.auth.info('Sign in attempt', {'email': email});
AppLogger
try {
final user = await _performSignIn(email, password);
.auth.info('Sign in successful', {'userId': user.id});
AppLogger.analytics.info('User signed in', {'method': 'email'});
AppLogger
return user;
} catch (e, stackTrace) {
.auth.warning('Sign in failed', e, stackTrace);
AppLoggerrethrow;
}
}
}
27.3 Benefits of This Approach
Organization: - All loggers in one place - Clear functional grouping - Easy to see what areas you’re logging
Consistency: - Same naming convention across the app - Centralized configuration - Uniform log formatting
Maintenance: - Easy to add new loggers - Simple to change log levels for debugging - One place to modify output routing
Flexibility: - Can easily disable specific areas: AppLogger.setLogLevel('MyApp.Network', Level.OFF)
- Easy to add new output handlers (files, network, etc.)
27.4 Advanced Features You Can Add
class AppLogger {
// ... existing code ...
/// Log user actions for analytics
static void logUserAction(String action, [Map<String, dynamic>? parameters]) {
.info('User action: $action', parameters);
analytics}
/// Log performance metrics
static void logPerformance(String operation, Duration duration) {
.info('Performance: $operation took ${duration.inMilliseconds}ms');
app}
/// Log with custom context
static void logWithContext(Logger logger, Level level, String message, {
Object? error,
? stackTrace,
StackTraceMap<String, dynamic>? context,
}) {
final enrichedMessage = context != null
? '$message | Context: $context'
: message;
.log(level, enrichedMessage, error, stackTrace);
logger}
}
This approach gives you a clean, maintainable logging system that grows with your app. It’s much better than scattered debugPrint()
statements and provides the foundation for professional app monitoring.
Would you like me to show you how to extend this for file logging or integration with Firebase Crashlytics?