extensions/opentelemetry/deployment/src/main/resources/META-INF/quarkus-skill.md
quarkus.otel.metrics.enabled=true (build-time — requires rebuild).quarkus.otel.logs.enabled=true (build-time — requires rebuild).quarkus.datasource.jdbc.telemetry=true.Use annotations from io.opentelemetry.instrumentation.annotations:
@WithSpan — creates a new span wrapping the method invocation.@WithSpan("custom-name") — span with an explicit name. Setting a custom SpanKind is also available.@SpanAttribute("key") — marks a method parameter as a span attribute.@AddingSpanAttributes — adds parameter attributes to the current span without creating a new one.@ApplicationScoped
public class OrderService {
@WithSpan("process-order")
public Order process(@SpanAttribute("order.id") String orderId) {
return doProcess(orderId);
}
@AddingSpanAttributes
public void enrich(@SpanAttribute("order.priority") String priority) {
// no new span — "order.priority" is added to the current span
}
}
Annotations work with Uni<T> and Multi<T> return types. They only work on CDI bean methods — not on instances created with new or on private methods.
Inject Tracer via CDI. Always call span.end() in a finally block and use span.makeCurrent() so downstream calls inherit the context:
@Inject
Tracer tracer;
public void charge(String paymentId) {
Span span = tracer.spanBuilder("charge-payment")
.setAttribute("payment.id", paymentId)
.setSpanKind(SpanKind.CLIENT)
.startSpan();
try (Scope scope = span.makeCurrent()) {
processPayment(paymentId);
} catch (Exception e) {
span.setStatus(StatusCode.ERROR, e.getMessage());
span.recordException(e);
throw e;
} finally {
span.end();
}
}
parentbased_always_on (all traces are sampled).quarkus.otel.traces.sampler=parentbased_traceidratio
quarkus.otel.traces.sampler.arg=0.1
quarkus.otel.traces.suppress-application-uris=health,ready,metrics.io.opentelemetry.sdk.trace.samplers.Sampler.Enable metrics (build-time property — requires rebuild):
quarkus.otel.metrics.enabled=true
Inject Meter via CDI to create instruments:
@ApplicationScoped
public class InventoryService {
@Inject
Meter meter;
void onItemSold(String category) {
meter.counterBuilder("items.sold")
.setDescription("Number of items sold")
.build()
.add(1, Attributes.of(AttributeKey.stringKey("category"), category));
}
}
Available instrument types: LongCounter, DoubleCounter, LongUpDownCounter, DoubleHistogram, DoubleGauge, and observable variants (ObservableLongCounter, etc.).
Avoid unbounded attribute values (e.g., userId, requestId) — they cause cardinality explosion and high memory usage.
For annotation-based metrics (@Timed, @Counted), use the quarkus-micrometer-opentelemetry bridge instead.
Enable log export (build-time property — requires rebuild):
quarkus.otel.logs.enabled=true
Quarkus automatically bridges JBoss LogManager to the OpenTelemetry Logs SDK. Existing Logger.info() calls are exported as OTel log records.
Use InMemorySpanExporter from opentelemetry-sdk-testing as a CDI bean to capture and assert spans in tests:
@ApplicationScoped
public class InMemorySpanExporterProducer {
@Produces
@Singleton
InMemorySpanExporter inMemorySpanExporter() {
return InMemorySpanExporter.create();
}
}
@QuarkusTest
class OrderResourceTest {
@Inject
InMemorySpanExporter spanExporter;
@BeforeEach
void reset() {
spanExporter.reset();
}
@Test
void testOrderSpan() {
given().when().get("/orders/123").then().statusCode(200);
await().atMost(5, SECONDS).untilAsserted(() -> {
List<SpanData> spans = spanExporter.getFinishedSpanItems();
assertThat(spans).anyMatch(s -> s.getName().equals("GET /orders/{id}"));
});
}
}
The tests use rest-assured and awatility as dependencies.
If @QuarkusIntegrationTest is required, because of native, a ExporterResource REST endpoint wrapping the access to InMemorySpanExporter is needed because the bean will be located in a separate process.
grpc.grpc uses port 4317, http/protobuf uses port 4318. A mismatch causes silent export failures.quarkus.otel.exporter.otlp.endpoint is a runtime property. quarkus.otel.traces.enabled is build-time. Changing a build-time property at runtime has no effect.quarkus.otel.simple=true for immediate export instead of batching — batched spans may be lost when the function terminates.quarkus.otel.instrument.<extension>=false (e.g., quarkus.otel.instrument.rest=false).quarkus-observability-devservices-lgtm (scope provided) to auto-start Grafana, Tempo, Prometheus, and an OTel Collector in dev/test mode.application.properties if the required values are already default.Awaitility or polling, not immediate assertions.