-
-
Notifications
You must be signed in to change notification settings - Fork 7
Internal Logging Mechanism
Squadron provides a sophisticated logging infrastructure that transparently captures log output from worker threads and forwards it to the main thread. This mechanism enables comprehensive observability of both Squadron's internal operations and your custom application logging.
When a worker starts up, Squadron registers a global output listener on the Logger class from the logger package. This listener intercepts all log events generated within the worker thread, regardless of which Logger instance produced them.
This means Squadron captures:
- Squadron's internal logging: Worker lifecycle events, channel operations, request/response communications, errors, etc.
-
Your custom logger output: Any
Loggerinstances you create in your worker service implementations
Captured log events are automatically:
- Serialized in the worker thread
- Sent to the main thread via special response messages
- Deserialized on the main thread
-
Forwarded to the
channelLoggerif one is configured
The forwarding process preserves all log event details:
- Log level (trace, debug, info, warning, error, fatal)
- Message content
- Timestamp
- Error objects
- Stack traces
The channelLogger on the main thread controls which messages are actually logged based on its configured log level. Even if a worker logs at Level.trace, those messages will only appear if the channelLogger is set to Level.trace or Level.all.
This design allows you to:
- Control verbosity from the main thread without modifying worker code
- Dynamically adjust logging levels at runtime
- Use different log levels for different workers or pools
If your worker service creates its own Logger instance, all output from that logger will be automatically forwarded to the main thread:
class MyService implements WorkerService {
final myLogger = Logger(
printer: PrettyPrinter(),
level: Level.debug,
);
void processRequest(String data) {
myLogger.i('Processing request with data: $data');
// ... processing logic ...
myLogger.d('Request completed successfully');
}
@override
late final operations = OperationsMap({
1: (r) => processRequest(r.args[0] as String),
});
}When you configure a channelLogger:
final mainLogger = Logger(
printer: SimplePrinter(),
level: Level.info,
);
final worker = MyServiceWorker();
worker.channelLogger = mainLogger;
await worker.start();All messages from myLogger in the worker will appear in mainLogger on the main thread. In this example, the info message will be logged, but the debug message will be filtered out because mainLogger is set to Level.info.
This mechanism provides a unified logging view where:
- You don't need separate logging infrastructure for worker threads
- All worker output appears in a single logger on the main thread
- You can use familiar logging tools and patterns
- Background thread debugging becomes significantly easier
Log forwarding is asynchronous and non-blocking. The worker thread continues execution immediately after sending a log message without waiting for it to be processed on the main thread.
However, keep in mind:
- Each log message involves serialization and cross-thread communication
- Excessive logging in hot code paths can impact performance
- Consider using appropriate log levels in production environments
Squadron uses a special InternalLogger class for its own internal operations. This logger:
- Logs all events (no filtering at the source)
- Uses a no-op output (doesn't write locally)
- Relies entirely on the forwarding mechanism
- Delegates filtering to the receiving
channelLogger
This design ensures that Squadron's internal logging has minimal overhead in the worker thread while still providing complete observability when needed.
-
Set appropriate log levels: Use
Level.debugorLevel.traceduring development, but considerLevel.infoor higher in production. -
Be mindful of log volume: Logging every iteration of a tight loop can generate thousands of messages and impact performance.
-
Use deferred messages: The logger package supports function-based messages that are only evaluated if the log level permits:
logger.d(() => 'Expensive computation: ${expensiveOperation()}');
-
Leverage log levels appropriately:
-
trace: Very detailed debugging information -
debug: Diagnostic information useful during development -
info: General informational messages -
warning: Warning messages for potentially problematic situations -
error: Error messages for failures -
fatal: Critical errors that may cause termination
-
-
Remember the delay: Log forwarding is asynchronous, so there may be a slight delay between when a log is generated in the worker and when it appears in the
channelLogger.
💖 Support the project! Sponsor d-markey on GitHub.