Prometheus and Elastic export¶
The native_observability_export submodule provides the Prometheus scrape endpoint and the Elastic NDJSON export. It is part of the RAW preset (drush no:preset:raw), the minimum tier for machine-only consumers.
Routes¶
| Route | Path | Permission |
|---|---|---|
native_observability_export.settings |
/admin/config/development/native-observability/settings/export |
administer native observability export |
native_observability_export.prometheus_metrics |
/admin/reports/native-observability/prometheus |
access native observability prometheus endpoint |
native_observability_export.prometheus_scrape |
/native-observability/prometheus |
none, route declares _access: 'TRUE' |
native_observability_export.elastic_export |
/admin/reports/native-observability/elastic |
view native observability traces |
Admin view and scrape endpoint¶
Both prometheus_metrics and prometheus_scrape resolve to the same controller method, PrometheusMetricsController::render. They return identical Prometheus text output; only the access model differs.
/admin/reports/native-observability/prometheusis gated by theaccess native observability prometheus endpointpermission, for manual inspection from inside the Drupal admin./native-observability/prometheuscarries no Drupal permission, because external scrapers do not send a Drupal session. Access control on this route is the bearer token described below.
sequenceDiagram
participant Prometheus
participant Drupal as PrometheusMetricsController
Prometheus->>Drupal: GET /native-observability/prometheus (Authorization: Bearer <token>)
alt no token configured
Drupal-->>Prometheus: 200 metrics body
else Authorization header missing
Drupal-->>Prometheus: 401
else token mismatch
Drupal-->>Prometheus: 403
else token matches
Drupal-->>Prometheus: 200 metrics body
end
When no bearer token is configured, the endpoint is publicly readable and the settings form shows a persistent security warning.
Metrics exposed¶
Ten metrics are served. Names and help text below are read from the endpoint itself, so they are what a scrape actually returns.
| Metric | Type | Meaning |
|---|---|---|
drupal_http_requests_total |
counter | Total HTTP requests recorded, all time. |
drupal_http_requests_window |
gauge | HTTP requests inside the rolling window. |
drupal_http_request_duration_ms |
summary | Request duration by quantile and route. |
drupal_http_error_rate |
gauge | Percentage of 5xx responses in the rolling window. |
drupal_http_ajax_requests_total |
counter | Total AJAX requests recorded, all time. |
drupal_cache_operations_total |
counter | Cache events by operation and bin, all time. |
drupal_span_count |
gauge | Spans by category in the rolling window. |
drupal_span_duration_avg_ms |
gauge | Average span duration by category in the rolling window. |
drupal_outbound_http_requests_total |
gauge | Outbound HTTP spans by destination host in the rolling window. |
drupal_outbound_http_duration_avg_ms |
gauge | Average outbound HTTP span duration by destination host. |
Two naming details will bite you when writing queries. Metrics ending in
_total are counters over all time, and the ones scoped to the rolling window
say window or are gauges, so mixing them in one query compares different time
bases. And drupal_outbound_http_requests_total is a gauge despite the
_total suffix, because it counts spans inside the window rather than
accumulating. Do not wrap it in rate().
The rolling window is prometheus_window_seconds, 300 seconds by default. See
Export settings for that and for the
label strategy, whose machine names do not describe what they emit.
Cache metrics carry operation and bin labels:
drupal_cache_operations_total{operation="HIT",bin="page_cache"} 828
drupal_cache_operations_total{operation="INVALIDATED",bin="cache_tags"} 1263
drupal_cache_operations_total{operation="CACHEABLE",bin="response_cacheability"} 63
When the series cap is exceeded the reader drops the lowest-count series and
emits drupal_prometheus_truncated{reason="series_limit"} 1. Alert on that
series: without it, truncation looks like a traffic drop.
Bearer token¶
Generate and store the token with Drush:
drush native_observability:token:generate prometheus
# alias:
drush no:token:generate prometheus
The command prints the generated value once and stores it in the state key native_observability_export.secret.prometheus_bearer_token. The token can also be set from the settings form field, which is write-only: an empty submission keeps the current token, and the sentinel value __clear__ removes it.
Metrics window, label strategy, and max series settings for the scrape output are documented in Configuration.
Elastic export¶
ElasticTelemetryExportController::export returns stored traces as application/x-ndjson, one JSON document per line, built by ElasticTelemetryBuilder. Supported query arguments:
| Argument | Meaning |
|---|---|
limit |
Row limit, clamped between 1 and 1000, default 200 |
status_code |
Filter by HTTP status code |
path |
Filter by request path |
min_duration_ms |
Minimum trace duration in milliseconds |
max_duration_ms |
Maximum trace duration in milliseconds |
Example:
curl "https://your-site.example/admin/reports/native-observability/elastic?limit=500&status_code=500"
Example Prometheus scrape_config¶
scrape_configs:
- job_name: drupal_native_observability
bearer_token: <your-token>
metrics_path: /native-observability/prometheus
scheme: https
static_configs:
- targets: ['your-site.example']