The logging system provides consistent, context-aware logging throughout the backtest-kit framework. It automatically enriches log messages with execution context (symbol, timestamp, mode) and method context (strategy name, exchange name, frame name) without requiring explicit parameter passing. The system uses a pluggable architecture that defaults to silent operation and allows users to configure custom logger implementations.
For information about the broader dependency injection architecture, see Dependency Injection System. For context propagation mechanisms, see Context Propagation.
The logging system consists of three primary components: the ILogger interface defining the contract, the LoggerService implementation providing context-aware logging, and the context services that supply metadata automatically.
The ILogger interface defines the contract for all logger implementations. It provides four severity levels with consistent method signatures.
| Method | Severity | Purpose |
|---|---|---|
log() |
General | Record significant events or state changes |
debug() |
Debug | Detailed diagnostic information for troubleshooting |
info() |
Info | Informational updates about system activity |
warn() |
Warning | Potentially problematic situations requiring attention |
All methods follow the same signature pattern:
method(topic: string, ...args: any[]): void
The interface is intentionally simple, allowing any logging library (Winston, Pino, Bunyan, console) to be adapted with minimal wrapper code.
LoggerService is the core logging implementation that wraps user-provided loggers and automatically enriches messages with context metadata.
| Responsibility | Implementation |
|---|---|
| Logger Delegation | Forwards all calls to user-configured logger via _commonLogger |
| Context Injection | Automatically appends methodContext and executionContext to every log call |
| Default Behavior | Uses NOOP_LOGGER when no custom logger configured |
| DI Integration | Injects MethodContextService and ExecutionContextService |
The framework defaults to NOOP_LOGGER, a silent logger that discards all messages. This design ensures the framework operates without logging overhead unless explicitly configured.
// Implementation discards all messages
const NOOP_LOGGER: ILogger = {
log() { void 0; },
debug() { void 0; },
info() { void 0; },
warn() { void 0; },
};
LoggerService automatically appends context metadata to every log call without requiring explicit parameter passing. This is the system's most important feature.
Method context provides information about which strategy, exchange, and frame are currently executing. Retrieved from MethodContextService using scoped DI.
| Property | Type | Description |
|---|---|---|
strategyName |
string | Name of the active strategy |
exchangeName |
string | Name of the active exchange |
frameName |
string | Name of the active timeframe generator |
Context availability is checked via MethodContextService.hasContext() before retrieval. If no context exists, an empty object is appended.
Execution context provides information about the current execution state, including trading pair, timestamp, and mode. Retrieved from ExecutionContextService using scoped DI.
| Property | Type | Description |
|---|---|---|
symbol |
string | Trading pair (e.g., "BTCUSDT") |
when |
number | Current timestamp in milliseconds |
backtest |
boolean | True if backtesting, false if live trading |
Context availability is checked via ExecutionContextService.hasContext() before retrieval. If no context exists, an empty object is appended.
Users configure custom loggers by calling setLogger() on the LoggerService instance. This replaces the default NOOP_LOGGER.
import backtest from 'backtest-kit';
// Configure custom logger
backtest.loggerService.setLogger({
log: (topic, ...args) => console.log(`[LOG] ${topic}`, ...args),
debug: (topic, ...args) => console.debug(`[DEBUG] ${topic}`, ...args),
info: (topic, ...args) => console.info(`[INFO] ${topic}`, ...args),
warn: (topic, ...args) => console.warn(`[WARN] ${topic}`, ...args),
});
The ILogger interface can wrap any logging library. Example with Winston:
import winston from 'winston';
import backtest from 'backtest-kit';
const winstonLogger = winston.createLogger({
level: 'debug',
format: winston.format.json(),
transports: [new winston.transports.Console()],
});
backtest.loggerService.setLogger({
log: (topic, ...args) => winstonLogger.log('info', topic, ...args),
debug: (topic, ...args) => winstonLogger.debug(topic, ...args),
info: (topic, ...args) => winstonLogger.info(topic, ...args),
warn: (topic, ...args) => winstonLogger.warn(topic, ...args),
});
LoggerService is registered in the DI container and injected into services throughout the framework.
The logger is registered as a singleton in the DI container during framework initialization:
TYPES.loggerService in src/lib/core/types.ts:2provide(TYPES.loggerService, () => new LoggerService()) in src/lib/core/provide.ts:25backtest.loggerService in src/lib/index.ts:30Services throughout the framework inject LoggerService using the standard DI pattern:
private readonly loggerService = inject<LoggerService>(TYPES.loggerService);
This pattern appears in:
Services log using the injected LoggerService instance. Context is automatically appended:
// In BacktestLogicPrivateService
await this.loggerService.info(
'backtest-start',
{ symbol, totalTimeframes: timeframes.length }
);
The resulting log output includes automatic context:
topic: "backtest-start"
args: [
{ symbol: "BTCUSDT", totalTimeframes: 1000 },
{ strategyName: "momentum", exchangeName: "binance", frameName: "daily" },
{ symbol: "BTCUSDT", when: 1704067200000, backtest: true }
]
Different severity levels serve different purposes:
| Level | Use Case | Example |
|---|---|---|
log() |
General events | Signal state changes, execution milestones |
debug() |
Diagnostic details | Validation results, intermediate calculations |
info() |
Operational updates | Successful completions, configuration changes |
warn() |
Potential issues | Missing data, unexpected conditions |
All LoggerService methods return Promise<void>, allowing asynchronous logger implementations:
// Logger can perform async operations
public log = async (topic: string, ...args: any[]) => {
await this._commonLogger.log(
topic,
...args,
this.methodContext,
this.executionContext
);
};
This design supports remote logging services, database writes, or file I/O without blocking execution.
The following table shows which framework services inject LoggerService:
| Service Layer | Services Using Logger |
|---|---|
| Logic Services | BacktestLogicPrivateService, BacktestLogicPublicService, LiveLogicPrivateService, LiveLogicPublicService |
| Global Services | StrategyGlobalService, ExchangeGlobalService, FrameGlobalService, LiveGlobalService, BacktestGlobalService |
| Connection Services | StrategyConnectionService, ExchangeConnectionService, FrameConnectionService |
| Schema Services | StrategySchemaService, ExchangeSchemaService, FrameSchemaService |
| Markdown Services | BacktestMarkdownService, LiveMarkdownService |
This pervasive integration ensures consistent logging behavior across all framework components.