Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for XXL-JOB #10421

Merged
merged 16 commits into from
Mar 12, 2024
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ These are the supported libraries and frameworks:
| [Vert.x SQL Client](https://github.com/eclipse-vertx/vertx-sql-client/) | 4.0+ | N/A | [Database Client Spans] |
| [Vert.x Web](https://vertx.io/docs/vertx-web/java/) | 3.0+ | N/A | Provides `http.route` [2] |
| [Vibur DBCP](https://www.vibur.org/) | 11.0+ | [opentelemetry-vibur-dbcp-11.0](../instrumentation/vibur-dbcp-11.0/library) | [Database Pool Metrics] |
| [XXL-JOB](https://www.xuxueli.com/xxl-job/en/) | 1.9.2+ | N/A | none |
steverao marked this conversation as resolved.
Show resolved Hide resolved
| [ZIO](https://zio.dev/) | 2.0+ | N/A | Context propagation |

**[1]** Standalone library instrumentation refers to instrumentation that can be used without the Java agent.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("com.xuxueli")
module.set("xxl-job-core")
versions.set("[1.9.2, 2.1.2)")
// except these versions, they are too old and the capabilities provided are not comprehensive.
skip("1.7.0", "1.7.1", "1.7.2", "1.8.0", "1.8.1", "1.8.2", "1.9.0", "1.9.1")
laurit marked this conversation as resolved.
Show resolved Hide resolved
assertInverse.set(true)
}
}

dependencies {
compileOnly("com.xuxueli:xxl-job-core:1.9.2")
steverao marked this conversation as resolved.
Show resolved Hide resolved
implementation(project(":instrumentation:xxl-job:xxl-job-common:javaagent"))

testLibrary("com.xuxueli:xxl-job-core:1.9.2") {
exclude("org.codehaus.groovy", "groovy")
}
testImplementation("javax.annotation:javax.annotation-api:1.3.2")
testImplementation(project(":instrumentation:xxl-job:xxl-job-common:testing"))
latestDepTestLibrary("com.xuxueli:xxl-job-core:2.1.1") {
exclude("org.codehaus.groovy", "groovy")
}
}

tasks.withType<Test>().configureEach {
// required on jdk17
jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
jvmArgs("-Dotel.instrumentation.xxl-job.experimental-span-attributes=true")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2;

import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.job.core.handler.IJobHandler;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobProcessRequest;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatcher;

public class GlueJobHandlerInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.xxl.job.core.handler.impl.GlueJobHandler");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("execute").and(isPublic()).and(takesArguments(1).and(takesArgument(0, String.class))),
ScheduleAdvice.class.getName());
steverao marked this conversation as resolved.
Show resolved Hide resolved
}

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

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onSchedule(
@Advice.FieldValue("jobHandler") IJobHandler handler,
@Advice.Local("otelRequest") XxlJobProcessRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
request = new XxlJobProcessRequest();
request.setDeclaringClass(handler.getClass());
request.setGlueTypeEnum(GlueTypeEnum.GLUE_GROOVY);
context = XxlJobHelper.startSpan(parentContext, request);
if (context == null) {
return;
}
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelRequest") XxlJobProcessRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
XxlJobHelper.stopSpan(result, request, throwable, scope, context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2;

import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.xxl.job.core.glue.GlueTypeEnum;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobProcessRequest;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatcher;

public class ScriptJobHandlerInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.xxl.job.core.handler.impl.ScriptJobHandler");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("execute").and(isPublic()).and(takesArguments(1).and(takesArgument(0, String.class))),
ScheduleAdvice.class.getName());
}

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

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onSchedule(
@Advice.FieldValue("glueType") GlueTypeEnum glueTypeEnum,
@Advice.FieldValue("jobId") int jobId,
@Advice.Local("otelRequest") XxlJobProcessRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
request = new XxlJobProcessRequest();
request.setGlueTypeEnum(glueTypeEnum);
request.setJobId(jobId);
context = XxlJobHelper.startSpan(parentContext, request);
if (context == null) {
return;
}
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelRequest") XxlJobProcessRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
XxlJobHelper.stopSpan(result, request, throwable, scope, context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2;

import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobConstants.XXL_GLUE_JOB_HANDLER;
import static io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobConstants.XXL_METHOD_JOB_HANDLER;
import static io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobConstants.XXL_SCRIPT_JOB_HANDLER;
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.xxl.job.core.glue.GlueTypeEnum;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.impl.GlueJobHandler;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobProcessRequest;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatcher;

public class SimpleJobHandlerInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return hasSuperType(named("com.xxl.job.core.handler.IJobHandler"))
.and(not(namedOneOf(XXL_GLUE_JOB_HANDLER, XXL_SCRIPT_JOB_HANDLER, XXL_METHOD_JOB_HANDLER)));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("execute").and(isPublic()).and(takesArguments(1).and(takesArgument(0, String.class))),
ScheduleAdvice.class.getName());
}

public static class ScheduleAdvice {

@SuppressWarnings("unused")
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onSchedule(
@Advice.This IJobHandler handler,
@Advice.Local("otelRequest") XxlJobProcessRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
VirtualField<GlueJobHandler, IJobHandler> handlerMap =
steverao marked this conversation as resolved.
Show resolved Hide resolved
VirtualField.find(GlueJobHandler.class, IJobHandler.class);
request = new XxlJobProcessRequest();
request.setDeclaringClass(handler.getClass());
request.setGlueTypeEnum(GlueTypeEnum.BEAN);
context = XxlJobHelper.startSpan(parentContext, request);
if (context == null) {
return;
}
scope = context.makeCurrent();
}

@SuppressWarnings("unused")
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Return(typing = Assigner.Typing.DYNAMIC) Object result,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelRequest") XxlJobProcessRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
XxlJobHelper.stopSpan(result, request, throwable, scope, context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2;

import com.xxl.job.core.biz.model.ReturnT;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobProcessRequest;

public class XxlJobHelper {

private XxlJobHelper() {}

public static Context startSpan(Context parentContext, XxlJobProcessRequest request) {
if (!XxlJobSingletons.xxlJobProcessInstrumenter().shouldStart(parentContext, request)) {
steverao marked this conversation as resolved.
Show resolved Hide resolved
return null;
}
return XxlJobSingletons.xxlJobProcessInstrumenter().start(parentContext, request);
}

public static void stopSpan(
Object result,
XxlJobProcessRequest request,
Throwable throwable,
Scope scope,
Context context) {
if (result != null && (result instanceof ReturnT)) {
ReturnT<?> res = (ReturnT<?>) result;
if (res.getCode() == ReturnT.FAIL_CODE) {
request.setResultStatus(Boolean.FALSE.toString());
}
}
if (throwable != null) {
request.setResultStatus(Boolean.FALSE.toString());
}
if (scope != null) {
steverao marked this conversation as resolved.
Show resolved Hide resolved
scope.close();
XxlJobSingletons.xxlJobProcessInstrumenter().end(context, request, null, throwable);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static java.util.Arrays.asList;
import static net.bytebuddy.matcher.ElementMatchers.not;

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

@AutoService(InstrumentationModule.class)
public class XxlJobInstrumentationModule extends InstrumentationModule {

public XxlJobInstrumentationModule() {
super("xxl-job", "xxl-job-1.9.2");
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return not(hasClassesNamed("com.xxl.job.core.handler.impl.MethodJobHandler"));
steverao marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(
new ScriptJobHandlerInstrumentation(),
new SimpleJobHandlerInstrumentation(),
new GlueJobHandlerInstrumentation());
}
}
Loading
Loading