opentelemetry-java-instrume.../examples/extension/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerP...

136 lines
5.1 KiB
Java

/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package com.example.javaagent;
import com.google.auto.service.AutoService;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.instrumentation.api.incubator.instrumenter.InstrumenterCustomizer;
import io.opentelemetry.instrumentation.api.incubator.instrumenter.InstrumenterCustomizerProvider;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer;
import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import java.util.concurrent.atomic.AtomicLong;
/**
* This example demonstrates how to use the InstrumenterCustomizerProvider SPI to customize
* instrumentation behavior without modifying the core instrumentation code.
*
* <p>This customizer adds:
*
* <ul>
* <li>Custom attributes to HTTP server spans
* <li>Custom metrics for HTTP operations
* <li>Request correlation IDs via context customization
* <li>Custom span name transformation
* </ul>
*
* <p>The customizer will be automatically applied to instrumenters that match the specified
* instrumentation name and span kind.
*
* @see InstrumenterCustomizerProvider
* @see InstrumenterCustomizer
*/
@AutoService(InstrumenterCustomizerProvider.class)
public class DemoInstrumenterCustomizerProvider implements InstrumenterCustomizerProvider {
@Override
public void customize(InstrumenterCustomizer customizer) {
String instrumentationName = customizer.getInstrumentationName();
if (isHttpServerInstrumentation(instrumentationName)) {
customizeHttpServer(customizer);
}
}
private boolean isHttpServerInstrumentation(String instrumentationName) {
return instrumentationName.contains("servlet")
|| instrumentationName.contains("jetty")
|| instrumentationName.contains("tomcat")
|| instrumentationName.contains("undertow")
|| instrumentationName.contains("spring-webmvc");
}
private void customizeHttpServer(InstrumenterCustomizer customizer) {
customizer.addAttributesExtractor(new DemoAttributesExtractor());
customizer.addOperationMetrics(new DemoMetrics());
customizer.addContextCustomizer(new DemoContextCustomizer());
customizer.setSpanNameExtractor(
unused -> (SpanNameExtractor<Object>) object -> "CustomHTTP/" + object.toString());
}
/** Custom attributes extractor that adds demo-specific attributes. */
private static class DemoAttributesExtractor implements AttributesExtractor<Object, Object> {
private static final AttributeKey<String> CUSTOM_ATTR = AttributeKey.stringKey("demo.custom");
private static final AttributeKey<String> ERROR_ATTR = AttributeKey.stringKey("demo.error");
@Override
public void onStart(AttributesBuilder attributes, Context context, Object request) {
attributes.put(CUSTOM_ATTR, "demo-extension");
}
@Override
public void onEnd(
AttributesBuilder attributes,
Context context,
Object request,
Object response,
Throwable error) {
if (error != null) {
attributes.put(ERROR_ATTR, error.getClass().getSimpleName());
}
}
}
/** Custom metrics that track request counts. */
private static class DemoMetrics implements OperationMetrics {
@Override
public OperationListener create(Meter meter) {
LongCounter requestCounter =
meter
.counterBuilder("demo.requests")
.setDescription("Number of requests")
.setUnit("requests")
.build();
return new OperationListener() {
@Override
public Context onStart(Context context, Attributes attributes, long startNanos) {
requestCounter.add(1, attributes);
return context;
}
@Override
public void onEnd(Context context, Attributes attributes, long endNanos) {
// Could add duration metrics here if needed
}
};
}
}
/** Context customizer that adds request correlation IDs and custom context data. */
private static class DemoContextCustomizer implements ContextCustomizer<Object> {
private static final AtomicLong requestIdCounter = new AtomicLong(1);
private static final ContextKey<String> REQUEST_ID_KEY = ContextKey.named("demo.request.id");
@Override
public Context onStart(Context context, Object request, Attributes startAttributes) {
// Generate a unique request ID for correlation
String requestId = "req-" + requestIdCounter.getAndIncrement();
// Add custom context data that can be accessed throughout the request lifecycle
context = context.with(REQUEST_ID_KEY, requestId);
return context;
}
}
}