Skip to content

Logging

  • Start here: Logging guide
  • Common recipes: configure(...) for json, logfmt, text, pretty output. AccessLog() for one structured record per HTTP request. dict_config() to hand the configuration to the application server. Filters: DuplicateFilter, RateLimitFilter.

grelmicro.log

Logging.

LogBackend

LogBackend = Literal['loguru', 'structlog', 'stdlib']

Logging backend selection, matched in any case.

LogFormat

LogFormat = Literal[
    "auto",
    "json",
    "logfmt",
    "text",
    "pretty",
    "AUTO",
    "JSON",
    "LOGFMT",
    "TEXT",
    "PRETTY",
]

Built-in log format, matched in any case.

Type checkers accept the uppercase spellings until 0.45.0.

LogSerializer

LogSerializer = Literal['auto', 'stdlib', 'orjson']

JSON serializer selection, matched in any case.

AccessLog

AccessLog(
    *,
    include: tuple[str, ...] | None = None,
    exclude: tuple[str, ...] | None = None,
    quiet: tuple[str, ...] | None = None,
    query: bool | None = None,
    user_agent: bool | None = None,
    enduser: bool | None = None,
    name: str = "default",
    env_prefix: str | None = None,
    env_load: bool | None = None,
)

Bases: Reconfigurable[AccessLogConfig]

Write one structured record per HTTP request.

Register it and micro.install(app) adds the middleware:

from grelmicro import Grelmicro
from grelmicro.log import AccessLog, Log

micro = Grelmicro(uses=[Log(), AccessLog()])
micro.install(app)

While it is registered, uvicorn's own access log is silenced, because two access logs on one stream is worse than either alone. Nothing else about uvicorn's logging changes.

The level follows the answer: 5xx is an error, 4xx a warning, and anything else information. The probe paths and /metrics are logged at debug while they answer, so they stay out of the way without going missing when they fail.

Every field is live: a mounted ConfigMap that adds a path to GREL_ACCESS_LOG_EXCLUDE stops the records for it on the next request, without a restart. Read more in Live reconfiguration.

Read more in the Access Log docs.

Initialize the component with what to log and what to leave out.

PARAMETER DESCRIPTION
include

Paths to log. Empty (the default) means every path. A pattern ending in * matches as a prefix. Reads GREL_ACCESS_LOG_INCLUDE when unset.

TYPE: tuple[str, ...] | None DEFAULT: None

exclude

Paths to leave alone, whatever include says. Nothing is written for them, at any level. Reads GREL_ACCESS_LOG_EXCLUDE when unset.

TYPE: tuple[str, ...] | None DEFAULT: None

quiet

Paths logged at debug while they answer, and at the level their status earns when they do not. Defaults to the probe paths and /metrics. Pass () to log them like anything else.

TYPE: tuple[str, ...] | None DEFAULT: None

query

Whether the record carries url.query. Redacted through the same rules the rest of the library redacts a URL with, so a token in a query string never reaches the sink.

TYPE: bool | None DEFAULT: None

user_agent

Whether the record carries user_agent.original.

TYPE: bool | None DEFAULT: None

enduser

Whether the record carries enduser.id, the subject of an authenticated caller. Off by default, because a subject can be personal data. Reads GREL_ACCESS_LOG_ENDUSER when unset.

TYPE: bool | None DEFAULT: None

name

Registration name. Only one may be registered.

TYPE: str DEFAULT: 'default'

env_prefix

Override the derived prefix, GREL_ACCESS_LOG_ for the default instance.

TYPE: str | None DEFAULT: None

env_load

Whether to read environment variables. None follows the process-wide GREL_ENV_LOAD flag.

TYPE: bool | None DEFAULT: None

kind class-attribute

kind: str = 'access_log'

singleton class-attribute

singleton: bool = True

asgi_observes class-attribute

asgi_observes: bool = True

singleton_reason class-attribute

singleton_reason: str = "One access log answers for the whole app, so two would write every request twice"

name property

name: str

Return the registration name.

from_config classmethod

from_config(
    config: AccessLogConfig, *, name: str = "default"
) -> AccessLog

Build the component from a configuration that is already whole.

The one declarative door. What you pass is what runs: no environment variable is read, and the instance is not registered for live reload.

PARAMETER DESCRIPTION
config

The pre-built access log configuration.

TYPE: AccessLogConfig

name

Registration name. Only one may be registered.

TYPE: str DEFAULT: 'default'

asgi_middleware

asgi_middleware() -> tuple[type[Any], dict[str, Any]]

Return the middleware class and the arguments to build it with.

Being asked is what says this app serves HTTP, which is what makes uvicorn's access log worth silencing. A FastStream app serves none, so install never asks, and its uvicorn access log is left exactly where it was.

The middleware is handed the cell rather than the values, so a live reconfigure reaches it without the stack being rebuilt, which a framework will not do once it is serving.

AccessLogMiddleware

AccessLogMiddleware(
    app: ASGIApp,
    *,
    include: tuple[str, ...] = (),
    exclude: tuple[str, ...] = (),
    quiet: tuple[str, ...] = DEFAULT_QUIET,
    query: bool = True,
    user_agent: bool = True,
    enduser: bool = False,
    live: Live[_State] | None = None,
)

Write one structured record per request.

Pure ASGI, so it runs on FastAPI, Starlette, Litestar, and anything else that speaks ASGI. micro.install(app) adds it for a registered AccessLog, and an app on a framework install does not know wraps itself with it.

The record carries OpenTelemetry semantic conventions field names, the same ones the request span carries, so a backend reads one vocabulary across the log and the trace.

A request already seen by an access log further out, such as the one of an installed app mounting this one, is passed through, so each request writes one record. The outermost access log decides, its include and exclude included: a path it leaves out is not recorded by a nested one either.

Initialize the middleware with what to log and what to leave out.

PARAMETER DESCRIPTION
app

The next ASGI application in the middleware chain.

TYPE: ASGIApp

include

Paths to log. Empty means every path. A pattern ending in * matches as a prefix.

TYPE: tuple[str, ...] DEFAULT: ()

exclude

Paths to leave alone, whatever include says. Nothing is written for them, at any level.

TYPE: tuple[str, ...] DEFAULT: ()

quiet

Paths logged at debug while they answer, and at the level their status earns when they do not.

TYPE: tuple[str, ...] DEFAULT: DEFAULT_QUIET

query

Whether the record carries url.query, redacted.

TYPE: bool DEFAULT: True

user_agent

Whether the record carries user_agent.original.

TYPE: bool DEFAULT: True

enduser

Whether the record carries enduser.id, the subject of an authenticated caller.

TYPE: bool DEFAULT: False

live

The cell a registered AccessLog publishes its snapshot into, filled by micro.install(app). Passing it makes the other options the component's to decide.

TYPE: Live[_State] | None DEFAULT: None

app instance-attribute

app = app

DuplicateFilter

DuplicateFilter(
    *,
    allowed_repetitions: PositiveInt | None = None,
    cache_size: PositiveInt | None = None,
    key_mode: KeyMode | None = None,
    ttl: int | timedelta | None = None,
    key: Callable[[LogRecord], Hashable] | None = None,
    env_name: str = "default",
    env_prefix: str | None = None,
    env_load: bool | None = None,
)

Bases: Filter

Drop log records that repeat beyond allowed_repetitions.

Keys are tracked in an LRU cache with at most cache_size entries. Use key_mode to pick the default key, or pass key to override it.

Set ttl to emit a new burst of allowed_repetitions records every ttl during a long flood. This gives operators regular reminders that the issue is still active.

key_mode="template" is about 3 times faster than "rendered" because it skips message formatting.

Thread-safe.

State is kept in the current process only. There is no reset method. Create a new filter to clear all counters.

Initialize the filter.

PARAMETER DESCRIPTION
allowed_repetitions

Maximum number of records per key that pass the filter before subsequent records are dropped.

Default: 5. When unset and env reads are enabled (see env_load and GREL_ENV_LOAD), resolves from the environment variable GREL_DUPLICATEFILTER_ALLOWED_REPETITIONS if present, otherwise falls back to the DuplicateFilterConfig default.

TYPE: PositiveInt | None DEFAULT: None

cache_size

Maximum number of distinct keys tracked. When exceeded, the least-recently-seen key is evicted.

Default: 100.

TYPE: PositiveInt | None DEFAULT: None

key_mode

Default key strategy: "logger", "level", "global", "template" (default), or "rendered". Ignored when key is set.

TYPE: KeyMode | None DEFAULT: None

ttl

Silence window for automatic counter reset, in whole seconds or as a timedelta. A float is refused. None (default) disables time-based expiry.

TYPE: int | timedelta | None DEFAULT: None

key

Override the default key function. Receives the record and returns any hashable value.

TYPE: Callable[[LogRecord], Hashable] | None DEFAULT: None

env_name

Environment variable namespace segment.

Default: "default", which reads the bare prefix. A named instance segments its env vars under the name, so env_name="audit" reads GREL_DUPLICATEFILTER_AUDIT_*.

TYPE: str DEFAULT: 'default'

env_prefix

Override the auto-derived environment variable prefix.

Default: GREL_DUPLICATEFILTER_. A named instance segments via env_name to GREL_DUPLICATEFILTER_{NAME}_.

TYPE: str | None DEFAULT: None

env_load

Whether to read environment variables.

When None (the default), follow the process-wide GREL_ENV_LOAD flag. Pass True or False to override the flag for this construction.

Pass False when the values here are the whole truth. Env reads fill every field not passed, so a config half taken from somewhere else silently gets the rest from the environment.

TYPE: bool | None DEFAULT: None

config property

Return the config.

from_config classmethod

from_config(
    config: DuplicateFilterConfig,
    *,
    key: Callable[[LogRecord], Hashable] | None = None,
) -> Self

Construct a DuplicateFilter from a pre-built DuplicateFilterConfig.

PARAMETER DESCRIPTION
config

The pre-built duplicate filter configuration.

Use this path when the configuration is assembled at startup from a settings tree. The environment path is bypassed and the config is used as-is.

TYPE: DuplicateFilterConfig

key

Override the default key function. Receives the record and returns any hashable value.

TYPE: Callable[[LogRecord], Hashable] | None DEFAULT: None

filter

filter(record: LogRecord) -> bool

Return True if the record should pass, False to drop.

DuplicateFilterConfig

Bases: BaseModel

Duplicate Filter Config.

allowed_repetitions class-attribute instance-attribute

allowed_repetitions: PositiveInt = 5

Maximum number of records per key that pass the filter before subsequent records are dropped.

cache_size class-attribute instance-attribute

cache_size: PositiveInt = 100

Maximum number of distinct keys tracked. When exceeded, the least-recently-seen key is evicted.

key_mode class-attribute instance-attribute

key_mode: KeyMode = 'template'

Default key strategy. "logger" keys per logger name. "level" keys per log level. "global" shares one counter for every record. "template" (default) keys per (logger, level, str(record.msg)): collapses across arg values of the same template and is about 3x faster. "rendered" keys per (logger, level, record.getMessage()): distinguishes fully-rendered messages.

ttl class-attribute instance-attribute

ttl: Duration | None = None

Silence window for automatic counter reset, in whole seconds or as a timedelta. A float is refused. If a key has not been seen for ttl, its counter resets the next time it appears. None (default) disables time-based expiry, so only LRU eviction can reset a counter. From text, such as an environment variable, it reads whole seconds ("60"), an ISO 8601 duration ("PT0.5S"), or "none" for no time limit.

ErrorDict

Bases: TypedDict

Structured error representation.

type instance-attribute

type: str

message instance-attribute

message: str

stack instance-attribute

stack: NotRequired[str]

JSONRecordDict

Bases: TypedDict

Structured JSON log record after serialization.

Describes the shape of each log line after it is serialized to JSON. Internally, formatters use datetime objects for time. The JSON serializer converts them to ISO 8601.

The core fields follow common structured-logging conventions. Extra context fields are added flat at the top level.

Example::

{"time": "2026-03-30T14:00:00+00:00", "level": "INFO",
 "msg": "request handled", "logger": "myapp.api",
 "caller": "handle:45",
 "method": "GET", "status": 200}

time instance-attribute

time: str

level instance-attribute

level: str

msg instance-attribute

msg: str

logger instance-attribute

logger: str

caller instance-attribute

caller: NotRequired[str]

trace_id instance-attribute

trace_id: NotRequired[str]

span_id instance-attribute

span_id: NotRequired[str]

