App Observability
Grafana LabsOfficialPublished by Grafana Labs
- Skill
- Tool integration
- Cloud and DevOps
- Apache-2.0
- Low risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a unified approach to monitoring modern software stacks using Grafana Cloud. It covers three core pillars: Application Observability for backend APM via OTel spanmetrics, Frontend Observability using the Faro Web SDK for RUM and session replay, and AI Observability via OpenLIT for monitoring LLM performance, costs, and model evaluations. The guide details the necessary plumbing, including Grafana Alloy configuration, OTel resource attribute requirements, and correlation strategies across metrics, logs, and traces.
What it does
- Configures backend APM using OpenTelemetry and Grafana Alloy for RED metrics and service maps.
- Implements frontend RUM and session replay using the Faro Web SDK with React integration.
- Monitors LLM and GenAI applications for token usage, latency, costs, and hallucination evals via OpenLIT.
- Establishes cross-signal correlation between frontend errors, backend traces, and logs.
- Provides troubleshooting steps for missing service map edges and OTLP ingestion issues.
- Offers standardized environment variable and resource attribute configurations for consistent observability.
When to use it
- When setting up full-stack APM for a new or existing service.
- When you need to monitor browser performance and user sessions in a web application.
- When tracking LLM API costs, latency, and model quality in AI-powered applications.
- When debugging missing service map edges or correlating frontend errors to backend traces.
- When migrating to or configuring Grafana Cloud as your primary observability backend.
Advantages
- Unified observability across frontend, backend, and AI components.
- Leverages open standards like OpenTelemetry for vendor-agnostic instrumentation.
- Reduces manual configuration through pre-built dashboards and auto-instrumentation.
- Enables deep correlation between metrics, logs, traces, and profiles.
- Provides clear paths for monitoring modern GenAI workloads alongside traditional services.
Usage examples
- “How do I set up APM for my Node.js service to show up in Grafana Cloud?”
- The agent provides the OTel SDK installation steps, the required resource attributes (service.name, service.namespace), and the Grafana Alloy configuration to forward traces to your Cloud instance.
- “I need to monitor my OpenAI API usage and costs in Grafana.”
- The agent guides you through installing OpenLIT, initializing it in your Python app, and configuring the OTLP exporter to send GenAI metrics to your Grafana Cloud instance.
- “How can I link my React frontend errors to backend traces?”
- The agent explains how to initialize the Faro Web SDK with TracingInstrumentation, ensuring the traceparent header is injected to correlate browser events with backend Tempo traces.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/grafana/app-observability/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip app-observability.zip -d .agents/skills
Documentation by Grafana Labs
Grafana Cloud Application Observability
Docs: https://grafana.com/docs/grafana-cloud/monitor-applications/
Three products that share the same OTLP + Mimir / Loki / Tempo / Pyroscope plumbing:
Application Observability — APM from OTel spanmetrics
Frontend Observability — Faro Web SDK, RUM + session replay
AI Observability — LLM / vector-DB monitoring via OpenLIT
Prerequisites
Grafana Cloud stack + OTLP endpoint + numeric instance ID + API key with
MetricsPublisher+LogsPublisher+TracesPublisherFor APM: app instrumented with OTel SDK; for Frontend: a web app + Faro app key; for AI: Python ≥ 3.10
Grafana Alloy as the local OTLP receiver (recommended)
Common Workflows
1. Stand up APM — Alloy receiver → Grafana Cloud + verify
# 1. Set Cloud creds + start Alloy with config from references/apm.md
export GRAFANA_CLOUD_OTLP_ENDPOINT=https://otlp-gateway-prod-us-east-0.grafana.net/otlp
export GRAFANA_CLOUD_INSTANCE_ID=123456
export GRAFANA_CLOUD_API_KEY=glc_eyJ...
alloy fmt /etc/alloy/config.alloy # syntax check
alloy run /etc/alloy/config.alloy
# 2. Verify Alloy is receiving + forwarding
curl -s http://localhost:12345/api/v0/web/components \
| jq '.[] | select(.id|test("otelcol\\.exporter\\.otlphttp"))
| {id, health:.health.state}'
# Expect health.state == "healthy"
curl -s http://localhost:12345/metrics \
| grep -E 'otelcol_(receiver_accepted_spans|exporter_sent_spans)'
# 3. Point your app at Alloy (with required attributes!)
export OTEL_SERVICE_NAME="my-api"
export OTEL_RESOURCE_ATTRIBUTES="service.namespace=myteam,deployment.environment=production"
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
# 4. Verify spans landed in Tempo + spanmetrics generated
# Tempo (TraceQL): { resource.service.name = "my-api" }
# Mimir (PromQL): sum by (job) (rate(traces_spanmetrics_calls_total{service_name="my-api"}[5m]))
# Expect > 0 within ~1 minute.
# 5. Verify it's wired to App Observability
# Grafana → Application → Service Inventory: "my-api" should appear with RED metrics
# Click into it → Service Map edges visible (requires span.kind on outbound calls)Full Alloy block + required resource attributes + spanmetric names + correlation links: references/apm.md.
2. Instrument a React frontend with Faro
# 1. Install
npm install @grafana/faro-react @grafana/faro-web-tracing// 2. initializeFaro with TracingInstrumentation + ReactIntegration (see references/faro.md)
// Push a smoketest event so we have a known signal:
faro.api.pushEvent('faro_smoketest', { ts: Date.now().toString() });# 3. Verify in DevTools Network — POST to /collect returns 202
# (401 → wrong app key; 404 → wrong url region)
# 4. Verify in Grafana Cloud
# - Frontend Observability → your app → Sessions: your session appears
# - LogQL on Loki: {kind="event"} |= "faro_smoketest"
# - With TracingInstrumentation: open the session → the trace ID links to TempoFull React example, CDN setup, session config: references/faro.md.
3. Add AI / LLM observability
pip install openlit==1.42.0# At app startup
import openlit
openlit.init(application_name="my-ai-app", environment="production")
# Your existing OpenAI / Anthropic / Cohere calls now emit OTel spans + metrics.# Env (same OTLP endpoint as APM)
export OTEL_SERVICE_NAME="my-ai-app"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://otlp-gateway-<region>.grafana.net/otlp"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic $(echo -n $ID:$KEY | base64)"
# Verify after a few LLM calls:
# PromQL: sum by (gen_ai_request_model) (rate(gen_ai_usage_input_tokens_total[5m]))
# Dashboard: Grafana → AI Observability → "GenAI Observability" auto-populatesFull OpenLIT install, evals/guards, GenAI metric list, dashboard names: references/ai-observability.md.
Full-stack correlation cheat sheet
| Signal | Product | Storage | Query |
|---|---|---|---|
| RED metrics | App Observability | Mimir | PromQL |
| Traces | Tempo | Tempo | TraceQL |
| Logs | Loki | Loki | LogQL |
| Profiles | Pyroscope | Pyroscope | ProfileQL |
| Browser RUM | Frontend Observability | Loki + Tempo | LogQL / TraceQL |
| LLM metrics | AI Observability | Mimir | PromQL |
Correlation keys: service.name joins all signals; trace exemplars embed trace IDs in metric points; traceID in logs and traceparent injected by Faro for FE → BE linking.
Troubleshooting
Service missing from Service Inventory → missing
service.namespace(job label) ordeployment.environmentresource attributeService Map edges missing →
span.kindnot set on outbound calls (must be CLIENT) or inbound (SERVER)Faro
/collectreturns 401 → wrong app key; 404 → region in URL doesn't match the Faro appNo GenAI metrics → confirm OpenLIT version matches OTel semantic-conv version expected by Cloud; verify auth with curl as in workflow #3
References
references/apm.md— APM essentials: how RED metrics are generated, required OTel resource attributes, Alloy config, correlation linksreferences/apm-setup.md— deep dive: full per-language OTel SDK setup (Node / Python / Java / Go), span-metrics options, complete Alloy configreferences/faro.md— Faro essentials: SDK init, instrumentations, session replayreferences/frontend-observability.md— deep dive: full Faro SDK reference, React/Vue/Angular integration, custom events, source mapsreferences/ai-observability.md— OpenLIT auto-instrumentation for OpenAI / Anthropic / Bedrock / Vertex AI
Resources
Files
- ATTRIBUTION.mdAdded by AutoGineer855 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md7.2 KiB
- references/ai-observability.md2.4 KiB
- references/apm-setup.md23.1 KiB
- references/apm.md3.2 KiB
- references/faro.md3.2 KiB
- references/frontend-observability.md14.7 KiB
references/apm-setup.md
Grafana Cloud Application Observability - APM Setup Reference
Overview
Application Observability is Grafana Cloud's pre-built APM (Application Performance Monitoring) product. It requires:
Applications instrumented with OpenTelemetry (OTel)
Traces sent to Grafana Cloud (via Alloy or direct OTLP)
Span metrics generated from those traces (auto-configured in Grafana Cloud)
Docs: https://grafana.com/docs/grafana-cloud/monitor-applications/application-observability/
Architecture
Your Application
|
| OTel SDK (OTLP gRPC/HTTP)
v
Grafana Alloy (local collector) OR direct to Grafana Cloud
|
|-- Traces --> Grafana Tempo --> Span Metrics Generator
| |
| v
|-- Metrics ----------------------> Grafana Mimir
|
|-- Logs ----> Grafana Loki
|
v
Application Observability UI (Service Inventory, Service Map, etc.)Step 1: Instrument Your Application with OTel
Required Resource Attributes
These MUST be set for Application Observability to function correctly:
# Minimum required
OTEL_SERVICE_NAME=my-api
# Strongly recommended
OTEL_RESOURCE_ATTRIBUTES=service.namespace=my-team,deployment.environment=production,service.version=1.2.3How Grafana uses these:
service.name->service_namelabel, used throughout UIservice.namespace+service.name->joblabel (namespace/service.name)deployment.environment->deployment_environmentlabel, used for environment filteringservice.version-> shown in Service Overview; used for release comparison
Additional attributes for enriched UI:
OTEL_RESOURCE_ATTRIBUTES=\
service.namespace=payments,\
deployment.environment=production,\
service.version=2.3.1,\
k8s.cluster.name=prod-us-east,\
k8s.namespace.name=payments-ns,\
cloud.region=us-east-1,\
host.name=payments-worker-1Node.js Example
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-grpc// tracing.js - load BEFORE your app
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const { Resource } = require('@opentelemetry/resources');
const { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_NAMESPACE } = require('@opentelemetry/semantic-conventions');
const sdk = new NodeSDK({
resource: new Resource({
[SEMRESATTRS_SERVICE_NAME]: 'my-node-api',
[SEMRESATTRS_SERVICE_NAMESPACE]: 'my-team',
'deployment.environment': 'production',
'service.version': '1.2.0',
}),
traceExporter: new OTLPTraceExporter({
url: 'http://localhost:4317', // Alloy or direct
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();Run with:
node -r ./tracing.js app.jsPython Example
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install# Manual setup (alternatively use opentelemetry-instrument CLI)
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
resource = Resource.create({
"service.name": "my-python-api",
"service.namespace": "my-team",
"deployment.environment": "production",
"service.version": "1.0.0",
})
provider = TracerProvider(resource=resource)
exporter = OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True)
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)Or using the auto-instrumentation CLI:
OTEL_SERVICE_NAME=my-python-api \
OTEL_RESOURCE_ATTRIBUTES="service.namespace=my-team,deployment.environment=production" \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
opentelemetry-instrument python app.pyJava Example
Using the OTel Java agent (zero-code instrumentation):
wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
OTEL_SERVICE_NAME=my-java-api \
OTEL_RESOURCE_ATTRIBUTES="service.namespace=my-team,deployment.environment=production,service.version=1.0.0" \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
java -javaagent:opentelemetry-javaagent.jar -jar myapp.jarGrafana also provides a custom Java distribution:
# Grafana's distribution with extra features
wget https://github.com/grafana/grafana-opentelemetry-java/releases/latest/download/grafana-opentelemetry-java.jarGo Example
go get go.opentelemetry.io/otel \
go.opentelemetry.io/otel/sdk \
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpcpackage main
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)
func initTracer() func() {
ctx := context.Background()
exporter, _ := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("localhost:4317"),
otlptracegrpc.WithInsecure(),
)
res, _ := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("my-go-api"),
semconv.ServiceNamespace("my-team"),
semconv.ServiceVersion("1.0.0"),
attribute.String("deployment.environment", "production"),
),
)
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
)
otel.SetTracerProvider(tp)
return func() { tp.Shutdown(ctx) }
}Step 2: Install and Configure Grafana Alloy
Alloy is the recommended local OTel collector that forwards data to Grafana Cloud.
Install Alloy
# macOS
brew install grafana/grafana/alloy
# Linux (Debian/Ubuntu)
sudo apt-get install alloy
# Docker
docker run --rm \
-v ./config.alloy:/etc/alloy/config.alloy \
-p 4317:4317 -p 4318:4318 \
grafana/alloy:latest run /etc/alloy/config.alloy
# Kubernetes (via Helm)
helm repo add grafana https://grafana.github.io/helm-charts
helm install alloy grafana/alloy \
--set-file alloy.configMap.content=config.alloyBasic Alloy Config (config.alloy)
// =========================================================
// OTLP Receiver - accepts data from instrumented apps
// =========================================================
otelcol.receiver.otlp "default" {
grpc {
endpoint = "0.0.0.0:4317"
}
http {
endpoint = "0.0.0.0:4318"
}
output {
metrics = [otelcol.processor.resourcedetection.default.input]
logs = [otelcol.processor.resourcedetection.default.input]
traces = [otelcol.processor.resourcedetection.default.input]
}
}
// =========================================================
// Resource Detection - auto-adds cloud/host metadata
// =========================================================
otelcol.processor.resourcedetection "default" {
// Detect from: env vars, system info, cloud providers
detectors = ["env", "system", "gcp", "ec2", "azure"]
system {
hostname_sources = ["os"]
}
output {
metrics = [otelcol.processor.transform.drop_unneeded.input]
logs = [otelcol.processor.transform.drop_unneeded.input]
traces = [otelcol.processor.transform.drop_unneeded.input]
}
}
// =========================================================
// Transform - remove noisy/expensive attributes
// =========================================================
otelcol.processor.transform "drop_unneeded" {
error_mode = "ignore"
trace_statements {
context = "resource"
statements = [
// Remove high-cardinality attributes that increase storage cost
// "delete_key(attributes, \"process.command_args\")",
]
}
output {
metrics = [otelcol.processor.batch.default.input]
logs = [otelcol.processor.batch.default.input]
traces = [otelcol.processor.batch.default.input]
}
}
// =========================================================
// Batch Processor - groups signals for efficient export
// =========================================================
otelcol.processor.batch "default" {
send_batch_size = 8192
send_batch_max_size = 0
timeout = "200ms"
output {
metrics = [otelcol.exporter.otlphttp.grafana_cloud.input]
logs = [otelcol.exporter.otlphttp.grafana_cloud.input]
traces = [otelcol.exporter.otlphttp.grafana_cloud.input]
}
}
// =========================================================
// Authentication
// =========================================================
otelcol.auth.basic "grafana_cloud" {
username = env("GRAFANA_CLOUD_INSTANCE_ID")
password = env("GRAFANA_CLOUD_API_KEY")
}
// =========================================================
// Export to Grafana Cloud
// =========================================================
otelcol.exporter.otlphttp "grafana_cloud" {
client {
endpoint = env("GRAFANA_CLOUD_OTLP_ENDPOINT")
auth = otelcol.auth.basic.grafana_cloud.handler
}
}Environment Variables for Alloy
# From Grafana Cloud > My Account > Stack > OpenTelemetry
GRAFANA_CLOUD_OTLP_ENDPOINT=https://otlp-gateway-prod-us-east-0.grafana.net/otlp
GRAFANA_CLOUD_INSTANCE_ID=123456
GRAFANA_CLOUD_API_KEY=glc_eyJ...Step 3: Direct OTLP to Grafana Cloud (without Alloy)
If you prefer sending directly from your application:
# In your app's environment
OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-<region>.grafana.net/otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
# Header value: Basic base64("instanceID:apiKey")
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic <base64-instanceid:apikey>
OTEL_SERVICE_NAME=my-api
OTEL_RESOURCE_ATTRIBUTES=service.namespace=myteam,deployment.environment=productionGet the base64 value:
echo -n "123456:glc_eyJ..." | base64Step 4: Span Metrics (How RED Metrics Are Generated)
Application Observability generates RED metrics from traces. This happens in two places:
Option A: Tempo Metrics-Generator (Grafana Cloud default)
Grafana Cloud Tempo automatically runs the metrics-generator. No configuration needed for basic usage.
Generated metrics:
traces_spanmetrics_calls_total- request rate (labeled byspan_name,status_code,service_name)traces_spanmetrics_duration_seconds- latency histogramtraces_service_graph_request_total- for service maptraces_service_graph_request_failed_total- error count for service map
Span metric dimensions include by default:
service.nameservice.namespacedeployment.environmentservice.versionk8s.cluster.namecloud.region
Option B: OTel Collector Spanmetrics Connector
If using your own OTel Collector:
# otelcol-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
connectors:
spanmetrics:
histogram:
explicit:
buckets: [1ms, 5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 5s]
dimensions:
- name: service.namespace
- name: deployment.environment
- name: service.version
- name: http.method
- name: http.status_code
exemplars:
enabled: true
namespace: "traces.span"
exporters:
otlphttp/grafana:
endpoint: "${GRAFANA_CLOUD_OTLP_ENDPOINT}"
auth:
authenticator: basicauth/grafana
prometheusremotewrite/grafana:
endpoint: "${GRAFANA_CLOUD_PROMETHEUS_ENDPOINT}"
auth:
authenticator: basicauth/grafana
extensions:
basicauth/grafana:
client_auth:
username: "${GRAFANA_CLOUD_INSTANCE_ID}"
password: "${GRAFANA_CLOUD_API_KEY}"
service:
extensions: [basicauth/grafana]
pipelines:
traces:
receivers: [otlp]
exporters: [otlphttp/grafana, spanmetrics]
metrics/spanmetrics:
receivers: [spanmetrics]
exporters: [prometheusremotewrite/grafana]Span Attributes for Best Results
App Observability uses specific span attributes to populate UI panels:
HTTP Spans
http.method GET, POST, etc.
http.status_code 200, 404, 500, etc.
http.route /api/users/{id}
http.url Full URL (use sparingly - high cardinality)
http.target /api/users/123
http.scheme http, httpsRPC Spans (gRPC)
rpc.system grpc
rpc.service mypackage.MyService
rpc.method GetUser
rpc.grpc.status_code 0 (OK), 2 (UNKNOWN), 13 (INTERNAL), etc.Database Spans
db.system postgresql, mysql, redis, mongodb
db.name database name
db.operation SELECT, INSERT, UPDATE
db.statement SQL statement (careful with PII)Messaging Spans
messaging.system kafka, rabbitmq, sqs
messaging.destination topic or queue name
messaging.operation publish, receive, processService Inventory and Service Map
Service Inventory
Shows all services as a table with columns:
Service name
Rate (requests/sec)
Error rate (%)
Duration (p95 latency)
Environment
Technology badge (auto-detected from
telemetry.sdk.language)
Filters available:
Environment (
deployment.environment)Namespace (
service.namespace)Technology
Service Map
Uses Tempo's service-graphs metrics generator:
Nodes = services (sized by request rate)
Edges = calls between services (direction determined by span.kind):
SPAN_KIND_CLIENT= outgoing call (source node)SPAN_KIND_SERVER= incoming call (destination node)
For service map edges to form correctly, your spans need:
Server spans:
span.kind = SERVERwithserver.addressorpeer.serviceClient spans:
span.kind = CLIENTtargeting another service
Manual peer.service annotation (when automatic detection fails):
with tracer.start_as_current_span("call-payment-service") as span:
span.set_attribute("peer.service", "payment-api")
span.set_attribute("span.kind", "CLIENT")
# ... make the callService Overview Page
Each service has a dedicated overview showing:
Rate panel: requests/sec over time (from
traces_spanmetrics_calls_total)Errors panel: error rate % (from status code != OK)
Duration panel: p50, p95, p99 latency histograms
Operations panel: top endpoints with individual RED metrics
Logs panel: correlated logs (requires matching
service.name)Infrastructure panel: pod/node metrics (requires K8s monitoring)
Profiles button: link to Pyroscope (requires profiling setup)
Configuring Baselines and Anomaly Detection
App Observability can show baseline metrics (ML-based) for anomaly highlighting:
Requirements:
deployment.environmentattribute must be setMinimum 2 weeks of data for baseline training
Enable in Grafana Cloud > Application Observability > Settings
Alert Rules for APM
Pre-built alert rules available in App Observability:
High error rate (> threshold % of requests)
High latency (p95 > SLO)
Low request rate (sudden drops = potential outage)
Create custom PromQL alerts against span metrics:
# Error rate > 5% for any service
(
sum by (service_name) (rate(traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}[5m]))
/
sum by (service_name) (rate(traces_spanmetrics_calls_total[5m]))
) > 0.05
# p95 latency > 500ms
histogram_quantile(0.95,
sum by (le, service_name) (
rate(traces_spanmetrics_duration_seconds_bucket[5m])
)
) > 0.5SLO Configuration
App Observability integrates with Grafana Cloud SLOs:
Navigate to Service Overview
Click "Create SLO" button
Select metric type: Availability (error rate) or Latency
Set target (e.g., 99.9% availability) and window (e.g., 28 days)
SLO dashboard auto-generates with error budget burn rate alerts
Sampling Considerations
IMPORTANT: Generate span metrics BEFORE sampling. App Observability requires complete, unsampled trace data for accurate RED metrics.
Recommended pipeline with tail sampling:
App -> Alloy (receive all traces) -> Span Metrics (computed here) -> Tail Sampler -> Export samples to Tempo
|
v
Grafana Mimir (all RED metrics, not just sampled)In Alloy config:
// Generate metrics from ALL traces before sampling
otelcol.connector.spanmetrics "default" {
// dimensions...
output {
metrics = [otelcol.exporter.otlphttp.grafana_cloud.input]
}
}
// Then apply tail sampling for trace storage cost control
otelcol.processor.tail_sampling "default" {
decision_wait = "10s"
policies = [
{ name = "errors", type = "status_code", status_code = { status_codes = ["ERROR"] } },
{ name = "slow", type = "latency", latency = { threshold_ms = 500 } },
{ name = "sample", type = "probabilistic", probabilistic = { sampling_percentage = 10 } },
]
output {
traces = [otelcol.exporter.otlphttp.grafana_cloud.input]
}
}Kubernetes Deployment
Alloy as DaemonSet (recommended)
Running Alloy on every node enables host-level resource detection:
# alloy-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: alloy
namespace: monitoring
spec:
selector:
matchLabels:
app: alloy
template:
metadata:
labels:
app: alloy
spec:
containers:
- name: alloy
image: grafana/alloy:latest
args:
- run
- /etc/alloy/config.alloy
ports:
- containerPort: 4317 # OTLP gRPC
hostPort: 4317
- containerPort: 4318 # OTLP HTTP
hostPort: 4318
env:
- name: GRAFANA_CLOUD_OTLP_ENDPOINT
valueFrom:
secretKeyRef:
name: grafana-cloud-creds
key: otlp-endpoint
- name: GRAFANA_CLOUD_INSTANCE_ID
valueFrom:
secretKeyRef:
name: grafana-cloud-creds
key: instance-id
- name: GRAFANA_CLOUD_API_KEY
valueFrom:
secretKeyRef:
name: grafana-cloud-creds
key: api-key
volumeMounts:
- name: alloy-config
mountPath: /etc/alloy
volumes:
- name: alloy-config
configMap:
name: alloy-configApplication Pod Configuration
Point pods to the node-local Alloy instance:
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://$(HOST_IP):4317"
- name: HOST_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: OTEL_SERVICE_NAME
value: "my-api"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "service.namespace=my-team,deployment.environment=production,k8s.pod.name=$(POD_NAME),k8s.namespace.name=$(NAMESPACE)"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespaceTraces to Logs Correlation
For App Observability to show correlated logs in the Service Overview:
Inject trace context into log output
Use structured logging with
traceIdandspanIdfieldsEnsure logs have
service.namelabel matching the OTelservice.name
Example (Node.js with pino):
const { trace } = require('@opentelemetry/api');
// In your logging middleware/utility:
function getTraceContext() {
const span = trace.getActiveSpan();
if (!span) return {};
const { traceId, spanId } = span.spanContext();
return { traceId, spanId };
}
logger.info({ ...getTraceContext(), userId: '123' }, 'User logged in');
// Output: {"traceId":"abc123","spanId":"def456","userId":"123","msg":"User logged in"}Alloy can then extract these fields for Loki:
loki.process "extract_trace" {
stage.json {
expressions = {
traceId = "traceId",
spanId = "spanId",
}
}
stage.labels {
values = {
traceId = "",
spanId = "",
}
}
forward_to = [loki.write.grafana_cloud.receiver]
}Troubleshooting
Service Not Appearing in Inventory
Check:
service.nameresource attribute is set - this is mandatoryTraces are actually arriving in Tempo (check Explore > Tempo)
Span metrics are being generated (check Explore > Mimir, query
traces_spanmetrics_calls_total)deployment.environmentis set (required for some views)
Service Map Has Missing Connections
Check:
span.kindis set on spans (CLIENT for outgoing, SERVER for incoming)peer.serviceis set on client spans (Alloy/Tempo uses this to create edges)Service-graphs metrics generator is enabled (it is in Grafana Cloud by default)
RED Metrics Show Wrong Values
Check:
Span metrics use
deployment.environment(dotted) as source - not sanitizeddeployment_environmentSampling is happening AFTER span metrics generation (not before)
Clock skew between services is not too large (affects histogram accuracy)
Log Correlation Not Working
Check:
Log labels in Loki include
service_namematching the OTelservice.nameLoki data source is configured in Grafana
App Observability data source settings point to correct Loki instance
Key Metric Names Reference
| Metric | Source | Description |
|---|---|---|
traces_spanmetrics_calls_total | Tempo | Request count by service/operation |
traces_spanmetrics_duration_seconds | Tempo | Latency histogram |
traces_spanmetrics_duration_seconds_bucket | Tempo | Latency histogram buckets |
traces_service_graph_request_total | Tempo | Service-to-service calls |
traces_service_graph_request_failed_total | Tempo | Failed service-to-service calls |
traces_service_graph_request_server_seconds | Tempo | Server-side latency for service graph |
traces_span_metrics_calls_total | OTel Collector | Same as above (different source) |
traces_span_metrics_duration_seconds | OTel Collector | Same as above (different source) |
Common labels on span metrics:
service_name- fromservice.nameattributespan_name- operation name (e.g.,GET /api/users)span_kind- SERVER, CLIENT, etc.status_code-STATUS_CODE_OK,STATUS_CODE_ERROR,STATUS_CODE_UNSETdeployment_environment- fromdeployment.environment
References
Application Observability: https://grafana.com/docs/grafana-cloud/monitor-applications/application-observability/
Setup guide: https://grafana.com/docs/grafana-cloud/monitor-applications/application-observability/setup/
Resource attributes: https://grafana.com/docs/grafana-cloud/monitor-applications/application-observability/setup/resource-attributes/
Metrics and labels: https://grafana.com/docs/grafana-cloud/monitor-applications/application-observability/setup/metrics-labels/
Alloy for OTel: https://grafana.com/docs/opentelemetry/collector/grafana-alloy/
OTel semantic conventions: https://opentelemetry.io/docs/specs/semconv/
Span metrics processor: https://grafana.com/docs/tempo/latest/metrics-from-traces/span-metrics/
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs network access, Needs an API key, Needs Python, Needs Node.js
Security
What our scanner noticed· Low risk
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Grafana Labs
- Publisher
- Grafana Labs (Official)
- Source
- grafana/skills · skills/grafana-cloud/app-observability · commit 8fb72a5
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Grafana Labs or Grafana Labs.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download app-observability.zip