Remove static instrumenter module (#1755)

This commit is contained in:
Trask Stalnaker 2025-02-20 13:19:45 -08:00 committed by GitHub
parent fb98ed26c5
commit 2ae70759ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
68 changed files with 0 additions and 3057 deletions

View File

@ -28,7 +28,6 @@ body:
- resource-providers
- runtime-attach
- samplers
- static-instrumenter
- type: textarea
attributes:
label: What happened?

View File

@ -28,7 +28,6 @@ body:
- resource-providers
- runtime-attach
- samplers
- static-instrumenter
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe.

View File

@ -74,8 +74,6 @@ components:
samplers:
- trask
- jack-berg
static-instrumenter:
- anosek-an
kafka-exporter:
- spockz
- vincentfree

View File

@ -40,7 +40,6 @@ feature or via instrumentation, this project is hopefully for you.
| alpha | [Runtime Attach](./runtime-attach/README.md) |
| alpha | [Samplers](./samplers/README.md) |
| beta | [Span Stacktrace Capture](./span-stacktrace/README.md) |
| alpha | [Static Instrumenter](./static-instrumenter/README.md) |
\* `alpha`, `beta` and `stable` are currently used to denote library status per [otep 0232](https://github.com/open-telemetry/oteps/blob/main/text/0232-maturity-of-otel.md).
To reach stable status, the library needs to have stable APIs, stable semantic conventions, and be production ready.

View File

@ -53,11 +53,6 @@ include(":resource-providers")
include(":runtime-attach:runtime-attach")
include(":runtime-attach:runtime-attach-core")
include(":samplers")
include(":static-instrumenter:agent-instrumenter")
include(":static-instrumenter:maven-plugin")
include(":static-instrumenter:agent-extension")
include(":static-instrumenter:bootstrap")
include(":static-instrumenter:test-app")
include(":kafka-exporter")
include(":gcp-resources")
include(":span-stacktrace")

View File

@ -1,67 +0,0 @@
# OpenTelemetry Java static instrumenter
## Structure
### Agent instrumenter
Module enhancing OpenTelemetry Java Agent for static instrumentation. The modified agent can
instrument and save a new JAR with all relevant instrumentations applied and necessary helper
class-code included.
#### Generate a new application jar containing the static instrumentation
Execute the following command line with your application jar name and the desired output folder of
the new jar:
`java -javaagent:opentelemetry-static-agent.jar -cp <your-app.jar> io.opentelemetry.contrib.staticinstrumenter.agent.main.Main <output-folder>`
The `opentelemetry-static-agent.jar` agent needs to be both attached (`-javaagent:`) and run as the
main method (`io.opentelemetry.contrib.staticinstrumenter.agent.main.Main` class).
The generated jar will keep the name of your non-instrumented jar.
#### Run the instrumented application
Execute the following command line:
`java -cp <output-folder>/<your-app.jar><file-separator>no-inst-agent.jar <your-main-class-with-package-name>`
`<file-separator>` is `:` on UNIX systems and `;` on Windows systems.
### Maven 3 plugin
Maven 3 plugin running the static instrumentation agent during the `package` phase. Packaged archive
contains statically instrumented class code.
| Parameter | Description |
|---------------|------------------------------------------------------------------------------------------------------------|
| artifactName | Name of the artifact to instrument. If not provided, the plugin will instrument all the project artifacts. |
| outputFolder | Path to the folder where the plugin will generate the instrumented artifacts. |
| suffix | Suffix added to the generated artifact. The default value is `-instrumented`. |
```xml
<plugin>
<groupId>io.opentelemetry.contrib</groupId>
<artifactId>static-instrumentation-maven-plugin</artifactId>
<version>...</version>
<configuration>
<artifactName>...</artifactName>
<outputFolder>...</outputFolder>
<suffix>...</suffix>
</configuration>
<executions>
<execution>
<goals>
<goal>instrument</goal>
</goals>
</execution>
</executions>
</plugin>
```
## Component owners
- [Anna Nosek](https://github.com/anosek-an), Splunk
Learn more about component owners in [component_owners.yml](../.github/component_owners.yml).

View File

@ -1,22 +0,0 @@
plugins {
id("otel.java-conventions")
}
description = "Extension for OpenTelemetry Java Agent"
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_11)
}
dependencies {
annotationProcessor("com.google.auto.service:auto-service")
compileOnly("com.google.auto.service:auto-service")
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling")
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api")
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
compileOnly("io.opentelemetry.javaagent:opentelemetry-muzzle")
compileOnly(project(":static-instrumenter:bootstrap"))
}

View File

@ -1,24 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.config;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.tooling.bootstrap.BootstrapPackagesBuilder;
import io.opentelemetry.javaagent.tooling.bootstrap.BootstrapPackagesConfigurer;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
/**
* Makes classes from {@link io.opentelemetry.contrib.staticinstrumenter.agent.main} package
* available both in agent's premain and static instrumenter's main.
*/
@AutoService(BootstrapPackagesConfigurer.class)
public class StaticPackagesConfigurer implements BootstrapPackagesConfigurer {
@Override
public void configure(BootstrapPackagesBuilder builder, ConfigProperties config) {
builder.add("io.opentelemetry.contrib.staticinstrumenter.agent.main");
}
}

View File

@ -1,25 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.extension;
import io.opentelemetry.contrib.staticinstrumenter.agent.main.AdditionalClasses;
import io.opentelemetry.javaagent.tooling.HelperInjectorListener;
import java.util.Map;
/**
* A listener to be registered in {@link io.opentelemetry.javaagent.tooling.HelperInjector}. It
* saves all additional classes created by the agent to the AdditionalClasses class.
*/
public class AdditionalClassesInjectorListener implements HelperInjectorListener {
@Override
public void onInjection(Map<String, byte[]> classnameToBytes) {
for (Map.Entry<String, byte[]> classEntry : classnameToBytes.entrySet()) {
String classFileName = classEntry.getKey().replace(".", "/") + ".class";
AdditionalClasses.put(classFileName, classEntry.getValue());
}
}
}

View File

@ -1,25 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.extension;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.tooling.BeforeAgentListener;
import io.opentelemetry.javaagent.tooling.HelperInjector;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
/**
* Configures a listener on {@link io.opentelemetry.javaagent.tooling.HelperInjector} before the
* agents starts. The listener enables passing additional classes created by the agent to the static
* instrumenter, which in turn saves them into the app jar.
*/
@AutoService(BeforeAgentListener.class)
public class AdditionalClassesInjectorListenerInstaller implements BeforeAgentListener {
@Override
public void beforeAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
HelperInjector.setHelperInjectorListener(new AdditionalClassesInjectorListener());
}
}

View File

@ -1,146 +0,0 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id("otel.java-conventions")
id("com.github.johnrengelman.shadow")
}
description = "OpenTelemetry Java Static Instrumentation Agent"
val javaagentLibs: Configuration by configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
}
val bootstrapLibs: Configuration by configurations.creating
configurations.getByName("implementation").extendsFrom(bootstrapLibs)
val javaagent: Configuration by configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
}
configurations.getByName("implementation").extendsFrom(javaagent)
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_11)
}
dependencies {
annotationProcessor("com.google.auto.service:auto-service")
compileOnly("com.google.auto.service:auto-service")
implementation("org.slf4j:slf4j-api")
runtimeOnly("org.slf4j:slf4j-simple")
javaagent("io.opentelemetry.javaagent:opentelemetry-javaagent")
bootstrapLibs(project(":static-instrumenter:bootstrap"))
javaagentLibs(project(":static-instrumenter:agent-extension"))
}
tasks {
val relocateJavaagentLibs by registering(ShadowJar::class) {
configurations = listOf(javaagentLibs)
duplicatesStrategy = DuplicatesStrategy.FAIL
archiveFileName.set("javaagentLibs-relocated.jar")
}
shadowJar {
configurations = listOf(javaagent, bootstrapLibs)
dependsOn(relocateJavaagentLibs)
isolateClasses(relocateJavaagentLibs.get().outputs.files)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
manifest {
attributes.put("Main-Class", "io.opentelemetry.contrib.staticinstrumenter.agent.main.Main")
attributes.put("Agent-Class", "io.opentelemetry.contrib.staticinstrumenter.agent.OpenTelemetryStaticAgent")
attributes.put("Premain-Class", "io.opentelemetry.contrib.staticinstrumenter.agent.OpenTelemetryStaticAgent")
attributes.put("Can-Redefine-Classes", "true")
attributes.put("Can-Retransform-Classes", "true")
attributes.put("Implementation-Vendor", "OpenTelemetry")
attributes.put("Implementation-Version", "demo-${project.version}")
}
}
val createNoInstAgent by registering(Jar::class) {
archiveClassifier.set("no-inst")
// source jar has no timestamps don't add any to the destination
isPreserveFileTimestamps = false
from(zipTree(shadowJar.get().archiveFile)) {
// renaming inst/ leaves behind empty directories
includeEmptyDirs = false
// skip to avoid duplicate entry error
exclude("inst/META-INF/MANIFEST.MF")
eachFile {
if (path.startsWith("inst/")) {
path = path.substring("inst/".length)
if (path.endsWith(".classdata")) {
path = path.substring(0, path.length - 4)
}
}
}
}
manifest {
attributes(shadowJar.get().manifest.attributes)
}
}
withType<ShadowJar>().configureEach {
// we depend on opentelemetry-instrumentation-api in agent-extension, so we need to relocate its usage
relocate("io.opentelemetry.instrumentation.api", "io.opentelemetry.javaagent.shaded.instrumentation.api")
}
assemble {
dependsOn(shadowJar, createNoInstAgent)
}
check {
dependsOn(testing.suites)
}
}
testing {
suites {
val integrationTest by registering(JvmTestSuite::class) {
targets.all {
testTask.configure {
jvmArgumentProviders.add(
AgentJarsProvider(
tasks.shadowJar.flatMap { it.archiveFile },
tasks.named<Jar>("createNoInstAgent").flatMap { it.archiveFile },
),
)
}
}
}
}
}
fun CopySpec.isolateClasses(jars: Iterable<File>) {
jars.forEach {
from(zipTree(it)) {
into("inst")
rename("^(.*)\\.class\$", "\$1.classdata")
// Rename LICENSE file since it clashes with license dir on non-case sensitive FSs (i.e. Mac)
rename("""^LICENSE$""", "LICENSE.renamed")
exclude("META-INF/INDEX.LIST")
exclude("META-INF/*.DSA")
exclude("META-INF/*.SF")
}
}
}
class AgentJarsProvider(
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
val agentJar: Provider<RegularFile>,
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
val noInstAgentJar: Provider<RegularFile>,
) : CommandLineArgumentProvider {
override fun asArguments(): Iterable<String> = listOf("-Dagent=${file(agentJar).path}", "-Dno.inst.agent=${file(noInstAgentJar).path}")
}

