any_logger 1.0.0
any_logger: ^1.0.0 copied to clipboard
A powerful, flexible, and intuitive logging library for Dart and Flutter with progressive complexity - from one-line setup to enterprise configurations.
Any Logger #
A powerful, flexible, and intuitive logging library for Dart and Flutter applications with automatic device/session tracking and progressive complexity - from one-line setup to enterprise-grade configurations.
Flutter-first design with proper mobile app support, persistent device IDs, and clear error messages.
✨ Why Any Logger? #
- 🚀 Zero Configuration - Start logging with literally one line of code
- 📱 Flutter-First - Built for mobile apps with proper app directory support
- 🔍 Automatic User Tracking - Built-in anonymous device/session/version identification
- 📈 Progressive Complexity - Simple for beginners, powerful for experts
- ⚡ Performance First - Optimized with early exits, caching, and lazy evaluation
- 🎯 Production Ready - Battle-tested with file rotation, batching, and error handling
- 🚨 Fail-Fast Design - Clear errors instead of silent failures
- 📦 Minimal Dependencies - Core library has only one dependency (
crypto
)
📦 Installation #
Core Package (Console & File logging) #
dependencies:
any_logger: ^1.0.0
path_provider: ^2.1.5 # Required for Flutter apps using %did or %sid
Extension Packages (Add only what you need) #
dependencies:
# For JSON HTTP logging
any_logger_json_http: ^1.0.0
# For Email logging
any_logger_email: ^1.0.0
# For MySQL logging
any_logger_mysql: ^1.0.0
🚀 Quick Start #
Flutter Apps #
import 'package:any_logger/any_logger.dart';
import 'package:path_provider/path_provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Required for Flutter if using %did or %sid
AnyLoggerFileIdProvider.getAppDocumentsDirectory = getApplicationDocumentsDirectory;
await LoggerFactory.initConsole(
format: '[%did][%sid] %l: %m',
);
Logger.info("Flutter app started!");
runApp(MyApp());
}
What Happens Without path_provider? #
If you forget to add path_provider
or set getAppDocumentsDirectory
, the logger will fail fast with clear instructions:
════════════════════════════════════════════════════════════════════════════════
🚨 LOGGING DISABLED: path_provider Not Configured
add dependency to pubspec.yaml:
path_provider: ^2.1.1
and set before LoggerFactory.init(...):
AnyLoggerFileIdProvider.getAppDocumentsDirectory = getApplicationDocumentsDirectory;
════════════════════════════════════════════════════════════════════════════════
Your Flutter app REQUIRES path_provider to use device/session IDs (%did, %sid).
Step 1: Add to pubspec.yaml
dependencies:
path_provider: ^2.1.1
Step 2: Run command
flutter pub get
Step 3: Add 2 lines to main.dart
import 'package:path_provider/path_provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// ADD THIS LINE:
AnyLoggerFileIdProvider.getAppDocumentsDirectory = getApplicationDocumentsDirectory;
await LoggerFactory.init(yourConfig);
runApp(MyApp());
}
ALTERNATIVE: Remove %did and %sid from your log format.
Error: $error
════════════════════════════════════════════════════════════════════════════════
🔧 ID Provider Configuration for Flutter #
Any Logger automatically detects Flutter apps and requires proper setup for device/session tracking:
Flutter Mobile Setup (Required for %did/%sid) #
import 'package:path_provider/path_provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Connect path_provider to AnyLogger (one line!)
FileIdProvider.getAppDocumentsDirectory = getApplicationDocumentsDirectory;
await LoggerFactory.init({
'appenders': [{
'type': 'CONSOLE',
'format': '[%app][%did][%sid] %l: %m',
'level': 'INFO',
}]
}, appVersion: '1.0.0');
Logger.info('Device ID persists across app restarts!');
runApp(MyApp());
}
Flutter Without Device/Session IDs (Simplest) #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// No path_provider needed if you don't use %did or %sid
await LoggerFactory.initConsole(
format: '%l: %m', // No IDs = no path_provider needed
);
runApp(MyApp());
}
Flutter Web / No Persistence Needed #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Use MemoryIdProvider for web or when persistence isn't needed
LoggerFactory.setIdProvider(MemoryIdProvider());
await LoggerFactory.initConsole(
format: '[%did][%sid] %l: %m', // IDs work but don't persist
);
runApp(MyApp());
}
Dart Console Apps / Servers #
import 'package:any_logger/any_logger.dart';
void main() {
Logger.info("I'm logging!"); // That's it! Auto-configures everything
}
Yes, really! No initialization needed for simple cases. The logger auto-configures on first use.
One Line with Options #
void main() {
LoggerFactory.initSimpleConsole(level: Level.DEBUG);
Logger.debug("Debug mode enabled");
Logger.info("Application started");
Logger.error("An error occurred");
}
🔍 Automatic Anonymous User Tracking #
Any Logger automatically generates and persists anonymous IDs to help you understand user behavior without compromising privacy:
- Device ID (
%did
) - Persists across app restarts, unique per device - Session ID (
%sid
) - New for each app launch, tracks individual sessions - App Version (
%app
) - Your application version for tracking deployments
// Enable tracking in your format
LoggerFactory.initConsole(
format: '[%app][%did][%sid] %l: %m',
);
// Set app version
LoggerFactory.setAppVersion('1.2.3');
// Output: [1.2.3][a3f5c8d2][e7b9f1a4] INFO: User clicked button
// Now you can track what users do across sessions and app versions!
This helps you:
- Debug user-reported issues by asking for their logs
- Track which app versions have specific issues
- Understand user journeys without collecting personal data
- Track session-specific problems
- Maintain GDPR compliance with anonymous identifiers
📖 Progressive Usage Examples #
Level 1: Using Built-in Presets #
// Development - verbose with full stack traces
await LoggerFactory.initWithPreset(LoggerPresets.development);
// Production - optimized with essential info only
await LoggerFactory.initWithPreset(LoggerPresets.production);
// Professional - includes device/session tracking
await LoggerFactory.initWithPreset(LoggerPresets.developmentPro);
// Production with app version tracking
await LoggerFactory.initWithPreset(
LoggerPresets.productionWithApp,
appVersion: '1.2.3',
);
// Mobile optimized with version
await LoggerFactory.initWithPreset(
LoggerPresets.mobileProduction,
appVersion: '1.2.3',
);
Level 2: Simple Configurations #
// Console with custom format
LoggerFactory.initConsole(
format: '🚀 %l: %m',
level: Level.DEBUG,
);
// Professional console with file location
LoggerFactory.initProConsole(
level: Level.DEBUG,
includeIds: true, // Enable device/session tracking
);
// File logging with app version
await LoggerFactory.initFile(
filePattern: 'myapp',
fileLevel: Level.DEBUG,
consoleLevel: Level.INFO, // Optional console output
appVersion: '1.2.3', // Include app version in logs
);
// Professional file with all metadata
await LoggerFactory.initProFileWithApp(
filePattern: 'myapp',
appVersion: '1.2.3',
fileLevel: Level.DEBUG,
consoleLevel: Level.INFO,
);
Level 3: Fluent Builder Pattern #
// Simple builder - Core package only
await LoggerFactory.builder()
.console(level: Level.INFO)
.file(
filePattern: 'app',
level: Level.DEBUG,
path: 'logs/',
)
.build();
// With app version tracking - Core package only
await LoggerFactory.builder()
.console(
format: '[%app][%sid][%l] %m',
)
.file(
filePattern: 'app',
path: 'logs/',
)
.withAppVersion('1.2.3')
.build();
Level 4: With Extension Packages #
// First, add the required packages to pubspec.yaml:
// dependencies:
// any_logger_json_http: ^1.0.0
// any_logger_email: ^1.0.0
import 'package:any_logger/any_logger.dart';
import 'package:any_logger_json_http/any_logger_json_http.dart';
import 'package:any_logger_email/any_logger_email.dart';
// Advanced multi-appender setup with extensions
await LoggerFactory.builder()
.console(
level: Level.WARN,
format: '[%d][%app][%did][%sid][%l] %m',
)
.file(
filePattern: 'app',
level: Level.DEBUG,
path: 'logs/',
)
.jsonHttp( // Requires any_logger_json_http package
url: 'https://logs.example.com',
level: Level.ERROR,
bufferSize: 100,
)
.email( // Requires any_logger_email package
host: 'smtp.gmail.com',
to: ['admin@example.com'],
level: Level.FATAL,
)
.withAppVersion('1.2.3')
.withMdcValue('environment', 'production')
.build();
🏷️ MDC - Mapped Diagnostic Context #
Track context across all your logs - perfect for request tracking, user sessions, or feature flags:
// Set global context
LoggerFactory.setMdcValue('userId', 'user-123');
LoggerFactory.setMdcValue('feature', 'new-checkout');
// Use in format with %X{key}
LoggerFactory.initConsole(
format: '[%X{userId}][%X{feature}] %l: %m',
);
// All logs now include context
Logger.info('Checkout started');
// Output: [user-123][new-checkout] INFO: Checkout started
// Clean up when done
LoggerFactory.removeMdcValue('userId');
🎨 Using the AnyLogger Mixin #
Add logging superpowers to any class:
class PaymentService with AnyLogger {
@override
String get loggerName => 'PaymentService';
Future<void> processPayment(String userId, double amount) async {
logInfo('Processing payment for $userId: \$$amount');
try {
// Only compute expensive debug info if needed
if (isDebugEnabled) {
logDebug('Payment details: ${_getDetailedInfo()}');
}
await _chargeCard(amount);
logInfo('Payment successful');
} catch (e, stack) {
logError('Payment failed', exception: e, stackTrace: stack);
rethrow;
}
}
}
🔧 ID Provider Configuration for Flutter #
Any Logger needs proper setup for device/session tracking on Flutter:
Flutter Mobile Setup (Recommended) #
import 'package:path_provider/path_provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Configure for Flutter - REQUIRED if using %did or %sid
AnyLoggerFileIdProvider.getAppDocumentsDirectory = getApplicationDocumentsDirectory;
await LoggerFactory.init({
'appenders': [{
'type': 'CONSOLE',
'format': '[%app][%did][%sid] %l: %m',
'level': 'INFO',
}]
}, appVersion: '1.0.0');
Logger.info('Device ID persists across app restarts!');
runApp(MyApp());
}
Flutter Web / No Persistence Needed #
void main() {
// Use MemoryIdProvider for web or when persistence isn't needed
LoggerFactory.setIdProvider(MemoryIdProvider());
LoggerFactory.initConsole(
format: '[%did][%sid] %l: %m',
);
runApp(MyApp());
}
No IDs Needed (Simplest) #
void main() {
// If you don't use %did or %sid, no setup needed!
LoggerFactory.initConsole(
format: '%l: %m', // No IDs = works everywhere
);
runApp(MyApp());
}
📝 Format Patterns #
Pattern | Description | Example Output |
---|---|---|
%d |
Date/time | 2025-01-20 10:30:45 |
%did |
Device ID (anonymous) | a3f5c8d2 |
%sid |
Session ID | e7b9f1a4 |
%app |
App version | 1.2.3 |
%l |
Log level | INFO |
%m |
Message | User logged in |
%c |
Class.method:line | UserService.login:42 |
%f |
File location | lib/user.dart(42:5) |
%i |
Logger name | UserService |
%t |
Tag | AUTH |
%X{key} |
MDC value | production |
Example Formats #
// Minimal
'%l: %m'
// Output: INFO: User logged in
// With timestamp
'%d [%l] %m'
// Output: 10:30:45 [INFO] User logged in
// Production with tracking
'[%did][%sid][%l][%c] %m'
// Output: [a3f5c8d2][e7b9f1a4][INFO][UserService.login:42] User logged in
// With app version
'[%app][%sid][%l] %m'
// Output: [1.2.3][e7b9f1a4][INFO] User logged in
// Full debug format
'[%d][%app][%did][%sid][%X{env}][%i][%l][%c] %m [%f]'
// Output: [10:30:45][1.2.3][a3f5c8d2][e7b9f1a4][prod][UserService][INFO][login:42] User logged in [lib/user.dart(42:5)]
🧩 Extension Packages #
The core any_logger
library is intentionally kept lightweight with minimal dependencies (only crypto
). Additional appenders for network and database logging are provided through separate, optional extension packages.
Core package includes:
- ✅ Console appender (stdout and developer tools)
- ✅ File appender with rotation
Extension packages provide:
- 📦 JSON HTTP appender (requires
any_logger_json_http
) - 📦 Email appender (requires
any_logger_email
) - 📦 MySQL appender (requires
any_logger_mysql
)
You only need to add the packages for the appenders you actually use:
Package | Description | When to Use |
---|---|---|
any_logger |
Core library with Console & File appenders | Always required |
any_logger_json_http |
JSON over HTTP logging | When sending logs to REST APIs, Logstash, etc. |
any_logger_email |
Email notifications | For critical alerts and error notifications |
any_logger_mysql |
MySQL database logging | For structured, queryable log storage |
Using Extension Packages #
// 1. Add to pubspec.yaml
dependencies:
any_logger: ^1.0.0
any_logger_json_http: ^1.0.0 # Only if needed
any_logger_email: ^1.0.0 # Only if needed
any_logger_mysql: ^1.0.0 # Only if needed
// 2. Import the extensions you need
import 'package:any_logger/any_logger.dart';
import 'package:any_logger_json_http/any_logger_json_http.dart';
import 'package:any_logger_email/any_logger_email.dart';
// 3. Use them in your configuration
await LoggerFactory.builder()
.console() // Core package
.file() // Core package
.jsonHttp( // Extension package
url: 'https://api.example.com/logs',
)
.email( // Extension package
host: 'smtp.gmail.com',
to: ['admin@example.com'],
)
.build();
🎯 Appenders #
Core Appenders (Always Available) #
Console Appender
LoggerFactory.builder()
.console(
level: Level.DEBUG,
format: '%d [%l] %m',
devtools: true, // Use Flutter DevTools output
)
.build();
File Appender with Rotation
await LoggerFactory.builder()
.file(
filePattern: 'myapp',
path: 'logs/',
rotationCycle: 'DAY', // DAY, WEEK, MONTH, YEAR, NEVER
level: Level.DEBUG,
)
.build();
// Creates: logs/myapp_2025-01-20.log
Extension Appenders (Require Additional Packages) #
JSON HTTP Appender
Requires: any_logger_json_http: ^1.0.0
import 'package:any_logger_json_http/any_logger_json_http.dart';
await LoggerFactory.builder()
.jsonHttp(
url: 'https://api.example.com/logs',
username: 'apiuser',
password: 'apipass',
bufferSize: 100, // Batch 100 logs
flushIntervalSeconds: 60, // Or flush every minute
enableCompression: true, // Gzip compression
)
.build();
Email Appender
Requires: any_logger_email: ^1.0.0
import 'package:any_logger_email/any_logger_email.dart';
await LoggerFactory.builder()
.email(
host: 'smtp.gmail.com',
port: 587,
user: 'alerts@example.com',
password: 'password',
to: ['admin@example.com'],
minLevelForImmediate: Level.ERROR, // Send errors immediately
batchSize: 10, // Batch info/debug logs
)
.build();
MySQL Appender
Requires: any_logger_mysql: ^1.0.0
import 'package:any_logger_mysql/any_logger_mysql.dart';
await LoggerFactory.builder()
.mysql(
host: 'localhost',
database: 'logs',
user: 'logger',
password: 'password',
table: 'app_logs',
batchSize: 50,
)
.build();
⚡ Performance Optimization #
Early Exit Pattern #
// ❌ Bad - always computes expensive operation
logger.logDebug(expensiveComputation());
// ✅ Good - only computes if debug is enabled
if (logger.isDebugEnabled) {
logger.logDebug(expensiveComputation());
}
// ✅ Better - use supplier for lazy evaluation
logger.logDebugSupplier(() => expensiveComputation());
Batch Processing #
// Configure batching for network appenders (requires extension package)
import 'package:any_logger_json_http/any_logger_json_http.dart';
final logger = LoggerFactory.builder()
.jsonHttp(
url: 'https://logs.example.com',
bufferSize: 100, // Send after 100 logs
flushIntervalSeconds: 30, // Or after 30 seconds
)
.build();
// Manual flush when needed (e.g., before app terminates)
await LoggerFactory.flushAll();
🔍 Self-Debugging #
Having issues with the logger itself? Enable self-debugging:
LoggerFactory.builder()
.console(level: Level.INFO)
.withSelfDebug(Level.DEBUG) // See what the logger is doing
.build();
📊 Real-World Examples #
Flutter App with Crash Reporting #
// Add to pubspec.yaml:
// dependencies:
// any_logger: ^5.2.0
// any_logger_json_http: ^1.0.0
import 'package:any_logger/any_logger.dart';
import 'package:any_logger_json_http/any_logger_json_http.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Configure path provider for Flutter
AnyLoggerFileIdProvider.getAppDocumentsDirectory = getApplicationDocumentsDirectory;
// Setup logging with automatic tracking
await LoggerFactory.builder()
.console(
level: kDebugMode ? Level.DEBUG : Level.INFO,
format: '[%app][%sid][%l] %m',
)
.file(
filePattern: 'app',
level: Level.DEBUG,
path: (await getApplicationDocumentsDirectory()).path,
format: '[%d][%app][%did][%sid][%l][%c] %m [%f]',
)
.jsonHttp( // Requires extension package
url: 'https://logs.myapp.com',
level: Level.ERROR, // Only send errors to server
)
.withAppVersion('1.2.3')
.withMdcValue('platform', Platform.operatingSystem)
.build();
// Catch Flutter errors
FlutterError.onError = (details) {
Logger.fatal('Flutter error', exception: details.exception, stackTrace: details.stack);
};
runApp(MyApp());
}
Backend Service with Request Tracking #
class ApiServer {
void handleRequest(Request request) {
final requestId = Uuid().v4();
// Set request context
LoggerFactory.setMdcValue('requestId', requestId);
LoggerFactory.setMdcValue('endpoint', request.uri.path);
LoggerFactory.setMdcValue('method', request.method);
try {
Logger.info('Request started');
// Process request...
Logger.info('Request completed');
} finally {
// Clean up context
LoggerFactory.clearMdc();
}
}
}
🏆 Best Practices #
- Always set app version with
%app
to track issues across releases - Use device/session IDs (
%did
/%sid
) for anonymous user tracking and debugging - Set appropriate log levels - DEBUG for development, INFO/WARN for production
- Use MDC for adding context to all logs (user ID, request ID, etc.)
- Enable batching for network/database appenders to reduce overhead (extension packages)
- Use early exit checks or suppliers for expensive debug logging
- Configure rotation for file appenders to manage disk space
- Set up immediate alerts for ERROR/FATAL with email appender (extension package)
- Use named loggers for better organization in large apps
- Enable self-debugging when troubleshooting logger issues
- Flush logs before app termination to avoid data loss
- Only add extension packages you actually use to minimize dependencies
🔄 Dynamic Configuration #
Modify logger behavior at runtime:
final logger = LoggerFactory.getRootLogger();
// Change log level dynamically
logger.setLevelAll(Level.WARN);
// Change format
logger.setFormatAll('[%sid] %l: %m');
// Enable/disable specific appenders by type
LoggerFactory.disableAppender('FILE');
LoggerFactory.enableAppender('CONSOLE');
// Reset to initial configuration
logger.resetFormatToInitialConfig();
🔍 Troubleshooting #
Common Flutter Issues #
"FileIdProvider failed to persist device ID"
Solution: Configure path_provider:
AnyLoggerFileIdProvider.getAppDocumentsDirectory = getApplicationDocumentsDirectory;
"Permission denied" on Mobile
Solution: Add storage permissions or use MemoryIdProvider:
LoggerFactory.setIdProvider(MemoryIdProvider());
"Cannot use sync init with Flutter"
Solution: Use async initialization:
await LoggerFactory.init(...); // Not initSync()
"No appender registered for type 'JSON_HTTP'"
Solution: Add and import the required extension package:
dependencies:
any_logger_json_http: ^1.0.0
import 'package:any_logger_json_http/any_logger_json_http.dart';
Performance Tips #
- If you don't use
%did
or%sid
, the ID provider never runs - Use
MemoryIdProvider
for web apps - Enable batching for network appenders (extension packages)
- Only import extension packages you actually use
🤝 Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License #
MIT License - see LICENSE file for details.
🙏 Acknowledgments #
This library is a fork of Log4Dart2 by Ephenodrom, enhanced with modern features, performance optimizations, automatic ID tracking, and a simplified API.
📮 Support #
- 📧 Email: hello@raoulsson.com
- 🐛 Issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
👏 Funding #
Happy Logging! 🎉