Author integration tests that query a tracing backend for cross-service trace verification - Jaeger, Zipkin, or Grafana Tempo, same run-query-assert workflow. Jaeger all-in-one Docker for CI (OTLP gRPC :4317 + HTTP :4318 ingest, query API on :16686), `/api/traces?service=X&operation=Y` query patterns, span set + parent-child + duration assertions; Zipkin (:9411 REST API, B3 single/multi-header propagation tests, dependency graph) in references/zipkin.md; Tempo (TraceQL span selectors + structural operators, /api/search, single-binary Docker) in references/tempo.md. Use when verifying that a request produces the expected spans across service boundaries in a running Jaeger, Zipkin, or Tempo backend.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Deep reference for the jaeger-trace-tests SKILL.md. Consult for the full
Jaeger query API surface, parent-child + duration assertion patterns, the
GitHub Actions service block, and per-test isolation / retention on a shared
CI backend.
Jaeger exposes trace data over an HTTP query API on :16686:
| Endpoint | Returns |
|---|---|
GET /api/services | List of service names |
GET /api/services/{service}/operations | Operations for a service |
GET /api/traces?service=X&operation=Y&lookback=5m&limit=10 | Trace JSON |
GET /api/traces/{traceId} | Single trace by ID |
Trace JSON response shape (selected fields):
{
"data": [{
"traceID": "abc...",
"spans": [
{
"spanID": "def...",
"operationName": "order.create",
"duration": 12345,
"tags": [{"key": "order.item_count", "type": "int64", "value": 1}],
"references": [{"refType": "CHILD_OF", "spanID": "parent..."}]
}
]
}]
}duration is in microseconds; tags is a flat list of typed key/value
pairs; parent links live in references, not on the child span directly.
referencesJaeger encodes parent links as references with refType: "CHILD_OF".
def parent_id(span):
refs = span.get("references", [])
child_of = [r for r in refs if r["refType"] == "CHILD_OF"]
return child_of[0]["spanID"] if child_of else None
assert parent_id(db_span) == order_span["spanID"]Assert a duration ceiling straight off the microsecond duration field:
assert order_span["duration"] < 500_000 # under 500msRun the all-in-one image as a job service so every step can reach OTLP ingest and the query API:
services:
jaeger:
image: cr.jaegertracing.io/jaegertracing/jaeger:2.17.0
ports:
- 16686:16686
- 4317:4317
- 4318:4318Full port map: 16686 query API + UI, 4317 OTLP/gRPC ingest, 4318
OTLP/HTTP ingest, 5778 sampling config, 9411 Zipkin (B3) compatibility.
CI runs many tests against one Jaeger. Scope each test with a unique
service.name (or a unique trace tag) so a query never sees another
test's spans:
service_name = f"orders-test-{uuid4()}"
# configure the SDK with this service name, then query filtered by itIn-memory storage is bounded by Jaeger's eviction; long test runs should restart the container or accept eviction.
All-in-one uses transient in-memory storage per the Jaeger getting-started docs. For longer runs mount a config or restart the container between workflows:
docker run ... \
-v /path/to/config.yaml:/jaeger/config.yaml \
cr.jaegertracing.io/jaegertracing/jaeger:2.17.0 \
--config /jaeger/config.yamlProduction storage backends (Cassandra, Elasticsearch, OpenSearch, Badger) matter for real deployments; the in-memory all-in-one is sufficient for CI.