Skip to content

Services

This page lists the services a third-party module could realistically inject: storages, builders, resolvers, and similar API-shaped classes. It skips event subscribers, controllers-as-services, and Guzzle middleware, even where they are declared public: true, because that flag on those classes exists only to satisfy Drupal's compiler passes (event subscribers and router-resolved controllers must be public; middleware resolved by service ID from HandlerStackConfigurator must be public too), not to offer an API surface.

Across the whole family's *.services.yml files, 48 service definitions are marked public: true. Of those, 19 are event subscribers, controllers, or middleware (wiring, not API). The remaining 29 are listed below. Everything not in these tables is private: it can still be autowired by class or interface from your own module's service definitions (compile-time DI does not care about visibility), but it cannot be fetched with \Drupal::service('id') or $container->get('id'), and no snake_case service ID is guaranteed to exist for it.

native_observability (core)

Service ID Class / interface Public Purpose
native_observability.request_log_exclusion_rule_normalizer RequestLogExclusionRuleNormalizer Yes Normalizes stored request-log exclusion rules for runtime matching.
native_observability.request_log_exclusion_repository RequestLogExclusionRepository Yes Stores and loads request-log exclusion rules.
native_observability.diagnostics DiagnosticsService Yes Collects safe runtime diagnostics (no secrets) for the status page.
native_observability.trace_storage TraceStorageInterface (DatabaseTraceStorage) Yes Reads and writes the native_observability_trace table. The interface a third-party module should type-hint to query or insert traces.
native_observability.destination_redirect_guard DestinationRedirectGuard Yes Guards against open-redirect attacks driven by a ?destination= query parameter on module forms.
native_observability.ui_builder NativeObservabilityUiBuilder Yes Builds safe, reusable UI render arrays for Native Observability admin pages.
native_observability.cleanup_manager ObservabilityCleanupManager Yes Coordinates observability data cleanup across every service tagged native_observability.cleanup_handler.
native_observability.service_integrity_inspector ServiceIntegrityInspector Yes Inspects the live container/kernel for the family's decorators, wrappers, middleware, and subscribers; backs drush no:sanity-check.

native_observability.request_log_exclusion_matcher (RequestLogExclusionMatcher) is private, but its isRequestExcluded(?Request $request): bool method is static and reads the exclusion flag already set on the request by the (public) subscriber. Code that only needs to check whether the current request is excluded can call RequestLogExclusionMatcher::isRequestExcluded($request) directly without injecting the service, see Checking whether a request is excluded below. native_observability.request_id_generator (RequestIdGeneratorInterface) and native_observability.privacy_policy (PrivacyPolicy) are private with no public alias.

native_observability_cache_observer

No service in this module is public. CacheObserverStorage, CacheObserverEnvironmentInspector, CacheObserverEnvironmentSnapshotManager, and CacheEventInsertBuffer are all private, resolved only through autowiring inside the module (or, for CacheObserverStorage and CacheObserverEnvironmentSnapshotManager, through the @? optional-reference syntax that native_observability_dashboard uses in its own services.yml). A third-party module wanting the cache observer's data should read it from native_observability_export's Prometheus output, or type-hint CacheObserverStorage directly as a class (autowiring resolves it regardless of visibility, but there is no service ID to look it up by string).

native_observability_dashboard

Service ID / class Public Purpose
Drupal\native_observability_dashboard\Service\TraceMetricsReader Yes Reads raw trace metrics from storage for the dashboard's KPI sections.
native_observability_dashboard.time_range_resolver (DashboardTimeRangeResolver) Yes Resolves the selected dashboard time range from the current request.
native_observability_dashboard.overview_builder (DashboardOverviewBuilder) Yes Builds the Overview tab payload.
native_observability_dashboard.cache_builder (DashboardCacheBuilder) Yes Builds the Cache tab payload.
native_observability_dashboard.database_builder (DashboardDatabaseBuilder) Yes Builds the Database tab payload.
native_observability_dashboard.forensic_route_analysis_builder (ForensicRouteAnalysisBuilder) Yes Builds the Forensic Route Analysis payload that correlates traces, spans, and outbound HTTP/SQL signals for a route.