error instance-attribute

error: NotRequired[ErrorDict]

Log

Log(
    *,
    name: str = "default",
    backend: LogBackend | None = None,
    level: LogLevel | None = None,
    format: LogFormat | str | None = None,
    timezone: TimeZoneName | None = None,
    json_serializer: LogSerializer | None = None,
    caller_enabled: bool | None = None,
    otel_enabled: bool | None = None,
    uvicorn_enabled: bool | None = None,
    queue_enabled: bool | None = None,
    queue_size: int | None = None,
    env_load: bool | None = None,
)

Log component: installs logging on enter, restores stdlib root state on exit.

Registered as micro.log after Grelmicro.use(Log(...)). Mirrors the knobs on grelmicro.log.configure(...). Construction stays cheap, the backend is configured when the surrounding Grelmicro opens.

Example
from grelmicro import Grelmicro
from grelmicro.log import Log

micro = Grelmicro(uses=[Log()])

async with micro:
    ...

On exit, the previous stdlib root handlers and level are restored so sequential Grelmicro(...) blocks do not pile handlers up. Every backend installs a root handler, so every backend gives the root logger back. What each one installs for its own records, loguru's sink and structlog's processors, stays configured: those are process-wide settings of libraries the app chose, not state this component took.

The stdlib root logger is a single global. Log.__aenter__ and Log.__aexit__ serialize on a class-level threading.Lock so the snapshot/restore sequence cannot interleave across concurrent Grelmicro lifecycles in the same process. Run one Log at a time per process.

Read more in the Logging docs.

Initialize the component (defer configuration until __aenter__).

PARAMETER DESCRIPTION
name

Registration name. Log configures the process-wide root logger, so only one may be registered per app.

TYPE: str DEFAULT: 'default'

backend

Logging backend (stdlib, loguru, structlog).

TYPE: LogBackend | None DEFAULT: None

level

Log level threshold.

TYPE: LogLevel | None DEFAULT: None

format

Log format.

TYPE: LogFormat | str | None DEFAULT: None

timezone

IANA timezone for timestamps.

TYPE: TimeZoneName | None DEFAULT: None

json_serializer

JSON serializer.

TYPE: LogSerializer | None DEFAULT: None

caller_enabled

Include caller (function and line) in log records.

TYPE: bool | None DEFAULT: None

otel_enabled

Extract OpenTelemetry trace context.

TYPE: bool | None DEFAULT: None

uvicorn_enabled

Take over uvicorn's own loggers so they match this format and write to the same stream. Turn it off only when something other than grelmicro owns uvicorn's logging.

TYPE: bool | None DEFAULT: None

queue_enabled

Hand log writes to a background thread.

TYPE: bool | None DEFAULT: None

queue_size

How many rendered records the queue holds before dropping.

TYPE: int | None DEFAULT: None

env_load

Whether to read GREL_LOG_* environment variables. When None (default), follow GREL_ENV_LOAD.

TYPE: bool | None DEFAULT: None

kind class-attribute

kind: str = 'log'

singleton class-attribute

singleton: bool = True

name property

name: str

Return the registration name.

config property

config: LogConfig

Return the resolved LogConfig.

RAISES DESCRIPTION
RuntimeError

If accessed before the component has been entered.

from_config classmethod

from_config(
    config: LogConfig, *, name: str = "default"
) -> Self

Construct a Log from a pre-built LogConfig.

PARAMETER DESCRIPTION
config

The pre-built logging configuration.

Use this path when the configuration is assembled at startup from a settings tree (for example YAML, Vault, or a pydantic-settings aggregator). The environment path is bypassed and the config is used as-is.

TYPE: LogConfig

name

Registration name. Defaults to 'default'.

TYPE: str DEFAULT: 'default'

LogConfig

Bases: BaseModel

Log Config.

backend class-attribute instance-attribute

backend: LogBackend = 'stdlib'

Logging backend implementation.

level class-attribute instance-attribute

level: LogLevel = 'INFO'

Log level threshold.

format class-attribute instance-attribute

format: LogFormat | str = _DEFAULT_FORMAT

Log format. Built-in option or a custom template string.

timezone class-attribute instance-attribute

timezone: TimeZoneName = TimeZoneName(UTC_NAME)

