Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions examples/distro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This repository has four main submodules:
## Extensions examples

- [DemoIdGenerator](custom/src/main/java/com/example/javaagent/DemoIdGenerator.java) - custom `IdGenerator`
- [DemoInstrumenterCustomizerProvider](custom/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java) - custom instrumentation customization
- [DemoPropagator](custom/src/main/java/com/example/javaagent/DemoPropagator.java) - custom `TextMapPropagator`
- [DemoSampler](custom/src/main/java/com/example/javaagent/DemoSampler.java) - custom `Sampler`
- [DemoSpanProcessor](custom/src/main/java/com/example/javaagent/DemoSpanProcessor.java) - custom `SpanProcessor`
Expand All @@ -36,6 +37,15 @@ The following description follows one specific use-case:
As an example, let us take some database client instrumentation that creates a span for database call
and extracts data from db connection to provide attributes for that span.

### I want to customize instrumentation without modifying core code

The `InstrumenterCustomizerProvider` extension point allows you to customize instrumentation behavior without modifying core code:

- Add custom attributes and metrics to existing instrumentations
- Implement context customizers for request correlation
- Transform span names to match your naming conventions
- Apply customizations conditionally based on instrumentation type and span kind

### I don't want this span at all

The easiest case. You can just pre-configure your distribution and disable given instrumentation.
Expand Down
1 change: 1 addition & 0 deletions examples/distro/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ subprojects {
}

repositories {
mavenLocal()
mavenCentral()
maven {
name = "sonatype"
Expand Down
2 changes: 2 additions & 0 deletions examples/distro/custom/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ dependencies {
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling")
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api")
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package com.example.javaagent;

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 in a custom distribution.
*
* <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
*/
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-distro");
}

@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
// This follows the pattern used in real implementations like UndertowSingletons
context = context.with(REQUEST_ID_KEY, requestId);
return context;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.example.javaagent.DemoInstrumenterCustomizerProvider
11 changes: 11 additions & 0 deletions examples/extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ For more information, see the `extendedAgent` task in [build.gradle](https://git

[DemoAutoConfigurationCustomizerProvider]: src/main/java/com/example/javaagent/DemoAutoConfigurationCustomizerProvider.java
[DemoIdGenerator]: src/main/java/com/example/javaagent/DemoIdGenerator.java
[DemoInstrumenterCustomizerProvider]: src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java
[DemoPropagator]: src/main/java/com/example/javaagent/DemoPropagator.java
[DemoSampler]: src/main/java/com/example/javaagent/DemoSampler.java
[DemoSpanProcessor]: src/main/java/com/example/javaagent/DemoSpanProcessor.java
Expand All @@ -49,6 +50,7 @@ For more information, see the `extendedAgent` task in [build.gradle](https://git
- Custom `SpanProcessor`: [DemoSpanProcessor][DemoSpanProcessor]
- Custom `SpanExporter`: [DemoSpanExporter][DemoSpanExporter]
- Additional instrumentation: [DemoServlet3InstrumentationModule][DemoServlet3InstrumentationModule]
- Instrumenter Customization: [DemoInstrumenterCustomizerProvider][DemoInstrumenterCustomizerProvider] - Add custom attributes, metrics, context customizers, and span name transformations to existing instrumentations

`ConfigurablePropagatorProvider` and `AutoConfigurationCustomizer` implementations and custom
instrumentation (`InstrumentationModule`) need the correct SPI (through `@AutoService`) in
Expand All @@ -65,6 +67,15 @@ Extensions are designed to override or customize the instrumentation provided by

Consider an instrumented database client that creates a span per database call and extracts data from the database connection to provide span attributes. The following are sample use cases for that scenario that can be solved by using extensions.

### "I want to customize instrumentation without modifying core code"

The `InstrumenterCustomizerProvider` extension point allows you to customize instrumentation behavior without modifying core code:

- Add custom attributes and metrics to existing instrumentations
- Implement context customizers for request correlation
- Transform span names to match your naming conventions
- Apply customizations conditionally based on instrumentation type and span kind

### "I don't want this span at all"

Create an extension to disable selected instrumentation by providing new default settings.
Expand Down
2 changes: 2 additions & 0 deletions examples/extension/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ ext {
}

repositories {
mavenLocal()
mavenCentral()
maven {
name = "sonatype"
Expand Down Expand Up @@ -73,6 +74,7 @@ dependencies {
*/
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api")
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator")
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")

//Provides @AutoService annotation that makes registration of our SPI implementations much easier
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* 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
// This follows the pattern used in real implementations like UndertowSingletons
context = context.with(REQUEST_ID_KEY, requestId);
return context;
}
}
}
Loading
Loading