Logging#
OpenSTEF uses Python’s standard logging library and attaches no handlers
by default. You stay in control: configure logging in your application before
importing OpenSTEF modules and choose your own format, destinations, and levels.
This page covers the logger naming convention, what each level emits, and common configuration patterns for development and production.
The Default Behavior#
Each top-level OpenSTEF package installs a logging.NullHandler on its
root logger at import time. Until you configure logging yourself, OpenSTEF
produces no output, not even uncaught warnings. This is the standard practice
for Python libraries: the application, not the library, decides where logs go.
To start seeing output, configure logging once at the start of your script, notebook, or service entry point:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(name)s | %(levelname)s | %(message)s",
)
# Subsequent imports will produce log output
from openstef_models.presets import create_forecasting_workflow
Logger Naming#
OpenSTEF loggers follow Python’s hierarchical naming, with one logger per module. The three top-level prefixes correspond to the three packages:
Logger prefix |
What it covers |
|---|---|
|
Dataset construction, validation, type coercion |
|
Model fitting and prediction, transform pipelines, workflow callbacks, MLflow integration |
|
Backtesting, evaluation, benchmarking, analysis pipelines |
Because loggers are hierarchical, you can configure each subsystem independently.
For example, logging.getLogger("openstef_beam").setLevel(logging.WARNING)
quiets only the backtesting/evaluation chatter, leaving model training logs at
their previous level.
What You’ll See at Each Level#
Level |
Typical messages |
|---|---|
|
Workflow lifecycle events. “Created MLflow run X for model Y.” “Stored trained model for run Z.” “Skipping model fitting: <reason>.” |
|
Degraded but recoverable conditions. “No validation metrics found in fit results. Skipping performance evaluation.” Missing optional columns, dropped empty features, weight-normalization edge cases. |
|
Unrecoverable failures that prevent a stage from completing. |
|
Verbose internal state. Off by default; enable per-subsystem only when investigating a specific issue. |
OpenSTEF is conservative about INFO chatter: a successful fit()
followed by predict() typically emits only a handful of lines. If a
subsystem feels noisy, raise its level rather than tuning the global root.
Common Configurations#
Minimal setup#
For most local development and one-off scripts:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(name)s | %(levelname)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
Quieting noisy subsystems#
When you want to keep training logs but silence backtest iteration progress,
configure the package-level loggers after basicConfig:
import logging
logging.basicConfig(level=logging.INFO)
# Keep openstef_models at INFO, drop openstef_beam to WARNING
logging.getLogger("openstef_beam").setLevel(logging.WARNING)
# Or drill in further: only the MLflow callback at DEBUG
logging.getLogger(
"openstef_models.integrations.mlflow.mlflow_storage_callback"
).setLevel(logging.DEBUG)
Notebooks#
In Jupyter, basicConfig is sometimes a no-op because the kernel has already
attached a handler to the root logger. Pass force=True to replace it:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(name)-25s | %(levelname)-8s | %(message)s",
force=True,
)
Production: structured logs#
OpenSTEF emits unstructured log records via stdlib logging: no JSON fields, no contextvars, no opinions about your log shipping pipeline. If you want structured output (e.g., one JSON object per line, fields parsed by your log aggregator), wrap the root logger using a structured-logging library.
structlog is a well-maintained option that
integrates cleanly with stdlib logging. Use its
structlog.stdlib.LoggerFactory adapter so OpenSTEF’s loggers flow through
structlog’s processors:
import logging
import structlog
structlog.configure(
processors=[
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
],
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
logging.basicConfig(format="%(message)s", level=logging.INFO)
OpenSTEF does not require any specific structured-logging library; adapt the standard logger hierarchy described above to whatever your stack uses.
Troubleshooting#
No log output appearing#
If you call OpenSTEF functions and see nothing:
Check that you actually called
logging.basicConfig()(or set up handlers another way) before the OpenSTEF call.In notebooks, retry with
logging.basicConfig(..., force=True)to override the kernel’s preconfigured root handler.Inspect the logger state directly:
import logging logger = logging.getLogger("openstef_models") print("level:", logger.level, "effective:", logger.getEffectiveLevel()) print("handlers:", logger.handlers) print("root handlers:", logging.getLogger().handlers)
Too much log output#
OpenSTEF subsystems can be quieted individually:
import logging
logging.getLogger("openstef_models").setLevel(logging.WARNING)
logging.getLogger("openstef_beam").setLevel(logging.WARNING)
If a specific module is the culprit (e.g., a transform that warns on every
batch), set WARNING or ERROR on that module’s logger by its full
dotted name.
See also
Deployment for callback-based monitoring hooks in production deployments.