最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

maven - Otel Extension with External Libraries - Stack Overflow

programmeradmin4浏览0评论

What am I doing ?

trying to create a Otel Extension and everything is working fine until the moment I use external libraries.

Relevant context information:

  • I am using Maven instead of Gradle.

  • I don't have any premain method or main method. I am pretty newbie in Javaagent and extensions.

  • I am using only InstrumentationModule and TypeInstrumentation from Otel Java Instrumentation.

  • I can inject new helper classes overriding getAdditionalHelperClassNames. It works fine.

Issue:

  • Even being able to inject new helper classes I don't know how to inject external libraries like com.jayway.jsonpath. For that reason I am having a lot of ClassNotFoundException.

Request:

  • Can you help me inject an external library into classpath to use on application or show me how to use external libraries and don't have ClassNotFoundException?
  • Muzzle does that but it only works with Gradle.

Error:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Filter execution threw an exception] with root cause

java.lang.ClassNotFoundException: com.fasterxml.jackson.dataformat.yaml.YAMLFactory
        at java.base/java.URLClassLoader.findClass(URLClassLoader.java:445) ~[na:na]
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:593) ~[na:na]
        at .springframework.boot.loader.protocol.jar.JarUrlClassLoader.loadClass(JarUrlClassLoader.java:104) ~[test-rest-api.jar:na]
        at .springframework.boot.loader.launch.LaunchedClassLoader.loadClass(LaunchedClassLoader.java:91) ~[test-rest-api.jar:na]
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526) ~[na:na]
        at .springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:92) ~[spring-web-6.1.3.jar!/:6.1.3]
        at .apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.tomcat.util.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.tomcat.util.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]

Code:

Usefull links:

  • Otel Javaagent v1.33.6
  • Instrument class example
  • Module class example
  • Instrumentation documentation(Most specifically where talk about Gradle and Muzzle

How to run the extension

After build the extension artifact:

  1. You need to download the Otel Javaagent version 1.33.6
  2. You need 1 application to inject the extension (I am using a simple springboot application with 2 endpoints)
  3. Run application with java command. Use the following flags: -javaagent -Dotel.javaagent.extensions

Example: java -javaagent:/path/to/opentelemetry/javaagent/opentelemetry-javaagent.jar
-Dotel.javaagent.extensions=/path/to/your/extension/otel-extension-1.0.0-SNAPSHOT.jar
-jar test-rest-api.jar \

Maven:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="; xmlns=".0.0"
        xsi:schemaLocation=".0.0 .0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example.extension</groupId>
    <artifactId>otel-extension</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <name>otel-extension</name>

    <properties>
        <java.version>17</java.version>

        <!-- JSON VERSIONS -->
        <jackson-dataformat-yaml.version>2.15.2</jackson-dataformat-yaml.version>
        <jackson-datatype-jsr310.version>2.9.8</jackson-datatype-jsr310.version>
    </properties>

    <dependencies>

        <!-- JAKARTA SERVLET -->
        <dependency>
            <groupId>.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>10.1.18</version>
            <scope>compile</scope>
        </dependency>

        <!-- SPRINGFRAMEWORK -->
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>6.2.3</version>
        </dependency>

        <!-- TEST -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

        <!-- LOMBOK -->
        <dependency>
            <groupId>.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.36</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>.projectlombok</groupId>
            <artifactId>lombok-mapstruct-binding</artifactId>
            <version>0.2.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- GOOGLE -->
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.1.1</version>
        </dependency>

        <!-- OPENTELEMETRY -->
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-sdk</artifactId>
            <version>1.26.0</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-api</artifactId>
            <version>1.26.0</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-exporter-otlp</artifactId>
            <version>1.26.0</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-sdk-extension-autoconfigure-spi</artifactId>
            <version>1.26.0</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry.instrumentation</groupId>
            <artifactId>opentelemetry-instrumentation-api</artifactId>
            <version>1.24.0</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry.javaagent</groupId>
            <artifactId>opentelemetry-javaagent-extension-api</artifactId>
            <version>1.24.0-alpha</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-semconv</artifactId>
            <version>1.26.0-alpha</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry.javaagent</groupId>
            <artifactId>opentelemetry-muzzle</artifactId>
            <version>2.13.3-alpha</version>
            <scope>runtime</scope>
        </dependency>

        <!-- JSON -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-yaml</artifactId>
            <version>${jackson-dataformat-yaml.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson-dataformat-yaml.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson-datatype-jsr310.version}</version>
        </dependency>

        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <version>2.7.0</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

InstrumentationModule:

package com.example;


import java.util.Collections;
import java.util.List;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumentationModule.class)
public final class ServletInstrumentationModule extends InstrumentationModule {

