Skip to main content
Version: Next

Metrics Service (alpha)

The Metrics Service provides a unified interface for emitting application-level metrics from Backstage backend plugins. It wraps the OpenTelemetry Meter API and automatically scopes each plugin's metrics using the OpenTelemetry Instrumentation Scope, so telemetry backends can identify which plugin produced each metric.

note

This service is currently in alpha and is imported from @backstage/backend-plugin-api/alpha. The API may change in future releases.

Setting up OpenTelemetry

The Metrics Service does not configure the OpenTelemetry SDK itself. You are responsible for initializing the OpenTelemetry Node SDK — including exporters, resource attributes, and views — before starting the Backstage backend. Follow the tutorial for more information.

How it Relates to OpenTelemetry Auto-Instrumentation

The Metrics Service complements auto-instrumentation rather than replacing it. Auto-instrumentation captures infrastructure-level signals like HTTP request counts and durations automatically. The Metrics Service is for application-level metrics that only your plugin can provide — things like entities processed, tasks completed, or active sessions.

// Auto-instrumentation provides automatically:
// http.server.request.duration{method="GET", route="/catalog/entities"}

// MetricsService provides manually:
const processed = metrics.createCounter('entities.processed.total', {
description: 'Total entities processed during refresh',
});
processed.add(entities.length, { operation: 'refresh' });

Using the Service

Since the Metrics Service is an alpha API, the service reference is imported from @backstage/backend-plugin-api/alpha instead of coreServices.

import { createBackendPlugin } from '@backstage/backend-plugin-api';
import { metricsServiceRef } from '@backstage/backend-plugin-api/alpha';

createBackendPlugin({
pluginId: 'todos',
register(env) {
env.registerInit({
deps: {
metrics: metricsServiceRef,
},
async init({ metrics }) {
const todoCount = metrics.createCounter('todos.total', {
description: 'Total number of todos',
});

// Later, when adding a todo:
todoCount.add(1, { 'todo.category': 'personal' });
},
});
},
});

Instrument Types

The service provides both synchronous and observable (asynchronous) instrument types, matching the OpenTelemetry specification.

Synchronous Instruments

Synchronous instruments are used inline where the measurement occurs.

MethodDescriptionExample Use Case
createCounterMonotonically increasing sum (non-negative increments)Total requests, entities processed
createUpDownCounterSum that can increase or decreaseActive connections, queue depth
createHistogramDistribution of values (e.g. durations, sizes)Request latency, payload sizes
createGaugePoint-in-time valueCPU usage, memory utilization
const counter = metrics.createCounter('todos.completed.total', {
description: 'Total todos completed',
});
counter.add(1, { 'todo.status': 'completed' });

const histogram = metrics.createHistogram('todo.duration', {
description: 'Time spent processing a todo',
unit: 'seconds',
advice: { explicitBucketBoundaries: [0.01, 0.05, 0.1, 0.5, 1, 5] },
});
histogram.record(durationInSeconds, {
'todo.category': 'personal',
'todo.status': 'completed',
});

const upDown = metrics.createUpDownCounter('todos.in_flight', {
description: 'Number of todos currently in flight',
});
upDown.add(1);
// ... later
upDown.add(-1);

Observable Instruments

Observable instruments use callbacks that are invoked when a metric collection occurs. This is useful for metrics that are expensive to compute or that come from external sources like databases.

MethodDescriptionExample Use Case
createObservableCounterMonotonically increasing sum, reported via callbackTotal items ingested from external API
createObservableUpDownCounterSum that can go up or down, reported via callbackConnection pool size
createObservableGaugePoint-in-time value, reported via callbackRow counts, cache hit ratios
const entityCount = metrics.createObservableGauge('catalog.entities.count', {
description: 'Total amount of entities in the catalog',
});

entityCount.addCallback(async gauge => {
const results = await getEntityCountsByKind();
for (const { kind, count } of results) {
gauge.observe(count, { kind });
}
});

Metric Options

All create* methods accept an optional MetricOptions object:

PropertyTypeDescription
descriptionstringHuman-readable description of the metric
unitstringUnit of measurement (e.g. 'seconds', '{entity}', 'bytes')
adviceobjectAggregation hints, such as explicitBucketBoundaries for histograms

Type-Safe Attributes

Metric instruments accept a generic type parameter that constrains the attributes passed to add, record, or observe:

interface TodoAttributes {
'todo.category': string;
'todo.status': 'completed' | 'in_progress' | 'blocked';
}

const completed = metrics.createCounter<TodoAttributes>(
'todos.completed.total',
{ description: 'Total todos completed' },
);

// Type-safe attributes are enforced
completed.add(1, { 'todo.category': 'personal', 'todo.status': 'completed' });

Advanced Configuration

The service reads optional configuration from app-config.yaml under backend.metrics.plugin.<pluginId>.meter. This lets operators override the OpenTelemetry Instrumentation Scope for a specific plugin without code changes.

tip

Each plugin automatically receives a meter named backstage-plugin-<pluginId>. You typically won't need to configure it.

backend:
metrics:
plugin:
catalog:
meter:
name: 'custom-catalog-meter'
version: '2.0.0'
schemaUrl: 'https://example.com/schema'
PropertyTypeDefaultDescription
namestringbackstage-plugin-<pluginId>Name of the OpenTelemetry meter
versionstringVersion string for the meter
schemaUrlstringSchema URL for the meter