Skip to main content

Overview

Nexus observability is built on the OpenTelemetry SDK for PHP, providing traces, metrics, and logs in a unified signal. Observability is disabled by default — if you do not call withObservability(), no tracer or meter is created and there is zero overhead.

Package model

Nexus observability ships as a set of focused packages:

PackagePurpose
nexus-observabilityCore contracts: Observability interface, no-op implementations, config value object
nexus-observability-otelOTel SDK wiring: OtelObservability, ObservabilityFactory, OTLP exporter, samplers, propagators
nexus-observability-actorActorSystemMetrics — live-actor, dead-letter, and running-state gauges
nexus-observability-httpServerSpanMiddleware (server spans) and HttpMetricsListener (request duration and active count)
nexus-observability-persistenceTracing decorators for event stores, snapshot stores, and durable-state stores
nexus-observability-worker-poolTracingWorkerTransport — context propagation and metrics across worker threads
nexus-observability-loggerTraceCorrelationProcessor — stamps trace/span IDs onto log records
nexus-observability-doctrineDbalPoolMetricsListener, OrmPoolMetricsListener, and Sql\TracingDriverMiddleware
nexus-observability-swooleSwooleContextRegistrar and SwooleAdminMetrics — Swoole coroutine context and server gauges

Start with nexus-observability-otel and add satellites as your application grows.

Three guarantees

1. Disabled by default

If you do not call withObservability() on NexusApp, no tracer or meter is created. The actor runtime does not reference any OTel SDK classes. There is no overhead.

2. Zero actor-code change

Inside actor handlers, $ctx->tracer() and $ctx->meter() always return valid objects. When observability is disabled they return the OTel SDK's built-in no-op implementations. Your actor code does not need if ($observabilityEnabled) guards — the same handler works with or without observability wired in.

3. Fail isolation

OTel export errors — network timeouts, collector unavailability, serialization failures — are swallowed by the OTel SDK's RetryingExporter and never surface as actor failures. An actor that cannot export a span continues processing normally.

End-to-end trace picture

When all satellites are wired, a single HTTP request produces a trace that spans the entire call chain:

HTTP POST /orders  [http.server]
└── orders actor: Handle(PlaceOrder) [nexus.actor.message]
├── persistence: persist event [nexus.persistence.event]
│ └── SQL INSERT [db.query]
└── payments actor: tell(ChargeCard) [nexus.actor.message]

Trace context is propagated via Envelope::metadata using W3C Trace Context (traceparent and tracestate headers). The receiving actor extracts the context and creates a child span automatically. This works transparently across:

  • Local actors — same process, same thread
  • Worker-pool actors — cross-thread via Swoole threads, using TracingWorkerTransport
  • Future cluster actors — remote nodes, using the cluster transport layer

The entire call chain appears as one trace in Jaeger, Grafana Tempo, or any OTel-compatible backend.

Next steps