Skip to content

Commit 98c20cd

Browse files
authored
feat: added o11y instrumentation (#170)
* docs: o11y design * feat: added o11y instrumentation * feat: added o11y instrumentation to storage package * feat: added o11y instrumentation to pubsub package * feat: add telemetry.Labels + LoggerFromContext, tighten custom-metric validation * feat: finalized feature and added examples * fix: fixed lint issues * feat: added magic request logger * fix: fixed traces and request logger * fix: fixed a casting issue in leadership package * fix: fixed nil pointer panic in auth middleware * fix: support skipping paths in o11y middleware * fix: address review comments * fix: fix lint issues
1 parent 5563ff4 commit 98c20cd

87 files changed

Lines changed: 12306 additions & 312 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/observability.md

Lines changed: 1824 additions & 0 deletions
Large diffs are not rendered by default.

examples/README.md

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# Examples: Storage + Observability
2+
3+
The sample app in `examples/main.go` demonstrates:
4+
5+
- storage CRUD using the `storage` package
6+
- observability bootstrap via `observability.Init(...)` (or `observability.New`, same behavior)
7+
- HTTP middleware instrumentation via `middlewares.Observability(obs)`
8+
- custom metric registration (`orders_created_total`)
9+
- logger bootstrap via `logger.Init(...)` and `slog` request logging
10+
11+
Note: to get `trace_id` / `span_id` on request logs, keep request logging middleware
12+
after `middlewares.Observability(obs)` in the chi middleware chain.
13+
14+
It exposes:
15+
16+
- `POST /orders` (creates an item in storage)
17+
- `GET /orders/{id}` (reads item from storage)
18+
- `GET /health/liveness` (always 204)
19+
- `GET /health/readiness` (checks storage ping, 204/503)
20+
- `GET /api-docs` (minimal OpenAPI JSON document)
21+
- `/metrics` (Prometheus mode only)
22+
23+
---
24+
25+
## Prerequisites
26+
27+
- Go installed
28+
- `grep` for quick metric filtering
29+
- `jq` (optional, for parsing JSON in the curl examples below)
30+
- Docker (optional, for local OTLP collector)
31+
32+
### Optional: trace outbound HTTP from your own code
33+
34+
Server-side spans come from `middlewares.Observability`. For **client** calls, wrap the transport (add module `go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp` to your app):
35+
36+
```go
37+
import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
38+
39+
client := &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}
40+
```
41+
42+
This is separate from the example’s chi server instrumentation.
43+
44+
---
45+
46+
## Run with Prometheus Metrics (quickest path)
47+
48+
From repo root:
49+
50+
```bash
51+
METRICS_MODE=prometheus ENABLE_TRACING=false LOGGER_LEVEL=info LOGGER_JSON=false go run ./examples
52+
```
53+
54+
In another terminal, generate traffic:
55+
56+
```bash
57+
curl -i http://localhost:8080/health/liveness
58+
curl -i http://localhost:8080/health/readiness
59+
curl -s http://localhost:8080/api-docs
60+
ORDER_ID=$(curl -sf -X POST http://localhost:8080/orders | jq -r '.id')
61+
curl -i "http://localhost:8080/orders/${ORDER_ID}"
62+
```
63+
64+
Without `jq`, create an order with `curl -i -X POST http://localhost:8080/orders` and copy the `id` field from the JSON body for the GET URL.
65+
66+
Verify metrics from the app directly:
67+
68+
```bash
69+
curl -s http://localhost:8080/metrics | grep -E "http_requests_total|magic_storage_operations_total|orders_created_total"
70+
```
71+
72+
You should see series for:
73+
74+
- `http_requests_total` (HTTP middleware)
75+
- `magic_storage_operations_total` (storage instrumentation)
76+
- `orders_created_total` (custom metric from app code)
77+
78+
### Optional: view with Prometheus UI
79+
80+
The repo includes `examples/prometheus.yml`:
81+
82+
```bash
83+
docker run --rm --name prom-magic-example \
84+
-p "9090:9090" \
85+
-v "./examples/prometheus.yml:/etc/prometheus/prometheus.yml:ro" \
86+
prom/prometheus
87+
```
88+
89+
Then open <http://localhost:9090> and query:
90+
91+
- `http_requests_total`
92+
- `magic_storage_operations_total`
93+
- `orders_created_total`
94+
95+
---
96+
97+
## Run with OTLP Metrics + Traces (with Jaeger UI)
98+
99+
Start Jaeger (OTLP gRPC receiver + UI):
100+
101+
```bash
102+
docker run --rm --name jaeger-magic-example \
103+
-p "16686:16686" \
104+
-p "4318:4317" \
105+
-v "$(pwd)/examples/jaeger.json:/etc/jaeger/ui-config.json:ro" \
106+
jaegertracing/all-in-one:latest \
107+
--query.ui-config /etc/jaeger/ui-config.json
108+
```
109+
110+
Open the trace UI at <http://localhost:16686>.
111+
112+
Start the local collector (config in `examples/otel-collector.yml`):
113+
114+
```bash
115+
docker run --rm --name otelcol-magic-example \
116+
-p "4317:4317" \
117+
-p "9464:9464" \
118+
-v "./examples/otel-collector.yml:/etc/otelcol-contrib/config.yaml:ro" \
119+
otel/opentelemetry-collector-contrib:latest
120+
```
121+
122+
This collector setup:
123+
124+
- receives app OTLP metrics + traces on `:4317`
125+
- re-exposes metrics on `:9464` (Prometheus format)
126+
- forwards traces to Jaeger OTLP endpoint on `host.docker.internal:4318`
127+
128+
Run the app in OTLP mode:
129+
130+
```bash
131+
METRICS_MODE=otlp ENABLE_TRACING=true OTLP_ENDPOINT=host.docker.internal:4317 LOGGER_LEVEL=debug LOGGER_JSON=true go run ./examples
132+
```
133+
134+
Generate traffic:
135+
136+
```bash
137+
curl -i http://localhost:8080/health/liveness
138+
curl -i http://localhost:8080/health/readiness
139+
curl -s http://localhost:8080/api-docs
140+
ORDER_ID=$(curl -sf -X POST http://localhost:8080/orders | jq -r '.id')
141+
curl -i "http://localhost:8080/orders/${ORDER_ID}"
142+
```
143+
144+
Without `jq`, create an order with `curl -i -X POST http://localhost:8080/orders` and copy the `id` field from the JSON body for the GET URL.
145+
146+
### Verify metrics in OTLP mode
147+
148+
In OTLP mode, the app's `/metrics` endpoint intentionally returns 404.
149+
Metrics are exported to the collector, which re-exposes them at `:9464`:
150+
151+
```bash
152+
curl -s http://localhost:9464/metrics | grep -E "http_requests_total|magic_storage_operations_total|orders_created_total"
153+
```
154+
155+
### Verify traces are working
156+
157+
Collector still exports traces to `debug` too, so you can inspect raw output:
158+
159+
```bash
160+
docker logs otelcol-magic-example
161+
```
162+
163+
You should see trace export entries after hitting `/orders` routes.
164+
165+
To visualize traces in Jaeger:
166+
167+
1. Open <http://localhost:16686>
168+
2. Select service `magic-storage-observability-example`
169+
3. Click **Find Traces**
170+
171+
Expected span shape for a successful create request:
172+
173+
- `HTTP POST /orders` (server/root span from middleware)
174+
- `orders.create` (example business span)
175+
- `storage.create` (storage instrumentation span)
176+
177+
Expected span shape for a successful get request:
178+
179+
- `HTTP GET /orders/{id}`
180+
- `orders.get`
181+
- `storage.get`
182+
183+
### Verify log correlation (`trace_id` / `span_id`)
184+
185+
When tracing is enabled and logs use `slog.*Context`, the logger wrapper injects `trace_id` and `span_id`.
186+
187+
In this example, the `/orders` handlers log via `slog.InfoContext` / `slog.ErrorContext`.
188+
189+
Run with JSON logs:
190+
191+
```bash
192+
METRICS_MODE=otlp ENABLE_TRACING=true OTLP_ENDPOINT=localhost:4317 LOGGER_LEVEL=debug LOGGER_JSON=true go run ./examples
193+
```
194+
195+
Then hit:
196+
197+
```bash
198+
ORDER_ID=$(curl -sf -X POST http://localhost:8080/orders | jq -r '.id')
199+
curl -i "http://localhost:8080/orders/${ORDER_ID}"
200+
```
201+
202+
Without `jq`, create an order with `curl -i -X POST http://localhost:8080/orders` and copy the `id` field from the JSON body for the GET URL.
203+
204+
You should see log lines containing `trace_id` and `span_id`.
205+
206+
---
207+
208+
## Notes
209+
210+
- `METRICS_MODE` supports:
211+
- `prometheus` (default)
212+
- `otlp`
213+
- `ENABLE_TRACING=true` enables spans from HTTP + storage + pubsub instrumentation paths.
214+
- Logger config (environment variables):
215+
- `LOGGER_LEVEL` -> `debug|info|warn|error` (default: `info`)
216+
- `LOGGER_JSON` -> `true|false` (default: `false`)
217+
- For storage adapters outside this example, update the adapter config block in `examples/main.go`.

examples/jaeger.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"themes": {
3+
"enabled": true
4+
}
5+
}

0 commit comments

Comments
 (0)