Loguru
A Loguru sink that forwards records to axonpush as OpenTelemetry-shaped app.log events.
pip install "axonpush[loguru]"Tested against loguru>=0.7,<1.0.
Add the sink
from loguru import logger
from axonpush import AxonPush
from axonpush.integrations.loguru import create_axonpush_loguru_sink
client = AxonPush()
sink = create_axonpush_loguru_sink(
client=client,
channel_id=channel_id,
service_name="my-api",
environment="production",
)
logger.add(sink, serialize=True)serialize=True is required. It tells Loguru to hand the sink a JSON string of
the record, which is what the sink parses. Without it the sink receives a
formatted message and cannot recover the structured fields.
Then log normally. Keyword arguments and bound context both become
attributes:
logger.info("user signed in", user_id=42, method="oauth")
logger.warning("rate limit approaching", endpoint="/api/search", remaining=3)
try:
search()
except RuntimeError:
logger.exception("search backend failed", endpoint="/api/search")
request_logger = logger.bind(request_id="req-9f21", user_id=42)
request_logger.info("handling request")Constructor
create_axonpush_loguru_sink(
*,
client, # AxonPush or AsyncAxonPush - required
channel_id, # str UUID - required
source="app", # "app" -> app.log, "agent" -> agent.log
service_name=None,
service_version=None,
environment=None,
agent_id=None,
mode=None, # "background" (default) | "sync"
queue_size=1000,
shutdown_timeout=2.0,
)Every argument is keyword-only. source and mode are validated at
construction, anything other than "agent"/"app" and
"background"/"sync" raises ValueError.
In "background" mode the call site stays O(microseconds): the sink pushes
onto a bounded queue drained by one daemon thread. Pass a sync AxonPush for
this, an AsyncAxonPush gets no worker and publishes inline.
What each record becomes
| Field | Value |
|---|---|
identifier | The record’s name, the module that logged it |
event_type | app.log, or agent.log when source="agent" |
payload.body | The Loguru message |
payload.severityNumber / payload.severityText | OTel severity mapped from the Loguru level name |
payload.timeUnixNano | The record timestamp, in nanoseconds |
payload.attributes | code.filepath, code.filename, code.function, code.lineno, code.namespace, logger.name, process.pid, thread.name, plus everything in record["extra"] |
payload.resource | service.name, service.version, deployment.environment when configured |
A record with an exception also carries exception.type and
exception.message.
The trace id comes from the active trace context, so log lines emitted inside a traced request join that trace.
Flushing and removing
sink.flush(timeout=1.0) # block until the queue drains
sink.close() # drain and stop the workerLoguru identifies sinks by the id logger.add() returned, so removing one is a
two-step:
sink_id = logger.add(sink, serialize=True)
...
sink.flush(timeout=1.0)
logger.remove(sink_id)
sink.close()In Lambda and other freeze-between-invocations runtimes, flush once per
invocation. flush_after_invocation is re-exported from this module and works
the same way as it does for the
stdlib handler:
from axonpush.integrations.loguru import (
create_axonpush_loguru_sink,
flush_after_invocation,
)
@flush_after_invocation(sink)
def lambda_handler(event, context):
...