Files
otel-quarkus-demo/CLAUDE.md
2026-05-29 16:17:38 +02:00

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 OTel Tracer, sets span attributes, and records status/exceptions explicitly. It also lazily registers Micrometer meters (orders counter tagged by status, orders_amount counter, order_processing_duration timer with percentiles, order_item_count summary) 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.sleep calls simulate latency, and ~10% of orders throw a synthetic RuntimeException to generate error traces.
  • InventoryService@ApplicationScoped CDI bean. Seeds six products on @PostConstruct and registers one inventory_level Micrometer gauge per product (tagged product=...) bound to an AtomicInteger. Decrementing below 10 auto-restocks by 100 — keep this in mind when interpreting metric dips.
  • Order — DTO with auto-generated id and computed totalPrice. The in-memory order store lives on OrderResource as a CopyOnWriteArrayList (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/spanId from MDC; quarkus.log.console.json.enabled=false despite the quarkus-logging-json dependency being present (toggle it on for Loki-friendly output).

Conventions worth knowing

  • Span lifecycle in OrderResource is manual (spanBuilder().startSpan() + try/finally span.end()). When adding new endpoints, follow the same pattern rather than relying on @WithSpan so 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 package must succeed before docker build will work.