"""Per-study logging adapter for the MultiStudyOrchestrator engine.
This module provides :class:`StudyContextLogAdapter`, which enriches every log
record emitted by a worker thread with ``study_key`` and ``studyKey`` fields.
When combined
with a JSON formatter (e.g. :func:`~imednet.utils.json_logging.configure_json_logging`),
each log line carries structured metadata that can be indexed by log
aggregation systems such as Splunk, Datadog, or CloudWatch Logs.
Example JSON output
-------------------
With JSON logging enabled the adapter produces records like::
{
"timestamp": "2024-01-15T10:23:45.123456Z",
"level": "INFO",
"logger": "imednet.orchestration",
"message": "Starting data extraction",
"study_key": "PROT-01",
"studyKey": "PROT-01"
}
Usage::
from imednet.orchestration.logging import make_study_logger
study_logger = make_study_logger("PROT-01")
study_logger.info("Starting data extraction")
# → emits a record with extra={"study_key": "PROT-01", "studyKey": "PROT-01"}
"""
from __future__ import annotations
import logging
from typing import Any, MutableMapping
[docs]class StudyContextLogAdapter(logging.LoggerAdapter):
"""A logger adapter that enriches records with a bound study key."""
[docs] def __init__(self, logger: logging.Logger, study_key: str) -> None:
super().__init__(logger, extra={"study_key": study_key})
self._study_key = study_key
@property
def study_key(self) -> str:
"""The study identifier bound to this adapter."""
return self._study_key
[docs] def process(
self, msg: Any, kwargs: MutableMapping[str, Any]
) -> tuple[Any, MutableMapping[str, Any]]:
"""Inject ``study_key`` and ``studyKey`` into the log record ``extra`` mapping."""
kwargs = dict(kwargs)
extra = dict(kwargs.get("extra", {}))
extra["study_key"] = self._study_key
extra["studyKey"] = self._study_key
kwargs["extra"] = extra
return msg, kwargs
[docs]def make_study_logger(study_key: str) -> StudyContextLogAdapter:
"""Create a :class:`StudyContextLogAdapter` for a study key.
Args:
study_key: The study identifier to bind to log records.
Returns:
A logger adapter that enriches records with the study key.
"""
return StudyContextLogAdapter(logging.getLogger("imednet.orchestration"), study_key)
__all__ = ["StudyContextLogAdapter", "make_study_logger"]