Skip to main content

mellea.core.utils

Logging utilities for the mellea core library.

Provides MelleaLogger, a singleton logger with colour-coded console output, an optional rotating file handler, and optional OTLP / webhook forwarding. All internal mellea modules obtain their logger via MelleaLogger.get_logger().

Handler setup is performed by :func:configure_logging, which is called automatically on the first :meth:MelleaLogger.get_logger invocation.

Environment variables

MELLEA_LOGS_ENABLED Master switch for all logging handlers. Set to false / 0 / no to suppress all handlers (useful in test environments). Defaults to true. MELLEA_LOGS_LEVEL Minimum log level name (e.g. DEBUG, INFO, WARNING). Defaults to INFO. MELLEA_LOGS_JSON Set to any truthy value (1, true, yes) to emit structured JSON instead of colour-coded human-readable text. Applies to both the console and file handlers. MELLEA_LOGS_CONSOLE Set to false / 0 / no to disable the console (stdout) handler. Defaults to true. MELLEA_LOGS_FILE Absolute or relative path for rotating file output (e.g. /var/log/mellea.log). When unset no file handler is attached. MELLEA_LOGS_FILE_MAX_BYTES Maximum size in bytes before the log file is rotated. Defaults to 10485760 (10 MB). MELLEA_LOGS_FILE_BACKUP_COUNT Number of rotated backup files to keep. Defaults to 5. MELLEA_LOGS_OTLP Set to true / 1 / yes to export logs via OpenTelemetry Logs Protocol. Requires opentelemetry-sdk and an OTLP endpoint configured via OTEL_EXPORTER_OTLP_LOGS_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT. MELLEA_LOGS_WEBHOOK HTTP(S) URL to forward log records to via HTTP POST. When set a :class:RESTHandler is attached.

Functions

FUNC set_log_context

set_log_context(**fields: Any) -> None

Inject extra fields into every log record emitted from this coroutine or thread.

Call this at the start of a request or task to attach identifiers such as trace_id or request_id without modifying individual log calls.

.. note:: Prefer :func:log_context as the primary API — it guarantees cleanup (including restoring outer values on same-key nesting) even on exceptions.

Args:

  • **fields: Arbitrary key-value pairs to include in log records.

Raises:

  • ValueError: If any key clashes with a standard logging.LogRecord attribute (e.g. levelname, module, thread).

FUNC clear_log_context

clear_log_context() -> None

Remove all context fields set by :func:set_log_context for this coroutine/thread.

FUNC log_context

log_context(**fields: Any) -> Generator[None, None, None]

Context manager that injects fields for the duration of the block.

On exit — including on exceptions — the context is restored to its state before the block via a ContextVar token. This is safe for both nested usage and concurrent asyncio tasks: each asyncio.Task owns an isolated copy of the context variable, so coroutines running on the same event-loop thread cannot overwrite each other's fields.

Example::

with log_context(request_id="req-1", user_id="u-42"): logger.info("Handling request") # both IDs appear here logger.info("After request") # IDs are gone

Args:

  • **fields: Key-value pairs to inject. Same restrictions as :func:set_log_context — reserved LogRecord attribute names are rejected with ValueError.

Raises:

  • ValueError: If any key clashes with a reserved LogRecord attribute.

FUNC configure_logging

configure_logging(logger: logging.Logger) -> None

Attach log handlers to logger based on current environment variables.

It always appends new handlers (the caller is responsible for clearing existing ones before re-configuring). It is invoked automatically on the first call to :meth:MelleaLogger.get_logger; subsequent calls return the same singleton logger with its handlers already in place. It is also available for programmatic use when you need to attach handlers to a custom logger.

When MELLEA_LOGS_ENABLED is falsy no handlers are attached; the logger still exists and accepts records, but they are silently discarded.

If MELLEA_LOGS_FILE is set but the path cannot be opened (e.g. due to a permissions error), a :class:UserWarning is emitted and file logging is skipped. The remaining handlers are still attached and the application continues normally.

Args:

  • logger: The :class:logging.Logger to configure.

Classes

CLASS ContextFilter

Logging filter that injects async-safe ContextVar fields into every record.

Fields registered via :func:set_log_context are copied onto the logging.LogRecord before formatters see it, enabling trace/request IDs to appear in structured output without touching call sites.

Methods:

FUNC filter

filter(self, record: logging.LogRecord) -> bool

Attach async-safe ContextVar fields to record and allow it through.

Args:

  • record: The log record being processed.

Returns:

  • Always True — the record is never suppressed.

CLASS OtelTraceFilter

Logging filter that injects the current OpenTelemetry trace context into log records.

Adds trace_id and span_id attributes (hex strings) to every LogRecord when an active span exists. When OpenTelemetry is not installed the filter is a true no-op: it adds no attributes and takes no branches, so there is zero overhead on the hot logging path. Formatters use hasattr / getattr to handle the absent attributes gracefully.

Methods:

FUNC filter

filter(self, record: logging.LogRecord) -> bool