View File

@ -1,95 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.contrib.staticinstrumenter.agent.main.Main;
import java.io.File;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
final class JarTest {
private static final String INSTRUMENTATION_MAIN = Main.class.getCanonicalName();
// Class contained in test-app project
private static final String APP_MAIN =
"io.opentelemetry.contrib.staticinstrumenter.test.HttpClientTest";
@TempDir public Path outPath;
@Test
// TODO this test fails sporadically and is causing CI to fail often
// https://github.com/open-telemetry/opentelemetry-java-contrib/issues/877
@Disabled
void testSampleJar() throws Exception {
Path agentPath = Path.of(System.getProperty("agent"));
Path noInstAgentPath = Path.of(System.getProperty("no.inst.agent"));
// jar created in test-app module
Path appPath = getPath("app.jar");
// Create a jar with static instrumentation
ProcessBuilder instrumentationProcessBuilder =
new ProcessBuilder(
"java",
"-javaagent:" + agentPath,
"-cp",
appPath.toString(),
INSTRUMENTATION_MAIN,
outPath.toString());
Process instrumentationProcess = instrumentationProcessBuilder.start();
instrumentationProcess.waitFor(10, TimeUnit.SECONDS);
try (InputStream errorOutput = instrumentationProcess.getErrorStream()) {
String errorOutputAsString = new String(errorOutput.readAllBytes(), StandardCharsets.UTF_8);
assertThat(errorOutputAsString)
.contains(
"[main] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent");
}
assertThat(instrumentationProcess.exitValue()).isEqualTo(0);
Path resultAppPath = outPath.resolve("app.jar");
assertThat(resultAppPath).exists();
// Run the jar with static instrumentation
ProcessBuilder runtimeProcessBuilder =
new ProcessBuilder(
"java",
"-Dotel.traces.exporter=logging",
"-cp",
resultAppPath + File.pathSeparator + noInstAgentPath,
APP_MAIN);
// TODO autoconfigure GlobalOpenTelemetry explicitly in static instrumentation
runtimeProcessBuilder.environment().put("OTEL_JAVA_GLOBAL_AUTOCONFIGURE_ENABLED", "true");
Process runtimeProcess = runtimeProcessBuilder.start();
runtimeProcess.waitFor(10, TimeUnit.SECONDS);
assertThat(runtimeProcess.exitValue()).isEqualTo(0);
try (InputStream standardOutputOfInstrumentedApp = runtimeProcess.getInputStream()) {
String standardOutputAsStringIns =
new String(standardOutputOfInstrumentedApp.readAllBytes(), StandardCharsets.UTF_8);
assertThat(standardOutputAsStringIns).startsWith("SUCCESS - Trace parent value");
}
}
private static Path getPath(String resourceName) throws URISyntaxException {
URL resourceURL = JarTest.class.getResource(resourceName);
assertThat(resourceURL).isNotNull();
return Path.of(resourceURL.toURI());
}
}

View File

@ -1,121 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent;
import io.opentelemetry.contrib.staticinstrumenter.util.SystemLogger;
import io.opentelemetry.javaagent.OpenTelemetryAgent;
import java.io.File;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.security.CodeSource;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
/**
* Replaces {@link io.opentelemetry.javaagent.OpenTelemetryAgent} and wraps its methods with adding
* new {@link java.lang.instrument.ClassFileTransformer}s necessary for saving transformed classes.
*/
public final class OpenTelemetryStaticAgent {
public static void premain(String agentArgs, Instrumentation inst) {
try {
installBootstrapJar(inst);
beforeAgent(inst);
OpenTelemetryAgent.premain(agentArgs, inst);
afterAgent(inst);
} catch (Throwable ex) {
getLogger().error("Instrumentation failed", ex);
}
}
public static void agentmain(String agentArgs, Instrumentation inst) {
try {
installBootstrapJar(inst);
beforeAgent(inst);
OpenTelemetryAgent.agentmain(agentArgs, inst);
afterAgent(inst);
} catch (Throwable ex) {
getLogger().error("Instrumentation failed", ex);
}
}
private static void beforeAgent(Instrumentation inst) {
ClassLoader savedContextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(null);
Class<?> clazz = Class.forName("io.opentelemetry.contrib.staticinstrumenter.agent.main.Main");
Method getPreTransformer = clazz.getMethod("getPreTransformer");
inst.addTransformer((ClassFileTransformer) getPreTransformer.invoke(null));
} catch (Throwable e) {
getLogger().error("Could not configure static instrumenter", e);
} finally {
Thread.currentThread().setContextClassLoader(savedContextClassLoader);
}
}
private static void afterAgent(Instrumentation inst) {
ClassLoader savedContextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(null);
Class<?> clazz = Class.forName("io.opentelemetry.contrib.staticinstrumenter.agent.main.Main");
Method getPostTransformer = clazz.getMethod("getPostTransformer");
inst.addTransformer((ClassFileTransformer) getPostTransformer.invoke(null), true);
} catch (Throwable e) {
getLogger().error("Could not configure static instrumenter", e);
} finally {
Thread.currentThread().setContextClassLoader(savedContextClassLoader);
}
}
private static synchronized File installBootstrapJar(Instrumentation inst)
throws IOException, URISyntaxException {
CodeSource codeSource = OpenTelemetryAgent.class.getProtectionDomain().getCodeSource();
if (codeSource == null) {
throw new IllegalStateException("could not get agent jar location");
}
File javaagentFile = new File(codeSource.getLocation().toURI());
if (!javaagentFile.isFile()) {
throw new IllegalStateException(
"agent jar location doesn't appear to be a file: " + javaagentFile.getAbsolutePath());
}
// passing verify false for vendors who sign the agent jar, because jar file signature
// verification is very slow before the JIT compiler starts up, which on Java 8 is not until
// after premain execution completes
JarFile agentJar = new JarFile(javaagentFile, false);
verifyJarManifestMainClassIsThis(javaagentFile, agentJar);
inst.appendToBootstrapClassLoaderSearch(agentJar);
return javaagentFile;
}
private static void verifyJarManifestMainClassIsThis(File jarFile, JarFile agentJar)
throws IOException {
Manifest manifest = agentJar.getManifest();
if (manifest.getMainAttributes().getValue("Premain-Class") == null) {
throw new IllegalStateException(
"The agent was not installed, because the agent was found in '"
+ jarFile
+ "', which doesn't contain a Premain-Class manifest attribute. Make sure that you"
+ " haven't included the agent jar file inside of an application uber jar.");
}
}
// we shouldn't store any static data in this class
// so this utility method is used instead of a static logger field
private static SystemLogger getLogger() {
return SystemLogger.getLogger(OpenTelemetryStaticAgent.class);
}
private OpenTelemetryStaticAgent() {}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent.main;
class ArchiveEntry {
private static final ArchiveEntry NOT_CLASS =
new ArchiveEntry("", "", /* shouldInstrument= */ false);
private final String name;
private final String path;
private final boolean shouldInstrument;
private ArchiveEntry(String name, String path, boolean shouldInstrument) {
this.name = name;
this.path = path;
this.shouldInstrument = shouldInstrument;
}
static ArchiveEntry fromZipEntryName(String zipEntryName) {
if (!isClass(zipEntryName)) {
return NOT_CLASS;
}
String path = zipEntryName.substring(0, zipEntryName.indexOf(".class"));
return new ArchiveEntry(className(path), path, !isOTel(zipEntryName));
}
private static boolean isClass(String path) {
return path.endsWith(".class");
}
private static String className(String path) {
return path.replace("/", ".");
}
private static boolean isOTel(String zipEntryName) {
return zipEntryName.startsWith("io.opentelemetry");
}
String getName() {
return name;
}
String getPath() {
return path;
}
boolean shouldInstrument() {
return shouldInstrument;
}
}

View File

