cboxdk / statamic-telemetry
Statamic overlay for cboxdk/laravel-telemetry: content-aware trace names, Stache and static-cache instrumentation, site context and user attribution.
Requires
- php: ^8.3|^8.4|^8.5
- cboxdk/laravel-telemetry: ^0.1.0-alpha.1
- statamic/cms: ^6.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
This package is auto-updated.
Last update: 2026-07-03 11:16:20 UTC
README
Statamic overlay for cboxdk/laravel-telemetry: content-aware trace names, Stache and static-cache instrumentation, site context and user attribution — as a proper Statamic addon.
laravel-telemetry already traces requests, queries, queue jobs, cache and more. This addon teaches those traces Statamic's vocabulary:
- Request span naming — every Statamic frontend request goes through one
catch-all route, so the default
METHOD /route/{pattern}name collapses to a single useless value. Entry requests becomeGET entry:{collection}.{blueprint}, term requestsGET term:{taxonomy}— bounded names, whilehttp.routekeeps the raw pattern. - Content attributes —
statamic.entry.id,statamic.collection,statamic.blueprint,statamic.taxonomy,statamic.term.idandstatamic.siteon the request root span. - Site context —
statamic.siteas an ambient dimension on every span in the trace, propagated to queued jobs. - User attribution —
enduser.roles,enduser.groupsandenduser.superfor Statamic users (file or eloquent driven), on top of the coreenduser.id/type/guard. - Static cache — hit/miss/write outcome as
statamic.static_cacheon the root span, operation counters, and a fix for a subtle replay bug: the half-measure cacher snapshots response headers, so the addon strips theX-Trace-Idheader before it is baked into the cached response and replayed to every visitor. - Stache — cache keys classified into bounded groups (
stache.index,stache.item,stache.meta,static_cache,app) so cache counters and spans don't drown in thousands of raw keys; warm/clear counters and warm duration. - Glide, forms, content changes — counters per preset, per form, and per content type/action.
- Antlers render spans (opt-in) — a detail span per rendered view.
- Observable gauges (opt-in) — entries per collection, assets per container, user count, evaluated at scrape time.
Installation
composer require cboxdk/statamic-telemetry:^0.1.0-alpha.1
Alpha (
0.1.0-alpha.1). Both packages are on Packagist. Until a stable0.1.0is tagged, allow alpha stability in your app'scomposer.json(or Composer will refuse the pre-release):"minimum-stability": "alpha", "prefer-stable": true
Requires PHP 8.3+, Statamic 6 and cboxdk/laravel-telemetry. Everything is on by default except Antlers view/tag spans and the gauges. Publish the config to change toggles:
php artisan vendor:publish --tag=statamic-telemetry-config
What you get
Spans & attributes
| Attribute | Where | Values |
|---|---|---|
statamic.type |
root span | entry, term |
statamic.entry.id / statamic.term.id |
root span | id |
statamic.collection / statamic.blueprint / statamic.taxonomy |
root span | handles |
statamic.site |
root span + all spans (context) | site handle |
statamic.static_cache |
root span | hit, miss, write |
enduser.roles / enduser.groups |
root span | sorted, comma-joined handles |
enduser.super |
root span | true (only when super) |
cache.key.group |
cache spans (core) | stache.index, stache.item, … |
statamic.blink.hits / statamic.blink.misses |
root span (tallies) | per-request Blink memoization effectiveness |
view.path / view.engine |
view.render detail spans (opt-in) |
relative path, antlers |
antlers.tag / antlers.method |
antlers:{tag} detail spans (opt-in) |
bounded tag name (collection, partial) + unbounded method (blog, components/hero) |
Metrics
| Metric | Type | Labels |
|---|---|---|
statamic.static_cache.operations |
counter | operation: hit, miss, write, invalidate, flush |
statamic.stache.warms / statamic.stache.clears |
counter | — |
statamic.stache.warm_duration |
histogram (ms) | — |
statamic.glide.generations |
counter | preset (ad-hoc params → custom) |
statamic.forms.submissions |
counter | form |
statamic.content.changes |
counter | type, action |
statamic.search.index_updates |
counter | index |
statamic.auth.events |
counter | event: impersonation, 2FA, registrations, password changes |
statamic.glide.cache_clears |
counter | scope: all, asset |
statamic.entries.count |
gauge (opt-in) | collection |
statamic.assets.count |
gauge (opt-in) | container |
statamic.users.count |
gauge (opt-in) | — |
Core cache counters additionally carry the key_group label from the
classifier.
Grafana dashboard
The addon bundles a Statamic dashboard for the Grafana suite that
ships with laravel-telemetry (telemetry:dashboards): static cache hit
ratio and operations, Stache traffic by key group and warm duration,
content changes, form submissions, Glide generations, the opt-in
inventory gauges, and Tempo panels for content traces and slow uncached
pages.
php artisan statamic-telemetry:dashboards # import into http://localhost:3000
php artisan statamic-telemetry:dashboards --grafana=https://grafana.example.com --token=...
php artisan statamic-telemetry:dashboards --export=./grafana-provisioning
It carries the same telemetry tag as the core suite, so it appears as
a tab alongside Overview, Requests, Jobs, etc. Panels are regenerated
with python3 resources/grafana/generate.py.
Composing with your own hooks
laravel-telemetry's resolver hooks are single-slot — the last registration wins. This addon registers during boot, so if your app registers its own resolver it replaces the Statamic one. Delegate to the addon's public methods to compose:
use Cbox\StatamicTelemetry\Hooks; use Cbox\StatamicTelemetry\Support\CacheKeys; use Cbox\StatamicTelemetry\Support\Content; Telemetry::resolveUserUsing(fn ($user, $guard) => [ ...Hooks::userAttributes($user, $guard), 'enduser.plan' => $user->plan ?? null, ]); Telemetry::nameRequestsUsing(fn ($request, $response) => $request->is('api/*') ? 'API '.$request->method() : Content::spanName($request) ); Telemetry::classifyCacheKeysUsing(fn (string $store, string $key) => str_starts_with($key, 'tenant:') ? 'tenant' : CacheKeys::classify($store, $key) ); // The addon claims labelRequestsUsing too (for the statamic.route metric // dimension). If your app needs its own metric labels, compose them: Telemetry::labelRequestsUsing(fn ($request) => array_filter([ 'statamic.route' => Content::routeLabel($request), 'plan' => $request->user()?->plan, ], fn ($v) => $v !== null));
Design notes
- Static cache drivers are subclassed, not decorated. Statamic's
middleware and replacers use
instanceof FileCacher/ApplicationCacherchecks to pick code paths; a decorator would silently change static caching behaviour. The addon re-registers thefileandapplicationdrivers as tracing subclasses. Custom cacher drivers are not instrumented. - Full-measure hits are invisible. With the
fullstrategy, cached hits are served by the web server and never reach PHP — those requests produce no telemetry at all. Only PHP-served hits, misses and writes are recorded. - Cache hits keep the generic span name. A static cache hit never
reaches the frontend controller, so there is no entry to name the span
by — hit traces are
GET /{pattern}withstatamic.static_cache: hit(and are fast). Content-named spans are the renders. - Latency metrics break down by content. The base
http.routemetric label is the catch-all template — one bucket for the whole front end. The addon adds a boundedstatamic.routelabel sohttp.server.request.durationsplits per collection/taxonomy. See docs/design-notes. - Trace header stripping. The core package already skips the trace id
header on
Cache-Control: publicresponses (CDN caches). Statamic's half-measure cacher decides cacheability by its own rules and snapshots headers regardless, so the addon removes the header just before the snapshot — first visitors keep their header on file-measure, dynamic responses always keep it. - Augmentation is observed indirectly. Statamic fires no events around
field augmentation, so there is no direct per-field instrumentation.
Its cost shows up in three places instead: the Blink tallies (augmentation
memoization lives in Blink), the opt-in
antlers:{tag}spans (tags are where augmentation work runs), and the opt-inview.renderspans. Per-field visibility would need an upstream hook in statamic/cms. - Search queries are not counted. Statamic fires
SearchIndexUpdatedfor index writes (counted) but no event for queries — query latency is visible on the request span. - Telemetry never throws into the app. The resolver hooks run inside
laravel-telemetry's FailSafe; every event listener extends a
GuardedListenerbase that wraps its body in the same guard. A broken listener can never break the entry save, upload or login it observes. Listeners also check their config toggle at event time, so toggles work at runtime and in tests.
More detail — the full metric/attribute/config catalog and the rationale behind each design decision — lives in docs/.
UI
None yet — the addon is structured as a regular Statamic addon
(AddonServiceProvider), so CP nav, widgets and routes can be added later
without restructuring.
Testing
composer install
composer check # pint + phpstan + pest
The test suite uses Statamic's AddonTestCase and Telemetry::fake(). Note
for addon/package authors: after swapping in the fake, re-register the hooks
(Hooks::register($fake)) — the boot-time registrations landed on the
manager the fake replaces. See tests/TestCase.php::fakeTelemetry().