Skip to content

Observability

Viames Marino edited this page May 5, 2026 · 2 revisions

Pair framework: 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.


Main classes

  • 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.

Configuration

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=false

Behavior:

  • PAIR_OBSERVABILITY_ENABLED=true enables instrumentation for the current runtime.
  • APP_DEBUG=true also enables instrumentation when debug headers are allowed, so local debugging can expose correlation and timing headers without extra setup.
  • PAIR_OBSERVABILITY_DEBUG_HEADERS=true allows explicit responses to send X-Pair-Correlation-Id, X-Pair-Trace-Spans, and X-Pair-Trace-Duration-Ms in debug mode.
  • PAIR_OBSERVABILITY_TRACE_SAMPLE_RATE controls span recording before adapters are called.
  • PAIR_OBSERVABILITY_ERROR_SAMPLE_RATE controls warning/error event recording.
  • PAIR_OBSERVABILITY_MAX_SPANS and PAIR_OBSERVABILITY_MAX_EVENTS cap 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.

Adapter

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.


Manual spans

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.

Log events

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.


Runtime spans

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.render and view.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.


Debug headers

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.481

Applications 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.

Provider adapters

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.

Production overhead controls

Recommended production defaults:

  • keep PAIR_OBSERVABILITY_ENABLED=false until an adapter is configured intentionally
  • use PAIR_OBSERVABILITY_TRACE_SAMPLE_RATE below 1.0 for high-traffic systems
  • keep PAIR_OBSERVABILITY_ERROR_SAMPLE_RATE=1.0 unless 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.

Clone this wiki locally