Skip to content

0034. Scope Sentry to error tracking, not tracing

Date: 2026-09-23 Status: Accepted

Context

Conflux already has an observability tool for distributed tracing: the OpenTelemetry PHP SDK, adopted in ADR-0011, instruments every HTTP request, queue job, and CLI command with a span, propagates trace context across process boundaries via the W3C Trace Context standard, and exports to VictoriaTraces. What Conflux has never had is dedicated exception tracking: uncaught exceptions are rendered (via App\Exceptions\ApiProblemRenderer for API requests) and logged, but there is no aggregation, no grouping of recurring errors, no alerting, and no UI for triaging them beyond reading log files.

Sentry is being added to close that gap. Sentry's Laravel SDK (sentry/sentry-laravel), however, does not only capture exceptions — by default it also instruments performance tracing (HTTP requests, queue jobs, outgoing HTTP calls, database queries) via its own traces_sample_rate setting, using its own span model layered on top of the same events OpenTelemetry already spans. Enabling both would mean two SDKs independently creating spans for the same request, with no shared trace ID between them and no benefit over the tracing Conflux already has.

Decision

Sentry is scoped to error capture only. config/sentry.php's traces_sample_rate and profiles_sample_rate are left null (Sentry's disabled state) rather than set from an env default, so a deployment cannot enable Sentry tracing by simply setting SENTRY_LARAVEL_DSN without a deliberate, separate decision to also set SENTRY_TRACES_SAMPLE_RATE. OpenTelemetry remains the sole tracer.

bootstrap/app.php's withExceptions() registers two reportable callbacks: App\Exceptions\SentryContext::register($exceptions) runs first, followed by Sentry\Laravel\Integration::handles($exceptions). SentryContext enriches the Sentry scope with the acting principal before Sentry's own handler captures the exception, so every reported issue carries who was affected:

  • If the request was authenticated via the connection guard (an App\Models\SystemConnection, Conflux's API caller), that connection becomes the Sentry "user" (id), tagged with connection_id and, when the connection has one, tenant_id — connections can be tenant-less pre-assignment, so the tag is conditional rather than always present.
  • Otherwise, if there is an authenticated App\Models\User (the Filament admin-panel guard), that user (id, email) becomes the Sentry "user".
  • Neither applies to queue-worker or console exceptions: there is no request and no authenticated actor to attribute them to, the same limitation ApiProblemRenderer::context() already accepts for its own logging.

Consequences

Conflux now runs two observability SDKs side by side with a deliberately non-overlapping split: OpenTelemetry for tracing (spans, trace propagation, VictoriaTraces), Sentry for exception capture and triage. Neither instruments the other's concern, so there is no double span overhead and no risk of the two producing conflicting trace data for the same request.

The trade-off is that Sentry's own performance-monitoring features (transaction traces, slow-query detection tied to Sentry's UI) are unavailable, since traces_sample_rate stays disabled. If Sentry-side tracing is wanted later — its UI or alerting on slow transactions turns out to be worth having alongside VictoriaTraces — that is a new decision, not a silent flip of this one, since it reopens the double-instrumentation question this ADR closes.

Context enrichment is HTTP-only. An exception thrown inside a queue job or a scheduled command still reaches Sentry, but without a connection_id, tenant_id, or user tag — triaging those requires the job's own log context (correlated via trace_id/span_id, per Tracing) rather than the Sentry issue's "user" field.