kinetis / telemetry
OpenTelemetry tracing for Kinetis — request spans, SQL and queue decorators, a traced HTTP transport, and OTLP export over the Revolt-backed client.
Requires
- php: ^8.4
- kinetis/framework: ^1.3.1
- kinetis/revolt-http-client: ^1.1.3
- nyholm/psr7: ^1.8.2
- open-telemetry/exporter-otlp: ^1.4.0
- open-telemetry/sdk: ^1.15.0
- psr/log: ^3.0.2
- symfony/http-client: ^8.1.4
- symfony/http-client-contracts: ^3.7.1
Requires (Dev)
- infection/infection: ^0.35.0
- kinetis/cache-redis: ^1.1.9
- kinetis/persistence: ^1.1.3
- kinetis/queue: ^1.2.0
- kinetis/search-opensearch: ^1.0.2
- kinetis/session: ^1.1.2
- phpstan/phpstan: ^2.2.8
- phpunit/phpunit: ^12.5.33
- vimeo/psalm: ^6.16.1
Suggests
- kinetis/cache-redis: Enables the TracingSimpleCache decorator — a span per cache operation.
- kinetis/persistence: Enables the TracingMysqlLink/TracingPostgresLink decorators, a span per SQL query.
- kinetis/queue: Enables the TracingQueue decorator — producer spans on push(), consumer spans from pop() to ack().
- kinetis/search-opensearch: Enables the TracingOpenSearchTransport decorator, via OpenSearchClientFactory's transportDecorator seam — a span per OpenSearch call.
- kinetis/session: Enables the TracingSessionStore decorator — a span per session read/write/destroy.
Provides
None
Conflicts
None
Replaces
None
README
kinetis/telemetry
OpenTelemetry tracing for Kinetis
Part of Kinetis, a non-blocking PHP framework for API-first applications, developed in the kinetis-dev/kinetis monorepo.
A span per request, per SQL query, per queue job, and per outgoing
HTTP call, exported over OTLP to any tracing backend. Export goes
through kinetis/revolt-http-client's Fiber-suspending transport, so
flushing a span batch never blocks the worker.
The distinctive trace this produces: spans that overlap in time. A
request running two queries and an HTTP call through concurrently()
shows all three side by side inside the request span — what
non-blocking I/O actually did for that request, visible.
Set one environment variable and requests start tracing:
OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318
Provides
Installing this package auto-registers, via extra.kinetis:
- Global middleware
RequestSpanMiddleware— one server span per request (method, status,php.memory.usage; an incomingtraceparentjoins the caller's trace). - A container binding for
OpenTelemetry\API\Trace\TracerProviderInterface— the OTLP-exporting provider whenOTEL_EXPORTER_OTLP_ENDPOINTis set, a no-op provider otherwise, so an unconfigured install costs near nothing. - The framework's instrumentation hooks, turned on — when the OTLP
endpoint is set, the bootstrap swaps an OTel backend into core's
Kinetis\Instrumentation\Telemetryholder, so the spans the framework reports from inside itself (boot phases, per-middleware timing, route match, hydration, controller, queries split at the pool boundary, transactions,concurrently()tasks, events, MCP calls, queue jobs) start exporting with no further wiring.
Nothing else. The decorators below are explicit opt-ins wired in your
own bootstrap.php.
Decorators
TracingMysqlLink/TracingPostgresLink— a span per SQL query (named by its opening keyword, with a fingerprint of the statement and the number of parameters bound), wrapping anykinetis/persistencelink while keeping its dialect marker. Transactions they begin spanCOMMITandROLLBACKtoo.TracingQueue— a producer span perpush(); a consumer span frompop()toack()/release()/fail()carrying the outcome, active while the job runs so its own queries and HTTP calls nest under it. Build it withTracingQueue::wrap($queue, $tracerProvider), which returns aClearableTracingQueuefor a backend declaringkinetis/queue'sClearableQueueInterfaceso wrapping the queue in spans doesn't cost itclear().wrap()'s return type follows its argument's;TracingQueue::wrapClearable()takes a backend already typed as clearable and returns one.TracingHttpClient— a client span per outgoing request withtraceparentinjection, ending when the response is consumed rather than whenrequest()returns. Carries the URL's scheme, host and port. Hand it toHttpas its transport.TracingSimpleCache— a span per cache operation, wrapping any PSR-16CacheInterface. A key-list fingerprint and a batch size travel; neither the keys nor the values do.TracingSessionStore— a span perread/write/destroy, wrapping anykinetis/sessionSessionStoreInterface. The session id never travels verbatim (it's a bearer credential) — only its fingerprint does.TracingOpenSearchTransport— a span per OpenSearch call, wrapping the PSR-18 client viaOpenSearchClientFactory::fromConfig()'stransportDecoratorparameter.TraceAwareLogger— wraps any PSR-3 logger, adding the active span'strace_id/span_idto every entry's context.
What never reaches a span
A trace is exported to a third-party backend, retained there, and readable by everyone with access to it — a wider audience than the database, cache, or upstream service an operation's input was addressed to. So a span here describes an operation and never the data it carried. Every decorator and hook routes an operation's inputs through one internal policy point, and there is no setting that turns it off:
| Never exported | Exported instead |
|---|---|
| A SQL statement, its literal values, its bound parameters | The opening keyword from a fixed vocabulary, a fingerprint of the statement, the parameter count |
| A cache key, single or batched, and every cached value | A fingerprint of the operation's key list, and db.operation.batch.size for the multi-key methods |
| A URL's userinfo, path, query string, and fragment | url.scheme, server.address, server.port, and a fingerprint of the whole URL |
| An incoming request's path or query string | The method, and the router's own template as http.route on the route.match span |
| An OpenSearch index name, document id or alias | The action from a fixed vocabulary, and a fingerprint of the path |
| A session id, and the session payload | A fingerprint of the id |
| A failure's message and stack trace | The exception's type — an anonymous subclass reports its nearest named ancestor — as the span status and as an exception event's exception.type |
A fingerprint is a 128-bit SHA-256 prefix: two spans covering the same
statement, key list, URL or path carry the same one, so a backend still
groups them, and neither carries the value. Each digest covers the kind
of input as well as the input, so one byte sequence seen as a cache key
and as a URL fingerprints differently in each. It is pseudonymous
correlation data, not a secret — the digest is unkeyed, so a value
drawn from an enumerable set stays guessable to anyone who can hash
candidates. A failing operation's exception propagates unchanged, so an
application that wants the message logs it where its own redaction
policy applies — TraceAwareLogger puts the trace id on that log line,
which joins the two back together.
Span names and the attributes that say what an operation did come from closed vocabularies for the same reason — the rule, and what each vocabulary falls back to, is stated once at kinetis.dev/docs/telemetry.html.
Configuration
| Key | Default | Purpose |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
— | Collector's OTLP/HTTP base URL. Unset = tracing off (no-op provider). |
OTEL_SERVICE_NAME |
kinetis |
The service.name resource attribute. |
OTEL_EXPORTER_OTLP_HEADERS |
— | Export headers, key=value,key2=value2 — a hosted backend's auth. |
OTEL_TRACES_SAMPLER |
parentbased_always_on |
Standard sampler names; traceidratio + OTEL_TRACES_SAMPLER_ARG for a rate. |
OTEL_TRACES_SAMPLER_ARG |
1.0 |
Ratio for the traceidratio samplers, 0–1. |
Installation
composer require kinetis/telemetry
Requires PHP 8.4+, kinetis/framework,
and kinetis/revolt-http-client.
Full documentation:
kinetis.dev/docs/telemetry.html.
License
MIT — see LICENSE.