Native Observability
Native Observability started on 12 February 2026 out of a practical question: working out what Drupal was doing in production on a site where I could not install an external agent. I wrote it, and on drupal.org I am its only maintainer, so read every comparison on this page knowing that. The figures about the alternative modules come from their own project pages, read on 24 September 2026, and each one can be checked. The judgement about when to reach for one module rather than another is mine, and you will find it kept apart from the data.
Who builds Native Observability, and since when
What Native Observability is built with
What problem Native Observability solves
Drupal has no native way to tell you which route is slow. The question "why is my site slow, where do I start" comes back on the drupal.org forum at least six times between 2007 and 2015, and the indexed answer never changes: disable modules one at a time, read dblog, read the MySQL slow query log. Between a development profiler, which is meant to be switched off in production, and a commercial APM, which wants an agent on the server and a subscription, there is nothing. Anyone who cannot install a PHP extension on a production box, or would rather not send the data out, is left without instruments.
How Native Observability solves the problem
Native Observability instruments Drupal by wrapping core services rather than patching them: a decorator on `http_client`, an observer on `cache_tags.invalidator`, a stack middleware around `page_cache` and a few kernel event subscribers. From there it collects traces carrying a ULID and ties every child request to the one that started it, AJAX calls included. On that same request it builds the execution spans, watches cache and database, and aggregates the metrics per route. The data stays inside Drupal and is read from a built-in dashboard, or leaves for Prometheus, Elastic or an OpenTelemetry collector. The family is split into eleven sub-modules and three install tiers, one per consumer profile, each with a single Drush command.
What Native Observability achieved
The module is at stable release 2.0.1, dated 21 September 2026, declares compatibility with Drupal 10 and 11, requires PHP 8.2 or newer and is covered by the drupal.org security advisory policy. On the same date drupal.org reports twelve sites using it and two open issues, both about documentation or future compatibility, no functional bug. Version 2.0.0 removed the overhead estimate the module used to publish and replaced it with a measurement each installation runs for itself.
What Native Observability records
The module records five families of data, all tied to the single HTTP request.
Traces
Every request is given a ULID, and child requests, AJAX calls included, are tied back to the request that started them. The correlation identifier also comes back in a response header, so it can be picked up from outside.
Execution spans
The timings of the services that ran during the request, categorised and viewable per trace. Outbound HTTP calls are watched by a middleware on Guzzle's shared handler stack, so every request made through Drupal's http_client lands in the trace without touching the module that issued it.
Route metrics
Request volume, average duration and two percentiles, aggregated per route rather than per URL.
A percentile tells you how slow the page is for whoever has it worst. P95 is the time below which 95 requests out of 100 stay: if it reads 800 milliseconds, five requests in a hundred took longer. P99 raises the bar to 99 out of 100 and photographs the worst tail. They matter because an average hides exactly those: ninety-five fast pages and five very slow ones make a reassuring average and a site somebody finds unusable.
Aggregating per route rather than per URL is what makes two pages of the same kind comparable when their content differs.
Cache events
Real behaviour of the response cache layer, cacheability and tag invalidations, each with a filterable report and a JSON export.
How to read these events on real requests, including the page cache HIT that Drupal never handles, is explained in the article why a Drupal page is not cached.
Database queries
Slow or otherwise relevant queries, with the threshold set by slow_query_threshold_ms and a per request ceiling set by max_stored_queries_per_request, both in native_observability_database_observer.settings.
Requirements and compatibility
| Requirement | Value |
|---|---|
| Drupal | 10 or 11, declared on every module in the family |
| PHP | 8.2 or newer |
| Database | any database Drupal core supports |
| Current stable release | 2.0.1, 21 September 2026 |
| Security advisory policy | covered |
The PHP 8.2 floor is not caution: the module uses readonly classes, which PHP 8.1 cannot parse. Drupal 10 still accepts PHP 8.1, so on that major the module is stricter than core, and composer.json refuses the install up front instead of letting it fail later.
The database has one special case. The dashboard ranks its samples with a window function, and MySQL 5.7 is the only server Drupal allows that has none. It only concerns Drupal 10, because Drupal 11 already requires MySQL 8.0. On MySQL 5.7 the ranking moves into PHP, the pages keep working, and the status report says so.
How to install it
Installation is Composer, then one of three Drush commands that install a whole tier.
composer require drupal/native_observability
drush en native_observability -yThe second command is needed the first time and is not a formality: Drush only discovers a module's commands once that module is enabled, so on a clean site the drush no:preset:* commands do not exist yet.
| Tier | For | Command |
|---|---|---|
raw |
external scrapers, Prometheus, Mimir, CI. No interface | drush no:preset:raw |
dashboard |
people administering the site and reading the data inside Drupal | drush no:preset:dashboard |
integrations |
sites pushing telemetry to an external stack over OTLP, or wiring the events into ECA | drush no:preset:integrations |
Every preset is idempotent: running it again on a provisioned site prints "Already enabled" for the modules it knows and exits without error.
Why the module may refuse to install
Native Observability wraps core services rather than patching them, and that leaves room for a precise problem: another module decorating or re-tagging the same service can quietly take precedence. The instrumentation would stop seeing part of the data with no error showing anywhere, and the charts would keep drawing, simply incomplete.
The sanity check answers one question: is the instrumentation actually wired up? It lists every integration point the family installs, confirms it is the live implementation, and flags anything else competing for the same point. It sits at /admin/reports/native-observability/sanity-check or behind drush no:sanity-check.
When it finds an incompatible override, the base module and every sub-module refuse to install. A measuring instrument that measures badly is worse than no instrument, because nobody has any reason to doubt its numbers.
What it costs, and who measures it
You measure the cost yourself, on your own server, with a command the module carries. There is no percentage to take on trust, because a percentage measured somewhere else does not describe your installation.
drush no:overhead:measureThe measurement compares two states of one site, interleaves the blocks, works on paired differences with a deterministic significance test, discards the warm-up and declares what is not inside the number. The module ships two calibration routes for it, closed outside a measurement run: one that runs no query and no rendering, and one that runs a declared, constant load. They serve two purposes: making the same measurement ask the same question on any installation, and making two runs on one site comparable.
Two machines, two different numbers
The same measurement, with the same protocol, on two machines of mine gives 4.684 and 6.953 milliseconds per request. Neither is a mistake: it is the reason the module stopped publishing a single figure.
The first comes from the development machine the module was born on, and is the one carried in the documentation with its provenance table. Repeating it eight times over two days, 9 to 15 pairs each, the spread there ran from 4.59 to 5.72 milliseconds.
The second comes from a containerised DDEV environment on an iMac, Drupal 11.4.5 and PHP 8.3, measured on 25 September 2026 with Xdebug switched off, because Xdebug instruments every PHP function call and any measurement taken with it running is worthless. This is the output, cut down to the lines that matter:
Fixed cost per request (a): 6.953 ms/req
| Workload | OFF | ON | Delta | Verdict |
| calibration-minimal | 3.778 ms | 10.757 | 6.953 | meas |
| calibration-calibrated | 22.476 ms | 30.279 | 7.707 | meas |
calibration-minimal:
- budget: 0.87% of the declared TTFB budget (good)
- N 15 | IQR [6.908, 7.020] | noise 0.040 | signs 7/7 | p=0.01562The last two lines are what make the figure readable. The machine's noise floor is 0.040 milliseconds and the measured effect is 6.953: the signal sits a hundred and seventy-three times above the noise, with the sign test at 7 out of 7 and p = 0.01562. A delta that failed to clear the noise would be marked unclear rather than meas, and I would not publish it.
Against a response budget of 800 milliseconds that is 0.87%, but the denominator is mine: replace it with your own site's budget.
The cost does not grow with the load
The two calibration routes run different amounts of work, and the module's cost between them does not change measurably: the difference is 0.754 milliseconds across 20 declared units, with p = 0.125 against a noise floor of 0.293. The tool does not round it to zero and does not dress it up as a positive result. It declares it unresolved and explains what that means:
> This is a result, not a failure: it means the cost does not grow with the declared load, as far as > this run can resolve.
In practice: the module costs a fixed amount per request, and no more on a page that works harder. If you need that difference resolved, the command takes more pairs to raise the test's power.
What a production site reported
Under the Talking Drupal episode about the module, a reader described installing it on a production site serving around a hundred pages a minute and leaving it on for a couple of days without noticing any drop in performance. In the same comment he flags an effect that weighs more: the database grew three or four times.
That is exactly what has to happen. A module that records what happens on every request writes rows, and those rows take disk space. Public discussion of this module has been almost entirely about milliseconds: whoever runs it notices the disk first.
Keeping the space in check
| Setting | Default | What it caps |
|---|---|---|
retention.max_rows |
50,000 | row ceiling across traces, spans, cache events and queries |
trace_retention_hours |
72 | how many hours before traces are deleted |
spans_retention_hours |
24 | the same for spans |
metrics_retention_days |
7 | the same for aggregated metrics |
max_stored_queries_per_request |
20 | how many queries are kept per request |
max_stored_invalidations_per_request |
50 | how many cache invalidations per request |
There is also a master switch, capture.enabled: turning it off stops the recording without uninstalling anything, and what has already been collected stays available for analysis. That is the intended way to open the observation over a window of time, close it, and work through what was gathered at your own pace.
The number you will not find here
The table also reports +184.05% on the minimal route, and that percentage is not to be read. The tool's own legend says why: the minimal calibration route runs no query and no rendering, so its denominator is artificially small and the percentage coming out of it describes no real page. Read the absolute delta, and the share of your own page's budget.
Why the number was not there before
Up to version 1.1.x the module published "Estimated overhead: 10%", a percentage arrived at by adding four constants written into the code: not one of the four had been measured, and the figure described no real installation, including the one reading it. Version 2.0.0 deleted that code. After the upgrade the overhead section of the report starts hidden, and comes back only once a measurement has actually been run.
How it sits next to the other tools
Native Observability is not a profiler. It is a flight recorder. The distinction is not marketing, and it changes who is standing in front of the tool.
You switch a profiler on when you are already investigating: you are at the keyboard, you reproduce the problem, and in exchange you get a level of detail you will not find here. XHProf breaks the time down per function, with the tree of who calls whom. Native Observability times services and stops there.
A flight recorder, instead, was already running when the thing happened. It records less, and all of its value lies in not having had to predict the failure. It is the difference between asking "why is this page slow while I watch it" and "why did that request yesterday, the one I cannot reproduce, take six seconds".
Figures read from the project pages on drupal.org on 24 September 2026.
| Tool | Latest release | Sites | Security policy | What it measures |
|---|---|---|---|---|
webprofiler |
11.2.3, 9 September 2026 | 1,164 | covered | one request at a time, from a toolbar on the page you are looking at |
monitoring |
8.x-1.22, 9 July 2026 | 2,595 | covered | the health of the site through sensors, not per-route latency |
opentelemetry |
1.0.0-beta7, 1 April 2026 | 765 | no stable release exists | request time and queries, exported to an external collector |
xhprof |
2.0.0-beta1, 12 March 2025 | 205 | not covered | cost per PHP function, needs an extension on the server |
native_observability |
2.0.1, 21 September 2026 | 12 | covered | traces, spans, per-route metrics, cache and queries, with export |
Two rows in that table deserve attention and are rarely written down. The opentelemetry module, installed on 765 sites, has no stable release: its page states "There are currently no supported stable releases". The monitoring module, the most widely installed of the group at 2,595 sites, measures whether cron runs and whether updates are pending, which is health, a different question from which route is slow.
The judgement, kept apart from the data, and it reads as directions rather than as a comparison. If you are looking at the page while it is slow, open WebProfiler: the feedback is immediate and you have nothing to run. If you need to know which function is spending the time, XHProf or Blackfire answer that question and Native Observability does not. If you want index suggestions on your slow queries, db_performance does that. If you have several services and an infrastructure to correlate, an external stack is still the right call.
Native Observability answers a question the others do not take on: what happened to one specific request, already served, that you cannot reproduce. Every response carries an identifier in the X-Native-Observability-Request-Id header, and whoever hit the problem can hand that string to you. From there you reach the route, the spans and the slow queries of that request, not of a similar one.
The price belongs in the same breath. It records less than a profiler. There are thresholds and caps that throw data away, which is what staying always on costs. The per-request cost is real, and it is measured rather than estimated. It suggests no indexes. In exchange there is no external agent, no subscription, and the data stays in your own database.
Where the project stands today
| Item | Value |
|---|---|
| Stable release | 2.0.1, 21 September 2026 |
| Current branch | 2.0.x. The 1.1.x branch takes fixes only |
| Sites reported on drupal.org | 12 |
| Open issues | 2, both minor |
| Maintainer | Giorgio Pagano, sole |
| Coverage | drupal.org security advisory policy |
Twelve installations are few, and I write it rather than leave it out. The two issues open on 24 September 2026 are a wrong version reference in the installation documentation and an automated Drupal 12 compatibility check raised by a bot: no functional bug, and no support request left unanswered.
The full documentation, twenty-seven pages of user guide and developer reference, is published at project.pages.drupalcode.org/native_observability and is generated from the module's own repository, so it follows the code instead of being a copy that ages somewhere else.
This content was produced by AI and edited by a person.
How was AI used?
Drafts are produced with AI assistance and then directed, edited and fact checked by a person. Figures, dates and version numbers are verified against their public sources before publication, and the date of that check is stated in the text.