-
Notifications
You must be signed in to change notification settings - Fork 2
Observability
Pair\Core\Observability provides lightweight runtime instrumentation for Pair v4.
It is intentionally dependency-free: the framework records spans through a small adapter interface, while applications may forward those spans to tools such as OpenTelemetry, Sentry, Datadog, logs, or a custom collector.
Observability comments and docblocks are only explanatory. Runtime behavior is controlled by code and .env values, not by comments.
-
Pair\Core\Observability: static facade used by the framework and applications. -
Pair\Core\ObservabilitySpan: completed timing record for one operation. -
Pair\Core\ObservabilityAdapter: interface implemented by external collectors. -
Pair\Core\ObservabilityEvent: non-span event for warning/error signals. -
Pair\Core\ObservabilityEventAdapter: optional event adapter interface. -
Pair\Services\SentryObservabilityAdapter: dependency-free Sentry envelope adapter. -
Pair\Services\OpenTelemetryHttpExporter: dependency-free OTLP/HTTP JSON trace exporter.
PAIR_OBSERVABILITY_ENABLED=false
PAIR_OBSERVABILITY_DEBUG_HEADERS=true
PAIR_OBSERVABILITY_TRACE_SAMPLE_RATE=1.0
PAIR_OBSERVABILITY_ERROR_SAMPLE_RATE=1.0
PAIR_OBSERVABILITY_MAX_SPANS=100
PAIR_OBSERVABILITY_MAX_EVENTS=50
APP_DEBUG=falseBehavior:
-
PAIR_OBSERVABILITY_ENABLED=trueenables instrumentation for the current runtime. -
APP_DEBUG=truealso enables instrumentation when debug headers are allowed, so local debugging can expose correlation and timing headers without extra setup. -
PAIR_OBSERVABILITY_DEBUG_HEADERS=trueallows explicit responses to sendX-Pair-Correlation-Id,X-Pair-Trace-Spans, andX-Pair-Trace-Duration-Msin debug mode. -
PAIR_OBSERVABILITY_TRACE_SAMPLE_RATEcontrols span recording before adapters are called. -
PAIR_OBSERVABILITY_ERROR_SAMPLE_RATEcontrols warning/error event recording. -
PAIR_OBSERVABILITY_MAX_SPANSandPAIR_OBSERVABILITY_MAX_EVENTScap process-local retained telemetry used by tests and debug headers. - LogBar uses the same correlation ID for its local request inspector header, but it keeps SQL parameter values out of observability span attributes.
- In production defaults, instrumentation is no-op unless an adapter is installed or the env flag is enabled.
use Pair\Core\Observability;
use Pair\Core\ObservabilityAdapter;
use Pair\Core\ObservabilitySpan;
final class LogSpanAdapter implements ObservabilityAdapter {
public function record(ObservabilitySpan $span): void {
error_log(json_encode($span->toArray(), JSON_UNESCAPED_SLASHES));
}
}
Observability::setAdapter(new LogSpanAdapter());Installing an adapter enables instrumentation for the current PHP process unless Observability::enable(false) explicitly disables it.
Adapters can also be registered through the application registry:
use Pair\Core\AdapterKeys;
use Pair\Core\Application;
use Pair\Services\OpenTelemetryHttpExporter;
Application::getInstance()->setAdapter(
AdapterKeys::OBSERVABILITY,
new OpenTelemetryHttpExporter()
);Using AdapterKeys::OBSERVABILITY calls Observability::setAdapter(...) when the adapter implements ObservabilityAdapter.
use Pair\Core\Observability;
$result = Observability::trace('report.build', function () {
return buildReport();
}, [
'report' => 'monthly',
]);If the callback throws, the exception is rethrown and the span is recorded with status=error.
Attribute values are sanitized before leaving the process. Sensitive attribute names such as token, secret, password, authorization, and cookie are redacted.
Pair warning/error logs are forwarded to event-aware observability adapters as ObservabilityEvent objects.
use Pair\Core\Observability;
Observability::recordLogEvent('error', 'Report export failed', [
'job' => 'monthly-report',
]);The logger attaches the current correlation ID to warning/error context before forwarding events.
The same warning/error pipeline also persists a LogEvent row. The row stores the correlation ID and, when available, the W3C traceparent trace ID. If an incoming trace ID is not available, Pair derives a trace-compatible identifier from the correlation ID so database records can still be grouped with request-level diagnostics.
Pair emits spans for these runtime areas:
-
app.run: full application run for non-terminating web flows. -
router.parse: route parsing without exposing the raw URL or query string. -
api.controller: API controller action execution. -
api.middleware: API middleware pipeline execution. -
web.controller: web controller action execution. -
db.query: database query execution with operation/result metadata only, never SQL parameters. -
cache.get,cache.set,cache.has,cache.delete,cache.clear,cache.remember: cache facade operations using hashed keys. -
template.render: outer HTML template rendering. -
view.renderandview.layout: legacy MVC view rendering. -
page.response: explicit v4 page response layout rendering.
The framework does not expose raw SQL, bound parameters, raw cache keys, cookies, authorization headers, or request bodies in default span attributes.
Explicit responses such as JsonResponse, TextResponse, and EmptyResponse include observability debug headers only when APP_DEBUG=true and PAIR_OBSERVABILITY_DEBUG_HEADERS=true.
Example response headers:
X-Pair-Correlation-Id: 5f8e2c9a7f9d4a5b9c4d3e2a1b0c9d8e
X-Pair-Trace-Spans: 3
X-Pair-Trace-Duration-Ms: 12.481Applications can set a known correlation ID before dispatch:
use Pair\Core\Observability;
Observability::setCorrelationId($_SERVER['HTTP_X_REQUEST_ID'] ?? null);If no correlation ID is set, Pair accepts X-Pair-Correlation-Id, X-Correlation-Id, or traceparent-style server values when present, otherwise it generates a random ID.
Use SentryObservabilityAdapter to send events and transactions to Sentry through envelope ingestion.
Use OpenTelemetryHttpExporter to send spans to an OTLP/HTTP trace endpoint such as an OpenTelemetry Collector.
Both adapters are optional and dependency-free. Provider SDKs may still be preferable for projects that need deeper automatic instrumentation.
Recommended production defaults:
- keep
PAIR_OBSERVABILITY_ENABLED=falseuntil an adapter is configured intentionally - use
PAIR_OBSERVABILITY_TRACE_SAMPLE_RATEbelow1.0for high-traffic systems - keep
PAIR_OBSERVABILITY_ERROR_SAMPLE_RATE=1.0unless error volume is extreme - keep debug headers disabled outside
APP_DEBUG=true - avoid high-cardinality custom attributes such as raw URLs, emails, tokens, request bodies, and SQL parameters
See also: Application, API, Cache, Database, Logger, LogEvent, ErrorLog, DatabaseLogWriter, LogBar, JsonResponse, TextResponse, EmptyResponse, SentryObservabilityAdapter, OpenTelemetryHttpExporter.