Skip to content

Commit 86c3263

Browse files
steveraolaurit
andauthored
Add support for XXL-JOB (#10421)
Co-authored-by: Lauri Tulmin <[email protected]>
1 parent 93bb1fe commit 86c3263

File tree

42 files changed

+1987
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1987
-0
lines changed

docs/supported-libraries.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ These are the supported libraries and frameworks:
145145
| [Vert.x SQL Client](https://github.com/eclipse-vertx/vertx-sql-client/) | 4.0+ | N/A | [Database Client Spans] |
146146
| [Vert.x Web](https://vertx.io/docs/vertx-web/java/) | 3.0+ | N/A | Provides `http.route` [2] |
147147
| [Vibur DBCP](https://www.vibur.org/) | 11.0+ | [opentelemetry-vibur-dbcp-11.0](../instrumentation/vibur-dbcp-11.0/library) | [Database Pool Metrics] |
148+
| [XXL-JOB](https://www.xuxueli.com/xxl-job/en/) | 1.9.2+ | N/A | none |
148149
| [ZIO](https://zio.dev/) | 2.0+ | N/A | Context propagation |
149150

150151
**[1]** Standalone library instrumentation refers to instrumentation that can be used without the Java agent.

instrumentation/xxl-job/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Settings for the XXL-JOB instrumentation
2+
3+
| System property | Type | Default | Description |
4+
|-------------------------------------------------------------|---------|---------|-----------------------------------------------------|
5+
| `otel.instrumentation.xxl-job.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. |
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("com.xuxueli")
8+
module.set("xxl-job-core")
9+
versions.set("[1.9.2, 2.1.2)")
10+
assertInverse.set(true)
11+
}
12+
}
13+
14+
dependencies {
15+
library("com.xuxueli:xxl-job-core:1.9.2") {
16+
exclude("org.codehaus.groovy", "groovy")
17+
}
18+
implementation(project(":instrumentation:xxl-job:xxl-job-common:javaagent"))
19+
20+
testInstrumentation(project(":instrumentation:xxl-job:xxl-job-2.1.2:javaagent"))
21+
testInstrumentation(project(":instrumentation:xxl-job:xxl-job-2.3.0:javaagent"))
22+
23+
// It needs the javax.annotation-api in xxl-job-core 1.9.2.
24+
testImplementation("javax.annotation:javax.annotation-api:1.3.2")
25+
testImplementation(project(":instrumentation:xxl-job:xxl-job-common:testing"))
26+
latestDepTestLibrary("com.xuxueli:xxl-job-core:2.1.1") {
27+
exclude("org.codehaus.groovy", "groovy")
28+
}
29+
}
30+
31+
tasks.withType<Test>().configureEach {
32+
// required on jdk17
33+
jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
34+
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
35+
jvmArgs("-Dotel.instrumentation.xxl-job.experimental-span-attributes=true")
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2;
7+
8+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
9+
import static io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2.XxlJobSingletons.helper;
10+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
13+
14+
import com.xxl.job.core.handler.IJobHandler;
15+
import io.opentelemetry.context.Context;
16+
import io.opentelemetry.context.Scope;
17+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
18+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
19+
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobProcessRequest;
20+
import net.bytebuddy.asm.Advice;
21+
import net.bytebuddy.description.type.TypeDescription;
22+
import net.bytebuddy.implementation.bytecode.assign.Assigner;
23+
import net.bytebuddy.matcher.ElementMatcher;
24+
25+
public class GlueJobHandlerInstrumentation implements TypeInstrumentation {
26+
27+
@Override
28+
public ElementMatcher<TypeDescription> typeMatcher() {
29+
return named("com.xxl.job.core.handler.impl.GlueJobHandler");
30+
}
31+
32+
@Override
33+
public void transform(TypeTransformer transformer) {
34+
transformer.applyAdviceToMethod(
35+
named("execute").and(isPublic()).and(takesArguments(String.class)),
36+
GlueJobHandlerInstrumentation.class.getName() + "$ScheduleAdvice");
37+
}
38+
39+
@SuppressWarnings("unused")
40+
public static class ScheduleAdvice {
41+
42+
@Advice.OnMethodEnter(suppress = Throwable.class)
43+
public static void onSchedule(
44+
@Advice.FieldValue("jobHandler") IJobHandler handler,
45+
@Advice.Local("otelRequest") XxlJobProcessRequest request,
46+
@Advice.Local("otelContext") Context context,
47+
@Advice.Local("otelScope") Scope scope) {
48+
Context parentContext = currentContext();
49+
request = XxlJobProcessRequest.createGlueJobRequest(handler);
50+
context = helper().startSpan(parentContext, request);
51+
if (context == null) {
52+
return;
53+
}
54+
scope = context.makeCurrent();
55+
}
56+
57+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
58+
public static void stopSpan(
59+
@Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result,
60+
@Advice.Thrown Throwable throwable,
61+
@Advice.Local("otelRequest") XxlJobProcessRequest request,
62+
@Advice.Local("otelContext") Context context,
63+
@Advice.Local("otelScope") Scope scope) {
64+
helper().stopSpan(result, request, throwable, scope, context);
65+
}
66+
}
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2;
7+
8+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
9+
import static io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2.XxlJobSingletons.helper;
10+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
13+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
14+
15+
import com.xxl.job.core.glue.GlueTypeEnum;
16+
import io.opentelemetry.context.Context;
17+
import io.opentelemetry.context.Scope;
18+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
19+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
20+
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobProcessRequest;
21+
import net.bytebuddy.asm.Advice;
22+
import net.bytebuddy.description.type.TypeDescription;
23+
import net.bytebuddy.implementation.bytecode.assign.Assigner;
24+
import net.bytebuddy.matcher.ElementMatcher;
25+
26+
public class ScriptJobHandlerInstrumentation implements TypeInstrumentation {
27+
28+
@Override
29+
public ElementMatcher<TypeDescription> typeMatcher() {
30+
return named("com.xxl.job.core.handler.impl.ScriptJobHandler");
31+
}
32+
33+
@Override
34+
public void transform(TypeTransformer transformer) {
35+
transformer.applyAdviceToMethod(
36+
named("execute").and(isPublic()).and(takesArguments(1).and(takesArgument(0, String.class))),
37+
ScriptJobHandlerInstrumentation.class.getName() + "$ScheduleAdvice");
38+
}
39+
40+
@SuppressWarnings("unused")
41+
public static class ScheduleAdvice {
42+
43+
@Advice.OnMethodEnter(suppress = Throwable.class)
44+
public static void onSchedule(
45+
@Advice.FieldValue("glueType") GlueTypeEnum glueType,
46+
@Advice.FieldValue("jobId") int jobId,
47+
@Advice.Local("otelRequest") XxlJobProcessRequest request,
48+
@Advice.Local("otelContext") Context context,
49+
@Advice.Local("otelScope") Scope scope) {
50+
Context parentContext = currentContext();
51+
request = XxlJobProcessRequest.createScriptJobRequest(glueType, jobId);
52+
context = helper().startSpan(parentContext, request);
53+
if (context == null) {
54+
return;
55+
}
56+
scope = context.makeCurrent();
57+
}
58+
59+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
60+
public static void stopSpan(
61+
@Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result,
62+
@Advice.Thrown Throwable throwable,
63+
@Advice.Local("otelRequest") XxlJobProcessRequest request,
64+
@Advice.Local("otelContext") Context context,
65+
@Advice.Local("otelScope") Scope scope) {
66+
helper().stopSpan(result, request, throwable, scope, context);
67+
}
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2;
7+
8+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
9+
import static io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobConstants.XXL_GLUE_JOB_HANDLER;
10+
import static io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobConstants.XXL_METHOD_JOB_HANDLER;
11+
import static io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobConstants.XXL_SCRIPT_JOB_HANDLER;
12+
import static io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2.XxlJobSingletons.helper;
13+
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
14+
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
15+
import static net.bytebuddy.matcher.ElementMatchers.named;
16+
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
17+
import static net.bytebuddy.matcher.ElementMatchers.not;
18+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
19+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
20+
21+
import com.xxl.job.core.handler.IJobHandler;
22+
import io.opentelemetry.context.Context;
23+
import io.opentelemetry.context.Scope;
24+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
25+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
26+
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobProcessRequest;
27+
import net.bytebuddy.asm.Advice;
28+
import net.bytebuddy.description.type.TypeDescription;
29+
import net.bytebuddy.implementation.bytecode.assign.Assigner;
30+
import net.bytebuddy.matcher.ElementMatcher;
31+
32+
public class SimpleJobHandlerInstrumentation implements TypeInstrumentation {
33+
34+
@Override
35+
public ElementMatcher<TypeDescription> typeMatcher() {
36+
return hasSuperType(named("com.xxl.job.core.handler.IJobHandler"))
37+
.and(not(namedOneOf(XXL_GLUE_JOB_HANDLER, XXL_SCRIPT_JOB_HANDLER, XXL_METHOD_JOB_HANDLER)));
38+
}
39+
40+
@Override
41+
public void transform(TypeTransformer transformer) {
42+
transformer.applyAdviceToMethod(
43+
named("execute").and(isPublic()).and(takesArguments(1).and(takesArgument(0, String.class))),
44+
SimpleJobHandlerInstrumentation.class.getName() + "$ScheduleAdvice");
45+
}
46+
47+
public static class ScheduleAdvice {
48+
49+
@SuppressWarnings("unused")
50+
@Advice.OnMethodEnter(suppress = Throwable.class)
51+
public static void onSchedule(
52+
@Advice.This IJobHandler handler,
53+
@Advice.Local("otelRequest") XxlJobProcessRequest request,
54+
@Advice.Local("otelContext") Context context,
55+
@Advice.Local("otelScope") Scope scope) {
56+
Context parentContext = currentContext();
57+
request = XxlJobProcessRequest.createSimpleJobRequest(handler);
58+
context = helper().startSpan(parentContext, request);
59+
if (context == null) {
60+
return;
61+
}
62+
scope = context.makeCurrent();
63+
}
64+
65+
@SuppressWarnings("unused")
66+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
67+
public static void stopSpan(
68+
@Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result,
69+
@Advice.Thrown Throwable throwable,
70+
@Advice.Local("otelRequest") XxlJobProcessRequest request,
71+
@Advice.Local("otelContext") Context context,
72+
@Advice.Local("otelScope") Scope scope) {
73+
helper().stopSpan(result, request, throwable, scope, context);
74+
}
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9+
import static java.util.Arrays.asList;
10+
import static net.bytebuddy.matcher.ElementMatchers.not;
11+
12+
import com.google.auto.service.AutoService;
13+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
14+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
15+
import java.util.List;
16+
import net.bytebuddy.matcher.ElementMatcher;
17+
18+
@AutoService(InstrumentationModule.class)
19+
public class XxlJobInstrumentationModule extends InstrumentationModule {
20+
21+
public XxlJobInstrumentationModule() {
22+
super("xxl-job", "xxl-job-1.9.2");
23+
}
24+
25+
@Override
26+
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
27+
// Class was added in 2.1.2
28+
return not(hasClassesNamed("com.xxl.job.core.handler.impl.MethodJobHandler"));
29+
}
30+
31+
@Override
32+
public List<TypeInstrumentation> typeInstrumentations() {
33+
return asList(
34+
new ScriptJobHandlerInstrumentation(),
35+
new SimpleJobHandlerInstrumentation(),
36+
new GlueJobHandlerInstrumentation());
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2;
7+
8+
import com.xxl.job.core.biz.model.ReturnT;
9+
import com.xxl.job.core.glue.GlueTypeEnum;
10+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
11+
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobHelper;
12+
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobInstrumenterFactory;
13+
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobProcessRequest;
14+
15+
public final class XxlJobSingletons {
16+
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.xxl-job-1.9.2";
17+
private static final Instrumenter<XxlJobProcessRequest, Void> INSTRUMENTER =
18+
XxlJobInstrumenterFactory.create(INSTRUMENTATION_NAME);
19+
private static final XxlJobHelper HELPER =
20+
XxlJobHelper.create(
21+
INSTRUMENTER,
22+
object -> {
23+
if (object != null && (object instanceof ReturnT)) {
24+
ReturnT<?> result = (ReturnT<?>) object;
25+
return result.getCode() == ReturnT.FAIL_CODE;
26+
}
27+
return false;
28+
});
29+
30+
public static XxlJobHelper helper() {
31+
return HELPER;
32+
}
33+
34+
@SuppressWarnings({"Unused", "ReturnValueIgnored"})
35+
private static void limitSupportedVersions() {
36+
// GLUE_POWERSHELL was added in 1.9.2. Using this constant here ensures that muzzle will disable
37+
// this instrumentation on earlier versions where this constant does not exist.
38+
GlueTypeEnum.GLUE_POWERSHELL.name();
39+
}
40+
41+
private XxlJobSingletons() {}
42+
}

0 commit comments

Comments
 (0)