Skip to content

Commit 10bce56

Browse files
author
Anuraag Agrawal
authored
Add instrumentation for Quartz 2.0 (open-telemetry#4017)
* Add instrumentation for Quartz 2.0 * Fix drift in comment * Fix fix * Comment * Cleanup
1 parent 96f5708 commit 10bce56

File tree

16 files changed

+545
-0
lines changed

16 files changed

+545
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("org.quartz-scheduler")
8+
module.set("quartz")
9+
versions.set("[2.0.0,)")
10+
assertInverse.set(true)
11+
}
12+
}
13+
14+
dependencies {
15+
implementation(project(":instrumentation:quartz-2.0:library"))
16+
17+
library("org.quartz-scheduler:quartz:2.0.0")
18+
19+
testImplementation(project(":instrumentation:quartz-2.0:testing"))
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.quartz.v2_0;
7+
8+
import com.google.auto.service.AutoService;
9+
import io.opentelemetry.instrumentation.api.config.Config;
10+
import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesBuilder;
11+
import io.opentelemetry.javaagent.extension.ignore.IgnoredTypesConfigurer;
12+
13+
@AutoService(IgnoredTypesConfigurer.class)
14+
public class QuartzIgnoredTypesConfigurer implements IgnoredTypesConfigurer {
15+
16+
@Override
17+
public void configure(Config config, IgnoredTypesBuilder builder) {
18+
// Quartz executes jobs themselves in a synchronous way, there's no reason to propagate context
19+
// between its scheduler threads.
20+
builder.ignoreTaskClass("org.quartz");
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.quartz.v2_0;
7+
8+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
9+
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
10+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
11+
import static net.bytebuddy.matcher.ElementMatchers.named;
12+
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
15+
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
16+
import net.bytebuddy.asm.Advice;
17+
import net.bytebuddy.description.type.TypeDescription;
18+
import net.bytebuddy.matcher.ElementMatcher;
19+
import org.quartz.Scheduler;
20+
21+
public class QuartzInstrumentation implements TypeInstrumentation {
22+
@Override
23+
public ElementMatcher<ClassLoader> classLoaderOptimization() {
24+
return hasClassesNamed("org.quartz.Scheduler");
25+
}
26+
27+
@Override
28+
public ElementMatcher<TypeDescription> typeMatcher() {
29+
return hasSuperType(named("org.quartz.Scheduler"));
30+
}
31+
32+
@Override
33+
public void transform(TypeTransformer transformer) {
34+
transformer.applyAdviceToMethod(
35+
isConstructor(), this.getClass().getName() + "$ConstructorAdvice");
36+
}
37+
38+
@SuppressWarnings("unused")
39+
public static class ConstructorAdvice {
40+
41+
@Advice.OnMethodEnter(suppress = Throwable.class)
42+
public static void trackCallDepth(@Advice.Local("otelCallDepth") CallDepth callDepth) {
43+
callDepth = CallDepth.forClass(Scheduler.class);
44+
callDepth.getAndIncrement();
45+
}
46+
47+
@Advice.OnMethodExit(suppress = Throwable.class)
48+
public static void addTracingInterceptor(
49+
@Advice.This Scheduler scheduler, @Advice.Local("otelCallDepth") CallDepth callDepth) {
50+
// No-args constructor is automatically called by constructors with args, but we only want to
51+
// run once from the constructor with args because that is where the dedupe needs to happen.
52+
if (callDepth.decrementAndGet() > 0) {
53+
return;
54+
}
55+
QuartzSingletons.TRACING.configure(scheduler);
56+
}
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.quartz.v2_0;
7+
8+
import com.google.auto.service.AutoService;
9+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
10+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
11+
import java.util.Collections;
12+
import java.util.List;
13+
14+
@AutoService(InstrumentationModule.class)
15+
public class QuartzInstrumentationModule extends InstrumentationModule {
16+
17+
public QuartzInstrumentationModule() {
18+
super("quartz", "quartz-2.0");
19+
}
20+
21+
@Override
22+
public List<TypeInstrumentation> typeInstrumentations() {
23+
return Collections.singletonList(new QuartzInstrumentation());
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.quartz.v2_0;
7+
8+
import io.opentelemetry.api.GlobalOpenTelemetry;
9+
import io.opentelemetry.instrumentation.quartz.v2_0.QuartzTracing;
10+
11+
public final class QuartzSingletons {
12+
13+
public static final QuartzTracing TRACING = QuartzTracing.create(GlobalOpenTelemetry.get());
14+
15+
private QuartzSingletons() {}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.quartz.v2_0;
7+
8+
import io.opentelemetry.instrumentation.quartz.v2_0.AbstractQuartzTest;
9+
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
10+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
11+
import org.junit.jupiter.api.extension.RegisterExtension;
12+
import org.quartz.Scheduler;
13+
14+
class QuartzTest extends AbstractQuartzTest {
15+
16+
@RegisterExtension InstrumentationExtension testing = AgentInstrumentationExtension.create();
17+
18+
@Override
19+
protected void configureScheduler(Scheduler scheduler) {}
20+
21+
@Override
22+
protected InstrumentationExtension getTesting() {
23+
return testing;
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
plugins {
2+
id("otel.library-instrumentation")
3+
id("otel.nullaway-conventions")
4+
}
5+
6+
dependencies {
7+
library("org.quartz-scheduler:quartz:2.0.0")
8+
9+
testImplementation(project(":instrumentation:quartz-2.0:testing"))
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.quartz.v2_0;
7+
8+
import io.opentelemetry.instrumentation.api.instrumenter.ErrorCauseExtractor;
9+
import org.quartz.SchedulerException;
10+
11+
final class QuartzErrorCauseExtractor implements ErrorCauseExtractor {
12+
@Override
13+
public Throwable extractCause(Throwable error) {
14+
Throwable userError = error;
15+
while (userError instanceof SchedulerException) {
16+
userError = ((SchedulerException) userError).getUnderlyingException();
17+
}
18+
return userError;
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.quartz.v2_0;
7+
8+
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
9+
import org.quartz.JobExecutionContext;
10+
import org.quartz.JobKey;
11+
12+
final class QuartzSpanNameExtractor implements SpanNameExtractor<JobExecutionContext> {
13+
@Override
14+
public String extract(JobExecutionContext job) {
15+
JobKey key = job.getJobDetail().getKey();
16+
return key.getGroup() + '.' + key.getName();
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.quartz.v2_0;
7+
8+
import io.opentelemetry.api.OpenTelemetry;
9+
import org.quartz.JobListener;
10+
import org.quartz.Scheduler;
11+
import org.quartz.SchedulerException;
12+
import org.quartz.impl.matchers.EverythingMatcher;
13+
14+
/** Entrypoint for tracing execution of Quartz jobs. */
15+
public final class QuartzTracing {
16+
17+
/** Returns a new {@link QuartzTracing} configured with the given {@link OpenTelemetry}. */
18+
public static QuartzTracing create(OpenTelemetry openTelemetry) {
19+
return builder(openTelemetry).build();
20+
}
21+
22+
/** Returns a new {@link QuartzTracingBuilder} configured with the given {@link OpenTelemetry}. */
23+
public static QuartzTracingBuilder builder(OpenTelemetry openTelemetry) {
24+
return new QuartzTracingBuilder(openTelemetry);
25+
}
26+
27+
QuartzTracing(JobListener jobListener) {
28+
this.jobListener = jobListener;
29+
}
30+
31+
private final JobListener jobListener;
32+
33+
/**
34+
* Configures the {@link Scheduler} to enable tracing of jobs.
35+
*
36+
* <p><strong>NOTE:</strong> If there are job listeners already registered on the Scheduler that
37+
* may throw exceptions, tracing will be broken. It's important to call this as soon as possible
38+
* to avoid being affected by other bad listeners, or otherwise ensure listeners you register do
39+
* not throw exceptions.
40+
*/
41+
public void configure(Scheduler scheduler) {
42+
try {
43+
for (JobListener listener : scheduler.getListenerManager().getJobListeners()) {
44+
if (listener instanceof TracingJobListener) {
45+
return;
46+
}
47+
}
48+
} catch (SchedulerException e) {
49+
// Ignore
50+
}
51+
try {
52+
// We must pass a matcher to work around a bug in Quartz 2.0.0. It's unlikely anyone uses
53+
// a version before 2.0.2, but it makes muzzle simple.
54+
scheduler.getListenerManager().addJobListener(jobListener, EverythingMatcher.allJobs());
55+
} catch (SchedulerException e) {
56+
throw new IllegalStateException("Could not add JobListener to Scheduler", e);
57+
}
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.quartz.v2_0;
7+
8+
import io.opentelemetry.api.OpenTelemetry;
9+
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
10+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
11+
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import org.quartz.JobExecutionContext;
15+
16+
/** A builder of {@link QuartzTracing}. */
17+
public final class QuartzTracingBuilder {
18+
19+
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.quartz-1.7";
20+
21+
private final OpenTelemetry openTelemetry;
22+
23+
private final List<AttributesExtractor<? super JobExecutionContext, ? super Void>>
24+
additionalExtractors = new ArrayList<>();
25+
26+
QuartzTracingBuilder(OpenTelemetry openTelemetry) {
27+
this.openTelemetry = openTelemetry;
28+
}
29+
30+
/**
31+
* Adds an additional {@link AttributesExtractor} to invoke to set attributes to instrumented
32+
* items. The {@link AttributesExtractor} will be executed after all default extractors.
33+
*/
34+
public QuartzTracingBuilder addAttributeExtractor(
35+
AttributesExtractor<? super JobExecutionContext, ? super Void> attributesExtractor) {
36+
additionalExtractors.add(attributesExtractor);
37+
return this;
38+
}
39+
40+
/** Returns a new {@link QuartzTracing} with the settings of this {@link QuartzTracingBuilder}. */
41+
public QuartzTracing build() {
42+
InstrumenterBuilder<JobExecutionContext, Void> instrumenter =
43+
Instrumenter.newBuilder(openTelemetry, INSTRUMENTATION_NAME, new QuartzSpanNameExtractor());
44+
45+
instrumenter.setErrorCauseExtractor(new QuartzErrorCauseExtractor());
46+
instrumenter.addAttributesExtractors(additionalExtractors);
47+
48+
return new QuartzTracing(new TracingJobListener(instrumenter.newInstrumenter()));
49+
}
50+
}

0 commit comments

Comments
 (0)