IANA timezone for timestamps.

json_serializer class-attribute instance-attribute

json_serializer: LogSerializer = 'auto'

JSON serializer used for structured output. auto uses orjson when it is installed and the standard library otherwise. orjson requires it and raises when it is missing.

caller_enabled class-attribute instance-attribute

caller_enabled: bool = False

Include caller (function and line) in log records.

otel_enabled class-attribute instance-attribute

otel_enabled: bool = opentelemetry is not None

Extract OpenTelemetry trace context into log records.

uvicorn_enabled class-attribute instance-attribute

uvicorn_enabled: bool = True

Take over uvicorn's own loggers so they match this format and write to the same stream. Uvicorn installs its own handlers with propagation off, so without this its lines keep their own format and the process emits two. Turn it off only when something other than grelmicro owns uvicorn's logging.

queue_enabled class-attribute instance-attribute

queue_enabled: bool = False

Hand log writes to a background thread. The record is rendered on the calling thread and queued, so a slow sink no longer blocks the event loop. A full queue drops the arriving record and reports the count.

queue_size class-attribute instance-attribute

queue_size: int = 10000

How many rendered records the queue holds before dropping.

LogError

Bases: GrelmicroError

Base log error.

RateLimitFilter

RateLimitFilter(
    *,
    capacity: PositiveInt | None = None,
    refill_rate: PositiveFloat | None = None,
    key_mode: KeyMode | None = None,
    cost: PositiveFloat | None = None,
    key: Callable[[LogRecord], str] | None = None,
    env_name: str = "default",
    env_prefix: str | None = None,
    env_load: bool | None = None,
)

Bases: Filter

Rate-limit log records with a token bucket.

Drops records once the per-key bucket is empty. Each key refills at refill_rate tokens per second, capped at capacity.

Pick the default key strategy via key_mode (per-logger, per-level, global, template, rendered) or supply a custom key callable. Tune capacity and refill_rate to match your acceptable burst and sustained log rate.

Thread-safe. State lives in a MemoryTokenBucket.

State is kept in the current process only. It is not shared between processes. Create a new filter to clear all counters, or call reset for a single key.

Example:

import logging

from grelmicro.log import RateLimitFilter

logger = logging.getLogger("grelmicro.ingest")
logger.addFilter(RateLimitFilter(capacity=10, refill_rate=1))

Read more in the Logging docs.

Initialize the filter.

PARAMETER DESCRIPTION
capacity

Maximum burst size.

Default: 5. When unset and env reads are enabled (see env_load and GREL_ENV_LOAD), resolves from the environment variable GREL_RATELIMITFILTER_CAPACITY if present, otherwise falls back to the RateLimitFilterConfig default.

TYPE: PositiveInt | None DEFAULT: None

refill_rate

Tokens replenished per second.

Default: 1.

TYPE: PositiveFloat | None DEFAULT: None

key_mode

Default key strategy: "logger" (default), "level", "global", "template", or "rendered". Ignored when key is set.

TYPE: KeyMode | None DEFAULT: None

cost

Tokens consumed per record.

Default: 1.0.

TYPE: PositiveFloat | None DEFAULT: None

key

Override the default key function. It receives the record and returns a string key. Returned keys share the same namespace as the default key function. Add a unique prefix if you need to avoid collisions.

TYPE: Callable[[LogRecord], str] | None DEFAULT: None

env_name

Environment variable namespace segment.

Default: "default", which reads the bare prefix. A named instance segments its env vars under the name, so env_name="audit" reads GREL_RATELIMITFILTER_AUDIT_*.

TYPE: str DEFAULT: 'default'

env_prefix

Override the auto-derived environment variable prefix.

Default: GREL_RATELIMITFILTER_. A named instance segments via env_name to GREL_RATELIMITFILTER_{NAME}_.

TYPE: str | None DEFAULT: None

env_load

Whether to read environment variables.

When None (the default), follow the process-wide GREL_ENV_LOAD flag. Pass True or False to override the flag for this construction.

Pass False when the values here are the whole truth. Env reads fill every field not passed, so a config half taken from somewhere else silently gets the rest from the environment.

TYPE: bool | None DEFAULT: None

config property

Return the config.

from_config classmethod