    public ServletInstrumentationModule() {
        super("otel-extension");
    }

    @Override
    public int order() {
        return 1;
    }

    @Override
    public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
        return AgentElementMatchers.hasClassesNamed("jakarta.servlet.http.HttpServlet");
    }

    @Override
    public List<TypeInstrumentation> typeInstrumentations() {
        return Collections.singletonList(new HttpServletRequestInstrumentation());
    }
    
    @Override
    public boolean isHelperClass(String className) {
        return true;
    }

    @Override
    public List<String> getAdditionalHelperClassNames() {
        return List.of(
                "com.example.HttpServletRequestWrapper",
                "com.example.HttpServletRequestWrapper$CachedServletInputStream",
                "com.example.OtelExtensionFilter",
                "com.example.HttpMetricExtractorInjector",
                "com.example.MetricMap",
                "com.example.MetricMap$EnumConverter"
        );
    }
}

TypeInstrumentation

package com.example;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;

import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;

public class HttpServletRequestInstrumentation implements TypeInstrumentation {

    @Override
    public ElementMatcher<TypeDescription> typeMatcher() {
        return AgentElementMatchers.hasSuperType(
                namedOneOf("jakarta.servlet.Filter"));
    }

    @Override
    public void transform(TypeTransformer typeTransformer) {
        System.out.println("Transform");
        typeTransformer.applyAdviceToMethod(
                namedOneOf("doFilter")
                        .and(ElementMatchers.takesArgument(
                                0, ElementMatchers.named("jakarta.servlet.ServletRequest")))
                        .and(ElementMatchers.takesArgument(
                                1, ElementMatchers.named("jakarta.servlet.ServletResponse")))
                        .and(ElementMatchers.takesArgument(
                                2, ElementMatchers.named("jakarta.servlet.FilterChain"))),
                this.getClass().getName() + "$FilterAdvice");
    }

    @SuppressWarnings("unused")
    public static class FilterAdvice {

        @Advice.OnMethodEnter()
        public static void onGetInputStream(
                @Advice.Argument(value = 0, typing = Typing.DYNAMIC, readOnly = false) ServletRequest request,
                @Advice.Argument(value = 1) ServletResponse response,
                @Advice.Argument(value = 2) FilterChain filterChain
        ) throws IOException, ServletException {
            // TODO: Add here any class that doesn't exist inside the target springboot application
            if (request instanceof HttpServletRequest) {
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                if ("POST".equalsIgnoreCase(httpServletRequest.getMethod())) {
                    request = new HttpServletRequestWrapper(httpServletRequest);
                    System.out.println(new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
                }
            }
        }
    }
}

What am I doing ?

trying to create a Otel Extension and everything is working fine until the moment I use external libraries.

Relevant context information:

  • I am using Maven instead of Gradle.

  • I don't have any premain method or main method. I am pretty newbie in Javaagent and extensions.

  • I am using only InstrumentationModule and TypeInstrumentation from Otel Java Instrumentation.

  • I can inject new helper classes overriding getAdditionalHelperClassNames. It works fine.

Issue:

  • Even being able to inject new helper classes I don't know how to inject external libraries like com.jayway.jsonpath. For that reason I am having a lot of ClassNotFoundException.

Request:

  • Can you help me inject an external library into classpath to use on application or show me how to use external libraries and don't have ClassNotFoundException?
  • Muzzle does that but it only works with Gradle.

Error:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Filter execution threw an exception] with root cause

java.lang.ClassNotFoundException: com.fasterxml.jackson.dataformat.yaml.YAMLFactory
        at java.base/java.URLClassLoader.findClass(URLClassLoader.java:445) ~[na:na]
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:593) ~[na:na]
        at .springframework.boot.loader.protocol.jar.JarUrlClassLoader.loadClass(JarUrlClassLoader.java:104) ~[test-rest-api.jar:na]
        at .springframework.boot.loader.launch.LaunchedClassLoader.loadClass(LaunchedClassLoader.java:91) ~[test-rest-api.jar:na]
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526) ~[na:na]
        at .springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:92) ~[spring-web-6.1.3.jar!/:6.1.3]
        at .apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.tomcat.util.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.tomcat.util.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at .apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.18.jar!/:na]
        at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]

