Skip to content

Plugin & Integration Guide

obslog is extended through small, duck-typed protocols — no base classes, no inheritance (PRODUCT.md §14, EXT-001). Anything that satisfies a protocol can be dropped into the pipeline; plugins depend only on the public API and never require a core change (EXT-003).

Extension points

Protocol Contract Used for
Sink emit(record) any output target
Processor process(record) -> Record \| None enrich / redact / sample (returning None drops)
Formatter format(record) -> str serialization at a sink's edge
Exporter emit(record) + shutdown() delivery to external systems
ContextExtractor extract() -> Mapping pull correlation from a framework
Clock / IdGenerator time / id deterministic testing, custom ids

Implement one by structural typing:

class CountingSink:
    def __init__(self) -> None:
        self.count = 0

    def emit(self, record) -> None:  # matches the Sink protocol
        self.count += 1


obslog.configure(sinks=[CountingSink()])

Discovery via entry points (EXT-004)

Third-party packages advertise components through entry points; obslog discovers them by group:

# in a plugin's pyproject.toml
[project.entry-points."obslog.sinks"]
loki = "obslog_loki:LokiSink"
from obslog.plugins import discover, Registry

sinks = discover("obslog.sinks")  # {"loki": <class LokiSink>}
registry = Registry(group="obslog.sinks")
registry.load_entry_points()

Built-in integrations

Standard library logging (obslog.integrations, dependency-free):

from obslog.integrations import LoggingSink, ObslogHandler

obslog.configure(sinks=[LoggingSink()])  # obslog -> logging
logging.getLogger().addHandler(ObslogHandler())  # logging -> obslog

ASGI correlation (dependency-free; works with FastAPI, Starlette, …):

from obslog.integrations import ASGICorrelationMiddleware

app.add_middleware(ASGICorrelationMiddleware)  # request_id/trace_id/execution_id per request

OpenTelemetry (pip install obslog[otel]):

from obslog.integrations.opentelemetry import OTelExporter

obslog.configure(
    sinks=[OTelExporter()]
)  # severities are already OTel-aligned, so mapping is lossless