from_config(
    config: RateLimitFilterConfig,
    *,
    key: Callable[[LogRecord], str] | None = None,
) -> Self

Construct a RateLimitFilter from a pre-built RateLimitFilterConfig.

PARAMETER DESCRIPTION
config

The pre-built rate limit filter configuration.

Use this path when the configuration is assembled at startup from a settings tree. The environment path is bypassed and the config is used as-is.

TYPE: RateLimitFilterConfig

key

Override the default key function. It receives the record and returns a string key.

TYPE: Callable[[LogRecord], str] | None DEFAULT: None

filter

filter(record: LogRecord) -> bool

Return True to keep the record, False to drop it.

reset

reset(key: str = '') -> None

Restore key's bucket to full capacity.

PARAMETER DESCRIPTION
key

Identifier to reset. Use the same value the key function produces for the records you want to clear. Pass "" for the "global" key mode.

TYPE: str DEFAULT: ''

RateLimitFilterConfig

Bases: BaseModel

Rate Limit Filter Config.

capacity class-attribute instance-attribute

capacity: PositiveInt = 5

Maximum burst size: the bucket holds at most this many tokens. A burst of up to capacity records is allowed before any are dropped.

refill_rate class-attribute instance-attribute

refill_rate: PositiveFloat = 1

Tokens (records) replenished per second. Sets the sustained rate after the initial burst is exhausted.

key_mode class-attribute instance-attribute

key_mode: KeyMode = 'logger'

Default key strategy. "logger" (default) buckets per logger name (each logger gets its own burst budget). "level" buckets per log level. "global" uses a single shared bucket for all records. "template" buckets per (logger, level, str(record.msg)): collapses across arg values of the same template. "rendered" buckets per (logger, level, record.getMessage()): distinguishes fully-rendered messages. Ignored when key is set.

cost class-attribute instance-attribute

cost: PositiveFloat = 1.0

Tokens used per record. Increase this to make some filters drain their bucket faster than others. For example, a filter on a verbose-level handler.

configure

configure(
    *,
    backend: LogBackend | None = None,
    level: LogLevel | None = None,
    format: LogFormat | str | None = None,
    timezone: TimeZoneName | None = None,
    json_serializer: LogSerializer | None = None,
    caller_enabled: bool | None = None,
    otel_enabled: bool | None = None,
    uvicorn_enabled: bool | None = None,
    queue_enabled: bool | None = None,
    queue_size: int | None = None,
    env_load: bool | None = None,
) -> LogConfig

Configure logging with the selected backend.

Two paths:

  • Programmatic: pass any of the per-field kwargs. Unset fields resolve from GREL_LOG_* env vars (unless env_load=False or GREL_ENV_LOAD=0), then from LogConfig defaults.
  • Environmental: omit all kwargs. GREL_LOG_* env vars populate every field.

For the declarative path, use configure_with.

PARAMETER DESCRIPTION
backend

Logging backend (stdlib, loguru, structlog). Default: stdlib.

TYPE: LogBackend | None DEFAULT: None

level

Log level threshold. Default: INFO.

TYPE: LogLevel | None DEFAULT: None

format

Log format. Default: auto.

TYPE: LogFormat | str | None DEFAULT: None

timezone

IANA timezone for timestamps. Default: UTC.

TYPE: TimeZoneName | None DEFAULT: None

json_serializer

JSON serializer. Default: auto, which uses orjson when it is installed and the standard library otherwise.

TYPE: LogSerializer | None DEFAULT: None

caller_enabled

Include caller (function and line) in log records. Default: False.

TYPE: bool | None DEFAULT: None

otel_enabled

Extract OpenTelemetry trace context.

Default: True if OpenTelemetry is installed, else False.

TYPE: bool | None DEFAULT: None

uvicorn_enabled

Take over uvicorn's own loggers so they match this format.

Default: True. Pass False when something other than grelmicro owns uvicorn's logging. Leave it on when the server was given dict_config(), which is how the access handler follows the stream this call installs.

TYPE: bool | None DEFAULT: None

queue_enabled

Hand log writes to a background thread.

Default: False. Turn it on when the sink is slow enough that a write shows up as request latency.

TYPE: bool | None DEFAULT: None

queue_size

How many rendered records the queue holds before it starts dropping. Default: 10000.

