> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mellea.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# mellea.core.utils

> Logging utilities for the mellea core library.

export const SidebarFix = () => <script dangerouslySetInnerHTML={{
  __html: `
        (function () {
          const INTERVAL_MS = 500;

          const upgradeSidebar = () => {
            const links = document.querySelectorAll('a[href^="#"]');

            links.forEach((link) => {
              if (link.dataset.badged === "true") return;

              const rawText = (link.textContent || "").trim();

              // ========== FUNC ==========
              if (rawText.startsWith("FUNC ")) {
                const label = rawText.replace(/^FUNC\\s+/, "").trim();

                while (link.firstChild) link.removeChild(link.firstChild);

                // 👉 Make the whole link a single flex row & prevent wrapping
                link.style.display = "flex";
                link.style.alignItems = "center";
                link.style.whiteSpace = "nowrap";
                link.style.columnGap = "0.5rem";

                const badge = document.createElement("span");
                badge.style.marginRight = "0.5rem";
                badge.style.display = "inline-flex";
                badge.style.alignItems = "center";
                badge.style.borderRadius = "9999px";
                badge.style.padding = "0rem 0.6rem";
                badge.style.fontSize = "0.5rem";
                badge.style.fontWeight = "700";
                badge.style.letterSpacing = "0.05em";
                badge.style.backgroundColor = "rgba(48, 100, 227, 0.20)";
                badge.style.color = "#1D4ED8";

                badge.textContent = "FUNC";

                link.appendChild(badge);
                link.appendChild(document.createTextNode(label));
                link.dataset.badged = "true";
                return;
              }

              // ========== CLASS ==========
              if (rawText.startsWith("CLASS ")) {
                const label = rawText.replace(/^CLASS\\s+/, "").trim();

                while (link.firstChild) link.removeChild(link.firstChild);

                // 👉 Same flex / nowrap treatment for class links
                link.style.display = "flex";
                link.style.alignItems = "center";
                link.style.whiteSpace = "nowrap";
                link.style.columnGap = "0.5rem";

                const badge = document.createElement("span");
                badge.style.marginRight = "0.5rem";
                badge.style.display = "inline-flex";
                badge.style.alignItems = "center";
                badge.style.borderRadius = "9999px";
                badge.style.padding = "0rem 0.6rem";
                badge.style.fontSize = "0.5rem";
                badge.style.fontWeight = "700";
                badge.style.letterSpacing = "0.05em";
                badge.style.backgroundColor = "rgba(74, 222, 128, 0.20)";
                badge.style.color = "#15803D";

                badge.textContent = "CLASS";

                link.appendChild(badge);
                link.appendChild(document.createTextNode(label));
                link.dataset.badged = "true";
                return;
              }
            });
          };

          upgradeSidebar();
          setInterval(upgradeSidebar, INTERVAL_MS);
        })();
      `
}} />;

<SidebarFix />

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_LOG_ENABLED`
Master switch for all logging handlers.  Set to `false` / `0` / `no` to
suppress all handlers (useful in test environments).  Defaults to `true`.
`MELLEA_LOG_LEVEL`
Minimum log level name (e.g. `DEBUG`, `INFO`, `WARNING`).  Defaults to
`INFO`.
`MELLEA_LOG_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_LOG_CONSOLE`
Set to `false` / `0` / `no` to disable the console (stdout) handler.
Defaults to `true`.
`MELLEA_LOG_FILE`
Absolute or relative path for rotating file output (e.g.
`/var/log/mellea.log`).  When unset no file handler is attached.
`MELLEA_LOG_FILE_MAX_BYTES`
Maximum size in bytes before the log file is rotated.  Defaults to
`10485760` (10 MB).
`MELLEA_LOG_FILE_BACKUP_COUNT`
Number of rotated backup files to keep.  Defaults to `5`.
`MELLEA_LOG_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_LOG_ENDPOINT` or `OTEL_EXPORTER_OTLP_ENDPOINT`.
`MELLEA_LOG_WEBHOOK`
HTTP(S) URL to forward log records to via HTTP POST.  When set a
:class:`RESTHandler` is attached.  Supersedes the deprecated `MELLEA_FLOG`
and `FLOG` variables.
`MELLEA_FLOG`
Deprecated alias for `MELLEA_LOG_WEBHOOK`.  Activates a `RESTHandler`
pointed at `http://localhost:8000/api/receive`.

## Functions

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#3064E3]/20 text-[#1D4ED8]">FUNC</span> `set_log_context` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L110" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python theme={null}
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`).

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#3064E3]/20 text-[#1D4ED8]">FUNC</span> `clear_log_context` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L137" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python theme={null}
clear_log_context() -> None
```

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

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#3064E3]/20 text-[#1D4ED8]">FUNC</span> `log_context` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L143" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python theme={null}
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.

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#3064E3]/20 text-[#1D4ED8]">FUNC</span> `configure_logging` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L555" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python theme={null}
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_LOG_ENABLED` is falsy no handlers are attached; the logger
still exists and accepts records, but they are silently discarded.

If `MELLEA_LOG_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.

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

## Classes

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#4ADE8033]/20 text-[#15803D]">CLASS</span> `ContextFilter` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

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.

<div className="h-8" />

**Methods:**

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

#### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#3064E3]/20 text-[#1D4ED8]">FUNC</span> `filter` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L190" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python theme={null}
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.

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#4ADE8033]/20 text-[#15803D]">CLASS</span> `OtelTraceFilter` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L205" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

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.

<div className="h-8" />

**Methods:**

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

#### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#3064E3]/20 text-[#1D4ED8]">FUNC</span> `filter` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python theme={null}
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.

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#4ADE8033]/20 text-[#15803D]">CLASS</span> `RESTHandler` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L234" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

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`.

<div className="h-8" />

**Methods:**

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

#### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#3064E3]/20 text-[#1D4ED8]">FUNC</span> `emit` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L259" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python theme={null}
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.

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#4ADE8033]/20 text-[#15803D]">CLASS</span> `JsonFormatter` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L285" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

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.

<div className="h-8" />

**Methods:**

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

#### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#3064E3]/20 text-[#1D4ED8]">FUNC</span> `format_as_dict` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L347" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python theme={null}
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.

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

#### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#3064E3]/20 text-[#1D4ED8]">FUNC</span> `format` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L430" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python theme={null}
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.

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#4ADE8033]/20 text-[#15803D]">CLASS</span> `CustomFormatter` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L446" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

A nice custom formatter copied from [Sergey Pleshakov's post on StackOverflow](https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output).

**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.

<div className="h-8" />

**Methods:**

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

#### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#3064E3]/20 text-[#1D4ED8]">FUNC</span> `format` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L475" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python theme={null}
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.

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#4ADE8033]/20 text-[#15803D]">CLASS</span> `MelleaLogger` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L633" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

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_LOG_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).

<div className="h-8" />

**Methods:**

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />

#### <span className="ml-2 inline-flex items-center rounded-full px-2 py-1 text-[0.7rem] font-bold tracking-wide bg-[#3064E3]/20 text-[#1D4ED8]">FUNC</span> `get_logger` <sup><a href="https://github.com/generative-computing/mellea/blob/v0.6.0/mellea/core/utils.py#L680" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>

```python theme={null}
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_LOG_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.

<div className="w-full h-px bg-gray-200 dark:bg-gray-700 my-4" />