Adds trace_id and span_id to the log record from the current OTel span.

No-op when OpenTelemetry is not installed or when there is no active span.

Args:

  • record: The log record to enrich.

Returns:

  • Always True — the record is never suppressed.

CLASS RESTHandler

Logging handler that forwards records to an HTTP endpoint unconditionally.

Attach this handler only when a webhook URL is configured; it sends every record it receives. Use :func:configure_logging or :meth:MelleaLogger.get_logger to obtain a pre-configured instance.

Failures are silently suppressed to avoid disrupting the application.

Args:

  • api_url: The URL of the REST endpoint that receives log records.
  • method: HTTP method to use when sending records (default "POST").
  • headers: HTTP headers to send; defaults to \{"Content-Type"\: "application/json"\} when None.

Methods:

FUNC emit

emit(self, record: logging.LogRecord) -> None

Forward record to the configured REST endpoint.

Silently suppresses any network or HTTP errors to avoid disrupting the application.

Args:

  • record: The log record to forward.

CLASS JsonFormatter

Logging formatter that serialises log records as structured JSON strings.

Produces a consistent JSON schema with a fixed set of core fields. Additional fields can be injected at construction time (extra_fields) or dynamically per-thread via :func:set_log_context / :class:ContextFilter. Includes trace_id and span_id when OpenTelemetry tracing is active.

Args:

  • timestamp_format: strftime format for the timestamp field. Defaults to ISO-8601 ("%Y-%m-%dT%H\:%M\:%S").
  • include_fields: Whitelist of core field names to keep. When None all core fields are included. Note: this filter applies only to the fields listed in _DEFAULT_FIELDS; extra_fields passed to the constructor and dynamic context fields (set via :func:set_log_context) are always included regardless of this setting.
  • exclude_fields: Set of core field names to drop. Applied after include_fields.
  • extra_fields: Static key-value pairs merged into every log record.

Attributes:

  • _DEFAULT_FIELDS: Canonical ordered list of core field names produced by this formatter.

Methods:

FUNC format_as_dict

format_as_dict(self, record: logging.LogRecord) -> dict[str, Any]

Return the log record as a dictionary (public API for external callers).

Equivalent to :meth:_build_log_dict but part of the public interface so handlers and other callers do not need to reach into private methods. Includes trace_id and span_id when OpenTelemetry tracing is active.

Args:

  • record: The log record to convert.

Returns:

  • A dictionary ready for JSON serialisation.

FUNC format

format(self, record: logging.LogRecord) -> str

Formats a log record as a JSON string.

Core fields are filtered by include_fields / exclude_fields. Static extra_fields and any per-task ContextVar fields (set via :func:set_log_context) are merged in after the core fields.

Args:

  • record: The log record to format.

Returns:

  • A JSON-serialised log record.

CLASS CustomFormatter

A nice custom formatter copied from Sergey Pleshakov's post on StackOverflow.

Attributes:

  • cyan: ANSI escape code for cyan text, used for DEBUG messages.
  • grey: ANSI escape code for grey text, used for INFO messages.
  • yellow: ANSI escape code for yellow text, used for WARNING messages.
  • red: ANSI escape code for red text, used for ERROR messages.
  • bold_red: ANSI escape code for bold red text, used for CRITICAL messages.
  • reset: ANSI escape code to reset text colour.
  • FORMATS: Mapping from logging level integer to the colour-formatted format string.

Methods:

FUNC format

format(self, record: logging.LogRecord) -> str

Formats a log record using a colour-coded ANSI format string based on the record's log level.

Appends [trace_id=… span_id=…] when OtelTraceFilter has populated those fields on the record and a trace is active.

Args:

  • record: The log record to format.

Returns:

  • The formatted log record string with ANSI colour codes applied.

CLASS MelleaLogger

Singleton logger with colour-coded console output and configurable handlers.

Obtain the shared logger instance via MelleaLogger.get_logger(). Log level defaults to INFO but can be overridden via MELLEA_LOGS_LEVEL. Handler setup is delegated to :func:configure_logging.

Attributes:

  • logger: The shared logging.Logger instance; None until first call to get_logger().
  • CRITICAL: Numeric level for critical log messages (50).
  • FATAL: Alias for CRITICAL (50).
  • ERROR: Numeric level for error log messages (40).
  • WARNING: Numeric level for warning log messages (30).
  • WARN: Alias for WARNING (30).
  • INFO: Numeric level for informational log messages (20).
  • DEBUG: Numeric level for debug log messages (10).
  • NOTSET: Numeric level meaning no level is set (0).

Methods:

FUNC get_logger

get_logger() -> logging.Logger

Return the shared :class:logging.Logger, creating it on first call.

The logger is created once (singleton). Subsequent calls return the cached instance. Initialisation is protected by a module-level lock so concurrent callers at startup cannot create duplicate handlers.

When MELLEA_LOGS_ENABLED is falsy :func:configure_logging attaches no handlers — the logger still exists, but records are silently discarded (useful for tests or environments that must produce no output).

Returns:

  • Configured logger instance.