TYPE: int | None DEFAULT: None

env_load

Whether to read GREL_LOG_* environment variables. When None (default), follow GREL_ENV_LOAD. Pass True or False to override.

TYPE: bool | None DEFAULT: None

RETURNS DESCRIPTION
LogConfig

The applied LogConfig. Snapshot of what was resolved.

RAISES DESCRIPTION
DependencyNotFoundError

If the selected backend module is not installed.

SettingsValidationError

If configuration is invalid.

configure_with

configure_with(config: LogConfig) -> LogConfig

Configure logging from a pre-built LogConfig.

PARAMETER DESCRIPTION
config

Pre-built logging configuration.

Use this path when the configuration is assembled at startup from a settings tree. The environment path is bypassed and the config is used as-is.

TYPE: LogConfig

RETURNS DESCRIPTION
LogConfig

The same LogConfig, for symmetry with configure.

dict_config

dict_config(
    *, env_load: bool | None = None
) -> dict[str, Any]

Return a logging configuration that renders records in this format.

Hand it to the application server and the process reads in one format from its first record, including the records written before the application module is imported:

uvicorn.run(app, log_config=dict_config())

The same document is what Gunicorn takes as logconfig_dict, Hypercorn as logconfig_dict, and Granian as log_dictconfig. Every logger those servers write to is given to the root logger, so a server line and an application line render the same way.

It is a plain logging.config.dictConfig document, so it is also what goes in the file uvicorn --log-config reads:

Path("logging.json").write_text(json.dumps(dict_config()))

Fields resolve from GREL_LOG_* when the document is built, and the document carries them, so build it where the process starts rather than where an image does. Two answers are still worked out where the document is applied: the auto format is carried unresolved and reads the format from the process that renders, and whether to colorize follows the stream that process writes to. The environment is read unless GREL_ENV_LOAD=0, the same as everywhere else, and env_load= overrides that for one call. To render against settings that never reach the environment, use dict_config_with().

A document applied on its own is behind the queue queue_enabled asks for, because the handler starts the writer when none is running.

A GREL_LOG_* variable set with the environment path off is reported as a warning on this path rather than as a log record, because nothing has configured the logger the record would be written to.

The document is applied by a process with the same packages installed. otel_enabled defaults to whether OpenTelemetry is importable, and a document carrying it as true refuses to apply where it is not, the same as configure(otel_enabled=True) does.

An application that also writes through loguru or structlog calls configure() as well, which adds the backend. Each pass replaces the root handler rather than adding one, so the last one applied is what the process reads in, and which one that is depends on how the server was started. uvicorn app:app imports the application after it builds Config, so configure() runs last. uvicorn.run(app, ...) has already imported it, so the document runs last.

Build the document from what configure() returns and the order stops mattering, which is what to do whenever both run:

config = configure(format="pretty")
uvicorn.run(app, log_config=dict_config_with(config))
PARAMETER DESCRIPTION
env_load

Whether to read GREL_LOG_* environment variables. When None (default), follow GREL_ENV_LOAD. Pass True or False to override.

TYPE: bool | None DEFAULT: None

RETURNS DESCRIPTION
dict[str, Any]

A dictConfig document. JSON-serializable, so it can be written to

dict[str, Any]

a file, and a fresh copy on every call, so a server that writes

dict[str, Any]

into it changes nothing else.

RAISES DESCRIPTION
DependencyNotFoundError

If orjson or OpenTelemetry is enabled but not installed.

SettingsValidationError

If configuration is invalid.

dict_config_with

dict_config_with(config: LogConfig) -> dict[str, Any]

Return a logging configuration built from a pre-built LogConfig.

The same document dict_config() returns, built from settings given here rather than read from the environment.

PARAMETER DESCRIPTION
config

Pre-built logging configuration.

Use this path when the configuration is assembled at startup from a settings tree. The environment path is bypassed and the config is used as-is.

TYPE: LogConfig

RETURNS DESCRIPTION
dict[str, Any]

A dictConfig document, JSON-serializable like the one

dict[str, Any]

dict_config() returns.

RAISES DESCRIPTION
DependencyNotFoundError

If orjson or OpenTelemetry is enabled but not installed.

formatter

