Everything below is exported from widelog.
Configuration
init(**config)
Call once at startup. Later calls update only the keys you pass.
| Option | Type | Default | Notes |
|---|---|---|---|
service |
str |
$SERVICE_NAME or "app" |
Stamped on every event. |
environment |
str |
$ENVIRONMENT or "development" |
Stamped on every event. |
redact |
iterable of str |
see Redaction | Replaces the default name list. |
sink |
Callable[[dict], None] |
None |
Where events go. Unset writes NDJSON to stdout. OTLPSink sends to a collector, see OTLP. |
stack_depth |
int |
5 |
Frames kept per traceback, counted from the innermost. |
Recording
use_logger()
Returns the event for the current operation. Outside one, returns a standalone logger that emits on every call, so nothing is silently dropped.
wide_event(**fields)
Context manager that scopes one event. Emits exactly once when the block exits, including when the block raises. An exception is attached to the event and then re-raised untouched.
WideEvent
| Method | Notes |
|---|---|
set(dict) or set(**kw) |
Attach context. Dicts merge, lists concatenate, anything else overwrites. |
info(msg, ctx=None) |
Record a message. Appends to messages. |
warn(msg, ctx=None) |
Same, and raises the level to warn. |
debug(msg, ctx=None) |
Same, at debug. |
error(exc_or_str, ctx=None) |
Attach an error and raise the level to error. |
set_level(level) |
Pin the level so later error() and warn() calls cannot raise it. |
emit(**overrides) |
Emit and seal. Idempotent, and never raises. |
The level of an event is the most severe thing recorded on it, unless set_level() pinned it.
Errors
WidelogError(message, *, code=, status=, why=, fix=, link=, cause=, internal=)
status defaults to 500. to_dict() returns every field that is set except internal.
ErrorSpec(message, *, status=, why=, fix=, link=, tags=, internal=)
One error declared once. message is a string, or a function whose parameters become required
keyword arguments at the call site. status defaults to 500, tags never reaches the wire.
ErrorCatalog
Subclass with prefix=. Every ErrorSpec in the body becomes a factory whose code is
prefix.ATTRIBUTE_NAME. codes() returns them all in declaration order.
ErrorFactory(code, spec)
A spec bound to a code, for an error with no catalog. Reads spec fields off the factory:
factory.status, factory.tags.
Adapters
WidelogMiddleware(app)
ASGI. Records method, path, status, and duration_ms. A 5xx raises the level to error.
Non-HTTP scopes such as lifespan and websocket pass through untouched.
lambda_wide_event(handler)
Decorator. Adds request_id, cold_start, function, remaining_ms, and trace_id, reads
method and path from all three payload shapes, and emits early with "timed_out": true
500ms before the deadline. See AWS Lambda.
Event shape
Every event carries these, then your fields:
| Field | Notes |
|---|---|
timestamp |
UTC, millisecond precision, so a backend can order same-second events. |
level |
debug, info, warn, or error. |
service, environment |
From init(). |
duration_ms |
Absent on standalone one-off lines, which measure nothing. |
Errors on an event
log.error(exc) and an exception that ends a wide_event() both write an error object:
| Field | Notes |
|---|---|
message, type |
str(exc) and the exception class name. |
status, code, why, fix, link |
From WidelogError, when set. |
stack |
The innermost frames, path:line in function. widelog’s own frames are filtered out, so the first entry is your code. |
causes |
The chain behind this error, outermost first, each with its own type, message, and stack. |
The chain follows __cause__ first, so raise X from Y and WidelogError(cause=Y) win over an
incidental __context__. raise X from None ends it, and the chain stops after five links or on
a cycle.
{
"message": "Payment failed",
"status": 402,
"type": "WidelogError",
"stack": ["app/checkout.py:41 in checkout"],
"causes": [
{
"type": "ValueError",
"message": "connection reset by peer",
"stack": ["app/gateway.py:12 in charge", "app/gateway.py:6 in post"]
}
]
}to_dict() carries none of this. The stack and the chain stay in your logs and never reach the
client.
Redaction
Keys ending in password, token, secret, authorization, apikey, or cookie are replaced
with [REDACTED] at any depth, in dicts and inside lists.
Matching ignores case, underscores, and hyphens, and looks at the end of the key:
| Redacted | Not redacted |
|---|---|
password, user_password |
tokens_used |
token, refresh_token, access_token |
token_count |
api_key, x-api-key, apiKey |
secretary |
cookie, set-cookie |
cookies_accepted |
authorization, proxy-authorization |
|
secret, client_secret |
Suffix matching is why refresh_token is caught and tokens_used is not. Pass
init(redact={...}) to replace the list with your own names, which are normalized the same way.
WidelogError(internal=...) is never serialized, into the event or into to_dict().
Limits
Values are copied as they enter the event, so widelog never writes back into the dicts and lists
you passed to set(). Nesting is kept to 32 levels, and anything deeper becomes [TRUNCATED],
which also makes a self-referential payload safe to log.
Failure
emit() never raises. If the sink is down, or a field cannot be serialized, widelog reports the
dropped event on stderr and returns None. Logging is not allowed to fail the request it is
describing, or to replace the exception the application is already handling.
Mutating a sealed event prints a warning to stderr and drops the data.