4.0 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project
Quarkus 3.35 / Java 21 demo app (otel-quarkus-demo) that exercises a full observability stack: OpenTelemetry traces/metrics/logs via OTLP, Micrometer with both a Prometheus scrape endpoint and an OTLP push exporter, structured JSON logging, and SmallRye health checks. The OTLP endpoints in application.properties point at an in-cluster collector (otel-collector-opentelemetry-collector.observability.svc.cluster.local) — running locally without that collector means OTLP exporters will fail, but the app still serves requests and exposes /q/metrics.
Commands
Use the Maven wrapper (./mvnw) — there is no mvn requirement.
- Dev mode (hot reload, dev UI at
/q/dev):./mvnw quarkus:dev - Build runnable app (fast-jar layout in
target/quarkus-app/):./mvnw package - Build container image (jib):
./mvnw package -Dquarkus.container-image.build=true - Run packaged app:
java -jar target/quarkus-app/quarkus-run.jar - Tests:
./mvnw test(no tests exist yet; surefire/failsafe are configured via the Quarkus BOM) - Single test once added:
./mvnw test -Dtest=ClassName#methodName
The app listens on :8080. Key endpoints: POST /api/orders, GET /api/orders, GET /api/orders/{id}, GET /api/inventory, GET /q/health, GET /q/metrics.
Architecture
Three classes under src/main/java/com/demo/, all part of a single REST surface:
OrderResource— JAX-RS resource at/api. Every request path manually builds a parent span (processOrder) and child spans (validateOrder,checkInventory,processPayment) using the injected OTelTracer, sets span attributes, and records status/exceptions explicitly. It also lazily registers Micrometer meters (orderscounter tagged by status,orders_amountcounter,order_processing_durationtimer with percentiles,order_item_countsummary) on each call — the registry deduplicates, but be aware that meter definitions live alongside the request handler rather than in a separate config class. Two behaviors are intentional for demo signal:Thread.sleepcalls simulate latency, and ~10% of orders throw a syntheticRuntimeExceptionto generate error traces.InventoryService—@ApplicationScopedCDI bean. Seeds six products on@PostConstructand registers oneinventory_levelMicrometer gauge per product (taggedproduct=...) bound to anAtomicInteger. Decrementing below 10 auto-restocks by 100 — keep this in mind when interpreting metric dips.Order— DTO with auto-generated id and computedtotalPrice. The in-memory order store lives onOrderResourceas aCopyOnWriteArrayList(non-persistent; cleared on restart).
Observability wiring is entirely in application.properties:
- OTLP traces/metrics/logs are exported to the collector via gRPC on
:4317, plus a parallel HTTP push of Micrometer metrics to:4318/v1/metrics. Both are configured — changing one without the other leaves a divergent metrics pipeline. - The trace sampler is
always_on(demo only — switch to ratio-based in any real deployment). - Console log format embeds
traceId/spanIdfrom MDC;quarkus.log.console.json.enabled=falsedespite thequarkus-logging-jsondependency being present (toggle it on for Loki-friendly output).
Conventions worth knowing
- Span lifecycle in
OrderResourceis manual (spanBuilder().startSpan()+try/finally span.end()). When adding new endpoints, follow the same pattern rather than relying on@WithSpanso attributes/status handling stays consistent. - Meter names use snake_case (
orders_amount,order_processing_duration,inventory_level); tags use snake_case too (order.product,inventory.in_stock). Match this when adding metrics so dashboards/PromQL stay uniform. - The Dockerfile is a two-stage Maven → JRE-alpine build that copies the
target/quarkus-app/fast-jar layout —./mvnw packagemust succeed beforedocker buildwill work.