Logging
The platform provides Rust logging for backtesting and live trading through the standard log crate facade.
The core logger operates in a separate thread and uses a multi-producer single-consumer (MPSC) channel to receive log messages. This moves log output I/O off the calling thread. Message arguments are still formatted on the calling thread before the event is queued.
Logging output is configurable and supports:
- stdout/stderr writer for console output
- file writer for persistent storage of logs
Infrastructure such as Vector can be integrated to collect and aggregate events within your system.
Architecture
The logging subsystem captures events from multiple sources and routes them through an MPSC channel to a dedicated logging thread:
- Python and Nautilus components: Log directly through the Nautilus Logger.
- External
logcrate users: Filtered bystdout_level/fileout_levelinLoggerConfig. - External
tracingcrate users: When enabled, output goes directly to stdout (separate from Nautilus logging), filtered by theRUST_LOGenvironment variable. - Logging thread: NautilusTrader sends all log events through an MPSC channel to a dedicated thread. Thus, log I/O operations do not block the main thread.
Configuration
Logging can be configured by importing the LoggerConfig object.
By default, log events with an INFO LogLevel and higher are written to stdout/stderr.
The following log levels are supported:
OFF- Disable logging.TRACE- Most verbose level.DEBUG- Detailed diagnostic information.INFO- General operational messages.WARNING- Potential issues that do not prevent operation.ERROR- Errors that may affect functionality.
See the LoggerConfig API Reference for further details.
Logging can be configured in the following ways:
- Minimum
LogLevelfor stdout/stderr. - Minimum
LogLevelfor log files. - Maximum size before rotating a log file.
- Maximum number of backup log files to maintain when rotating.
- Automatic log file naming with date or timestamp components, or custom log file name.
- Directory for writing log files.
- Plain text or JSON log file formatting.
- Filtering of individual components by log level.
- ANSI colors in log lines.
- Bypass logging entirely.
- Print Rust config to stdout at initialization.
- Truncate an existing log file on startup (
clear_log_file).
Standard output logging
The stdout/stderr writers write log messages to the console. Set the minimum level with stdout_level.
File logging
Log files are written to the current working directory by default. Settings control file naming and rotation.
Set the log directory and custom file basename with FileWriterConfig.directory and
FileWriterConfig.file_name.
When file logging is enabled, logging initialization fails if the log directory cannot be created or the log
file cannot be opened. Thus, a node with an unusable log path does not start. Rust binaries that initialize
logging lazily from NAUTILUS_LOG report the error and continue with console logging only.
The logging thread flushes the file buffer to the operating system at approximately 100 ms intervals without syncing to disk. Thus, a tailed log file stays current. Blocking console output can delay a flush, and an abrupt process exit can still lose lines that are buffered or queued for the logging thread.
Log file formats:
None(default) - Plain text format with.logextension."json"- JSON format with.jsonlextension, useful for log aggregation tools.
For detailed information about log file naming conventions and rotation behavior, see the Log file rotation and Log file naming convention sections below.
Log file rotation
Rotation behavior depends on both the presence of a size limit and whether a custom file name is provided:
- Size-based rotation:
- Set
FileWriterConfig.file_rotateto a(max_file_size, max_backup_count)tuple, such as(100_000_000, 5)for 100 MB and five backup files. - When writing a log entry would make the current file exceed this size, the file is closed and a new one is created.
- Rotation file names have millisecond resolution. If a rotation resolves to the active path, logging continues to that file, which may briefly exceed the configured maximum size.
- Set
- Date-based rotation (default naming only):
- Applies when
file_rotateandfile_nameare both unset. - On the first write after each UTC date change (midnight), the logger closes the current log file and starts a new one. This creates one file per UTC day.
- Applies when
- No rotation:
- When
file_nameis set withoutfile_rotate, logs continue to append to the same file. - Note: Size-based rotation takes precedence: if both a custom name and size limit are provided, rotation still occurs.
- When
- Backup file management:
- The second value in
file_rotatelimits the total number of rotated files kept. - When this limit is exceeded, the oldest backup files are automatically removed.
- The limit applies to files rotated by the current process. Rotated files from earlier runs are not
removed. Use an external tool such as
logrotatefor retention across restarts.
- The second value in
- Rotation failures:
- If a rotation fails (for example, the new file cannot be opened), logging continues to the current file and rotation is retried at the next size or date boundary.
Log file naming convention
The default naming convention ensures log files are uniquely identifiable and timestamped. The format depends on whether file rotation is enabled:
With file rotation enabled:
- Format:
{trader_id}_{%Y-%m-%d_%H%M%S-%3f}_{instance_id}.{log|jsonl} - Example:
TESTER-001_2025-04-09_210721-521_d7dc12c8-7008-4042-8ac4-017c3db0fc38.log - Components:
{trader_id}: The trader identifier (for example,TESTER-001).{%Y-%m-%d_%H%M%S-%3f}: UTC datetime with millisecond resolution.{instance_id}: A unique instance identifier.{log|jsonl}: File suffix based on format setting.
Without size-based rotation (default naming):
- Format:
{trader_id}_{%Y-%m-%d}_{instance_id}.{log|jsonl} - Example:
TESTER-001_2025-04-09_d7dc12c8-7008-4042-8ac4-017c3db0fc38.log - Components:
{trader_id}: The trader identifier.{%Y-%m-%d}: Date only (YYYY-MM-DD).{instance_id}: A unique instance identifier.{log|jsonl}: File suffix based on format setting.
- Note: With default naming and no size limit, logs rotate daily at UTC midnight.
Custom naming:
If file_name is set (for example, my_custom_log):
- With rotation disabled: The file will be named exactly as provided (for example,
my_custom_log.log). - With rotation enabled: The file will include the custom name and timestamp (for example,
my_custom_log_2025-04-09_210721-521.log). - An extension in
file_nameis replaced by the format suffix, somy_custom_log.logproduces the same names asmy_custom_log.
Component log filtering
The component_levels parameter sets log levels for individual components.
The input value should be a dictionary of component ID strings to log level strings: dict[str, str].
Below is an example of a trading node logging configuration that includes some of the options mentioned above:
from nautilus_trader.common import LogLevel
from nautilus_trader.config import FileWriterConfig
from nautilus_trader.config import LoggerConfig
from nautilus_trader.config import LiveNodeConfig
from nautilus_trader.model import TraderId
config_node = LiveNodeConfig(
trader_id=TraderId.from_str("TESTER-001"),
logging=LoggerConfig(
stdout_level=LogLevel.INFO,
fileout_level=LogLevel.DEBUG,
component_levels={"Portfolio": "INFO"},
file_config=FileWriterConfig(file_format="json"),
),
)For backtesting, the BacktestEngineConfig class can be used instead of LiveNodeConfig, as the same options are available.
Environment variable configuration
The NAUTILUS_LOG environment variable provides an alternative way to configure logging using a semicolon-separated spec string. This is useful for Rust-only binaries or when you want to override logging settings without modifying code.
export NAUTILUS_LOG="stdout=Info;fileout=Debug;RiskEngine=Error;is_colored"Supported keys:
| Key | Type | Description |
|---|---|---|
stdout | Log level | Maximum level for stdout output. |
fileout | Log level | Maximum level for file output. |
is_colored | Flag | Enable ANSI colors (default: true). |
print_config | Flag | Print config to stdout at startup. |
log_components_only | Flag | Only log components with explicit filters. |
<Component> | Log level | Component-specific level (exact match). |
<module::path> | Log level | Module-specific level (prefix match, Rust only). |
Flags are enabled by their presence in the spec string (no value needed). Log levels are case-insensitive: Off, Trace, Debug, Info, Warn, Error.
For Rust-only binaries, the logging subsystem initializes lazily on first use. Setting
NAUTILUS_LOG configures it without requiring explicit init_logging() calls.
Components-only logging
When focusing on a subset of noisy systems, enable log_components_only to log messages only from
components listed in component_levels. All other components are suppressed regardless of the
global stdout or file level.
Example (Python configuration):
logging = LoggerConfig(
stdout_level=LogLevel.INFO,
component_levels={
"RiskEngine": "DEBUG",
"Portfolio": "INFO",
},
log_components_only=True,
)If you use the Rust spec string through the environment, include log_components_only alongside component
filters, for example:
export NAUTILUS_LOG="stdout=Info;log_components_only;RiskEngine=Debug;Portfolio=Info"Module path filtering (Rust only)
When using the NAUTILUS_LOG environment variable, you can filter by Rust module paths in addition to component names. Keys containing :: are treated as module path filters with prefix matching, while keys without :: are component filters with exact matching.
# Filter all OKX adapter modules to Warn, but allow Debug for the websocket modules
export NAUTILUS_LOG="stdout=Info;nautilus_okx::=Warn;nautilus_okx::websocket=Debug"The longest matching prefix takes precedence. In the example above, nautilus_okx::websocket::handler would use the Debug level (longer prefix), while nautilus_okx::data would use Warn.
Rust log macros automatically capture the module path when no explicit component is provided. This enables module-level filtering to work with standard logging calls.
Module path filtering is only available through the NAUTILUS_LOG environment variable. The Python
component_levels configuration uses component name matching only.
If log_components_only=True (or log_components_only is present in the spec string) and
component_levels is empty, no log messages will be emitted to stdout/stderr or files. Add at
least one component filter or disable components-only logging.
Log colors
ANSI color codes improve log readability in terminals. In environments that do not support ANSI color rendering (such as some cloud environments or text editors), these color codes may not be appropriate as they can appear as raw text.
Set LoggerConfig.is_colored=False for these environments.
Python callback exceptions
Python callback errors include a traceback and chained exceptions. If traceback formatting fails, reporting falls back to the exception type and message.
Python strategy and execution algorithm order and position callbacks log failures at ERROR, with the
component identity and callback name. Strategy market-exit callbacks use the same reporting. Python data and
timer callback errors also reach ERROR. Those records use the emitting Rust module as their component.
For these event callbacks, an exception interrupts that Python invocation. By default, the runtime continues dispatching events. It does not roll back work the callback completed before raising.
To request a normal shutdown after an error, enable shutdown_on_error in the backtest
engine or live node
configuration. The request takes effect when the runtime next checks for shutdown, rather than interrupting
the current event dispatch.
Using a logger directly
You can initialize and use Logger objects directly anywhere, much like Python's built-in logging API.
Without an object that initializes a NautilusKernel and logging (such as BacktestEngine or LiveNode),
activate logging as follows:
from nautilus_trader.common import init_logging
from nautilus_trader.common import Logger
from nautilus_trader.common import LogLevel
from nautilus_trader.core import UUID4
from nautilus_trader.model import TraderId
log_guard = init_logging(
trader_id=TraderId.from_str("TESTER-001"),
instance_id=UUID4(),
level_stdout=LogLevel.INFO,
)
logger = Logger("MyLogger")See the init_logging API Reference for further details.
Keep the returned LogGuard alive for as long as direct logging is needed. The logging subsystem
supports up to 255 concurrent guards.
LogGuard: managing log lifecycle
init_logging returns a LogGuard that tracks one user of the process-global logging subsystem.
BacktestEngine and LiveNode own their guards internally, so application code does not need to
acquire a guard from an engine or node.
Reference counting implementation
The logging system uses reference counting to track active LogGuard instances:
- Counter increments: When a new
LogGuardis created, an atomic counter is incremented. - Counter decrements: When a
LogGuardis dropped, the counter is decremented. - Last guard: When the counter reaches zero, pending file logs are flushed and synced. The process-global logging thread stays available for later guards.
- Maximum guards: The system supports up to 255 concurrent
LogGuardinstances. Attempting to create more raises aValueErrorfrominit_logging, or aRuntimeErrorfrom engine or node creation.
Abrupt termination can still lose buffered logs. Dispose engines and nodes normally, and keep the guard
returned by direct init_logging calls until the application no longer needs logging.
Tracing subscriber for external Rust libraries
External Rust crates that use the tracing crate can have their log output displayed by enabling
the tracing subscriber. This is useful for debugging external dependencies or when integrating
custom Rust components (such as feature extractors or adapters) compiled as separate PyO3 extensions.
Enabling the subscriber
Initialize the tracing subscriber directly:
from nautilus_trader.common import init_tracing
init_tracing()Filtering with RUST_LOG
The RUST_LOG environment variable controls which tracing events are displayed:
# Show debug logs from your crate, warn and above from hyper
RUST_LOG=my_feature_extractor=debug,hyper=warn python my_script.pyIf RUST_LOG is not set, the default filter level is warn.
How it works
The tracing subscriber uses a tracing-subscriber fmt layer with a custom formatter to output
directly to stdout. This is separate from the Nautilus logging infrastructure - tracing output
uses a Nautilus-aligned format with nanosecond timestamps.
Example tracing output:
2026-01-24T05:51:42.809619000Z [DEBUG] hyper_util::client::legacy::connect::http: connecting to 104.18.5.240:443
2026-01-24T05:51:42.810543000Z [DEBUG] hyper_util::client::legacy::pool: pooling idle connection for ("https", api.example.com)Differences from Nautilus logging:
- Tracing output goes directly to stdout, not through the Nautilus logging thread.
- Tracing events are not written to Nautilus log files.
- Filtering is controlled exclusively by
RUST_LOG, independent ofLoggerConfig.
For external libraries that use the log crate (such as rustls), their events go through
the Nautilus logger and are filtered by stdout_level/fileout_level in LoggerConfig.
RUST_LOG only affects crates using tracing. For crates using log, configure verbosity through
LoggerConfig or the NAUTILUS_LOG environment variable (for example, NAUTILUS_LOG=stdout=Debug).
The tracing subscriber can only be initialized once per process. A second init_tracing() call
raises an error.
Platform-specific considerations
Windows shutdown behavior
On Windows, non-deterministic garbage collection during interpreter shutdown can occasionally
delay the final LogGuard drop until after interpreter teardown has begun. Dropping the last
guard is what flushes and syncs pending file logs, so a delayed drop can result in truncated
logs.
Related guides
- Architecture - System architecture including logging infrastructure.
Configuration
NautilusTrader uses typed configuration objects for data clients, execution clients, engines, and strategies. Higher-level configs compose these component...
Backtesting
Backtesting simulates trading against historical data with the same core components as live trading. These components include built-in engines, the Cache...