Skip to content

Provide solution for testing custom in app instrumentations with built-in agent instrumentations using agent-for-testing #13187

Open
@SKozak

Description

@SKozak

Is your feature request related to a problem? Please describe.

I have a Kafka Streams application where I'm linking spans through stream processing.
I'm heavily using kafka-clients and kafka-streams built-in instrumentations, @WithSpan and OpenTelemetry API.

I know how to do this properly and it's working in runtime, but I need some official solutions to test this custom instrumentation tailored for my app.

I found that there is a package testing/agent-for-testing in official repo, and want to use it, but this agent is very simple and doesn't have additional instrumentations I needed.

Looking at repo and documentations i found otel.javaagent.extensions feature which in theory should allow adding additional instrumentations like this:

configurations {
 ...
    testOtelAgent
    kafka
    kafkaStreams
}


...
kafkaStreams 'io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-kafka-streams-0.11:2.12.0-alpha'
kafka 'io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-kafka-clients-0.11:2.12.0-alpha'

def allInstrumentationFiles = [
        configurations.kafka.files,
        configurations.kafkaCommon.files,
].flatten().collect { it.absolutePath }.join(',')

test {
...
  jvmArgs "-Dotel.javaagent.extensions=$allInstrumentationFiles"
  jvmArgs "-javaagent:${configurations.testOtelAgent.singleFile}"
}

But it doesn't work, because shaded classes aren't published to Maven Central.
When trying to use kafka-clients instrumentation (like above), we get NoClassDefFoundError for classes like io/opentelemetry/javaagent/shaded/instrumentation/kafka/internal/KafkaProducerRequest because they're not in the published artifact.

I struggled for 4-5 days and finally managed to build my own test-agent modifying testing/agent-for-testing/build.gradle.kts

// Create separate configurations for each Kafka module
val kafkaClientsLib by configurations.creating {
  isCanBeResolved = true
  isCanBeConsumed = false
}

val kafkaStreamsLib by configurations.creating {
  isCanBeResolved = true
  isCanBeConsumed = false
}

...
  // Split Kafka dependencies into separate configurations
  kafkaClientsLib(project(":instrumentation:kafka:kafka-clients:kafka-clients-0.11:javaagent", configuration = "shadowRuntimeElements"))
  kafkaStreamsLib(project(":instrumentation:kafka:kafka-streams-0.11:javaagent", configuration = "shadowRuntimeElements"))

...

tasks {
  jar {
   ...

    // Add Kafka clients with renamed jar
    from(kafkaClientsLib) {
      into("extensions")
      rename { "kafka-clients-$it" }
    }

    // Add Kafka streams with renamed jar
    from(kafkaStreamsLib) {
      into("extensions")
      rename { "kafka-streams-$it" }
    }

...

}

It works, but this isn't a solution I'm satisfied with. I think it would be helpful in general if there was an official solution to test own custom instrumentations in app.

Describe the solution you'd like

Publish the shaded jars that are currently built during the local build process. When building locally with ./gradlew :instrumentation:kafka:kafka-clients:kafka-clients-0.11:javaagent:build, a shaded agent-testing.jar is created containing needed classes, but this isn't published to Maven Central.

Then in application tests, I would be able to use:

configurations {
  ...
    testOtelAgent
    kafka
}

...
 testOtelAgent 'io.opentelemetry.javaagent:opentelemetry-agent-for-testing:2.12.0-alpha'
//shaded artifact
 kafka 'io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-kafka-clients-0.11-shaded:2.12.0-alpha' 

...
test {
...
    jvmArgs "-Dotel.javaagent.extensions=${configurations.kafka.files,}"
    jvmArgs "-javaagent:${configurations.testOtelAgent.singleFile}"
...

Describe alternatives you've considered

Provide an enhanced version of agent-for-testing that includes all instrumentations but has them disabled by default. Users could then enable only needed instrumentations via system properties like:

-Dotel.instrumentation.kafka-clients.enabled=true
-Dotel.instrumentation.kafka-streams.enabled=true

Additional context

Example desired usage:

Option 1) Using published shaded artifacts

dependencies {
    testOtelAgent 'io.opentelemetry.javaagent:opentelemetry-agent-for-testing:2.12.0-alpha'
    kafka 'io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-kafka-clients-0.11-shaded:2.12.0-alpha' // shaded artifact
}

test {
    jvmArgs "-javaagent:${configurations.testOtelAgent.singleFile}"
    jvmArgs "-Dotel.javaagent.extensions=${configurations.kafka.files.collect { it.absolutePath }.join(',')}"
}

Option 2) Using full agent with disabled instrumentations by default

dependencies {
    testOtelAgent 'io.opentelemetry.javaagent:opentelemetry-agent-for-testing-full:2.12.0-alpha' // full agent with disabled instrumentations 
}

test {
    systemProperty "otel.instrumentation.kafka-clients.enabled", "true" //enable wanted instrumentations 
    systemProperty "otel.instrumentation.spring-kafka.enabled", "true"
    jvmArgs "-javaagent:${configurations.testOtelAgent.singleFile}"
    jvmArgs "-Dotel.javaagent.extensions=${configurations.kafka.files.collect { it.absolutePath }.join(',')}"
}

I'm willing to contribute and provide a PR once we agree on the preferred approach (either publishing shaded artifacts or enhancing agent-for-testing with all instrumentations disabled by default).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds triageNew issue that requires triage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions