Repository navigation
feat(api): add optional observability adapter - #434
Conversation
Add a deployment-selected lifecycle hook for logging and tracing while preserving native telemetry. Keep request IDs through HTTP calls and streams, remove payload logging, and prevent new interpolated logging calls.
Initialize the native metric provider before the optional adapter. Move application log-call cleanup and payload assertions to a follow-up PR.
| Returns: | ||
| An OpenTelemetry Meter instance, or None if OTel is not configured | ||
| """ | ||
| if uses_observability_adapter(): |
There was a problem hiding this comment.
This turns off every custom instrument in adapter mode, not just the ones sgp-obs replaces.
db_metrics is fine — sgp-obs emits the same db.client.* family. But cache_metrics (hit / miss_expired / miss_absent) and schedule_metrics (Temporal create/update/delete) have no sgp-obs equivalent, so those series simply stop when the adapter is selected.
The PR description calls this intentional, which I'm not arguing with — but it doesn't say what currently reads them. Worth enumerating the dashboards and alert rules that consume cache_* and schedule_* before anyone sets AGENTEX_OBSERVABILITY_MODULE on a real environment. If nothing does, say so in the adapter doc so the next person doesn't have to re-derive it; if something does, those need re-creating on sgp-obs' meter rather than dropping.
| return | ||
|
|
||
| headers = scope.get("headers", []) | ||
| request_id = next( |
There was a problem hiding this comment.
This changes the trust boundary. Previously the ID was always minted here; now an inbound header wins. A caller can send the same x-request-id on every request and flatten log correlation for the whole service.
That's the right call behind the mesh — Envoy stamps the header, and sgp-obs' own request_id module documents exactly this assumption ("the shape check prevents log injection, not spoofing"). But observability_adapter.md describes the validator and not the assumption behind it, so there's no way to tell from the docs whether this is safe on a directly-exposed deployment.
One sentence saying inbound IDs are trusted because the mesh owns the header would cover it.
| ) | ||
|
|
||
| async def load(self): | ||
| from sqlalchemy.ext.asyncio import create_async_engine |
There was a problem hiding this comment.
Worth a comment on why this import is function-local: SQLAlchemyInstrumentor patches create_async_engine, so a module-level import binds the unpatched symbol and the adapter silently gets no SQL spans.
As written it reads like an accident, and that reasoning currently lives only in the PR description — the next cleanup pass will hoist it back to the top of the file and the failure will be invisible.
Pull Request Summary
Add an optional module that can replace API logging, tracing, and metrics. A selected, installed adapter owns instrumentation. When the selector is unset or the module is absent, the API uses its built-in telemetry.
Changes
uses_observability_adapter()so its purpose is explicit.initialize(app)after app assembly andshutdown()after application cleanup. Importing the adapter does not configure logging.AsyncExitStackfor cleanup after startup or shutdown failures.make_logger()calls and module reloads, preserving other handlers and the existing log format.Test Plan
Follow-up
Application log-call cleanup remains in #435, after this PR. Existing log message text is unchanged here. No logging helpers, lint baseline, or logging CI job is added. The adapter interface intentionally does not preserve native custom metrics when an adapter is selected. Review dashboard and alert consumers of
auth_cache.access,auth_cache.eviction, andagent_run_schedule.temporal_opbefore activation. Request-ID validation does not prevent spoofing; deployments requiring service-controlled IDs must overwrite caller-supplied IDs at trusted ingress.The PR appears safe to merge. No new blocking issue was found.
What we checked:
Summary
The API can now hand logging, tracing, and metrics to an optional installed observability module. It also keeps request IDs connected through middleware, errors, streams, and outgoing HTTP calls while preserving the built-in telemetry path when no adapter is selected.
Diagram
sequenceDiagram participant Worker as API worker participant Selector as observability selector participant Adapter as optional adapter participant Native as built-in telemetry participant Deps as dependencies participant Client as HTTP client Worker->>Selector: load configured module alt adapter is installed Worker->>Adapter: initialize(app) Worker->>Deps: start without native collectors else adapter is absent Worker->>Native: start auto-instrumentation and metrics Worker->>Deps: start with native collectors end Client->>Worker: request with optional x-request-id Worker->>Worker: keep or create request ID Worker-->>Client: response with x-request-id Worker->>Deps: close clients and dependencies alt adapter mode Worker->>Adapter: shutdown() else native mode Worker->>Native: flush metrics endReviews (9) · Last reviewed commit: "Merge branch 'main' into harvhan/runtime..."