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 |
| [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
5 changes: 5 additions & 0 deletions instrumentation/xxl-job/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Settings for the XXL-JOB instrumentation

| System property | Type | Default | Description |
|-------------------------------------------------------------|---------|---------|-----------------------------------------------------|
| `otel.instrumentation.xxl-job.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("com.xuxueli")
module.set("xxl-job-core")
versions.set("[1.9.2, 2.1.2)")
assertInverse.set(true)
}
}

dependencies {
library("com.xuxueli:xxl-job-core:1.9.2") {
exclude("org.codehaus.groovy", "groovy")
}
implementation(project(":instrumentation:xxl-job:xxl-job-common:javaagent"))

testInstrumentation(project(":instrumentation:xxl-job:xxl-job-2.1.2:javaagent"))
testInstrumentation(project(":instrumentation:xxl-job:xxl-job-2.3.0:javaagent"))

// It needs the javax.annotation-api in xxl-job-core 1.9.2.
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,67 @@
/*
* 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.v1_9_2.XxlJobSingletons.helper;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

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(String.class)),
GlueJobHandlerInstrumentation.class.getName() + "$ScheduleAdvice");
}

@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 = XxlJobProcessRequest.createGlueJobRequest(handler);
context = helper().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) {
helper().stopSpan(result, request, throwable, scope, context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.v1_9_2.XxlJobSingletons.helper;
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))),
ScriptJobHandlerInstrumentation.class.getName() + "$ScheduleAdvice");
}

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

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onSchedule(
@Advice.FieldValue("glueType") GlueTypeEnum glueType,
@Advice.FieldValue("jobId") int jobId,
@Advice.Local("otelRequest") XxlJobProcessRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
request = XxlJobProcessRequest.createScriptJobRequest(glueType, jobId);
context = helper().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) {
helper().stopSpan(result, request, throwable, scope, context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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 io.opentelemetry.javaagent.instrumentation.xxljob.v1_9_2.XxlJobSingletons.helper;
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.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 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))),
SimpleJobHandlerInstrumentation.class.getName() + "$ScheduleAdvice");
}

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();
request = XxlJobProcessRequest.createSimpleJobRequest(handler);
context = helper().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) {
helper().stopSpan(result, request, throwable, scope, context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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() {
// Class was added in 2.1.2
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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 com.xxl.job.core.glue.GlueTypeEnum;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobHelper;
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobInstrumenterFactory;
import io.opentelemetry.javaagent.instrumentation.xxljob.common.XxlJobProcessRequest;

public final class XxlJobSingletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.xxl-job-1.9.2";
private static final Instrumenter<XxlJobProcessRequest, Void> INSTRUMENTER =
XxlJobInstrumenterFactory.create(INSTRUMENTATION_NAME);
private static final XxlJobHelper HELPER =
XxlJobHelper.create(
INSTRUMENTER,
object -> {
if (object != null && (object instanceof ReturnT)) {
ReturnT<?> result = (ReturnT<?>) object;
return result.getCode() == ReturnT.FAIL_CODE;
}
return false;
});

public static XxlJobHelper helper() {
return HELPER;
}

@SuppressWarnings({"Unused", "ReturnValueIgnored"})
private static void limitSupportedVersions() {
// GLUE_POWERSHELL was added in 1.9.2. Using this constant here ensures that muzzle will disable
// this instrumentation on earlier versions where this constant does not exist.
GlueTypeEnum.GLUE_POWERSHELL.name();
}

private XxlJobSingletons() {}
}
Loading
Loading