Every other service in this module (section builders, formatters, resolvers) is private and internal to how the dashboard assembles its own render arrays.

native_observability_database_observer

No service in this module is public. DatabaseObserverStorage, DatabaseQueryRuntimeCollector, DatabaseQueryClassifier, and DatabaseQueryInsertBuffer are all private. native_observability_dashboard's own services.yml documents this directly: it injects DatabaseObserverStorage as '@?Drupal\native_observability_database_observer\Service\DatabaseObserverStorage' by fully-qualified class name, not by a snake_case service ID, because no such public ID exists.

native_observability_execution

Service ID Class / interface Public Purpose
native_observability_execution.timeline_builder ExecutionTimelineBuilder Yes Builds the render array for an execution timeline shown on the trace details page.
native_observability_execution.guzzle_trace_middleware GuzzleTraceMiddleware Yes (tagged http_client_middleware) Records outbound HTTP spans for every request issued through http_client. Public because HandlerStackConfigurator resolves it by service ID at runtime, not because it is meant for direct injection.

ExecutionSpanStack and ExecutionSpanRecorderInterface (ExecutionSpanRecorder) are private.

native_observability_export

Service ID Class / interface Public Purpose
native_observability_export.secret_storage SecretStorageInterface (StateSecretStorage) Yes Stores the write-only bearer tokens used by the Prometheus and OTLP scrape/push endpoints, backed by Drupal State (not exported with config:export).
native_observability_export.prometheus_metrics_builder PrometheusMetricsBuilder Yes Builds Prometheus-compatible metrics output from the live source tables.
native_observability_export.elastic_telemetry_builder ElasticTelemetryBuilder Yes Builds Elastic-compatible NDJSON telemetry documents from stored traces.

LivePrometheusMetricsReader, the service that actually queries the trace, spans, and cache event tables, is private; PrometheusMetricsBuilder depends on it internally.

native_observability_metrics

Service ID Class Public Purpose
native_observability_metrics.route_metrics_aggregator RouteMetricsAggregator Yes Aggregates route performance metrics (throughput, average duration, P95/P99) from observability traces. Backs drush native-observability-metric:aggregate.

PercentileCalculator and DatabaseMetricsStorage are private.

native_observability_otel

Service ID Class Public Purpose
native_observability_otel.exporter_manager ExporterManager Yes The plugin manager for NativeObservabilityExporter plugins. Use createInstance($id) or getDefinitions() to work with exporters directly instead of through the registry.
native_observability_otel.otel_payload_builder OpenTelemetryPayloadBuilder Yes Builds OTLP-compatible JSON payloads for exported traces.

ExporterRegistry, the thin helper OpenTelemetryTraceSubscriber itself uses (get(), getAvailableExporters()), is private with no public service ID. A third-party module that wants the same convenience should type-hint Drupal\native_observability_otel\Service\ExporterRegistry directly in its own service constructor; autowiring resolves it at compile time regardless of visibility.

native_observability_report

Service ID Class Public Purpose
native_observability_report.narrative_loader ReportNarrativeLoader Yes Loads and resolves report narrative YAML definitions.
native_observability_report.environment_resolver ObservabilityEnvironmentResolver Yes Resolves the current execution environment for reporting purposes.
native_observability_report.scoring ObservabilityScoringService Yes Computes normalized statuses for report sections.
native_observability_report.document_builder ObservabilityReportDocumentBuilder Yes Builds printable report documents from prepared preview payloads.
native_observability_report.narrative_path_validator NarrativePathValidator Yes Enforces the narrative file location policy for custom narrative overrides.

This is the only module in the family where every declared service is public; its own services.yml states the reason directly: all five are reached by string ID from callers.

native_observability_spans

Service ID Class / interface Public Purpose
native_observability_spans.span_storage SpanStorageInterface (DatabaseSpanStorage) Yes Reads and writes the native_observability_spans table. The interface a third-party module should type-hint to query spans.
native_observability_spans.span_insert_buffer SpanInsertBuffer Yes The deferred-persistence buffer for the spans table, flushed on kernel.terminate alongside every other tagged buffer.

SpanRecorderInterface (SpanRecorder) and ActiveSpanContextStack are private. GuzzleSpanMiddleware is public but, like its execution counterpart, only because HandlerStackConfigurator resolves it by service ID at runtime.

Writing a cleanup handler

A module that stores its own observability rows can hand retention to this module by tagging a service with native_observability.cleanup_handler and implementing ObservabilityCleanupHandlerInterface.

final class MyCleanupHandler implements ObservabilityCleanupHandlerInterface {

  public function timestampColumn(): string {
    return 'created_at';
  }

  // deleteOlderThan(), enforceMaxRows() and the rest of the interface.
}

timestampColumn() is required from 2.0.0 and returns a literal column name. An earlier version of this module guessed the column by probing the schema for a list of candidate names, which left a handler and its table silently out of step when neither name matched. The method turns that into an error the first time cleanup runs.

Checking whether a request is excluded

RequestLogExclusionMatcher answers two different questions about the current request, and every observer in this family, meaning TraceSubscriber, the cache and database observers, the spans subscribers and the OTLP exporter subscriber, asks it rather than deciding on its own:

  • Does a user-configured exclusion rule match? RequestLogExclusionSubscriber::onKernelRequest() calls RequestLogExclusionMatcher::isExcluded() on kernel.request, against the rules managed at /admin/config/development/native-observability/exclusions.
  • Does the request target one of this module's own routes? Once the route name is resolved, RequestLogExclusionSubscriber::onKernelRequestOwnRoute() calls RequestLogExclusionMatcher::isOwnRouteExcluded(), governed by the capture.exclude_own_routes setting (see Configuration). It is on by default: opening the dashboard, scraping the Prometheus endpoint, or browsing the settings forms would otherwise inflate request counters, skew latency percentiles, and fill the trace list with self-referential rows that say nothing about the site being observed.

Both decisions land on the same request attribute, RequestLogExclusionMatcher::ATTR_EXCLUDED (native_observability.request_excluded). A new observer does not need to know which of the two produced the answer, or evaluate either question itself: call the static RequestLogExclusionMatcher::isRequestExcluded($request) and honor what it returns.

The calibration exception

The module ships two calibration routes, native_observability.calibration_minimal and native_observability.calibration_calibrated, that exist to be measured by the overhead measurement protocol (see Measuring the overhead). They carry the same native_observability route-name prefix as every other page this module ships, so the own-route check above would ordinarily exclude them too, which would make a measurement run write nothing and go silently blank. RequestLogExclusionMatcher::isOwnRouteExcluded() exempts them, but only while CalibrationWindow::isOpen() reports a measurement in progress. Outside that window the calibration routes are indistinguishable from any other own route and are excluded like the rest.

Why an observer should not re-derive this itself

Subscribers that need to check own-route exclusion again later in the request lifecycle, after kernel.request already settled ATTR_EXCLUDED, for example at kernel.response, where TraceSubscriber and the OTLP exporter subscriber both do it, inject RequestLogExclusionMatcher and call isOwnRouteExcluded($request) directly, rather than reading capture.exclude_own_routes themselves or comparing the route name prefix by hand. That call is the only place both the config check and the calibration exemption above are applied together; a private copy of either piece will match today's behaviour and then quietly stop matching it the day the shared decision changes, for example if the calibration exemption above gains a new case. Nothing fails loudly when that happens; the symptom is data that is missing, or unexpectedly present, with no obvious link back to the copy that fell out of step.

Summary

Module Public API services Public but wiring-only (subscribers/controllers/middleware)
native_observability 8 3
native_observability_cache_observer 0 1
native_observability_dashboard 6 5
native_observability_database_observer 0 1
native_observability_execution 1 2
native_observability_export 3 0
native_observability_metrics 1 0
native_observability_otel 2 1
native_observability_report 6 0
native_observability_spans 2 6
Total 29 19

Two modules, cache_observer and database_observer, expose no injectable API at all: everything they store stays private, reachable only through the dashboard's optional @? references or through the exports built on top of it.