21 Logging
I’ll guide you through logging in Dart and Flutter, starting from the basics and building up to more advanced techniques. Let’s explore each approach systematically.
21.1 Understanding Logging in Dart/Flutter
Logging is essential for debugging, monitoring application behavior, and tracking issues in both development and production environments. Think of logging as leaving breadcrumbs through your code - these breadcrumbs help you understand what happened when something goes wrong or when you need to trace the flow of your application.
21.2 Basic Logging with print()
The print()
function is the simplest way to output messages in Dart. It’s like console.log() in JavaScript or print() in Python.
void main() {
'Application started');
print(
var user = 'John';
var age = 25;
'User: $user, Age: $age');
print(
// Printing objects
var userMap = {'name': 'John', 'age': 25};
// {name: John, age: 25}
print(userMap); }
When to use print():
- Quick debugging during development
- Simple scripts or command-line Dart applications
- When you need immediate, simple output without formatting
Limitations of print():
- In Flutter release builds, print() statements are removed for performance
- No timestamp or source information
- No log levels (info, warning, error)
- Can’t be filtered or redirected easily
21.3 Developer Tools Logging with developer.log()
The developer.log()
function from dart:developer
provides more sophisticated logging capabilities designed specifically for development tools.
import 'dart:developer' as developer;
void fetchUserData() {
.log('Fetching user data...', name: 'UserService');
developer
try {
// Simulate API call
var userData = {'id': 123, 'name': 'John'};
.log(
developer'User data fetched successfully',
: 'UserService',
name: null,
error: DateTime.now(),
time
);} catch (e, stackTrace) {
.log(
developer'Failed to fetch user data',
: 'UserService',
name: e,
error: stackTrace,
stackTrace: 1000, // Error level
level
);}
}
Key features of developer.log():
- name: Categorizes logs (like a logger name)
- time: Adds timestamp
- sequenceNumber: Orders logs
- level: Sets severity (0-2000, where 1000+ is severe)
- error and stackTrace: Properly formats exceptions
When to use developer.log():
- During Flutter development when you need structured logs
- When debugging with Flutter DevTools
- When you need to log errors with stack traces
- For temporary debugging that shouldn’t affect production
21.4 Advanced Logging with the logging Package
The logging
package provides a comprehensive logging solution similar to logging frameworks in other languages (like Python’s logging module).
First, add it to your pubspec.yaml
:
dependencies:
logging: ^1.2.0
Here’s a complete example showing various features:
import 'package:logging/logging.dart';
// Create a logger instance
final _logger = Logger('MyApp');
void setupLogging() {
// Set the root logging level
.root.level = Level.ALL;
Logger
// Configure output formatting
.root.onRecord.listen((LogRecord record) {
Loggerfinal time = record.time.toString().substring(11, 19); // Extract time
final level = record.level.name.padRight(7); // Pad level name
final logger = record.loggerName.padRight(15); // Pad logger name
'$time [$level] $logger: ${record.message}');
print(
// Print stack trace if available
if (record.stackTrace != null) {
.stackTrace);
print(record}
});
}
// Example usage in different parts of your app
class UserService {
static final _logger = Logger('UserService');
Future<void> loginUser(String username) async {
.info('Attempting login for user: $username');
_logger
try {
// Simulate API call
await Future.delayed(Duration(seconds: 1));
if (username.isEmpty) {
throw ArgumentError('Username cannot be empty');
}
.fine('Login successful for user: $username');
_logger} catch (e, stackTrace) {
.severe('Login failed for user: $username', e, stackTrace);
_loggerrethrow;
}
}
}
class DataProcessor {
static final _logger = Logger('DataProcessor');
void processData(List<int> data) {
.finest('Starting data processing with ${data.length} items');
_logger
for (var i = 0; i < data.length; i++) {
if (i % 100 == 0) {
.finer('Processed $i items');
_logger}
// Process item
if (data[i] < 0) {
.warning('Negative value found at index $i: ${data[i]}');
_logger}
}
.info('Data processing completed');
_logger}
}
21.5 Log Levels in the logging Package
The logging package provides these levels (from lowest to highest severity):
- FINEST (300): Most detailed information
- FINER (400): Fairly detailed information
- FINE (500): Useful debugging information
- CONFIG (700): Configuration information
- INFO (800): General informational messages
- WARNING (900): Potential problems
- SEVERE (1000): Serious failures
- SHOUT (1200): Extra loud severe messages
21.6 Configuring Logging for Different Environments
Here’s a practical setup for different environments:
import 'package:logging/logging.dart';
import 'package:flutter/foundation.dart';
void configureLogging() {
if (kDebugMode) {
// Development configuration
.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
Logger'${record.level.name}: ${record.time}: '
debugPrint('${record.loggerName}: ${record.message}');
});
} else if (kProfileMode) {
// Profile mode configuration
.root.level = Level.INFO;
Logger.root.onRecord.listen((record) {
Loggerif (record.level >= Level.INFO) {
'[${record.level.name}] ${record.message}');
print(}
});
} else {
// Release mode configuration
.root.level = Level.WARNING;
Logger// In production, you might send logs to a crash reporting service
.root.onRecord.listen((record) {
Loggerif (record.level >= Level.WARNING) {
// Send to crash analytics service
_sendToCrashlytics(record);}
});
}
}
void _sendToCrashlytics(LogRecord record) {
// Implementation would depend on your crash reporting service
// Example: FirebaseCrashlytics.instance.log(record.message);
}
21.7 Best Practices and When to Use Each Approach
Use print() when:
- You’re doing quick, temporary debugging
- Working on simple Dart scripts
- You need immediate output during development
- You don’t care about the output in production
Use developer.log() when:
- You’re debugging Flutter applications
- You need structured logs visible in Flutter DevTools
- You want to include error objects and stack traces
- You need categorized logs but don’t want a full logging framework
Use the logging package when:
- Building production applications
- You need fine-grained control over log levels
- Different parts of your app need different logging configurations
- You want to send logs to external services
- You need consistent, formatted logging across your application
21.8 Practical Example: Combining Approaches
Here’s how you might use different logging approaches in a real Flutter application:
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'dart:developer' as developer;
final _logger = Logger('MainApp');
class MyApp extends StatefulWidget {
@override
=> _MyAppState();
_MyAppState createState() }
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
// Quick debug during development
'App initializing...');
print(
// Structured development log
.log('Initializing app state', name: 'MyApp');
developer
// Production-ready logging
.info('Application started at ${DateTime.now()}');
_logger
_loadConfiguration();}
void _loadConfiguration() {
try {
.config('Loading application configuration');
_logger// Load config...
.fine('Configuration loaded successfully');
_logger} catch (e, stackTrace) {
.severe('Failed to load configuration', e, stackTrace);
_logger
// Also log to developer tools for immediate visibility
.log(
developer'Config loading failed',
: e,
error: stackTrace,
stackTrace: 1000,
level
);}
}
@override
{
Widget build(BuildContext context) return MaterialApp(
: Scaffold(
home: AppBar(title: Text('Logging Demo')),
appBar: Center(
body: ElevatedButton(
child: () {
onPressed// Quick debug
'Button pressed');
print(
// Detailed logging
.info('User interaction: Button pressed');
_logger},
: Text('Press Me'),
child,
),
),
)
);}
}
This layered approach gives you the flexibility to use quick debugging when needed while maintaining a robust logging system for production. Remember that effective logging is about finding the right balance - too little and you can’t debug issues, too much and you drown in noise.