@ -1,92 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent.main;
import io.opentelemetry.contrib.staticinstrumenter.util.SystemLogger;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
/** Represents an archive storing classes (JAR, WAR). */
class ClassArchive {
interface Factory {
ClassArchive createFor(JarFile source, Map<String, byte[]> instrumentedClasses);
}
private static final SystemLogger logger = SystemLogger.getLogger(ClassArchive.class);
private final JarFile source;
private final Map<String, byte[]> instrumentedClasses;
ClassArchive(JarFile source, Map<String, byte[]> instrumentedClasses) {
this.source = source;
this.instrumentedClasses = instrumentedClasses;
}
void copyAllClassesTo(JarOutputStream outJar) throws IOException {
Enumeration<JarEntry> inEntries = source.entries();
while (inEntries.hasMoreElements()) {
copyEntry(inEntries.nextElement(), outJar);
}
}
private void copyEntry(JarEntry inEntry, JarOutputStream outJar) throws IOException {
String inEntryName = inEntry.getName();
ZipEntry outEntry =
inEntryName.endsWith(".jar") ? new ZipEntry(inEntry) : new ZipEntry(inEntryName);
try (InputStream entryInputStream = getInputStreamForEntry(inEntry, outEntry)) {
outJar.putNextEntry(outEntry);
entryInputStream.transferTo(outJar);
outJar.closeEntry();
} catch (ZipException e) {
if (!isEntryDuplicate(e)) {
logger.error("Error while creating entry: ", e, outEntry.getName());
throw e;
}
}
}
private static boolean isEntryDuplicate(ZipException ze) {
return ze.getMessage() != null && ze.getMessage().contains("duplicate");
}
@SuppressWarnings("BanClassLoader")
private InputStream getInputStreamForEntry(JarEntry inEntry, ZipEntry outEntry)
throws IOException {
InputStream entryIn = null;
ArchiveEntry entry = ArchiveEntry.fromZipEntryName(inEntry.getName());
if (entry.shouldInstrument()) {
String className = entry.getName();
try {
ClassLoader loader = new URLClassLoader(new URL[0]);
loader.loadClass(className);
byte[] modified = instrumentedClasses.get(entry.getPath());
if (modified != null) {
logger.debug("Found instrumented class: {}", className);
entryIn = new ByteArrayInputStream(modified);
outEntry.setSize(modified.length);
}
} catch (ClassNotFoundException | NoClassDefFoundError e) {
logger.error("Problem with class: {}", e, className);
}
}
return entryIn == null ? source.getInputStream(inEntry) : entryIn;
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent.main;
class CurrentClass {
private static final ThreadLocal<TransformedClass> currentClass = new ThreadLocal<>();
private CurrentClass() {}
static TransformedClass getAndRemove() {
TransformedClass tc = currentClass.get();
currentClass.remove();
return tc;
}
static void set(TransformedClass clazz) {
currentClass.set(clazz);
}
}

View File

@ -1,141 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent.main;
import io.opentelemetry.contrib.staticinstrumenter.util.SystemLogger;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
public class Main {
private static final SystemLogger logger = SystemLogger.getLogger(Main.class);
private static final Main INSTANCE = new Main(ClassArchive::new);
private final ClassArchive.Factory classArchiveFactory;
// key is slashy name, not dotty
private final Map<String, byte[]> instrumentedClasses = new ConcurrentHashMap<>();
public static void main(String[] args) throws Exception {
if (args.length != 1) {
printUsage();
return;
}
File outDir = new File(args[0]);
if (!outDir.exists()) {
outDir.mkdir();
}
String classPath = System.getProperty("java.class.path");
logger.debug("Classpath (jars list): {}", classPath);
List<String> jarsList = List.of(classPath.split(File.pathSeparator));
getInstance().saveTransformedJarsTo(jarsList, outDir);
}
@SuppressWarnings("SystemOut")
private static void printUsage() {
System.out.println(
"OpenTelemetry Java Static Instrumenter\n"
+ "Usage:\njava "
+ Main.class.getCanonicalName()
+ " <output directory> (where instrumented archives will be stored)");
}
public static Main getInstance() {
return INSTANCE;
}
public static ClassFileTransformer getPreTransformer() {
return new PreTransformer();
}
public static ClassFileTransformer getPostTransformer() {
return new PostTransformer();
}
// for testing purposes
Main(ClassArchive.Factory classArchiveFactory) {
this.classArchiveFactory = classArchiveFactory;
}
// FIXME: java 9 / jmod support, proper handling of directories
// FIXME: jmod in particular introduces weirdness with adding helpers to the dependencies
/**
* Copies all class archives (JARs, WARs) to outDir. Classes that were instrumented and stored in
* instrumentedClasses will get replaced with the new version. All classes added to
* additionalClasses will be added to the new archive.
*
* @param outDir directory where jars will be written
* @throws IOException in case of file operation problem
*/
public void saveTransformedJarsTo(List<String> jarsList, File outDir) throws IOException {
for (String pathItem : jarsList) {
logger.info("Classpath item processed: " + pathItem);
if (isArchive(pathItem)) {
saveArchiveTo(new File(pathItem), outDir);
}
}
}
private static boolean isArchive(String pathItem) {
return (pathItem.endsWith(".jar") || pathItem.endsWith(".war"));
}
// FIXME: don't "instrument" our agent jar
// FIXME: detect and warn on signed jars (and drop the signing bits)
// FIXME: multiple jars with same name
private void saveArchiveTo(File inFile, File outDir) throws IOException {
try (JarFile inJar = new JarFile(inFile);
JarOutputStream outJar = jarOutputStreamFor(outDir, inFile.getName())) {
ClassArchive inClassArchive = classArchiveFactory.createFor(inJar, instrumentedClasses);
inClassArchive.copyAllClassesTo(outJar);
injectAdditionalClassesTo(outJar);
}
}
private static JarOutputStream jarOutputStreamFor(File outDir, String fileName)
throws IOException {
File outFile = new File(outDir, fileName);
return new JarOutputStream(new FileOutputStream(outFile));
}
// FIXME: only relevant additional classes should be injected
private static void injectAdditionalClassesTo(JarOutputStream outJar) throws IOException {
for (Map.Entry<String, byte[]> entry : AdditionalClasses.get().entrySet()) {
String className = entry.getKey();
byte[] classData = entry.getValue();
ZipEntry newEntry = new ZipEntry(className);
outJar.putNextEntry(newEntry);
if (classData != null) {
newEntry.setSize(classData.length);
outJar.write(classData);
}
outJar.closeEntry();
logger.debug("Additional class added: {}", className);
}
}
public Map<String, byte[]> getInstrumentedClasses() {
return instrumentedClasses;
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent.main;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import java.util.Arrays;
import javax.annotation.Nullable;
public class PostTransformer implements ClassFileTransformer {
@Override
@Nullable
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
TransformedClass pre = CurrentClass.getAndRemove();
if (pre != null
&& pre.getName().equals(className)
&& !Arrays.equals(pre.getClasscode(), classfileBuffer)) {
Main.getInstance().getInstrumentedClasses().put(className, classfileBuffer);
}
return null;
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent.main;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import javax.annotation.Nullable;
public class PreTransformer implements ClassFileTransformer {
@Override
@Nullable
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
CurrentClass.set(new TransformedClass(className, classfileBuffer));
return null;
}
}

View File

@ -1,24 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent.main;
public class TransformedClass {
private final byte[] classcode;
private final String name;
public TransformedClass(String name, byte[] classcode) {
this.classcode = classcode;
this.name = name;
}
public byte[] getClasscode() {
return classcode;
}
public String getName() {
return name;
}
}

View File

@ -1,46 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent.main;
import static io.opentelemetry.contrib.staticinstrumenter.agent.main.JarTestUtil.getResourcePath;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
public class ClassArchiveTest {
@Test
void shouldCopyAllClasses(@TempDir File destination) throws IOException {
// given
byte[] newTransformed = new byte[] {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE};
Map<String, byte[]> transformed = Collections.singletonMap("test/TestClass", newTransformed);
ClassArchive underTest =
new ClassArchive(new JarFile(getResourcePath("test.jar")), transformed);
// when
File jarOut = new File(destination, "output.jar");
try (JarOutputStream zout = jarOutputStream(jarOut)) {
underTest.copyAllClassesTo(zout);
}
JarTestUtil.assertJar(
destination,
"output.jar",
new String[] {"test/TestClass.class", "test/NotInstrumented.class"},
new byte[][] {newTransformed, null});
}
private static JarOutputStream jarOutputStream(File destination) throws IOException {
return new JarOutputStream(new FileOutputStream(destination));
}
}

View File

@ -1,48 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent.main;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
final class JarTestUtil {
private JarTestUtil() {}
static void assertJar(File dir, String jarName, String[] files, byte[][] contents)
throws IOException {
File jarFile = new File(dir, jarName);
assertThat(jarFile.exists()).isTrue();
try (JarFile jar = new JarFile(jarFile)) {
for (int i = 0; i < files.length; i++) {
String file = files[i];
ZipEntry entry = jar.getEntry(file);
assertThat(entry).isNotNull();
if (contents != null && contents[i] != null) {
assertContent(jar, entry, contents[i]);
}
}
}
}
private static void assertContent(JarFile jar, ZipEntry entry, byte[] content)
throws IOException {
InputStream is = jar.getInputStream(entry);
ByteArrayOutputStream baos = new ByteArrayOutputStream((int) entry.getSize());
is.transferTo(baos);
assertThat(content).isEqualTo(baos.toByteArray());
}
static String getResourcePath(String jarName) {
return JarTestUtil.class.getClassLoader().getResource(jarName).getFile();
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent.main;
import static io.opentelemetry.contrib.staticinstrumenter.agent.main.JarTestUtil.getResourcePath;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
public class MainTest {
@Test
void shouldInjectAdditionalClasses(@TempDir File destination) throws IOException {
// given
ClassArchive.Factory factory = mock(ClassArchive.Factory.class);
ClassArchive mockArchive = mock(ClassArchive.class);
when(factory.createFor(any(), anyMap())).thenReturn(mockArchive);
Main underTest = new Main(factory);
AdditionalClasses.put("additionalOne.class", new byte[0]);
AdditionalClasses.put("additionalTwo.class", new byte[0]);
// when
underTest.saveTransformedJarsTo(List.of(getResourcePath("test.jar")), destination);
// then
JarTestUtil.assertJar(
destination, "test.jar", new String[] {"additionalOne.class", "additionalTwo.class"}, null);
}
}

View File

@ -1,9 +0,0 @@
plugins {
id("otel.java-conventions")
}
description = "Bootstrap classes for static agent"
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_11)
}

View File

@ -1,25 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.agent.main;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** A holder for additional classes created by the agent. */
public final class AdditionalClasses {
private static final Map<String, byte[]> additionalClasses = new ConcurrentHashMap<>();
public static Map<String, byte[]> get() {
return additionalClasses;
}
public static void put(String name, byte[] code) {
additionalClasses.put(name, code);
}
private AdditionalClasses() {}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.util;
import java.text.MessageFormat;
import java.util.regex.Pattern;
/**
* A custom logger that uses System.err. This is necessary because we shouldn't configure any
* conventional logger in {@code
* io.opentelemetry.contrib.staticinstrumenter.agent.OpenTelemetryStaticAgent} (see {@code
* io.opentelemetry.javaagent.OpenTelemetryAgent} docs).
*/
@SuppressWarnings("SystemOut")
public class SystemLogger {
private static final String DEBUG_SYSTEM_LOGGER = "otel.javaagent.static-instrumentation.debug";
private final boolean debugEnabled;
enum LogLevel {
INFO,
DEBUG,
ERROR
}
private final Class<?> clazz;
public static SystemLogger getLogger(Class<?> clazz) {
return new SystemLogger(clazz);
}
private SystemLogger(Class<?> clazz) {
this.debugEnabled = Boolean.getBoolean(DEBUG_SYSTEM_LOGGER);
this.clazz = clazz;
}
public void info(String message, Object... args) {
System.err.println(format(LogLevel.INFO, message, args));
}
public void debug(String message, Object... args) {
if (debugEnabled) {
System.err.println(format(LogLevel.DEBUG, message, args));
}
}
public void error(String message, Object... args) {
System.err.println(format(LogLevel.ERROR, message, args));
}
public void error(String message, Throwable t, Object... args) {
System.err.println(format(LogLevel.ERROR, message, args));
t.printStackTrace();
}
// visible for testing
String format(LogLevel level, String message, Object... args) {
int i = 2;
while (message.contains("{}")) {
message = message.replaceFirst(Pattern.quote("{}"), "{" + i++ + "}");
}
Object[] newArgs = new Object[args.length + 2];
newArgs[0] = level;
newArgs[1] = clazz.getName();
System.arraycopy(args, 0, newArgs, 2, args.length);
return MessageFormat.format("{0} {1} - " + message, newArgs);
}
}

View File

@ -1,101 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.util;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import org.assertj.core.util.Throwables;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
final class SystemLoggerTest {
private final ByteArrayOutputStream errStreamCaptor = new ByteArrayOutputStream();
private static final SystemLogger logger = SystemLogger.getLogger(SystemLoggerTest.class);
@BeforeEach
void setUp() {
System.setErr(new PrintStream(errStreamCaptor));
}
@Test
void format() {
String message = "Could not open {} because {}";
String result = logger.format(SystemLogger.LogLevel.INFO, message, "aaa", "bbb");
assertThat(result)
.isEqualTo(
SystemLogger.LogLevel.INFO
+ " "
+ SystemLoggerTest.class.getName()
+ " - Could not open aaa because bbb");
}
@Test
void formatNoArgs() {
String message = "Some message";
String result = logger.format(SystemLogger.LogLevel.DEBUG, message);
assertThat(result)
.isEqualTo(
SystemLogger.LogLevel.DEBUG
+ " "
+ SystemLoggerTest.class.getName()
+ " - Some message");
}
@Test
void info() {
String message = "Could not open {} because {}";
logger.info(message, "aaa", "bbb");
assertThat(errStreamCaptor.toString(UTF_8).trim())
.isEqualTo(
SystemLogger.LogLevel.INFO
+ " "
+ SystemLoggerTest.class.getName()
+ " - Could not open aaa because bbb");
}
@Test
void error() {
String message = "Could not open {} because {}";
logger.error(message, "aaa", "bbb");
assertThat(errStreamCaptor.toString(UTF_8).trim())
.isEqualTo(
SystemLogger.LogLevel.ERROR
+ " "
+ SystemLoggerTest.class.getName()
+ " - Could not open aaa because bbb");
}
@Test
void stackTrace() {
String message = "Could not open {} because {}";
Exception e = new IOException("oh no");
logger.error(message, e, "aaa", "bbb");
assertThat(errStreamCaptor.toString(UTF_8).trim())
.startsWith(
SystemLogger.LogLevel.ERROR
+ " "
+ SystemLoggerTest.class.getName()
+ " - Could not open aaa because bbb")
.containsIgnoringWhitespaces(Throwables.getStackTrace(e));
}
}

View File

@ -1,5 +0,0 @@
plugins {
id("otel.java-conventions")
}
description = "OpenTelemetry Java static instrumentation module"

View File

@ -1,7 +0,0 @@
# Testing
## E2E test
Maven plugin has an E2E test `OpenTelemetryInstrumenterMojoTest`. This particular test uses sample HTTP test app, that can be built out of `test-app` module.
## Unit tests
Various unit tests use a bunch of JAR files to validate file operations. These JARs have been created manually and are reflected in test assertions. In case of a need, all of them can be changed along with the asserted data.

View File

@ -1,37 +0,0 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id("com.github.johnrengelman.shadow")
id("otel.java-conventions")
}
description = "Maven3 plugin for static instrumentation of projects code and dependencies"
otelJava.moduleName.set("io.opentelemetry.contrib.staticinstrumenter.plugin.maven")
base.archivesName.set("static-instrumentation-maven-plugin")
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_11)
}
dependencies {
compileOnly("org.apache.maven:maven-plugin-api:3.5.0") // do not auto-update this version
compileOnly("org.apache.maven:maven-project:2.2.1")
compileOnly("org.apache.maven.plugin-tools:maven-plugin-annotations:3.15.1")
compileOnly("org.apache.maven:maven-core:3.5.0") // do not auto-update this version
compileOnly("org.slf4j:slf4j-api")
testImplementation("org.apache.maven.plugin-tools:maven-plugin-annotations:3.15.1")
testImplementation("org.apache.maven:maven-core:3.5.0")
testImplementation("org.slf4j:slf4j-simple")
}
tasks {
processResources {
val agentJar = project(":static-instrumenter:agent-instrumenter").tasks.getByName("shadowJar", ShadowJar::class)
dependsOn(agentJar)
from(agentJar.archiveFile) {
rename { "opentelemetry-agent.jar" }
}
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.PackagingSupportFactory.packagingSupportFor;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class ArtifactProcessor {
private static final Logger log = LoggerFactory.getLogger(ArtifactProcessor.class);
private final InstrumentationAgent agent;
private final Instrumenter instrumenter;
private final Unpacker unpacker;
private final Packer packer;
static ArtifactProcessor createProcessor(
Path instrumentationFolder,
Path preparationFolder,
Path agentFolder,
Path finalFolder,
String finalNameSuffix)
throws IOException {
Unpacker unpacker = new Unpacker(preparationFolder);
InstrumentationAgent agent = InstrumentationAgent.createFromClasspathAgent(agentFolder);
Instrumenter instrumenter = new Instrumenter(agent, instrumentationFolder);
Packer packer = new Packer(finalFolder, finalNameSuffix);
return new ArtifactProcessor(unpacker, instrumenter, agent, packer);
}
ArtifactProcessor(
Unpacker unpacker, Instrumenter instrumenter, InstrumentationAgent agent, Packer packer) {
this.agent = agent;
this.instrumenter = instrumenter;
this.unpacker = unpacker;
this.packer = packer;
}
/**
* Instruments, repackages and enhances an artifacts. Exact steps:
*
* <ul>
* <li>unpacks artifacts into a temp dir
* <li>runs instrumentation agent, creating instrumented copy of a JAR and dependencies
* <li>packs all dependencies into a single JAR and adds open telemetry classes
* <li>move jar to final location, adding a suffix
* </ul>
*/
void process(Path artifact) throws IOException {
PackagingSupport packagingSupport = packagingSupportFor(artifact);
List<Path> artifactsToInstrument = unpacker.copyAndExtract(artifact, packagingSupport);
log.info("Unpacked artifacts: {}", artifactsToInstrument);
Path instrumentedArtifact =
instrumenter.instrument(artifact.getFileName(), artifactsToInstrument);
log.info("Main artifact after instrumentation: {}", instrumentedArtifact);
agent.copyAgentClassesTo(instrumentedArtifact, packagingSupport);
Path finalArtifact = packer.packAndCopy(instrumentedArtifact, packagingSupport);
log.info("Final artifact: {}", finalArtifact);
}
}

View File

@ -1,125 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarSupport.consumeEntries;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.ZipEntryCreator.moveEntryUpdating;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InstrumentationAgent {
public static final String JAR_FILE_NAME = "opentelemetry-agent.jar";
public static final String MAIN_CLASS =
"io.opentelemetry.contrib.staticinstrumenter.agent.main.Main";
private static final Logger logger = LoggerFactory.getLogger(InstrumentationAgent.class);
private final String agentPath;
private final JarFile agentJar;
private InstrumentationAgent(String agentPath) throws IOException {
this.agentPath = agentPath;
this.agentJar = new JarFile(agentPath);
}
/**
* Creates new instance using {@value #JAR_FILE_NAME} classpath agent. Agent is copied to the
* target folder.
*/
public static InstrumentationAgent createFromClasspathAgent(Path targetFolder)
throws IOException {
Path targetPath = targetFolder.resolve(JAR_FILE_NAME);
InputStream agentSource =
InstrumentationAgent.class.getClassLoader().getResourceAsStream(JAR_FILE_NAME);
if (agentSource == null) {
throw new IllegalStateException(
"Instrumented OTel agent not found in class path and JAR name: " + JAR_FILE_NAME);
}
try {
Files.copy(agentSource, targetPath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException exception) {
logger.error("Couldn't copy agent JAR using class resource: {}.", JAR_FILE_NAME);
throw exception;
}
return new InstrumentationAgent(targetPath.toString());
}
/**
* Returns the ProcessBuilder for instrumentation process. TODO: remove extra agent properties
* once static instr distro is released
*
* @param classpath classpath for instrumentation process (list of JARs to instrument, separated
* with path separator)
* @param outputFolder output folder
* @return ProcessBuilder for instrumentation process
*/
public ProcessBuilder getInstrumentationProcess(String classpath, Path outputFolder) {
return new ProcessBuilder(
"java",
"-Dotel.instrumentation.internal-class-loader.enabled=false",
String.format("-javaagent:%s", agentPath),
"-cp",
classpath,
MAIN_CLASS,
outputFolder.toString());
}
/**
* Adds classes from OpenTelemetry javaagent JAR to target file. Removes the shading and replaces
* classdata file extension with class file extension.
*/
public void copyAgentClassesTo(Path targetFile, PackagingSupport packagingSupport)
throws IOException {
Set<String> existing = allNames(targetFile);
try (FileSystem targetFs = FileSystems.newFileSystem(targetFile, (ClassLoader) null)) {
consumeEntries(
agentJar,
(entry) -> {
String entryName = entry.getName();
if (isInstrumentationClass(entry)) {
String modifiedName = modifyAgentEntryName(entryName, packagingSupport);
if (!existing.contains(modifiedName)) {
moveEntryUpdating(targetFs, modifiedName, entry, agentJar);
}
}
});
}
}
private static Set<String> allNames(Path jarPath) throws IOException {
Set<String> result = new HashSet<>();
try (JarFile jar = new JarFile(jarPath.toFile())) {
consumeEntries(jar, entry -> result.add(entry.getName()));
}
return result;
}
private static boolean isInstrumentationClass(JarEntry entry) {
return (entry.getName().startsWith("inst/") || entry.getName().startsWith("io/"))
&& !entry.isDirectory();
}
private static String modifyAgentEntryName(String entryName, PackagingSupport packagingSupport) {
String prefix = packagingSupport.getClassesPrefix();
String newEntryPath = entryName.replace(".classdata", ".class");
return newEntryPath.replace("inst/", prefix);
}
}

View File

@ -1,66 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Instrumenter {
private static final Logger log = LoggerFactory.getLogger(Instrumenter.class);
private final InstrumentationAgent agent;
private final Path targetFolder;
public Instrumenter(InstrumentationAgent agent, Path targetFolder) {
this.agent = agent;
this.targetFolder = targetFolder;
}
public Path instrument(Path mainArtifact, List<Path> artifactsToInstrument) throws IOException {
runInstrumentationProcess(artifactsToInstrument);
return targetFolder.resolve(mainArtifact);
}
private void runInstrumentationProcess(List<Path> artifactsToInstrument) throws IOException {
ProcessBuilder processBuilder =
agent
.getInstrumentationProcess(toClasspath(artifactsToInstrument), targetFolder)
.redirectErrorStream(true);
log.debug("Instrumentation process: {}", processBuilder.command());
Process process = processBuilder.start();
try {
int ret = process.waitFor();
if (ret != 0) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
process.getInputStream().transferTo(output);
StringBuilder builder =
new StringBuilder(
"The instrumentation process for JAR dependencies finished with exit value: ")
.append(ret)
.append("\nSystem out:\n")
.append(output.toString(Charset.defaultCharset()));
throw new IOException(builder.toString());
}
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new IOException(ie);
}
}
private static String toClasspath(List<Path> paths) {
return paths.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator));
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import java.io.IOException;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
final class JarSupport {
private JarSupport() {}
@FunctionalInterface
interface ThrowingConsumer<T, E extends Exception> {
void accept(T t) throws E;
}
static void consumeEntries(JarFile jarFile, ThrowingConsumer<JarEntry, IOException> consumer)
throws IOException {
Enumeration<JarEntry> enums = jarFile.entries();
while (enums.hasMoreElements()) {
JarEntry entry = enums.nextElement();
consumer.accept(entry);
}
}
}

View File

@ -1,108 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import javax.annotation.Nullable;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Mojo(
name = "instrument",
defaultPhase = LifecyclePhase.PACKAGE,
requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME,
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class OpenTelemetryInstrumenterMojo extends AbstractMojo {
@Parameter(defaultValue = "${project}", required = true, readonly = true)
@Nullable
private MavenProject project;
@Parameter(readonly = true)
@Nullable
private String artifactName;
@Parameter(readonly = true)
@Nullable
private String outputFolder;
@Parameter(readonly = true, defaultValue = "-instrumented")
@Nullable
private String suffix;
private final Logger logger = LoggerFactory.getLogger(OpenTelemetryInstrumenterMojo.class);
/**
* Conducts the process of application target file instrumentation. Cleans the temporary
* directories. Sets the output folder for instrumented target file. Executes the instrumentation
* process of artifacts chosen by the user.
*/
@Override
public void execute() throws MojoExecutionException {
try {
if (project == null) {
throw new MojoExecutionException("Project not set");
}
String finalFolder =
(outputFolder == null ? project.getBuild().getDirectory() : outputFolder);
String finalNameSuffix = (suffix == null ? "" : suffix);
List<Path> artifactsToInstrument =
new ProjectModel(project).chooseForInstrumentation(artifactName);
executeInternal(finalFolder, finalNameSuffix, artifactsToInstrument);
} catch (Exception ioe) {
throw new MojoExecutionException("Exception executing plugin", ioe);
}
}
@VisibleForTesting
final void executeInternal(
String finalFolder, String finalNameSuffix, List<Path> artifactsToInstrument)
throws IOException {
WorkingFolders workingFolders = null;
try {
workingFolders = new WorkingFolders(finalFolder);
ArtifactProcessor artifactProcessor = createProcessor(workingFolders, finalNameSuffix);
for (Path artifact : artifactsToInstrument) {
logger.info("Processing artifact: {}", artifact);
artifactProcessor.process(artifact);
workingFolders.cleanWorkingFolders();
}
} finally {
try {
if (workingFolders != null) {
workingFolders.delete();
}
} catch (Exception e) {
// ignored
}
}
}
private static ArtifactProcessor createProcessor(
WorkingFolders workingFolders, String finalNameSuffix) throws IOException {
return ArtifactProcessor.createProcessor(
workingFolders.instrumentationFolder(),
workingFolders.getPreparationFolder(),
workingFolders.agentFolder(),
workingFolders.finalFolder(),
finalNameSuffix);
}
}

View File

@ -1,58 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarSupport.consumeEntries;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.ZipEntryCreator.moveEntry;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipOutputStream;
class PackagingSupport {
static final PackagingSupport EMPTY = new PackagingSupport("");
private final String classesPrefix;
private final Set<String> filesToRepackage = new HashSet<>();
PackagingSupport(String classesPrefix) {
this.classesPrefix = classesPrefix;
}
String getClassesPrefix() {
return classesPrefix;
}
void copyRemovingPrefix(JarFile inputJar, ZipOutputStream targetOut) throws IOException {
consumeEntries(
inputJar,
(entry) -> {
if (!entry.isDirectory() && entry.getName().startsWith(classesPrefix)) {
String newEntryPath = entry.getName().replace(getClassesPrefix(), "");
moveEntry(targetOut, newEntryPath, entry, inputJar);
filesToRepackage.add(newEntryPath);
} else {
moveEntry(targetOut, entry.getName(), entry, inputJar);
}
});
}
void copyAddingPrefix(JarEntry entry, JarFile inputJar, ZipOutputStream targetOut)
throws IOException {
if (!entry.isDirectory() && filesToRepackage.contains(entry.getName())) {
String newEntryPath = classesPrefix + entry.getName();
moveEntry(targetOut, newEntryPath, entry, inputJar);
} else {
moveEntry(targetOut, entry.getName(), entry, inputJar);
}
}
}

View File

@ -1,33 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public final class PackagingSupportFactory {
private static final Set<String> SUPPORTED_FRAMEWORKS =
Set.of("BOOT-INF/classes/", "WEB-INF/classes/");
private PackagingSupportFactory() {}
public static PackagingSupport packagingSupportFor(Path mainArtifact) throws IOException {
try (JarFile jarFile = new JarFile(mainArtifact.toFile())) {
for (String frameworkKey : SUPPORTED_FRAMEWORKS) {
JarEntry jarEntry = jarFile.getJarEntry(frameworkKey);
if ((jarEntry != null) && jarEntry.isDirectory()) {
return new PackagingSupport(frameworkKey);
}
}
}
return PackagingSupport.EMPTY;
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarSupport.consumeEntries;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.ZipEntryCreator.createZipEntryFromFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.jar.JarFile;
import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class Packer {
private static final Logger log = LoggerFactory.getLogger(Packer.class);
private final Path targetFolder;
private final String finalNameSuffix;
Packer(Path targetFolder, String finalNameSuffix) {
this.targetFolder = targetFolder;
this.finalNameSuffix = finalNameSuffix;
}
/**
* Packs copies any nested JARs of the main artifacts from the its folder. Then, copies the main
* artifacts to the target folder, adding entries prefixes using the packaging support service.
* Copied artifact will carry the finalNameSuffix.
*
* @param mainArtifact the artifact to be packed/copied
* @param packagingSupport packaging support
* @return path to the final artifact in the target folder
* @throws IOException in case of any problems
*/
Path packAndCopy(Path mainArtifact, PackagingSupport packagingSupport) throws IOException {
Path mainDirectory = mainArtifact.getParent();
if (mainDirectory == null) {
throw new IOException("Main artifact " + mainArtifact + " needs to have a parent.");
}
String targetFileName = createFinalName(mainArtifact.getFileName().toString());
Path targetFile = targetFolder.resolve(targetFileName);
if (!Files.exists(targetFile)) {
Files.createFile(targetFile);
} else {
log.warn("Target file {} exists and will be overwritten.", targetFile);
}
try (JarFile sourceJar = new JarFile(mainArtifact.toFile());
ZipOutputStream targetOut = new ZipOutputStream(Files.newOutputStream(targetFile))) {
consumeEntries(
sourceJar,
(entry) -> {
if (entry.getName().endsWith(".jar")) {
createZipEntryFromFile(
targetOut, mainDirectory.resolve(entry.getName()), entry.getName());
} else {
packagingSupport.copyAddingPrefix(entry, sourceJar, targetOut);
}
});
}
return targetFile;
}
private String createFinalName(String instrumentedFileName) {
int lastDotIndex = instrumentedFileName.lastIndexOf('.');
return instrumentedFileName.substring(0, lastDotIndex)
+ finalNameSuffix
+ instrumentedFileName.substring(lastDotIndex);
}
}

View File

@ -1,92 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.project.MavenProject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Contains methods to retrieve all artifacts created in Maven project. */
class ProjectModel {
private static final Logger logger = LoggerFactory.getLogger(ProjectModel.class);
private final MavenProject project;
ProjectModel(MavenProject project) {
this.project = project;
}
/**
* Returns a list of all Maven project artifacts (attached included) or artifact specified by
* user.
*/
List<Path> chooseForInstrumentation(@Nullable String artifactName) {
List<Path> allArtifacts = findProjectArtifacts();
if (artifactName == null) {
if (logger.isDebugEnabled()) {
String fileNames = toFileNames(allArtifacts);
logger.debug(
"Artifact name not provided. Defaults to instrument all artifacts: {}", fileNames);
}
return allArtifacts;
}
for (Path artifactFile : allArtifacts) {
if (artifactName.equals(artifactFile.toFile().getName())) {
return new ArrayList<>(Collections.singletonList(artifactFile));
}
}
String message =
"Artifact with name "
+ artifactName
+ " not found. The available artifacts are: "
+ toFileNames(allArtifacts)
+ ". Project artifact: "
+ project.getArtifact()
+ ". "
+ "File of project artifact: "
+ project.getArtifact().getFile();
throw new IllegalArgumentException(message);
}
private static String toFileNames(List<Path> paths) {
return Arrays.toString(paths.stream().map(Path::toFile).map(File::getName).toArray());
}
private List<Path> findProjectArtifacts() {
List<Path> artifactFiles = new ArrayList<>();
Artifact artifact = project.getArtifact();
File file = artifact.getFile();
if (file != null) {
artifactFiles.add(file.toPath());
}
artifactFiles.addAll(findPathsOfAttachedArtifacts());
return artifactFiles;
}
private List<Path> findPathsOfAttachedArtifacts() {
return project.getAttachedArtifacts().stream()
.map(Artifact::getFile)
.map(File::toPath)
.collect(Collectors.toList());
}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarSupport.consumeEntries;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipOutputStream;
class Unpacker {
private final Path targetFolder;
Unpacker(Path targetFolder) {
this.targetFolder = targetFolder;
}
/**
* Copies the artifact to the target folder, removing class prefix using packagingSupport. If
* there are any nested JAR files, they will be extracted into the target folder.
*
* @param artifact artifact to be copied and unpacked
* @param packagingSupport relevant packaging support service
* @return list of JARs in target directory
* @throws IOException in case of any file operation problems
*/
List<Path> copyAndExtract(Path artifact, PackagingSupport packagingSupport) throws IOException {
List<Path> result = new ArrayList<>();
result.add(copy(artifact, packagingSupport));
extractNestedArchives(artifact, result);
return result;
}
private Path copy(Path artifact, PackagingSupport packagingSupport) throws IOException {
Path targetArtifact = Files.createFile(targetFolder.resolve(artifact.getFileName()));
try (ZipOutputStream targetOut = new ZipOutputStream(Files.newOutputStream(targetArtifact));
JarFile artifactJar = new JarFile(artifact.toFile())) {
packagingSupport.copyRemovingPrefix(artifactJar, targetOut);
}
return targetArtifact;
}
private void extractNestedArchives(Path artifact, List<Path> unpacked) throws IOException {
try (JarFile artifactJar = new JarFile(artifact.toFile())) {
consumeEntries(
artifactJar,
(entry) -> {
if (entry.getName().endsWith(".jar")) {
unpacked.add(unpackSingle(entry, artifactJar));
}
});
}
}
private Path unpackSingle(JarEntry entry, JarFile jarFile) throws IOException {
Path targetFile = targetFolder.resolve(entry.getName());
// ensure parent path is created
Files.createDirectories(targetFile.getParent());
try (InputStream jarFileInputStream = jarFile.getInputStream(entry)) {
Files.copy(jarFileInputStream, targetFile);
}
return targetFile;
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.codehaus.plexus.util.FileUtils;
class WorkingFolders {
// folder where OTel agent is stored
private final Path agentFolder;
// folder where jars for instrumentation are prepared
private final Path preparationFolder;
// folder where instrumentation and processing is conducted
private final Path instrumentationFolder;
// folder where final (instrumented, with OTel classes) JARs are stored
private final Path finalFolder;
WorkingFolders(String finalFolder) throws IOException {
try {
agentFolder = Files.createTempDirectory("OTEL_AGENT_FOLDER");
preparationFolder = Files.createTempDirectory("PREPARATION_FOLDER");
instrumentationFolder = Files.createTempDirectory("INSTRUMENTATION_WORKING_FOLDER");
this.finalFolder = Files.createDirectories(Paths.get(finalFolder));
} catch (IOException ioe) {
delete();
// unable to initialize so rethrow
throw ioe;
}
}
void delete() throws IOException {
FileUtils.deleteDirectory(agentFolder.toFile());
FileUtils.deleteDirectory(preparationFolder.toFile());
FileUtils.deleteDirectory(instrumentationFolder.toFile());
}
Path agentFolder() {
return agentFolder;
}
Path getPreparationFolder() {
return preparationFolder;
}
Path instrumentationFolder() {
return instrumentationFolder;
}
Path finalFolder() {
return finalFolder;
}
void cleanWorkingFolders() throws IOException {
FileUtils.cleanDirectory(instrumentationFolder.toFile());
FileUtils.cleanDirectory(preparationFolder.toFile());
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
final class ZipEntryCreator {
private ZipEntryCreator() {}
static void moveEntryUpdating(
FileSystem targetFs, String targetPath, JarEntry sourceEntry, JarFile sourceJar)
throws IOException {
if (targetPath.equals(
"META-INF/services/io.opentelemetry.javaagent.tooling.BeforeAgentListener")) {
// TEMPORARY hack to make things pass after a BeforeAgentListener was added in the agent
// in https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/9301
// which then causes conflict with the BeforeAgentListener in this module
return;
}
Path entry = targetFs.getPath("/", targetPath);
Files.createDirectories(entry.getParent());
try (InputStream sourceInput = sourceJar.getInputStream(sourceEntry)) {
Files.copy(sourceInput, entry);
}
}
static void moveEntry(
ZipOutputStream targetOut, String targetPath, JarEntry sourceEntry, JarFile sourceJar)
throws IOException {
ZipEntry entry = new ZipEntry(targetPath);
try (InputStream sourceInput = sourceJar.getInputStream(sourceEntry)) {
entry.setSize(sourceEntry.getSize());
entry.setCompressedSize(sourceEntry.getCompressedSize());
entry.setMethod(sourceEntry.getMethod());
entry.setCrc(sourceEntry.getCrc());
targetOut.putNextEntry(entry);
sourceInput.transferTo(targetOut);
targetOut.closeEntry();
}
}
static void createZipEntryFromFile(ZipOutputStream targetOut, Path sourceFile, String entryPath)
throws IOException {
ZipEntry entry = new ZipEntry(entryPath);
entry.setMethod(ZipOutputStream.DEFLATED);
byte[] bytes = Files.readAllBytes(sourceFile);
entry.setSize(bytes.length);
CRC32 crc = new CRC32();
crc.update(bytes);
entry.setCrc(crc.getValue());
targetOut.putNextEntry(entry);
targetOut.write(bytes);
targetOut.closeEntry();
}
}

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by maven-plugin-tools 3.6 -->
<plugin>
<name>maven-plugin</name>
<description>Maven3 plugin for static instrumentation of projects code and dependencies</description>
<groupId>io.opentelemetry.contrib</groupId>
<artifactId>static-instrumentation-maven-plugin</artifactId>
<version>1.19.0-alpha-SNAPSHOT</version>
<goalPrefix>static-instrumentation</goalPrefix>
<mojos>
<mojo>
<goal>instrument</goal>
<requiresDependencyResolution>compile+runtime</requiresDependencyResolution>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<phase>package</phase>
<implementation>io.opentelemetry.contrib.staticinstrumenter.plugin.maven.OpentelemetryInstrumenterMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<requiresDependencyCollection>compile+runtime</requiresDependencyCollection>
<threadSafe>false</threadSafe>
<parameters/>
</mojo>
</mojos>
</plugin>

View File

@ -1,213 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by maven-plugin-tools 3.6 -->
<plugin>
<name>maven-plugin</name>
<description>Maven3 plugin for static instrumentation of projects code and dependencies</description>
<groupId>io.opentelemetry.contrib</groupId>
<artifactId>static-instrumentation-maven-plugin</artifactId>
<version>1.19.0-alpha-SNAPSHOT</version>
<goalPrefix>static-instrumentation</goalPrefix>
<isolatedRealm>false</isolatedRealm>
<inheritedByDefault>true</inheritedByDefault>
<mojos>
<mojo>
<goal>instrument</goal>
<requiresDependencyResolution>compile+runtime</requiresDependencyResolution>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<phase>package</phase>
<implementation>io.opentelemetry.contrib.staticinstrumenter.plugin.maven.OpenTelemetryInstrumenterMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<requiresDependencyCollection>compile+runtime</requiresDependencyCollection>
<threadSafe>false</threadSafe>
<parameters>
<parameter>
<name>artifactName</name>
<type>java.lang.String</type>
<required>false</required>
<editable>false</editable>
<description></description>
</parameter>
<parameter>
<name>outputFolder</name>
<type>java.lang.String</type>
<required>false</required>
<editable>false</editable>
<description></description>
</parameter>
<parameter>
<name>project</name>
<type>org.apache.maven.project.MavenProject</type>
<required>true</required>
<editable>false</editable>
<description></description>
</parameter>
<parameter>
<name>suffix</name>
<type>java.lang.String</type>
<required>false</required>
<editable>false</editable>
<description></description>
</parameter>
</parameters>
<configuration>
<project implementation="org.apache.maven.project.MavenProject" default-value="${project}"/>
<suffix implementation="java.lang.String" default-value="-instrumented"/>
</configuration>
</mojo>
</mojos>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<type>jar</type>
<version>3.6.3</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-project</artifactId>
<type>jar</type>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-settings</artifactId>
<type>jar</type>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-profile</artifactId>
<type>jar</type>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<type>jar</type>
<version>3.6.3</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact-manager</artifactId>
<type>jar</type>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<type>jar</type>
<version>3.6.3</version>
</dependency>
<dependency>
<groupId>org.eclipse.sisu</groupId>
<artifactId>org.eclipse.sisu.plexus</artifactId>
<type>jar</type>
<version>0.3.4</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-registry</artifactId>
<type>jar</type>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
<type>jar</type>
<version>1.0-alpha-9-stable-1</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-repository-metadata</artifactId>
<type>jar</type>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-provider-api</artifactId>
<type>jar</type>
<version>1.0-beta-6</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<type>jar</type>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-classworlds</artifactId>
<type>jar</type>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-interpolation</artifactId>
<type>jar</type>
<version>1.11</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<type>jar</type>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<type>jar</type>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.sisu</groupId>
<artifactId>org.eclipse.sisu.inject</artifactId>
<type>jar</type>
<version>0.3.4</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-annotations</artifactId>
<type>jar</type>
<version>1.5.5</version>
</dependency>
<dependency>
<groupId>backport-util-concurrent</groupId>
<artifactId>backport-util-concurrent</artifactId>
<type>jar</type>
<version>3.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<type>jar</type>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>classworlds</groupId>
<artifactId>classworlds</artifactId>
<type>jar</type>
<version>1.1-alpha-2</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<type>jar</type>
<version>1.0</version>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<type>jar</type>
<version>1</version>
</dependency>
</dependencies>
</plugin>

View File

@ -1,28 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
// Workaround for https://github.com/junit-team/junit5/issues/2811
abstract class AbstractTempDirTest {
File tempDir;
@BeforeEach
public void before() throws IOException {
tempDir = Files.createTempDirectory(this.getClass().getSimpleName()).toFile();
}
@AfterEach
void after() {
tempDir.deleteOnExit();
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.getResourcePath;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.jar.JarFile;
import org.junit.jupiter.api.Test;
class InstrumentationAgentTest extends AbstractTempDirTest {
@Test
void shouldCreateAgentFromClasspath() throws Exception {
// given
// when
InstrumentationAgent.createFromClasspathAgent(tempDir.toPath());
// then
assertThat(Files.exists(tempDir.toPath().resolve(InstrumentationAgent.JAR_FILE_NAME))).isTrue();
}
@Test
void shouldCopyAgentClasses() throws IOException {
// given
Path targetFile = tempDir.toPath().resolve("copied-agent.jar");
Path sourceJar = getResourcePath("test.jar");
Files.copy(sourceJar, targetFile);
InstrumentationAgent agent = InstrumentationAgent.createFromClasspathAgent(tempDir.toPath());
// when
agent.copyAgentClassesTo(targetFile, PackagingSupport.EMPTY);
// then - verify jar content
JarSupport.consumeEntries(
new JarFile(targetFile.toFile()),
(entry) -> {
assertThat(entry.getName()).doesNotEndWith(".classdata");
assertThat(entry.getName()).doesNotStartWith("inst/");
});
}
}

View File

@ -1,36 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.assertJarContainsFiles;
import static java.util.Collections.singletonList;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
class InstrumenterTest extends AbstractTempDirTest {
@Test
void shouldInstrument() throws IOException {
// given
Instrumenter instrumenter =
new Instrumenter(
InstrumentationAgent.createFromClasspathAgent(tempDir.toPath()), tempDir.toPath());
Path testJar = JarTestUtil.getResourcePath("test.jar");
// when
Path instrumented = instrumenter.instrument(testJar.getFileName(), singletonList(testJar));
// then
// got the target file right?
assertJarContainsFiles(
instrumented,
"META-INF/MANIFEST.MF",
"test/NotInstrumented.class",
"test/TestClass.class",
"lib/firstNested.jar",
"lib/secondNested.jar");
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
final class JarTestUtil {
private JarTestUtil() {}
static void assertJarContainsFiles(Path jarFile, String... files) throws IOException {
assertThat(Files.exists(jarFile)).isTrue();
try (JarFile jar = new JarFile(jarFile.toFile())) {
for (int i = 0; i < files.length; i++) {
String file = files[i];
ZipEntry entry = jar.getEntry(file);
assertThat(entry).isNotNull();
}
}
}
static Path getResourcePath(String fileName) {
try {
URL resource = Thread.currentThread().getContextClassLoader().getResource(fileName);
if (resource == null) {
throw new RuntimeException("Resource not found for " + fileName);
}
URI uri = resource.toURI();
return Path.of(uri);
} catch (URISyntaxException e) {
throw new RuntimeException("Problem to find the path of " + fileName, e);
}
}
static Path createJar(
String prefix, JarSupport.ThrowingConsumer<ZipOutputStream, IOException> creator)
throws IOException {
Path path = Files.createTempFile(prefix, "jar");
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path))) {
creator.accept(out);
}
return path;
}
}

View File

@ -1,49 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static org.assertj.core.api.Assertions.assertThat;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import org.junit.jupiter.api.Test;
class OpenTelemetryInstrumenterMojoTest extends AbstractTempDirTest {
@Test
void shouldInstrumentSampleApplication() throws Exception {
// given
OpenTelemetryInstrumenterMojo mojo = new OpenTelemetryInstrumenterMojo();
Path testApp = JarTestUtil.getResourcePath("test-http-app.jar");
// when
mojo.executeInternal(tempDir.getPath(), "-instrumented", Collections.singletonList(testApp));
// then
Path instrumentedApp = tempDir.toPath().resolve("test-http-app-instrumented.jar");
assertThat(Files.exists(instrumentedApp)).isTrue();
verifyApplicationByExampleRun(instrumentedApp);
}
/**
* Test application does an http call using Apache HTTP client. If a response contains
* "Traceparent" header (result of autoinstrumentation), application writes "SUCCESS" to system
* out.
*/
private static void verifyApplicationByExampleRun(Path instrumentedApp) throws Exception {
ProcessBuilder pb =
new ProcessBuilder("java", "-jar", instrumentedApp.toString()).redirectErrorStream(true);
// TODO autoconfigure GlobalOpenTelemetry explicitly in static instrumentation
pb.environment().put("OTEL_JAVA_GLOBAL_AUTOCONFIGURE_ENABLED", "true");
Process process = pb.start();
process.waitFor();
String output = new String(process.getInputStream().readAllBytes(), Charset.defaultCharset());
assertThat(output).contains("SUCCESS");
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.getResourcePath;
import static org.assertj.core.api.Assertions.assertThat;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
class PackagingSupportFactoryTest {
@Test
void shouldReturnForSpringBoot() throws Exception {
// given
Path path = getResourcePath("spring-boot.jar");
// when
PackagingSupport result = PackagingSupportFactory.packagingSupportFor(path);
// then
assertThat(result).isNotEqualTo(PackagingSupport.EMPTY);
assertThat(result.getClassesPrefix()).isEqualTo("BOOT-INF/classes/");
}
@Test
void shouldReturnForWar() throws Exception {
// given
Path path = getResourcePath("web.war");
// when
PackagingSupport result = PackagingSupportFactory.packagingSupportFor(path);
// then
assertThat(result).isNotEqualTo(PackagingSupport.EMPTY);
assertThat(result.getClassesPrefix()).isEqualTo("WEB-INF/classes/");
}
@Test
void shouldReturnEmptyForUnsupported() throws Exception {
// given
Path path = getResourcePath("test.jar");
// when
PackagingSupport result = PackagingSupportFactory.packagingSupportFor(path);
// then
assertThat(result).isEqualTo(PackagingSupport.EMPTY);
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.assertJarContainsFiles;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.createJar;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.getResourcePath;
import java.nio.file.Path;
import java.util.jar.JarFile;
import org.junit.jupiter.api.Test;
class PackagingSupportTest {
@Test
void shouldCopyPreservingPrefix() throws Exception {
// given
Path path = getResourcePath("test.jar");
PackagingSupport underTest = PackagingSupportFactory.packagingSupportFor(path);
// when
Path withoutPrefix =
createJar(
"copied-jar",
(target) -> {
underTest.copyRemovingPrefix(new JarFile(path.toFile()), target);
});
// then
JarTestUtil.assertJarContainsFiles(
withoutPrefix,
"META-INF/MANIFEST.MF",
"test/NotInstrumented.class",
"test/TestClass.class",
"lib/firstNested.jar",
"lib/secondNested.jar");
}
@Test
void shouldCopyRemovingAndAddingPrefix() throws Exception {
// given
Path path = getResourcePath("spring-boot.jar");
PackagingSupport underTest = PackagingSupportFactory.packagingSupportFor(path);
// when
// copy files to new jar removing prefix
Path withoutPrefix =
createJar(
"without-prefix",
(target) -> {
underTest.copyRemovingPrefix(new JarFile(path.toFile()), target);
});
// copy files back, adding prefix
JarFile withoutPrefixJar = new JarFile(withoutPrefix.toFile());
Path clone =
createJar(
"clone",
(target) -> {
JarSupport.consumeEntries(
withoutPrefixJar,
(entry) -> underTest.copyAddingPrefix(entry, withoutPrefixJar, target));
});
// then
assertJarContainsFiles(
withoutPrefix,
"META-INF/MANIFEST.MF",
"first.class",
"com/test/second.class",
"BOOT-INF/lib/test.jar");
assertJarContainsFiles(
clone,
"META-INF/MANIFEST.MF",
"BOOT-INF/classes/first.class",
"BOOT-INF/classes/com/test/second.class",
"BOOT-INF/lib/test.jar");
}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.getResourcePath;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.then;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipOutputStream;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
class PackerTest extends AbstractTempDirTest {
@Test
void shouldCopyClassesAddingPrefix() throws Exception {
// given
Path jar = getResourcePath("test.jar");
Packer packer = new Packer(tempDir.toPath(), "-processed");
PackagingSupport support = Mockito.mock(PackagingSupport.class);
ArgumentCaptor<JarFile> captor = ArgumentCaptor.forClass(JarFile.class);
// when
Path copy = packer.packAndCopy(jar, support);
// then
assertThat(copy.getFileName().toString()).isEqualTo("test-processed.jar");
then(support)
.should(Mockito.times(6))
.copyAddingPrefix(any(JarEntry.class), captor.capture(), any(ZipOutputStream.class));
JarFile source = captor.getValue();
assertThat(source.getName()).isEqualTo(getResourcePath("test.jar").toString());
assertThat(Files.exists(copy)).isTrue();
}
@Test
void shouldPackNestedJarsToCopiedArtifact() throws Exception {
// given
Path jar = getResourcePath("test.jar");
Packer packer = new Packer(tempDir.toPath(), "-processed");
PackagingSupport support = Mockito.mock(PackagingSupport.class);
ArgumentCaptor<JarFile> captor = ArgumentCaptor.forClass(JarFile.class);
// when
Path copy = packer.packAndCopy(jar, support);
// then
JarFile copyJar = new JarFile(copy.toFile());
verifyEntry("lib/firstNested.jar", copyJar);
verifyEntry("lib/secondNested.jar", copyJar);
assertThat(copy.getFileName().toString()).isEqualTo("test-processed.jar");
then(support)
.should(Mockito.times(6))
.copyAddingPrefix(any(JarEntry.class), captor.capture(), any(ZipOutputStream.class));
JarFile source = captor.getValue();
assertThat(source.getName()).isEqualTo(getResourcePath("test.jar").toString());
assertThat(Files.exists(copy)).isTrue();
}
private static void verifyEntry(String name, JarFile jar) throws Exception {
JarEntry firstEntry = jar.getJarEntry(name);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
jar.getInputStream(firstEntry).transferTo(baos);
assertThat(baos.toString(Charset.defaultCharset())).isEqualTo("not a real jar file\n");
}
}

View File

@ -1,62 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.assertJarContainsFiles;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.getResourcePath;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.Test;
class UnpackerTest extends AbstractTempDirTest {
@Test
void shouldUnpackNestedJars() throws Exception {
// given
Unpacker unpacker = new Unpacker(tempDir.toPath());
PackagingSupport support = mock(PackagingSupport.class);
Path jar = getResourcePath("test.jar");
// when
unpacker.copyAndExtract(jar, support);
// then
assertThat(Files.exists(tempDir.toPath().resolve("lib/firstNested.jar"))).isTrue();
assertThat(Files.size(tempDir.toPath().resolve("lib/firstNested.jar"))).isGreaterThan(0);
assertThat(Files.exists(tempDir.toPath().resolve("lib/secondNested.jar"))).isTrue();
assertThat(Files.size(tempDir.toPath().resolve("lib/secondNested.jar"))).isGreaterThan(0);
}
@Test
void shouldCopyTestJarContent() throws Exception {
// given
Path targetFolderPath = tempDir.toPath();
Unpacker unpacker = new Unpacker(targetFolderPath);
PackagingSupport support = PackagingSupport.EMPTY;
Path jar = getResourcePath("test.jar");
// when
List<Path> copied = unpacker.copyAndExtract(jar, support);
// then
assertThat(copied).hasSize(3);
// copied the right file?
assertThat(copied)
.containsExactlyInAnyOrder(
targetFolderPath.resolve("test.jar"),
targetFolderPath.resolve("lib/firstNested.jar"),
targetFolderPath.resolve("lib/secondNested.jar"));
// got the target file right?
assertJarContainsFiles(
copied.get(0),
"META-INF/MANIFEST.MF",
"test/NotInstrumented.class",
"test/TestClass.class",
"lib/firstNested.jar",
"lib/secondNested.jar");
}
}

View File

@ -1,74 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.plugin.maven;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.assertJarContainsFiles;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.createJar;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.JarTestUtil.getResourcePath;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.ZipEntryCreator.createZipEntryFromFile;
import static io.opentelemetry.contrib.staticinstrumenter.plugin.maven.ZipEntryCreator.moveEntry;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.jar.JarFile;
import org.junit.jupiter.api.Test;
class ZipEntryCreatorTest {
@Test
void shouldMoveEntryWithoutRenaming() throws Exception {
// given
JarFile source = new JarFile(getResourcePath("test.jar").toString());
// when
Path targetFile =
createJar(
"test-copy",
(target) ->
moveEntry(
target,
"test/TestClass.class",
source.getJarEntry("test/TestClass.class"),
source));
// then
assertJarContainsFiles(targetFile, "test/TestClass.class");
}
@Test
void shouldMoveEntryRenaming() throws Exception {
// given
JarFile source = new JarFile(getResourcePath("test.jar").toString());
// when
Path targetFile =
createJar(
"test-copy",
(target) -> {
moveEntry(
target,
"newpath/TestClassRenamed.classdata",
source.getJarEntry("test/TestClass.class"),
source);
moveEntry(
target,
"test/NotInstrumented.class",
source.getJarEntry("test/NotInstrumented.class"),
source);
});
// then
assertJarContainsFiles(
targetFile, "newpath/TestClassRenamed.classdata", "test/NotInstrumented.class");
}
@Test
void shouldAddFileToJar() throws Exception {
// given
Path file = Paths.get(getResourcePath("testing.file").toString());
// when
Path targetFile =
createJar("new-jar", (target) -> createZipEntryFromFile(target, file, "stored/entry.file"));
// then
assertJarContainsFiles(targetFile, "stored/entry.file");
}
}

View File

@ -1 +0,0 @@
not a real jar file

View File

@ -1 +0,0 @@
not a real jar file

View File

@ -1 +0,0 @@
org.slf4j.simpleLogger.defaultLogLevel=debug

View File

@ -1,28 +0,0 @@
plugins {
id("otel.java-conventions")
id("com.github.johnrengelman.shadow")
}
description = "OpenTelemetry Java Static Instrumentation Test Application"
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_11)
}
dependencies {
implementation("org.apache.httpcomponents:httpclient:4.5.14")
implementation("org.slf4j:slf4j-api")
runtimeOnly("org.slf4j:slf4j-simple")
}
tasks {
jar {
manifest {
attributes("Main-Class" to "io.opentelemetry.contrib.staticinstrumenter.test.HttpClientTest")
}
}
assemble {
dependsOn(shadowJar)
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.staticinstrumenter.test;
import java.io.IOException;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
@SuppressWarnings("SystemOut")
public final class HttpClientTest {
private HttpClientTest() {}
public static void main(String[] args) throws Exception {
System.setProperty("otel.traces.exporter", "logging");
makeCall();
}
private static void makeCall() throws IOException {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet request = new HttpGet("https://httpbin.org/get");
try (CloseableHttpResponse response = httpClient.execute(request)) {
HttpEntity entity = response.getEntity();
if (entity != null) {
Header traceparent = request.getFirstHeader("traceparent");
if (traceparent != null) {
System.out.println("SUCCESS - Trace parent value: " + traceparent.getValue());
return;
}
}
}
}
System.out.println("FAILURE");
}
}