formatter(
    config: LogConfig | Mapping[str, Any] | None = None,
    *,
    use_colors: bool | None = None,
    env_load: bool | None = None,
) -> Formatter

Return the formatter that renders a record in grelmicro's format.

Built to be named from a logging.config.dictConfig, which is how an application server renders its own records the way the application does:

{"()": "grelmicro.log.formatter"}

dict_config() assembles the whole document, and this is the piece it names.

PARAMETER DESCRIPTION
config

A resolved LogConfig, or the mapping a document carries it as. Omit it to read GREL_LOG_*.

TYPE: LogConfig | Mapping[str, Any] | None DEFAULT: None

use_colors

Whether to colorize, overriding NO_COLOR, FORCE_COLOR and the terminal check. Uvicorn writes it into the document when it is started with --use-colors or --no-use-colors.

TYPE: bool | None DEFAULT: None

env_load

Whether to read GREL_LOG_*. When None (default), follow GREL_ENV_LOAD. Pass True or False to override.

TYPE: bool | None DEFAULT: None

RAISES DESCRIPTION
DependencyNotFoundError

If orjson or OpenTelemetry is enabled but not installed.

SettingsValidationError

If configuration is invalid.

handler

handler(
    config: LogConfig | Mapping[str, Any] | None = None,
    *,
    env_load: bool | None = None,
) -> Handler

Return the handler grelmicro writes every record through.

Built to be named from a logging.config.dictConfig:

{"()": "grelmicro.log.handler", "formatter": "default"}

It writes to the stream the rest of the process writes to, so a record a server renders through this document goes through the same queue as the application's own. When the settings ask for a queue and no writer is running, one is started here, which is what puts a document applied on its own behind a queue.

A running writer is never taken out, whatever the settings say. It belongs to whoever started it, and stopping it here would strand the records it is holding. To turn a queue off, reconfigure the owner.

PARAMETER DESCRIPTION
config

A resolved LogConfig, or the mapping a document carries it as. Omit it to read GREL_LOG_*.

TYPE: LogConfig | Mapping[str, Any] | None DEFAULT: None

env_load

Whether to read GREL_LOG_*. When None (default), follow GREL_ENV_LOAD. Pass True or False to override.

TYPE: bool | None DEFAULT: None

RAISES DESCRIPTION
DependencyNotFoundError

If orjson or OpenTelemetry is enabled but not installed.

SettingsValidationError

If configuration is invalid.

grelmicro.log.uvicorn

Uvicorn-friendly formatters for dictConfig usage.

UvicornFormatter

UvicornFormatter(
    config: LogConfig | Mapping[str, Any] | None = None,
    *,
    use_colors: bool | None = None,
    env_load: bool | None = None,
)

Bases: _BaseFormatter

Format-aware uvicorn formatter compatible with logging.config.dictConfig.

Reads GREL_LOG_FORMAT and produces the matching output (auto, json, logfmt, text, pretty). The environment is read unless GREL_ENV_LOAD=0, and env_load= overrides that for one formatter.

Pass config to format against an already-resolved LogConfig instead of re-reading the environment. configure() uses that path so uvicorn matches settings passed as keyword arguments, which never reach the environment.

Pass use_colors to override the terminal check. Uvicorn writes it into the document when it is started with --use-colors or --no-use-colors, so a formatter named from a dictConfig has to take it.

Initialize from a resolved config, or from the environment.

format

format(record: LogRecord) -> str

Format the log record.

UvicornAccessFormatter

UvicornAccessFormatter(
    config: LogConfig | Mapping[str, Any] | None = None,
    *,
    use_colors: bool | None = None,
    env_load: bool | None = None,
)

Bases: UvicornFormatter

Format-aware uvicorn access log formatter.

Parses uvicorn's access log tuple arguments into structured fields (client_addr, method, full_path, http_version, status_code).

format

format(record: LogRecord) -> str

Format access records with split request fields.

A record is split when it carries the request arguments and either uvicorn's access message or uvicorn's access logger. A record that is neither is formatted whole, so an application record reaching this formatter through a shared handler keeps its message instead of being read as a request.

The split runs on a copy. A record is formatted once per handler and stays readable afterwards, so rewriting msg and args in place would hand every later reader the rewritten record: a second handler on the same logger, a queue listener, or a test reading caplog.