8. Observability
Goal
Make BoxOffice explain itself. Attach the OpenTelemetry Java agent to every service and send traces, metrics and logs to grafana/otel-lgtm; build a RED dashboard as code; follow one order from the edge through Kafka to the e-mail in a single trace; and turn a promise to users into an SLO with a multiwindow burn-rate alert β then break the database and time how long the alert takes to fire.
The flash sale went live, and the support channel fills up: "checkout is slow", "the page shows an error". Is it every fan or a few? Which of the seven services? The database, the pool, Kafka, the e-mail provider? Until now the only instrument was k6, which measures from the outside. This lab adds the inside view β the three signals of the observability class: metrics say that something is wrong and how much, traces say where in the request the time went, logs say why.
This lab builds chapter 9 of the story of the course in BoxOffice: the RED dashboard of signals, the trace context carried through the outbox and Kafka of OpenTelemetry, and the SLO with the multiwindow burn-rate alert of alerting. It comes right before lab 9 for a reason: you instrument BoxOffice before you break it on purpose, otherwise the chaos experiments of lab 9 have nothing to show.
Slides
Click the slides and use β β to move through them; F goes full screen and P opens the presenter view, with the notes. Open the slides in a new tab
Before you start
| Time | about 4 hours |
| Tools | the lab 7 ones: Docker with Compose v2 (docker compose version), k6 (k6 version), curl, jq; a browser for Grafana |
| Docker resources | at least 4 CPUs and 8 GB (otel-lgtm needs about 1.5 GB; every JVM grows a little with the agent) |
| Ports | free on your machine: 3000, 4317, 4318 (plus the lab 7 ones) |
Every command runs from the root of your boxoffice repository, which must be in the state lab 7 left it:
boxoffice/
βββ docker-compose.yaml # lab 6 stack + kafka, kafka-init, kafka-ui, mailpit, order, notification
βββ infra/ # nginx/, postgres/, pgbouncer/
βββ load/ # ... orders-load.js, waiting-room-spike.js
βββ scripts/ # ... kafka-lag.sh
βββ services/
βββ auth/ catalog/ gateway/ inventory/
βββ notification/
βββ order/
Catch up
Skipped or broke lab 7? Download boxoffice-after-lab-7.zip, unzip it, cd boxoffice, and continue from here.
Every file of this lab is shown in full on these pages and published under https://insper.github.io/platform/2027.1/hands-on/8/code/<path>.
Architecture
flowchart LR
k6[k6 / fans] --> edge[edge]
edge --> gw[gateway]
gw --> catalog[catalog x3]
gw --> order[order]
order -.->|outbox + Kafka| notification[notification]
catalog -->|OTLP 4318| col
gw -->|OTLP| col
order -->|OTLP| col
notification -->|OTLP| col
subgraph lgtm [otel-lgtm]
col[OTel Collector] --> prom[(Prometheus)]
col --> tempo[(Tempo)]
col --> loki[(Loki)]
prom --> grafana[Grafana :3000]
tempo --> grafana
loki --> grafana
end
grafana -->|burn rate > 14.4| alert[alert: page] Each service runs the agent and pushes OTLP (OpenTelemetry Protocol) to the collector inside otel-lgtm, which stores each signal in its own database. Grafana links them: a point on a latency chart opens its trace, a span opens the logs it wrote.
BoxOffice components touched
| Palette component | In this lab |
|---|---|
| Metrics | Prometheus inside otel-lgtm; RED and USE panels |
| Tracing | Tempo; one trace across HTTP, JDBC, the outbox and Kafka |
| Logs | Loki; every line carries trace_id |
| Alerting | Grafana alert rules: multiwindow burn rate on the catalog SLO |
| Health Check | the actuator probes, excluded from the SLI |
What you will measure
| Question | Metric | Tool |
|---|---|---|
| What does the agent cost? | p50 / p95 / p99, CPU, memory, startup time with and without it | k6, docker stats |
| What does sampling save? | traces per minute, p95 | Tempo, k6 |
| How many time series does a service create? | series per service and per metric | PromQL |
| How long until a broken database pages someone? | stop β Pending β Firing | Grafana alerting |
Prerequisites
- Labs 1 to 7 done: BoxOffice runs with the edge, the gateway,
catalogreplicas,inventory,order,notificationand Kafka. - Around 1.5 GB of free memory for
otel-lgtm. - Read the observability class, especially signals, OpenTelemetry and alerting.
Steps
- Instrument everything β
otel-lgtm, the Java agent in every service, logs with trace ids, and the trace context carried through the outbox. - RED dashboard β a provisioned dashboard: Rate, Errors, Duration per service, plus USE for JVMs and connection pools.
- SLO and burn-rate alert β SLI, SLO and error budget for
catalog; fast- and slow-burn rules as code; fire one. - Measure it β agent overhead, sampling, cardinality and time to detect.
Try it in the playground
Try it: run the observability-stack scenario with the chaos profile. First leave the metrics node disconnected from a service: its utilization shows as "?" β you cannot fix what you do not measure. Connect it, then watch which signal reacts first when a random failure hits: the error rate, the latency percentiles or the health check.
The full simulator is in the playground.
Measure it
The Measure it step has the k6 script and the results table to fill in and keep in results/lab8.md.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
a service exits with Error opening zip file or JAR manifest missing : /otel/opentelemetry-javaagent.jar | the agent was not downloaded, or the volume path is wrong | sh scripts/get-otel-agent.sh from the repository root; ls infra/otel |
services stay starting for minutes | JVMs with the agent start slower; Docker is short of CPU/memory | wait; give Docker 8 GB; stop other stacks (docker ps) |
otel-lgtm exits: Error: Grafana exited before becoming ready | infra/grafana/dashboards.yaml did not exist at the first up, so Docker created a directory with that name | rm -r infra/grafana/dashboards.yaml, save the file of step 1, docker compose up -d --force-recreate otel-lgtm |
| Grafana shows No data | metrics are exported every 10 s and the panels use rate windows | send traffic, wait one minute, check the time range (last 15 min) |
no service_name in Prometheus | the service has no OTEL_SERVICE_NAME / <<: *otel | compare its environment with the order service |
| the dashboard or the alert rules are missing | provisioning files not mounted or invalid | docker compose logs otel-lgtm | grep -i provision; check the paths of the three volumes |
| still two traces per order | order was not rebuilt, or V2__outbox_traceparent.sql did not run | docker compose up -d --build order; docker compose logs order | grep -i flyway |
| port 3000 in use | another Grafana or a dev server | stop it, or map "3001:3000" and use http://localhost:3001 |
Best practices
Best practices in this lab
- Telemetry configured once, for every service β the
x-otelanchor indocker-compose.yaml, with oneOTEL_SERVICE_NAMEper service (OpenTelemetry). - Trace context carried across the asynchronous hop β
Outbox.java,OutboxRelay.javaandV2__outbox_traceparent.sqlstore and restore thetraceparent(OpenTelemetry β context propagation). - Dashboards and alerts as code β
infra/grafana/dashboards/boxoffice-red.jsonandinfra/grafana/alerting/slo-catalog.yaml, provisioned at startup (Signals β dashboards). - An SLI that counts only fans β
http_route!~"/actuator.*"in every query ofslo-catalog.yaml(SLOs). - A page only on a fast multiwindow burn, tested by breaking
dbβ 14.4Γ over 1 h and 5 min inslo-catalog.yaml; the 6Γ rule is a ticket (Alerting).
Clean up
docker compose stop # keep everything for lab 9
docker compose down -v # or remove containers and volumes (all data, including metrics)
Lab 9 uses this dashboard during its game day: keep infra/grafana/ and infra/otel/.
End state
boxoffice/
βββ .gitignore # + infra/otel/*.jar
βββ docker-compose.yaml # + x-otel anchor, agent volume in every Java service, otel-lgtm
βββ infra/
β βββ grafana/
β β βββ dashboards.yaml
β β βββ dashboards/boxoffice-red.json
β β βββ alerting/slo-catalog.yaml
β βββ otel/opentelemetry-javaagent.jar # downloaded, not committed
βββ load/catalog-otel.js
βββ scripts/get-otel-agent.sh
βββ services/order/
βββ pom.xml # + opentelemetry-api
βββ src/main/
βββ java/boxoffice/order/outbox/Outbox.java, OutboxRelay.java # traceparent
βββ resources/db/migration/V2__outbox_traceparent.sql
Commit and tag: git add -A && git commit -m "lab 8: observability" && git tag lab8.
Checklist
-
scripts/get-otel-agent.shdownloads the agent, anddocker-compose.yamlattaches it to every Java service through thex-otelanchor and theinfra/otelvolume, each with its ownOTEL_SERVICE_NAME - Grafana Explore shows
http_server_request_duration_seconds_*for every service, traces in Tempo and logs withtrace_idin Loki - a single trace goes from
POST /ordersinorderthrough the outbox relay and Kafka tonotification -
infra/grafana/holds the dashboard provider,boxoffice-red.jsonandslo-catalog.yaml, loaded at startup - the fast-burn alert fired when
dbwas stopped, and stopped firing after it came back - results table of the Measure it step filled in
results/lab8.mdand explained - self-check: after
docker compose down -v && sh scripts/get-otel-agent.sh && docker compose up -d, the dashboard and both alert rules appear without manual steps; pushed with the taglab8
Related
- Theory: Observability Β· Signals: RED, USE, golden signals Β· OpenTelemetry Β· Alerting and burn rate Β· Incident response Β· SLOs and error budgets
- Previous lab: 7. Async messaging β the flow traced here.
- Next lab: 9. Resilience & chaos uses this dashboard during a game day.