Skip to content

Commit 3bd593a

Browse files
committed
feat:support JFinal framework
Change-Id: Ie9d2a16071e77ce12f386227faaff4a4927512be
1 parent 92a0e4c commit 3bd593a

File tree

9 files changed

+483
-0
lines changed

9 files changed

+483
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("com.jfinal")
8+
module.set("jfinal")
9+
versions.set("[3.6,)")
10+
}
11+
}
12+
13+
otelJava {
14+
// jfinal doesn't work with Java 9+
15+
maxJavaVersionForTests.set(JavaVersion.VERSION_1_8)
16+
}
17+
18+
dependencies {
19+
library("com.jfinal:jfinal:3.6")
20+
testLibrary("com.jfinal:jetty-server:2019.3")
21+
testLibrary("com.jfinal:jfinal:3.6")
22+
testInstrumentation(project(":instrumentation:jetty:jetty-8.0:javaagent"))
23+
testInstrumentation(project(":instrumentation:jetty:jetty-11.0:javaagent"))
24+
testInstrumentation(project(":instrumentation:jetty:jetty-common:javaagent"))
25+
}
26+
27+
tasks.withType<Test>().configureEach {
28+
jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true")
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package io.opentelemetry.javaagent.instrumentation.jfinal;
2+
3+
import io.opentelemetry.context.Context;
4+
import io.opentelemetry.context.Scope;
5+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
6+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
7+
import net.bytebuddy.asm.Advice;
8+
import net.bytebuddy.description.type.TypeDescription;
9+
import net.bytebuddy.matcher.ElementMatcher;
10+
11+
12+
import javax.annotation.Nullable;
13+
14+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
15+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
16+
import static io.opentelemetry.javaagent.instrumentation.jfinal.JFinalSingletons.instrumenter;
17+
import static net.bytebuddy.matcher.ElementMatchers.named;
18+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
19+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
20+
21+
public class ActionHandlerInstrumentation implements TypeInstrumentation {
22+
@Override
23+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
24+
return hasClassesNamed("com.jfinal.core.ActionHandler");
25+
}
26+
27+
@Override
28+
public ElementMatcher<TypeDescription> typeMatcher() {
29+
return named("com.jfinal.core.ActionHandler");
30+
}
31+
32+
@Override
33+
public void transform(TypeTransformer transformer) {
34+
transformer.applyAdviceToMethod(
35+
named("handle").and(takesArguments(4)
36+
.and(takesArgument(0, String.class))
37+
.and(takesArgument(1, named("javax.servlet.http.HttpServletRequest")))
38+
.and(takesArgument(2, named("javax.servlet.http.HttpServletResponse")))
39+
.and(takesArgument(3, boolean[].class))),
40+
this.getClass().getName() + "$HandleAdvice");
41+
}
42+
43+
@SuppressWarnings("unused")
44+
public static class HandleAdvice {
45+
46+
public static class AdviceScope {
47+
private final Context context;
48+
private final Scope scope;
49+
50+
public AdviceScope(Context context, Scope scope) {
51+
this.context = context;
52+
this.scope = scope;
53+
}
54+
55+
@Nullable
56+
public static AdviceScope start(Context parentContext) {
57+
if (!instrumenter().shouldStart(parentContext, null)) {
58+
return null;
59+
}
60+
61+
Context context = instrumenter().start(parentContext, null);
62+
return new AdviceScope(context, context.makeCurrent());
63+
}
64+
65+
public void end(
66+
@Nullable Throwable throwable) {
67+
scope.close();
68+
instrumenter().end(context, null, null, throwable);
69+
}
70+
}
71+
72+
73+
@Advice.OnMethodEnter(suppress = Throwable.class)
74+
public static HandleAdvice.AdviceScope onEnter() {
75+
return HandleAdvice.AdviceScope.start(currentContext());
76+
}
77+
78+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
79+
public static void stopTraceOnResponse(
80+
@Advice.Thrown Throwable throwable,
81+
@Advice.Enter @Nullable AdviceScope actionScope) {
82+
if (actionScope == null) {
83+
return;
84+
}
85+
actionScope.end(throwable);
86+
}
87+
}
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.opentelemetry.javaagent.instrumentation.jfinal;
2+
3+
4+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
5+
import static net.bytebuddy.matcher.ElementMatchers.named;
6+
7+
import com.jfinal.core.Action;
8+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
9+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
10+
import net.bytebuddy.asm.Advice;
11+
import net.bytebuddy.description.type.TypeDescription;
12+
import net.bytebuddy.matcher.ElementMatcher;
13+
14+
15+
public class ActionMappingInstrumentation implements TypeInstrumentation {
16+
@Override
17+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
18+
return hasClassesNamed("com.jfinal.core.ActionMapping");
19+
}
20+
21+
@Override
22+
public ElementMatcher<TypeDescription> typeMatcher() {
23+
return named("com.jfinal.core.ActionMapping");
24+
}
25+
26+
@Override
27+
public void transform(TypeTransformer transformer) {
28+
transformer.applyAdviceToMethod(
29+
named("getAction"),
30+
this.getClass().getName() + "$GetActionAdvice");
31+
}
32+
33+
@SuppressWarnings("unused")
34+
public static class GetActionAdvice {
35+
36+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
37+
public static void existGetAction(
38+
@Advice.Return(readOnly = false) Action action) {
39+
JFinalSingletons.updateSpan(action);
40+
}
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.opentelemetry.javaagent.instrumentation.jfinal;
2+
3+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
4+
5+
import com.google.auto.service.AutoService;
6+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
7+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
8+
import java.util.Arrays;
9+
import java.util.List;
10+
import net.bytebuddy.matcher.ElementMatcher;
11+
12+
@AutoService(InstrumentationModule.class)
13+
public class JFinalInstrumentationModule extends InstrumentationModule {
14+
public JFinalInstrumentationModule() {
15+
super("jfinal");
16+
}
17+
18+
@Override
19+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
20+
return hasClassesNamed("com.jfinal.core.ActionMapping");
21+
}
22+
23+
@Override
24+
public List<TypeInstrumentation> typeInstrumentations() {
25+
return Arrays.asList(new ActionMappingInstrumentation(), new ActionHandlerInstrumentation());
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package io.opentelemetry.javaagent.instrumentation.jfinal;
2+
3+
import com.jfinal.core.Action;
4+
import com.jfinal.render.JsonRender;
5+
import io.opentelemetry.api.GlobalOpenTelemetry;
6+
import io.opentelemetry.context.Context;
7+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
8+
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRoute;
9+
import io.opentelemetry.instrumentation.api.semconv.http.HttpServerRouteSource;
10+
import io.opentelemetry.javaagent.bootstrap.internal.ExperimentalConfig;
11+
import java.util.logging.Level;
12+
import java.util.logging.Logger;
13+
14+
public final class JFinalSingletons {
15+
16+
private static final Logger logger = Logger.getLogger(JFinalSingletons.class.getName());
17+
18+
private static final String SPAN_NAME = "jfinal.handle";
19+
private static final Instrumenter<Void, Void> INSTRUMENTER =
20+
Instrumenter.<Void, Void>builder(
21+
GlobalOpenTelemetry.get(), "io.opentelemetry.jfinal-3.6", s -> SPAN_NAME)
22+
.setEnabled(ExperimentalConfig.get().controllerTelemetryEnabled())
23+
.buildInstrumenter();
24+
25+
static {
26+
//see https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/11465
27+
excludeOtAttrs();
28+
}
29+
30+
public static Instrumenter<Void, Void> instrumenter() {
31+
return INSTRUMENTER;
32+
}
33+
34+
public static void updateSpan(Action action) {
35+
if (action == null) {
36+
return;
37+
}
38+
String route = action.getActionKey();
39+
if (route == null) {
40+
return;
41+
}
42+
Context context = Context.current();
43+
HttpServerRoute.update(context, HttpServerRouteSource.CONTROLLER, route);
44+
}
45+
46+
private static void excludeOtAttrs() {
47+
try {
48+
JsonRender.addExcludedAttrs(
49+
"io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper.AsyncListenerResponse",
50+
"io.opentelemetry.javaagent.instrumentation.servlet.ServletHelper.Context",
51+
"trace_id",
52+
"span_id");
53+
} catch (Throwable t) {
54+
logger.log(Level.INFO, "exclude failed", t);
55+
}
56+
}
57+
58+
private JFinalSingletons() {}
59+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package io.opentelemetry.javaagent.instrumentation.jfinal;
2+
3+
import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
4+
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND;
5+
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT;
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
8+
import com.jfinal.core.JFinalFilter;
9+
import io.opentelemetry.api.common.Attributes;
10+
import io.opentelemetry.api.trace.SpanKind;
11+
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
12+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
13+
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest;
14+
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
15+
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
16+
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
17+
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
18+
import java.util.EnumSet;
19+
import javax.servlet.DispatcherType;
20+
import org.eclipse.jetty.server.Server;
21+
import org.eclipse.jetty.servlet.FilterHolder;
22+
import org.eclipse.jetty.servlet.ServletContextHandler;
23+
import org.eclipse.jetty.servlet.ServletHandler;
24+
import org.junit.jupiter.api.extension.RegisterExtension;
25+
26+
public class JFinalTest extends AbstractHttpServerTest<Server> {
27+
28+
@RegisterExtension
29+
static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();
30+
31+
@Override
32+
protected void configure(HttpServerTestOptions options) {
33+
// In the redirection scenario, the current test case fails to pass.
34+
// Modifying the logic of AbstractHttpServerTest to address this would
35+
// entail significant risk; therefore, we will temporarily skip validation
36+
// for this scenario.
37+
//
38+
// actual span relationship: expecting span relationship
39+
// GET /redirect GET /redirect
40+
// |---jfinal.handle |---jfinal.handle
41+
// |---controller |---controller
42+
// |---Response.sendRedirect |---Response.sendRedirect
43+
//
44+
options.setTestRedirect(false);
45+
options.setHasHandlerSpan(unused -> true);
46+
options.setExpectedHttpRoute(
47+
(endpoint, method) -> {
48+
if (endpoint == ServerEndpoint.PATH_PARAM) {
49+
return "/path/123/param";
50+
}
51+
if (HttpConstants._OTHER.equals(method)) {
52+
return endpoint.getPath();
53+
}
54+
if (NOT_FOUND.equals(endpoint)) {
55+
return "/*";
56+
}
57+
return expectedHttpRoute(endpoint, method);
58+
});
59+
}
60+
61+
@Override
62+
protected Server setupServer() throws Exception {
63+
Server server = new Server(port);
64+
65+
ServletContextHandler context = new ServletContextHandler(
66+
ServletContextHandler.SESSIONS);
67+
context.setContextPath("/");
68+
ServletHandler handler = new ServletHandler();
69+
70+
FilterHolder fh = handler.addFilterWithMapping(
71+
JFinalFilter.class.getName(), "/*", EnumSet.of(DispatcherType.REQUEST));
72+
fh.setInitParameter("configClass", TestConfig.class.getName());
73+
74+
context.addFilter(fh, "/*", EnumSet.of(DispatcherType.REQUEST));
75+
context.insertHandler(handler);
76+
server.setHandler(context);
77+
server.start();
78+
return server;
79+
}
80+
81+
@Override
82+
protected void stopServer(Server server) throws Exception {
83+
server.stop();
84+
}
85+
86+
@Override
87+
protected SpanDataAssert assertResponseSpan(
88+
SpanDataAssert span, String method, ServerEndpoint endpoint) {
89+
if (endpoint == REDIRECT) {
90+
span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendRedirect"));
91+
}
92+
span.hasKind(SpanKind.INTERNAL).hasAttributesSatisfying(Attributes::isEmpty);
93+
return span;
94+
}
95+
96+
@Override
97+
public SpanDataAssert assertHandlerSpan(
98+
SpanDataAssert span, String method, ServerEndpoint endpoint) {
99+
span.hasName("jfinal.handle").hasKind(INTERNAL);
100+
return span;
101+
}
102+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.opentelemetry.javaagent.instrumentation.jfinal;
2+
3+
import com.jfinal.config.Constants;
4+
import com.jfinal.config.Handlers;
5+
import com.jfinal.config.Interceptors;
6+
import com.jfinal.config.JFinalConfig;
7+
import com.jfinal.config.Plugins;
8+
import com.jfinal.config.Routes;
9+
import com.jfinal.template.Engine;
10+
11+
public class TestConfig extends JFinalConfig {
12+
// 配置常量值
13+
@Override
14+
public void configConstant(Constants me) {
15+
me.setDevMode(true);
16+
}
17+
18+
// 配置路由
19+
@Override
20+
public void configRoute(Routes me) {
21+
me.add("/", TestController.class);
22+
}
23+
24+
@Override
25+
public void configEngine(Engine me) {
26+
}
27+
28+
@Override
29+
public void configPlugin(Plugins me) {
30+
}
31+
32+
@Override
33+
public void configInterceptor(Interceptors me) {
34+
}
35+
36+
@Override
37+
public void configHandler(Handlers me) {
38+
}
39+
}

0 commit comments

Comments
 (0)