monikode_event_store 2.2.1
monikode_event_store: ^2.2.1 copied to clipboard
A singleton service to log events into a Firebase Firestore database. Can also log events locally into the console with different log levels (debug, info, warning, error, trace).
Event Store #
Description #
This is a singleton service to log events into a Firebase Firestore database. Can also log events locally into the console with different log levels (debug, info, warning, error, trace).
Features:
- 🔥 Firebase Firestore integration for remote logging
- 📝 Local console logging with structured JSON format
- ⚙️ Comprehensive configuration options
- 🔄 Automatic retry logic for failed network requests
- 🎯 Minimum log level filtering
- 🌍 Global parameters for all log entries
- 🚨 Global error handling callbacks
- 🔐 Configurable user information inclusion
- 🎭 Async and sync logging methods
Installation #
To install the event store, you can use the following command:
flutter pub add monikode_event_store
Configuration #
EventStore can be configured using the EventStoreConfig class, which provides comprehensive control over logging behavior.
Configuration Options #
| Option | Type | Default | Description |
|---|---|---|---|
collectionName |
String | "logs" | Firestore collection name for storing logs |
enableRemoteLogging |
bool | true | Enable/disable Firebase Firestore logging |
enableLocalLogging |
bool | true | Enable/disable console logging |
maxRetries |
int | 3 | Maximum retry attempts for failed remote logging |
retryDelayMs |
int | 1000 | Delay between retries in milliseconds |
onError |
Function? | null | Global error callback for logging failures |
includeUserInfo |
bool | true | Include user ID and email in logs |
globalParameters |
Map? | null | Parameters added to every log entry |
minimumLogLevel |
EventLevel | debug | Minimum log level to record |
enableBatchMode |
bool | false | Enable batch logging to reduce write operations |
batchSize |
int | 10 | Max logs per batch before auto-flush |
batchTimeoutMs |
int | 5000 | Max time before auto-flush (milliseconds) |
Basic Configuration #
import 'package:monikode_event_store/monikode_event_store.dart';
// Simple configuration with just a custom collection name
var instance = EventStore.getInstance(collectionName: 'my_logs');
Advanced Configuration #
var instance = EventStore.getInstance(
config: EventStoreConfig(
collectionName: 'production_logs',
enableRemoteLogging: true,
enableLocalLogging: true,
maxRetries: 5,
retryDelayMs: 2000,
minimumLogLevel: EventLevel.info, // Only log info, warning, and error
includeUserInfo: true,
globalParameters: {
'app_version': '1.0.0',
'environment': 'production',
'platform': 'mobile',
},
onError: (eventName, error) {
// Handle logging errors globally
print('Failed to log $eventName: $error');
// You could send this to an error tracking service
},
),
);
Privacy-Focused Configuration #
// For privacy-sensitive applications, disable user info collection
var instance = EventStore.getInstance(
config: EventStoreConfig(
includeUserInfo: false,
globalParameters: {
'session_id': generateSessionId(), // Use anonymous session ID instead
},
),
);
Development vs Production #
// Development: Enable all logging
var devConfig = EventStoreConfig(
enableRemoteLogging: true,
enableLocalLogging: true,
minimumLogLevel: EventLevel.debug,
);
// Production: Reduce noise, disable debug logs
var prodConfig = EventStoreConfig(
enableRemoteLogging: true,
enableLocalLogging: false, // Disable console logs in production
minimumLogLevel: EventLevel.info, // Skip debug and trace logs
maxRetries: 5, // More retries for production
);
var instance = EventStore.getInstance(
config: isProduction ? prodConfig : devConfig,
);
Local-Only Logging #
// Useful for testing or offline mode
var instance = EventStore.getInstance(
config: EventStoreConfig(
enableRemoteLogging: false,
enableLocalLogging: true,
),
);
Usage #
To use the event store, you need to import the package in your file:
import 'package:monikode_event_store/monikode_event_store.dart';
Log Levels #
EventStore supports five log levels, ordered by severity:
- debug - Detailed debugging information
- trace - Trace execution flow
- info - General informational messages
- warning - Warning messages
- error - Error messages
Basic Usage with Error Handling #
Get the singleton instance and log events asynchronously:
var instance = EventStore.getInstance();
// Log an event and handle the result
final result = await instance.eventLogger.log(
"user_login",
EventLevel.info,
{
"login_method": "email",
"timestamp": DateTime.now().toString(),
},
);
// Check if logging was successful
if (result.success) {
print('Event logged successfully!');
} else {
print('Failed to log event: ${result.error}');
}
Simple Usage (Fire and Forget) #
If you don't need to check the result:
var instance = EventStore.getInstance();
await instance.eventLogger.log("login", EventLevel.info, {
"custom_parameter": "custom_value",
});
Synchronous Logging (Non-Async Functions) #
If you're in a non-async function and can't use await, use logSync():
var instance = EventStore.getInstance();
// Simple fire-and-forget
instance.eventLogger.logSync("button_click", EventLevel.info, {
"button_id": "submit",
});
// With callback to handle the result
instance.eventLogger.logSync(
"user_action",
EventLevel.info,
{"action": "tap"},
onComplete: (result) {
if (!result.success) {
print('Failed to log: ${result.error}');
}
},
);
Note: logSync() is useful for:
- Widget builders and constructors
- Non-async event handlers
- Places where you can't use async/await
- Fire-and-forget logging scenarios
Using Different Log Levels #
var logger = EventStore.getInstance().eventLogger;
// Debug information
await logger.log("debug_info", EventLevel.debug, {"detail": "value"});
// Trace execution
await logger.log("method_called", EventLevel.trace, {"method": "fetchData"});
// Informational
await logger.log("user_action", EventLevel.info, {"action": "click"});
// Warnings
await logger.log("api_slow", EventLevel.warning, {"duration": "5s"});
// Errors
await logger.log("api_failed", EventLevel.error, {
"error": "timeout",
"endpoint": "/api/data"
});
Global Error Handling #
Configure a global error handler to catch all logging failures:
var instance = EventStore.getInstance(
config: EventStoreConfig(
onError: (eventName, error) {
// Send to error tracking service
ErrorTracker.logError('EventStore failed for $eventName: $error');
// Or show user notification
showSnackbar('Failed to log event');
},
),
);
Batch Logging (Reduce Firestore Costs by 90%!) #
Enable batch mode to group multiple log entries into a single Firestore write operation. This dramatically reduces costs and improves performance for high-volume logging.
How it works:
- Logs are queued in memory instead of written immediately
- Batch is automatically flushed when it reaches
batchSizeOR afterbatchTimeoutMs - You can also manually flush with
flushBatch()
var instance = EventStore.getInstance(
config: EventStoreConfig(
enableBatchMode: true,
batchSize: 10, // Flush after 10 logs
batchTimeoutMs: 5000, // Or after 5 seconds, whichever comes first
),
);
// Log events normally - they'll be batched automatically
await instance.eventLogger.log("event1", EventLevel.info, {"data": "1"});
await instance.eventLogger.log("event2", EventLevel.info, {"data": "2"});
await instance.eventLogger.log("event3", EventLevel.info, {"data": "3"});
// Manually flush the batch if needed (e.g., before app closes)
await instance.eventLogger.flushBatch();
Benefits:
- 💰 Save up to 90% on Firestore costs - 10 logs = 1 write operation
- ⚡ Better performance - Less network overhead
- 📊 Perfect for high-volume apps - Analytics, tracking, etc.
When to use:
- ✅ Apps with frequent logging (analytics, user tracking)
- ✅ Non-critical logs that can be delayed slightly
- ✅ Cost-sensitive applications
When NOT to use:
- ❌ Critical error logging that must be immediate
- ❌ Low-volume apps (won't see significant savings)
Important: Always call flushBatch() before app termination to ensure queued logs are written:
@override
void dispose() {
// Flush any pending logs before closing
EventStore.getInstance().eventLogger.flushBatch();
super.dispose();
}
Custom Collection Name #
You can specify a custom collection name when getting the instance for the first time:
// First call - creates instance with custom collection name
var instance = EventStore.getInstance(collectionName: 'my_custom_logs');
// All subsequent calls return the same instance
var sameInstance = EventStore.getInstance(); // Still uses 'my_custom_logs'
Note: The collection name parameter is only respected on the first call. Subsequent calls will return the existing singleton instance.
Local Logging Only #
For debugging purposes, you can use just the local event logger:
var instance = EventStore.getInstance();
instance.localEventStore.log("debug_event", EventLevel.debug, {
"debug_info": "some value",
});
Resetting the Singleton (Testing) #
For testing purposes, you can reset the singleton:
EventStore.reset(); // Clears the singleton instance
// Now you can create a new instance with different configuration
var newInstance = EventStore.getInstance(
config: EventStoreConfig(
collectionName: 'test_logs',
),
);
Complete Examples #
Example 1: E-commerce App #
// Initialize with app metadata
final store = EventStore.getInstance(
config: EventStoreConfig(
collectionName: 'ecommerce_events',
globalParameters: {
'app_version': '2.1.0',
'store_id': 'store_123',
},
minimumLogLevel: EventLevel.info,
),
);
// Track purchase
await store.eventLogger.log("purchase", EventLevel.info, {
"product_id": "prod_456",
"amount": 99.99,
"currency": "USD",
"payment_method": "credit_card",
});
// Track errors
await store.eventLogger.log("payment_failed", EventLevel.error, {
"reason": "insufficient_funds",
"amount": 99.99,
});
Example 2: Analytics Dashboard #
// Production configuration
final store = EventStore.getInstance(
config: EventStoreConfig(
collectionName: 'analytics',
enableLocalLogging: false, // No console spam in production
minimumLogLevel: EventLevel.info,
maxRetries: 5,
retryDelayMs: 2000,
globalParameters: {
'app_name': 'MyApp',
'version': '1.0.0',
'build': '42',
},
onError: (eventName, error) {
// Report to error tracking service
Sentry.captureMessage('Analytics event failed: $eventName');
},
),
);
// Track page views
store.eventLogger.logSync("page_view", EventLevel.info, {
"page": "/dashboard",
"referrer": "/login",
});
// Track feature usage
await store.eventLogger.log("feature_used", EventLevel.info, {
"feature": "export_csv",
"items_count": 1500,
});
Example 3: Debugging & Development #
// Development configuration with maximum visibility
final store = EventStore.getInstance(
config: EventStoreConfig(
collectionName: 'dev_logs',
enableRemoteLogging: false, // Don't pollute production database
enableLocalLogging: true, // See everything in console
minimumLogLevel: EventLevel.debug, // Log everything
),
);
// Debug logging
await store.eventLogger.log("api_request", EventLevel.debug, {
"endpoint": "/api/users",
"method": "GET",
"headers": {"Authorization": "Bearer ***"},
});
await store.eventLogger.log("api_response", EventLevel.debug, {
"status": 200,
"duration_ms": 245,
"data_size": 1024,
});
Example 4: Privacy-Compliant Logging #
// GDPR-compliant configuration
final store = EventStore.getInstance(
config: EventStoreConfig(
collectionName: 'anonymous_events',
includeUserInfo: false, // Don't log user ID or email
globalParameters: {
'session_id': generateAnonymousSessionId(),
'region': 'EU',
},
),
);
// All logs will be anonymous
await store.eventLogger.log("feature_accessed", EventLevel.info, {
"feature": "settings",
"timestamp": DateTime.now().toIso8601String(),
});
Example 5: Offline-First App #
// Configure with retries for spotty connectivity
final store = EventStore.getInstance(
config: EventStoreConfig(
maxRetries: 10,
retryDelayMs: 3000,
onError: (eventName, error) {
// Queue for later retry or show offline indicator
offlineQueue.add(eventName);
},
),
);
// Will retry up to 10 times if network is unavailable
await store.eventLogger.log("user_action", EventLevel.info, {
"action": "create_note",
"note_length": 150,
});
API Reference #
EventStoreConfig #
Constructor parameters for configuring EventStore behavior.
EventStore #
Singleton class that provides access to logging functionality.
Methods:
static EventStore getInstance({EventStoreConfig? config, String? collectionName})- Get singleton instancestatic void reset()- Reset singleton (for testing)
Properties:
eventLogger- Remote and local event loggerlocalEventStore- Local-only event loggerconfig- Current configuration
EventLogger #
Handles logging to Firebase Firestore and local console.
Methods:
Future<LogResult> log(String eventName, EventLevel level, Map<String, dynamic> parameters)- Async loggingvoid logSync(String eventName, EventLevel level, Map<String, dynamic> parameters, {Function(LogResult)? onComplete})- Sync logging
LocalEventLogger #
Handles local console logging only.
Methods:
void log(String eventName, EventLevel level, Map<String, dynamic> parameters, {String? userId})- Log to console
LogResult #
Result object returned from logging operations.
Properties:
bool success- Whether the operation succeededString? error- Error message if failed
Methods:
factory LogResult.success()- Create success resultfactory LogResult.failure(String errorMessage)- Create failure result
EventLevel #
Enum for log severity levels.
Values:
EventLevel.debug- Debug information (lowest priority)EventLevel.trace- Trace execution flowEventLevel.info- Informational messagesEventLevel.warning- Warning messagesEventLevel.error- Error messages (highest priority)
Best Practices #
- Use appropriate log levels: Reserve
errorfor actual errors,infofor business events,debugfor development - Configure minimum log level in production: Set to
EventLevel.infoor higher to reduce noise - Add global parameters: Include app version, environment, and other context in every log
- Handle errors gracefully: Use
onErrorcallback for global error handling - Test with retries: Configure appropriate retry settings based on your network conditions
- Respect user privacy: Disable
includeUserInfofor sensitive applications - Use logSync for non-async contexts: But prefer async
log()when possible for better error handling - Reset in tests: Always call
EventStore.reset()before each test
License #
This project is licensed under the MIT License - see the LICENSE file for details.