Testing OpenTelemetry JVM instrumentation with Kotlin

Share on:

The OpenTelemetry Java instrumentation agent automatically instruments many common frameworks but there are times you may want to customize your application's telemetry by adding your own custom spans, adding attributes to existing spans, etc.

In that case, it can be helpful to verify the custom instrumentation through automated testing. I recently added custom telemetry to my Projektor test reporting app and documented the steps here that I used to verify it in case this is helpful for others.

Add testing dependencies

First, add the OpenTelemetry SDK and testing dependencies into your project's build.gradle file:

1dependencies {
2    implementation platform("io.opentelemetry:opentelemetry-bom:$opentelemetry_version")
3    implementation 'io.opentelemetry:opentelemetry-api'
4    implementation 'io.opentelemetry:opentelemetry-extension-kotlin'
5    
6    // Needed for testing
7    testImplementation("io.opentelemetry:opentelemetry-sdk")
8    testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
9}

Test setup

Now we need a way to capture and verify the spans in a test.

For that, first create an in-memory span provider and hook it up into a tracer provider:

 1import io.opentelemetry.api.GlobalOpenTelemetry
 2import io.opentelemetry.sdk.OpenTelemetrySdk
 3import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter
 4import io.opentelemetry.sdk.trace.SdkTracerProvider
 5import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor
 6
 7class OpenTelemetryApplicationTest {
 8    private val exporter = InMemorySpanExporter.create()
 9    
10    private val tracerProvider = SdkTracerProvider
11        .builder()
12        .addSpanProcessor(SimpleSpanProcessor.create(exporter))
13        .build()
14}

Then register the tracer provider with the global Open Telemetry instance:

1// Needed when running the full test suite as the global telemetry
2// instance may be set by another test and it can only be set once.
3GlobalOpenTelemetry.resetForTest()
4
5OpenTelemetrySdk.builder()
6    .setTracerProvider(tracerProvider)
7    .buildAndRegisterGlobal()

Verification

Now the setup is complete! And after the code-under-test is executed and it creates spans, you can access those spans from the in-memory span exporter and verify them:

1val finishedSpans = exporter.finishedSpanItems
2
3expectThat(finishedSpans).hasSize(2)

In addition to just verifying how many spans are created by the code, you can find individual spans by name and verify their attributes:

1val childSpan = finishedSpans.find { it.name == "child-span" }
2
3expectThat(childSpan.attributes.size()).isEqualTo(1)
4expectThat(childSpan.attributes.get(AttributeKey.stringKey("test-attribute"))).isEqualTo("attribute-value") 

Conclusion

Hope this helps with testing your OpenTelemetry code on the JVM!

Resources