Skip to main content

Custom application metrics

Inspect 1.36

This page covers collecting metrics from your own applications. Unlike the platform components, which keep their metrics on each node's loopback, your app runs on the pod network, where an ordinary scrape works. Instrument it, expose a /metrics endpoint, and declare a ServiceMonitor or PodMonitor next to it. The reads those objects and scrapes what they select.

The interface is the same one the Prometheus operator uses, so nothing about writing them changes: a ServiceMonitor selects a Service, a PodMonitor selects pods directly for a workload with no Service in front of it.

Instrument and expose

Add a Prometheus client library for your language, register your counters and histograms, and serve them on an HTTP path, /metrics by convention, on a named container port:

yaml
		ports:
  - name: metrics
    containerPort: 8080
	

Declare the monitor

The Application Alloy reads every ServiceMonitor in every namespace unless you narrow it with a selector, so a new object is picked up with no collector change:

app-servicemonitor.yamlyaml
		apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: my-app
  namespace: my-app-namespace
spec:
  selector:
    matchLabels:
      app: my-app
  endpoints:
    - port: metrics # the named port on the Service
      interval: 30s
	

Keep the monitor in the same namespace as the workload it selects. That way the team that owns the app owns its scrape config too, and the collector needs no change when a new service appears.

Note

Because the agent reads every monitor by default, a ServiceMonitor anywhere in the cluster becomes a scrape target. Narrow the agent with a namespace list or a label selector if you want an explicit allow-list instead.

Why application metrics go to their own collector

A platform component's metrics are bounded: the cluster decides how many nodes and pods exist, so the series count moves slowly and predictably. An application's are not. One histogram with a label carrying a request id, a user id, or a full URL path turns into hundreds of thousands of series in an afternoon, and the collector scraping it runs out of memory.

That is survivable when the collector only handles applications. It is not survivable when the same agent also collects the node's data (kubelet, etcd, Cilium, and logs), because the crash then deletes the evidence you would investigate it with. Application scrape jobs stay on the clustered Application Alloy and never move to the node-level DaemonSet ( ).

Clustering also has a practical benefit: the replicas shard your targets between them, so a service with hundreds of pods spreads across the pool instead of loading one agent.

Keep the series count sane

Drop what you will not query before it reaches storage, with metricRelabelings on the monitor. The Application Alloy honors them, so this is still the right place for the fix, and it lives next to the metric's owner:

yaml
		endpoints:
  - port: metrics
    interval: 30s
    metricRelabelings:
      - sourceLabels: [__name__]
        regex: "myapp_debug_.*"
        action: drop
	

Prefer dropping a label to dropping a metric when the metric itself is useful. A latency histogram is worth keeping, but with a request id label on it, it splits into one series per request, and that label is what to drop.

Verify and scope

Check that the target appears and is up. Alloy's UI lists the targets each component resolved, which tells you whether the monitor was read at all:

		$ kubectl -n monitoring port-forward svc/app-alloy 12345:12345
	

Two things explain a missing target almost every time: the monitor's selector does not match the Service's labels, or the Service has no port with the name the monitor references. The other problem is duplicated data: the same sample shows up in Prometheus two or three times. That means every Alloy replica is scraping your app on its own instead of sharing the work. covers that failure.

For metrics you can trace back to a specific request, add exemplars so a spike on a graph links to the trace behind it. See .