Code:

Usefull links:

  • Otel Javaagent v1.33.6
  • Instrument class example
  • Module class example
  • Instrumentation documentation(Most specifically where talk about Gradle and Muzzle

How to run the extension

After build the extension artifact:

  1. You need to download the Otel Javaagent version 1.33.6
  2. You need 1 application to inject the extension (I am using a simple springboot application with 2 endpoints)
  3. Run application with java command. Use the following flags: -javaagent -Dotel.javaagent.extensions

Example: java -javaagent:/path/to/opentelemetry/javaagent/opentelemetry-javaagent.jar
-Dotel.javaagent.extensions=/path/to/your/extension/otel-extension-1.0.0-SNAPSHOT.jar
-jar test-rest-api.jar \

Maven:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3./2001/XMLSchema-instance" xmlns="http://maven.apache./POM/4.0.0"
        xsi:schemaLocation="http://maven.apache./POM/4.0.0 https://maven.apache./xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example.extension</groupId>
    <artifactId>otel-extension</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <name>otel-extension</name>

    <properties>
        <java.version>17</java.version>

        <!-- JSON VERSIONS -->
        <jackson-dataformat-yaml.version>2.15.2</jackson-dataformat-yaml.version>
        <jackson-datatype-jsr310.version>2.9.8</jackson-datatype-jsr310.version>
    </properties>

    <dependencies>

        <!-- JAKARTA SERVLET -->
        <dependency>
            <groupId>.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>10.1.18</version>
            <scope>compile</scope>
        </dependency>

        <!-- SPRINGFRAMEWORK -->
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>6.2.3</version>
        </dependency>

        <!-- TEST -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

        <!-- LOMBOK -->
        <dependency>
            <groupId>.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.36</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>.projectlombok</groupId>
            <artifactId>lombok-mapstruct-binding</artifactId>
            <version>0.2.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- GOOGLE -->
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.1.1</version>
        </dependency>

        <!-- OPENTELEMETRY -->
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-sdk</artifactId>
            <version>1.26.0</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-api</artifactId>
            <version>1.26.0</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-exporter-otlp</artifactId>
            <version>1.26.0</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-sdk-extension-autoconfigure-spi</artifactId>
            <version>1.26.0</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry.instrumentation</groupId>
            <artifactId>opentelemetry-instrumentation-api</artifactId>
            <version>1.24.0</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry.javaagent</groupId>
            <artifactId>opentelemetry-javaagent-extension-api</artifactId>
            <version>1.24.0-alpha</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-semconv</artifactId>
            <version>1.26.0-alpha</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry.javaagent</groupId>
            <artifactId>opentelemetry-muzzle</artifactId>
            <version>2.13.3-alpha</version>
            <scope>runtime</scope>
        </dependency>

        <!-- JSON -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-yaml</artifactId>
            <version>${jackson-dataformat-yaml.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson-dataformat-yaml.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson-datatype-jsr310.version}</version>
        </dependency>

        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <version>2.7.0</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

InstrumentationModule:

package com.example;


import java.util.Collections;
import java.util.List;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(InstrumentationModule.class)
public final class ServletInstrumentationModule extends InstrumentationModule {

    public ServletInstrumentationModule() {
        super("otel-extension");
    }

    @Override
    public int order() {
        return 1;
    }

    @Override
    public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
        return AgentElementMatchers.hasClassesNamed("jakarta.servlet.http.HttpServlet");
    }

    @Override
    public List<TypeInstrumentation> typeInstrumentations() {
        return Collections.singletonList(new HttpServletRequestInstrumentation());
    }
    
    @Override
    public boolean isHelperClass(String className) {
        return true;
    }

    @Override
    public List<String> getAdditionalHelperClassNames() {
        return List.of(
                "com.example.HttpServletRequestWrapper",
                "com.example.HttpServletRequestWrapper$CachedServletInputStream",
                "com.example.OtelExtensionFilter",
                "com.example.HttpMetricExtractorInjector",
                "com.example.MetricMap",
                "com.example.MetricMap$EnumConverter"
        );
    }
}

TypeInstrumentation

package com.example;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;

import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;

public class HttpServletRequestInstrumentation implements TypeInstrumentation {

    @Override
    public ElementMatcher<TypeDescription> typeMatcher() {
        return AgentElementMatchers.hasSuperType(
                namedOneOf("jakarta.servlet.Filter"));
    }

