Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Fix bug with consuming OTel extensions #168

Merged
merged 7 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion example-projects/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ plugins {

dependencies {
otel("io.opentelemetry.javaagent:opentelemetry-javaagent:2.8.0")
otelExtension("io.opentelemetry.contrib:opentelemetry-samplers:1.32.0-alpha")
otelExtension("com.google.cloud.opentelemetry:exporter-auto:0.33.0-alpha") {
artifact {
classifier = "shaded"
}
}
otelInstrumentation(project(":custom-instrumentation", "shadow"))
}

Expand Down
10 changes: 6 additions & 4 deletions example-projects/buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ repositories {
gradlePluginPortal()
mavenLocal()
}
/* uncomment when doing local testing on this project, leave commented out for functional test to resolve latest plugin
dependencies {
implementation("com.ryandens:otel:0.4.2")
implementation("com.ryandens:plugin:0.4.2")
/* uncomment when doing local testing on this project, leave commented out for functional test to resolve latest plugin
implementation("com.ryandens:otel:0.6.1")
implementation("com.ryandens:plugin:0.6.1")
*/
implementation("io.opentelemetry.instrumentation.muzzle-generation:io.opentelemetry.instrumentation.muzzle-generation.gradle.plugin:2.8.0-alpha")
implementation("io.opentelemetry.instrumentation.muzzle-check:io.opentelemetry.instrumentation.muzzle-check.gradle.plugin:2.8.0-alpha")
}
*/
6 changes: 3 additions & 3 deletions example-projects/custom-instrumentation/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
plugins {
id("com.ryandens.javaagent.example.java-library-conventions")
id("io.opentelemetry.instrumentation.muzzle-check") version "1.13.1-alpha"
id("io.opentelemetry.instrumentation.muzzle-generation") version "1.13.1-alpha"
id("com.github.johnrengelman.shadow")
id("io.opentelemetry.instrumentation.muzzle-check")
id("io.opentelemetry.instrumentation.muzzle-generation")
id("com.gradleup.shadow")
}

tasks.shadowJar {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.opentelemetry.javaagent.instrumentation.ryandens;
package com.ryandens.example;


import io.opentelemetry.api.trace.Span;
Expand Down Expand Up @@ -39,7 +39,6 @@ public static void onEnter(@Advice.Local("otelSpan") Span span,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = Context.current();
// TODO helper classes don't seem to work for some reaso
span = Singletons.INSTANCE.tracer.spanBuilder("iterative").setSpanKind(SpanKind.INTERNAL).setParent(parentContext).startSpan();
context = parentContext.with(span);
scope = context.makeCurrent();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.opentelemetry.javaagent.instrumentation.ryandens;
package com.ryandens.example;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
Expand All @@ -17,4 +17,9 @@ public SampleInstrumentationModule() {
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new SampleInstrumentation());
}

@Override
public boolean isHelperClass(String className) {
return className.contains("ryandens");
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.opentelemetry.javaagent.instrumentation.ryandens;
package com.ryandens.example;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAME
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
org.gradle.configuration-cache=true
org.gradle.caching=true
version=0.6.1
version=0.7.0
group=com.ryandens
1 change: 0 additions & 1 deletion otel/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ plugins {

dependencies {
implementation(project(":plugin"))
implementation("gradle.plugin.com.github.johnrengelman:shadow:8.0.0")
testImplementation(platform("org.junit:junit-bom:5.11.2"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.junit.jupiter:junit-jupiter-params")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@ class OTelJavaagentPluginFunctionalTest {
val runner = GradleRunner.create()
runner.forwardOutput()
runner.withPluginClasspath()
runner.withArguments("extendedAgent", "run")
runner.withArguments("mergeInstrumentationServiceFiles", "extendedAgent", "run")
runner.withProjectDir(projectDir)
runner.withDebug(true)
val result = runner.build()

// Verify the result
// TODO use testcontainers to start an otel fake backend and retrieve spans from it here to verify instrumentation worked
assertTrue(result.output.contains("AgentInstaller - Installed 1 extension(s)"))
assertTrue(result.output.contains("InstrumentationLoader - Installed 254 instrumenter(s)"))
assertTrue(
result.output.contains(
"Applying instrumentation: sample [class io.opentelemetry.javaagent.instrumentation.ryandens.SampleInstrumentationModule]",
"Applying instrumentation: sample [class com.ryandens.example.SampleInstrumentationModule]",
),
)
assertTrue(result.output.contains("LoggingSpanExporter - 'iterative'")) // span name
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.ryandens.javaagent.otel

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import com.ryandens.javaagent.JavaagentBasePlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.jvm.tasks.Jar

/**
* Enables easy consumption of external extensions and instrumentation libraries by creating a new jar with extra
Expand Down Expand Up @@ -37,17 +37,24 @@ class JavaagentOTelModificationPlugin : Plugin<Project> {
it.isTransitive = false
}

val mergeServiceFiles =
project.tasks.register("mergeInstrumentationServiceFiles", MergeServiceFiles::class.java) { mergeServiceFiles ->
mergeServiceFiles.dependsOn(otelInstrumentation)
mergeServiceFiles.inputFiles.from(project.files(otelInstrumentation, otel).map { jar -> project.zipTree(jar) })
mergeServiceFiles.outputDirectory.set(project.layout.buildDirectory.dir("merged-instrumentation-services"))
}

project.plugins.apply(JavaagentBasePlugin::class.java)
val extendedAgent =
project.tasks.register("extendedAgent", ShadowJar::class.java) { jar ->
project.tasks.register("extendedAgent", Jar::class.java) { jar ->
jar.inputs.files(otelInstrumentation)
jar.dependsOn(mergeServiceFiles)
jar.archiveFileName.set("extended-opentelemetry-javaagent.jar")
jar.destinationDirectory.set(project.layout.buildDirectory.dir("agents"))
jar.mergeServiceFiles {
it.include("inst/META-INF/services/*")
jar.from(otel.map { project.zipTree(it.singleFile) }) {
it.exclude("inst/META-INF/services/**")
}
jar.from(otel.map { project.zipTree(it.singleFile) })
jar.from(otelExtension) {
jar.from(otelExtension.map { it.singleFile }) {
it.into("extensions")
}
jar.manifest {
Expand All @@ -69,7 +76,12 @@ class JavaagentOTelModificationPlugin : Plugin<Project> {
it.into("inst")
it.exclude("META-INF/MANIFEST.MF")
it.rename("(^.*)\\.class\$", "\$1.classdata")
it.duplicatesStrategy = DuplicatesStrategy.INCLUDE
it.exclude("META-INF/services/**")
it.exclude("inst/META-INF/services/**")
it.duplicatesStrategy = DuplicatesStrategy.FAIL
}
jar.from(mergeServiceFiles.map { it.outputDirectory }) {
it.into("inst")
}
}
project.dependencies.add("javaagent", extendedAgent.map { project.files(it.outputs.files.singleFile) })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.ryandens.javaagent.otel

import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStreamWriter
import java.io.PrintWriter
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.util.stream.Collectors
import javax.inject.Inject

/**
* Internal task to merge service files between custom instrumentation JArs and the OpenTelemetry Javaagent.
*
* Inspired by
* [Micronaut](https://github.com/micronaut-projects/micronaut-gradle-plugin/blob/eb9d2b3ab4b3fc71379fc3c1c6df30de761576be/aot-plugin/src/main/java/io/micronaut/gradle/aot/MergeServiceFiles.java#L42)
* in lieu of a built-in solution in Gradle as referenced [here](https://github.com/gradle/gradle/issues/18751).
*/
@CacheableTask
abstract class MergeServiceFiles
@Inject
constructor(private val fileSystemOperations: FileSystemOperations) :
DefaultTask() {
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val inputFiles: ConfigurableFileCollection

@get:OutputDirectory
abstract val outputDirectory: DirectoryProperty

@TaskAction
fun execute() {
val serviceFiles: Set<File> =
inputFiles.asFileTree.matching { f -> f.include("META-INF/services/**").include("inst/META-INF/services/**") }
.files
val outputDir: File = outputDirectory.get().dir("META-INF/services").asFile
fileSystemOperations.delete {
it.delete(outputDir)
}
outputDir.mkdirs()
val perService: Map<String, List<File>> =
serviceFiles.stream()
.collect(Collectors.groupingBy(File::getName))
for ((serviceType, files) in perService) {
val mergedServiceFile = File(outputDir, serviceType)
try {
PrintWriter(
OutputStreamWriter(
FileOutputStream(mergedServiceFile),
StandardCharsets.UTF_8,
),
).use { wrt ->
for (file in files) {
Files.readAllLines(file.toPath()).forEach(wrt::println)
}
}
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
}