Remove static instrumenter module (#1755)
This commit is contained in:
parent
fb98ed26c5
commit
2ae70759ff
|
|
@ -28,7 +28,6 @@ body:
|
|||
- resource-providers
|
||||
- runtime-attach
|
||||
- samplers
|
||||
- static-instrumenter
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What happened?
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -74,8 +74,6 @@ components:
|
|||
samplers:
|
||||
- trask
|
||||
- jack-berg
|
||||
static-instrumenter:
|
||||
- anosek-an
|
||||
kafka-exporter:
|
||||
- spockz
|
||||
- vincentfree
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
@ -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"))
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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}")
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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() {}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,9 +0,0 @@
|
|||
plugins {
|
||||
id("otel.java-conventions")
|
||||
}
|
||||
|
||||
description = "Bootstrap classes for static agent"
|
||||
|
||||
otelJava {
|
||||
minJavaVersionSupported.set(JavaVersion.VERSION_11)
|
||||
}
|
||||
|
|
@ -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() {}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
plugins {
|
||||
id("otel.java-conventions")
|
||||
}
|
||||
|
||||
description = "OpenTelemetry Java static instrumentation module"
|
||||
|
|
@ -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.
|
||||
|
|
@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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/");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
not a real jar file
|
||||
|
|
@ -1 +0,0 @@
|
|||
not a real jar file
|
||||
|
|
@ -1 +0,0 @@
|
|||
org.slf4j.simpleLogger.defaultLogLevel=debug
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
ein zwei drei
|
||||
Binary file not shown.
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue