ouroboros/docs/examples/error_tracker/plugin.py
Ouroboros 7769ff27d5 Declare the record passport and two verified monitoring examples
ouroboros/contracts/record_contract.py names the subset of Ouroboros's local
JSONL records an external observer may rely on: fifteen anchor rows (task
lifecycle, model rounds and usage, model errors, tool calls, worker crashes,
supervisor stalls) with narrow guaranteed keys fixed within
RECORD_CONTRACT_VERSION 1, their nullable, optional and content-bearing fields,
the correlation ids, the planes (canonical logs and archive, the task's own log
directory, and child drives deleted after retention), and the envelope,
rotation, replica, legacy and accounting rules, plus the coverage gaps. It
describes what the writers already write and changes none of them; money is
read from the accounting views by API, never summed from llm_usage.

tests/test_record_contract.py proves every guaranteed field against its writer:
the row literals of the named writer modules by AST, and the tools rows through
the real tool loop (start, settlement, wait end, a refused browser call) with
the replica rule checked in both planes. Within a version the guaranteed sets
are pinned exactly.

docs/examples/log_collector is a Vector setup that tails the live logs, the
archive generations it missed and child-drive events, raises max_line_bytes for
~300 KB rows, and forwards only anchor rows and their metadata (no content, no
money). docs/examples/error_tracker is an in-process extension that starts a
Sentry-compatible SDK with frame locals, source context, default integrations
and PII off, keeps only the event's identity, message and exception, and masks
secrets with redact_projection; its README says what still leaves.
tests/test_monitoring_examples.py checks both against the passport.

DEPLOYMENT.md points to the passport and the examples; PERSISTENCE.md names
the passport for anchor rows and corrects two statements (child drives are
pruned by the ~300 s reconcile pass, not at startup; tools rows hold redacted,
truncated args). Architecture chapters 01 and 11 list the module (grants
added), the domain map and generated inventories are regenerated.
2026-10-05 23:18:32 +03:00

48 lines
2 KiB
Python

"""Send this process's ERROR log records to a Sentry-compatible error tracker (example).
The extension has no declared dependencies and no binary files, so it loads in
process in the server and in every pool worker. The SDK comes from Ouroboros's
own environment, and SENTRY_DSN names the destination; without it the SDK sends
nothing.
"""
import logging
import os
import sentry_sdk
from sentry_sdk.integrations.atexit import AtexitIntegration
from sentry_sdk.integrations.dedupe import DedupeIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
from ouroboros.observability import redact_projection
# The parts of an error event a tracker needs. The logging integration also copies a
# record's `extra` dict, which holds whatever a caller logged, so it is not forwarded.
_EVENT_FIELDS = ("event_id", "timestamp", "level", "logger", "platform", "sdk", "server_name", "release",
"environment", "tags", "fingerprint", "logentry", "message", "exception")
def _before_send(event, hint):
return redact_projection({key: event[key] for key in _EVENT_FIELDS if key in event}).value
def register(api):
role = "worker" if os.environ.get("OUROBOROS_IN_WORKER") == "1" else "server"
sentry_sdk.init(
include_local_variables=False, # frame locals hold prompts, keys and file contents
include_source_context=False,
send_default_pii=False,
default_integrations=False, # no auto-instrumented model clients, no trace headers on their requests
integrations=[
LoggingIntegration(level=None, event_level=logging.ERROR),
DedupeIntegration(),
AtexitIntegration(),
],
max_request_body_size="never",
trace_propagation_targets=[],
before_send=_before_send,
server_name=f"ouroboros-{role}", # not the machine's hostname
release=f"ouroboros@{api.get_runtime_info().get('app_version', '')}",
)
api.on_unload(lambda: sentry_sdk.get_client().close(timeout=1))