    @Override
    public void transform(TypeTransformer typeTransformer) {
        System.out.println("Transform");
        typeTransformer.applyAdviceToMethod(
                namedOneOf("doFilter")
                        .and(ElementMatchers.takesArgument(
                                0, ElementMatchers.named("jakarta.servlet.ServletRequest")))
                        .and(ElementMatchers.takesArgument(
                                1, ElementMatchers.named("jakarta.servlet.ServletResponse")))
                        .and(ElementMatchers.takesArgument(
                                2, ElementMatchers.named("jakarta.servlet.FilterChain"))),
                this.getClass().getName() + "$FilterAdvice");
    }

    @SuppressWarnings("unused")
    public static class FilterAdvice {

        @Advice.OnMethodEnter()
        public static void onGetInputStream(
                @Advice.Argument(value = 0, typing = Typing.DYNAMIC, readOnly = false) ServletRequest request,
                @Advice.Argument(value = 1) ServletResponse response,
                @Advice.Argument(value = 2) FilterChain filterChain
        ) throws IOException, ServletException {
            // TODO: Add here any class that doesn't exist inside the target springboot application
            if (request instanceof HttpServletRequest) {
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                if ("POST".equalsIgnoreCase(httpServletRequest.getMethod())) {
                    request = new HttpServletRequestWrapper(httpServletRequest);
                    System.out.println(new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
                }
            }
        }
    }
}
Share Improve this question edited Mar 13 at 4:55 kriegaex 67.8k15 gold badges120 silver badges224 bronze badges asked Mar 11 at 14:38 UnovaUnova 112 bronze badges 4
  • Welcome to SO. Is your question related to this one to which I got no response to my comments? Both are related to OTEL, Byte Buddy and @Advice. Please also mnetion how you start the agent, i.e. via -javaagent CLI argument or via hot-attachment, either manually or through some OTEL machanism? Sorry for the last question, but I never used OTEL before, I just know a few things about java agents. – kriegaex Commented Mar 12 at 2:48
  • Code present here is enough to simulate the issue. The error happens when using class that isn't injected inside ClassLoader and isn't imported inside the target application (ClassNotFoundException). Otel extension uses Gradle and Muzzle to manage classes and libraries injection but I am using Maven instead. Link for documentation: github/open-telemetry/opentelemetry-java-instrumentation/… – Unova Commented Mar 12 at 15:15
  • @kriegaex I edited post and added a topic on how to run the extension. I don't have premain methods or main methods. My extension uses Otel's starter classes. I just implement TypeInstrumentation interface and extend InstrumentationModule with bytebuddy to instrument the application. It's a pretty straightforward and simple approach – Unova Commented Mar 12 at 15:37
  • I would like to point the problem isn't in the code. The problem is: I am using Maven to manage dependencies and Extension needs Muzzle to inject libraries in a easier way. Because of I am using Maven I need to inject manually class by class instead of a package. I would like to know if there is any approach I could use to inject a whole package inside target application. – Unova Commented Mar 12 at 15:42
Add a comment  | 

1 Answer 1

Reset to default 0

With a few modifications to your code (e.g. adding a missing import and actually using classes from json-path in the extension code), I can reproduce the problem. The easiest way to fix it is to simply shade the necessary dependency classes into the extension, which solves the problem during build time:

  <build>
    <plugins>
      <plugin>
        <groupId>.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.5.2</version>
        <executions>
          <execution>
            <id>shade</id>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <artifactSet>
                <includes>
                  <include>com.jayway.jsonpath:json-path</include>
                </includes>
              </artifactSet>
              <!-- Make artifact as small as possible (optional) -->
              <minimizeJar>true</minimizeJar>
              <filters>
                <filter>
                  <artifact>com.jayway.jsonpath:*</artifact>
                  <excludes>
                    <exclude>META-INF/**</exclude>
                  </excludes>
                </filter>
                <filter>
                  <artifact>*:*</artifact>
                  <excludes>
                    <exclude>META-INF/maven/**</exclude>
                  </excludes>
                </filter>
              </filters>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

This works for me, also testing with a Spring Boot app. If for some reason auto-detection of used classes does not work in any given situation, simply do not use minimizeJar (it defaults to false).

BTW, writing one or two Muzzle Maven Plugins mirroring the behaviour of the Gradle plugins and contributing them to the OpenTelemetry project would be a more generic and sustainable solution for the benefit of all users.


Update: I thought it would be a good idea to create OTel issue #13611 to improve the situation upstream.

发布评论

评论列表(0)

  1. 暂无评论