Claude Usage Monitoring using built-in Otel Exporter
The documentation in the Claude AI website is clear and detailed so I will focus on the details needed to set up monitoring and clarify usage of additional attributes and labels for filtering.
Claude Usage Monitoring
Claude has a built-in OpenTelemetry exporter to expose usage metrics and logs. You can then use a Grafana dashboard to track cost and token usage per user, department, or any other criteria. It can be enabled and configured using environment variables or settings.json. In a corporate environment, settings.json can be predefined and locked.
Configuration can be set up so the Claude app sends metrics directly to an Observability ingest point (e.g., an OTel Gateway). This may be more convenient, as users do not need to install an additional OTel Collector on their laptops.
More details can be found here
Configuration
Simply enable monitoring and configure your ingestion details:
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp # Options: otlp, prometheus, console
export OTEL_LOGS_EXPORTER=otlp # Options: otlp, console
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
# You may replace default endpoint with your own
# The above setup requires Otel Collector running on localhost
You can use prometheus as the exporter for local testing. With the basic setting you will only get the default set of labels, which is not very useful in the multi-user environment and if you want to make filtering by user-friendly parameters.
Custom Attributes for Filtering
Large organizations may want to add custom labels to their metrics, e.g. user.name, email, department, cost center, etc… This can be done using theadditional environment variable: OTEL_RESOURCE_ATTRIBUTES.
OTEL_RESOURCE_ATTRIBUTES="user=john,email=john@doe.com,dept=it,cost.center=123"
This will add additional labels to the claude_ metrics. Here is the problem: this creates resource_attributes and it may cause ingest point to create additional target_info metric with a complete set of labels: Claude’s default labels + labels from OTEL_RESOURCE_ATTRIBUTES while the original claude_ metrics remain as they were. This is done to prevent cardinality issues in Prometheus/Mimir and Loki databases for metrics and logs. The side effect is that there won’t be custom labels in the original claude_ metrics as you would expect. On top of that, there will be no way to correlate target_info metric with claude_ metrics and join the labels in Grafana.
Logs are handled differently - additional labels from OTEL_RESOURCE_ATTRIBUTES will become metadata and you will be able to search the logs using your custom labels. They will not create additinal indexes in Loki.
The solution for metrics is to introduce transformation somewhere between Claude, as the source, and Prometheus/Mimir as the destination. This can be done in the local Otel Collector, if it exists, or at the ingestion gateway. The key is to copy resource_attributes to attributes.
transform:
metric_statements:
- context: datapoint
statements:
- set(attributes["user"], resource.attributes["user"])
- set(attributes["department"], resource.attributes["department"])
- set(attributes["email"], resource.attributes["email"])
You should be careful here and choose labels with low cardinality. For example, session_id would not be recommended due to the fact it changes very often for the single user and may produce cardinality issues in the database.