diff --git a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc index fe256aece..07df374ad 100644 --- a/docs/src/main/asciidoc/spring-cloud-sleuth.adoc +++ b/docs/src/main/asciidoc/spring-cloud-sleuth.adoc @@ -1229,7 +1229,7 @@ Spring Cloud Sleuth provides instrumentation for https://grpc.io/[gRPC] through ===== Dependencies IMPORTANT: The gRPC integration relies on two external libraries to instrument clients and servers and both of those libraries must be on the class path to enable the instrumentation. -Maven: +Maven: ``` io.github.lognet @@ -1248,7 +1248,7 @@ Gradle: ===== Server Instrumentation -Spring Cloud Sleuth leverages grpc-spring-boot-starter to register Brave's gRPC server interceptor with all services annotated with `@GRpcService`. +Spring Cloud Sleuth leverages grpc-spring-boot-starter to register Brave's gRPC server interceptor with all services annotated with `@GRpcService`. ===== Client Instrumentation @@ -1388,6 +1388,12 @@ To disable Zuul support, set the `spring.sleuth.zuul.enabled` property to `false We set `tracing` property to Lettcue `ClientResources` instance to enable Brave tracing built in Lettuce . To disable Redis support, set the `spring.sleuth.redis.enabled` property to `false`. +=== Quartz + +We instrument quartz jobs by adding Job/Trigger listeners to the Quartz Scheduler. + +To turn off this feature, set the `spring.sleuth.quartz.enabled` property to `false`. + == Running examples You can see the running examples deployed in the https://run.pivotal.io/[Pivotal Web Services]. diff --git a/spring-cloud-sleuth-core/pom.xml b/spring-cloud-sleuth-core/pom.xml index c468ba227..c8c91b457 100644 --- a/spring-cloud-sleuth-core/pom.xml +++ b/spring-cloud-sleuth-core/pom.xml @@ -289,6 +289,13 @@ true + + + org.springframework.boot + spring-boot-starter-quartz + true + + org.springframework.boot spring-boot-autoconfigure-processor diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfiguration.java new file mode 100644 index 000000000..460009871 --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.quartz; + +import brave.Tracing; +import org.quartz.Scheduler; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; +import org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration + * Auto-configuration} enables Quartz span information propagation. + * + * @author Branden Cash + * @since 2.2.0 + */ +@Configuration +@ConditionalOnBean({ Tracing.class, Scheduler.class }) +@AutoConfigureAfter({ TraceAutoConfiguration.class, QuartzAutoConfiguration.class }) +@ConditionalOnProperty(value = "spring.sleuth.quartz.enabled", matchIfMissing = true) +public class TraceQuartzAutoConfiguration implements InitializingBean { + + private Scheduler scheduler; + + private Tracing tracing; + + public TraceQuartzAutoConfiguration(Scheduler scheduler, Tracing tracing) { + this.scheduler = scheduler; + this.tracing = tracing; + } + + @Bean + public TracingJobListener tracingJobListener() { + return new TracingJobListener(tracing); + } + + @Override + public void afterPropertiesSet() throws Exception { + scheduler.getListenerManager().addTriggerListener(tracingJobListener()); + scheduler.getListenerManager().addJobListener(tracingJobListener()); + } + +} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListener.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListener.java new file mode 100644 index 000000000..dfc028adf --- /dev/null +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListener.java @@ -0,0 +1,120 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.quartz; + +import brave.Span; +import brave.Tracer.SpanInScope; +import brave.Tracing; +import brave.propagation.Propagation.Getter; +import brave.propagation.TraceContextOrSamplingFlags; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.JobListener; +import org.quartz.Trigger; +import org.quartz.Trigger.CompletedExecutionInstruction; +import org.quartz.TriggerListener; + +/** + * {@link org.quartz.JobListener JobListener} that will wrap a span around quartz jobs + * when they start and finish. + * + * @author Branden Cash + * @since 2.2.0 + */ +class TracingJobListener implements JobListener, TriggerListener { + + static final String TRIGGER_TAG_KEY = "quartz.trigger"; + + static final String CONTEXT_SPAN_KEY = Span.class.getName(); + + static final String CONTEXT_SPAN_IN_SCOPE_KEY = SpanInScope.class.getName(); + + private static final Getter GETTER = (carrier, key) -> { + Object value = carrier.get(key); + if (value instanceof String) { + return (String) value; + } + return null; + }; + + private final Tracing tracing; + + TracingJobListener(Tracing tracing) { + this.tracing = tracing; + } + + @Override + public String getName() { + return getClass().getName(); + } + + @Override + public void triggerFired(Trigger trigger, JobExecutionContext context) { + TraceContextOrSamplingFlags extracted = tracing.propagation().extractor(GETTER) + .extract(context.getMergedJobDataMap()); + Span span = tracing.tracer().nextSpan(extracted) + .name(context.getTrigger().getJobKey().toString()) + .tag(TRIGGER_TAG_KEY, context.getTrigger().getKey().toString()); + context.put(CONTEXT_SPAN_KEY, span); + context.put(CONTEXT_SPAN_IN_SCOPE_KEY, + tracing.tracer().withSpanInScope(span.start())); + } + + @Override + public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { + return false; + } + + @Override + public void triggerMisfired(Trigger trigger) { + + } + + @Override + public void triggerComplete(Trigger trigger, JobExecutionContext context, + CompletedExecutionInstruction triggerInstructionCode) { + closeTrace(context); + } + + @Override + public void jobToBeExecuted(JobExecutionContext context) { + + } + + @Override + public void jobExecutionVetoed(JobExecutionContext context) { + closeTrace(context); + } + + @Override + public void jobWasExecuted(JobExecutionContext context, + JobExecutionException jobException) { + } + + private void closeTrace(JobExecutionContext context) { + Object spanInScope = context.get(CONTEXT_SPAN_IN_SCOPE_KEY); + Object span = context.get(CONTEXT_SPAN_KEY); + if (spanInScope instanceof SpanInScope) { + ((SpanInScope) spanInScope).close(); + } + if (span instanceof Span) { + ((Span) span).finish(); + } + } + +} diff --git a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories index 38e80b152..a394fdf86 100644 --- a/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-sleuth-core/src/main/resources/META-INF/spring.factories @@ -26,7 +26,8 @@ org.springframework.cloud.sleuth.instrument.messaging.TraceMessagingAutoConfigur org.springframework.cloud.sleuth.instrument.messaging.TraceSpringIntegrationAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.messaging.websocket.TraceWebSocketAutoConfiguration,\ org.springframework.cloud.sleuth.instrument.opentracing.OpentracingAutoConfiguration,\ -org.springframework.cloud.sleuth.instrument.redis.TraceRedisAutoConfiguration +org.springframework.cloud.sleuth.instrument.redis.TraceRedisAutoConfiguration,\ +org.springframework.cloud.sleuth.instrument.quartz.TraceQuartzAutoConfiguration # Environment Post Processor org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.cloud.sleuth.autoconfig.TraceEnvironmentPostProcessor diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfigurationTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfigurationTest.java new file mode 100644 index 000000000..4eedeea91 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TraceQuartzAutoConfigurationTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.quartz; + +import brave.Tracer; +import brave.Tracing; +import org.junit.Test; +import org.quartz.ListenerManager; +import org.quartz.Scheduler; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author Branden Cash + */ +public class TraceQuartzAutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SchedulerConfig.class, + TracingConfig.class, TraceQuartzAutoConfiguration.class)); + + @Test + public void should_create_job_listener_bean_when_all_conditions_are_met() { + // when + this.contextRunner.run(context -> { + // expect + assertThat(context).hasSingleBean(TracingJobListener.class); + }); + } + + @Test + public void should_add_listener_to_trigger_listeners_when_conditions_are_met() { + // when + this.contextRunner.run(context -> { + // expect + verify(context.getBean(Scheduler.class).getListenerManager()) + .addTriggerListener(context.getBean(TracingJobListener.class)); + }); + } + + @Test + public void should_add_listener_job_listeners_when_conditions_are_met() { + // when + this.contextRunner.run(context -> { + // expect + verify(context.getBean(Scheduler.class).getListenerManager()) + .addJobListener(context.getBean(TracingJobListener.class)); + }); + } + + @Test + public void should_not_create_listener_bean_when_tracing_bean_is_not_present() { + new ApplicationContextRunner().withConfiguration(AutoConfigurations + // given + .of(SchedulerConfig.class, TraceQuartzAutoConfiguration.class)) + + // when + .run(context -> { + // expect + assertThat(context).doesNotHaveBean(TracingJobListener.class); + }); + } + + @Test + public void should_not_create_listener_when_scheduler_bean_is_not_present() { + new ApplicationContextRunner().withConfiguration(AutoConfigurations + // given + .of(TracingConfig.class, TraceQuartzAutoConfiguration.class)) + + // when + .run(context -> { + // expect + assertThat(context).doesNotHaveBean(TracingJobListener.class); + }); + } + + @Test + public void should_not_create_listener_when_sleuth_is_disabled() { + // when + this.contextRunner.withPropertyValues("spring.sleuth.quartz.enabled=false") + // expect + .run(context -> { + assertThat(context).doesNotHaveBean(TracingJobListener.class); + }); + } + + @Configuration + @AutoConfigureBefore(TraceQuartzAutoConfiguration.class) + public static class SchedulerConfig { + + @Bean + public Scheduler scheduler() throws Exception { + Scheduler scheduler = mock(Scheduler.class); + when(scheduler.getListenerManager()).thenReturn(mock(ListenerManager.class)); + return scheduler; + } + + } + + @Configuration + @AutoConfigureBefore(TraceQuartzAutoConfiguration.class) + public static class TracingConfig { + + @Bean + public Tracing tracing() { + return mock(Tracing.class); + } + + @Bean + public Tracer tracer() { + return mock(Tracer.class); + } + + } + +} diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListenerTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListenerTest.java new file mode 100644 index 000000000..53ea9f711 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/quartz/TracingJobListenerTest.java @@ -0,0 +1,355 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.quartz; + +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Properties; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; + +import brave.Tracer.SpanInScope; +import brave.Tracing; +import brave.propagation.Propagation.Setter; +import brave.propagation.StrictScopeDecorator; +import brave.propagation.ThreadLocalCurrentTraceContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.JobKey; +import org.quartz.JobListener; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.quartz.Trigger.CompletedExecutionInstruction; +import org.quartz.TriggerKey; +import org.quartz.TriggerListener; +import org.quartz.impl.StdSchedulerFactory; +import org.quartz.listeners.JobListenerSupport; +import org.quartz.listeners.TriggerListenerSupport; +import org.quartz.utils.StringKeyDirtyFlagMap; +import zipkin2.Span; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.quartz.JobBuilder.newJob; +import static org.quartz.TriggerBuilder.newTrigger; +import static org.springframework.cloud.sleuth.instrument.quartz.TracingJobListener.CONTEXT_SPAN_IN_SCOPE_KEY; +import static org.springframework.cloud.sleuth.instrument.quartz.TracingJobListener.CONTEXT_SPAN_KEY; +import static org.springframework.cloud.sleuth.instrument.quartz.TracingJobListener.TRIGGER_TAG_KEY; + +/** + * @author Branden Cash + */ +public class TracingJobListenerTest { + + private static final JobKey SUCCESSFUL_JOB_KEY = new JobKey("SuccessfulJob"); + + private static final JobKey EXCEPTIONAL_JOB_KEY = new JobKey("ExceptionalJob"); + + private static final TriggerKey TRIGGER_KEY = new TriggerKey("ExampleTrigger"); + + private TracingJobListener listener; + + private Tracing tracing; + + private Scheduler scheduler; + + private CompletableFuture completableJob; + + private Queue spans = new ArrayDeque<>(); + + @Before + public void setUp() throws Exception { + tracing = Tracing.newBuilder().spanReporter(spans::add) + .currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder() + .addScopeDecorator(StrictScopeDecorator.create()).build()) + .build(); + + listener = new TracingJobListener(tracing); + completableJob = new CompleteableTriggerListener(); + + scheduler = createScheduler(getClass().getSimpleName(), 1); + scheduler.addJob(newJob(ExceptionalJob.class).withIdentity(EXCEPTIONAL_JOB_KEY) + .storeDurably().build(), true); + scheduler.addJob(newJob(SuccessfulJob.class).withIdentity(SUCCESSFUL_JOB_KEY) + .storeDurably().build(), true); + + scheduler.getListenerManager().addTriggerListener(listener); + scheduler.getListenerManager().addJobListener(listener); + scheduler.getListenerManager() + .addTriggerListener((CompleteableTriggerListener) completableJob); + scheduler.getListenerManager() + .addJobListener((CompleteableTriggerListener) completableJob); + scheduler.start(); + } + + @After + public void tearDown() throws Exception { + scheduler.shutdown(true); + } + + @Test + public void should_return_class_name_all_the_time() { + // when + String name = listener.getName(); + + // expect + assertThat(name).isEqualTo(TracingJobListener.class.getName()); + } + + @Test + public void should_complete_span_when_job_is_successful() throws Exception { + // given + Trigger trigger = newTrigger().forJob(SUCCESSFUL_JOB_KEY).startNow().build(); + + // when + runJob(trigger); + + // expect + takeSpan(); + } + + @Test + public void should_have_span_with_proper_name_and_tag_when_job_is_successful() + throws Exception { + // given + Trigger trigger = newTrigger().withIdentity(TRIGGER_KEY) + .forJob(SUCCESSFUL_JOB_KEY).startNow().build(); + + // when + runJob(trigger); + + // expect + Span span = takeSpan(); + assertThat(span.name()).isEqualToIgnoringCase(SUCCESSFUL_JOB_KEY.toString()); + assertThat(span.tags().get(TRIGGER_TAG_KEY)) + .isEqualToIgnoringCase(TRIGGER_KEY.toString()); + } + + @Test + public void should_complete_span_when_job_throws_exception() throws Exception { + // given + Trigger trigger = newTrigger().forJob(EXCEPTIONAL_JOB_KEY).startNow().build(); + + // when + runJob(trigger); + + // expect + takeSpan(); + } + + @Test + public void should_complete_span_when_job_is_vetoed() throws Exception { + // given + scheduler.getListenerManager().addTriggerListener(new VetoJobTriggerListener()); + Trigger trigger = newTrigger().forJob(SUCCESSFUL_JOB_KEY).startNow().build(); + + // when + runJob(trigger); + + // expect + takeSpan(); + } + + @Test + public void should_not_complete_span_when_context_is_modified_to_remove_keys() + throws Exception { + // given + scheduler.getListenerManager().addJobListener(new ContextModifyingJobListener()); + Trigger trigger = newTrigger().forJob(EXCEPTIONAL_JOB_KEY).startNow().build(); + + // when + runJob(trigger); + + // expect + requireNoSpan(); + } + + @Test + public void should_have_parent_and_child_span_when_trigger_contains_span_info() + throws Exception { + // given + brave.Span span = tracing.tracer().nextSpan(); + JobDataMap data = new JobDataMap(); + addSpanToJobData(data); + Trigger trigger = newTrigger().forJob(SUCCESSFUL_JOB_KEY).usingJobData(data) + .startNow().build(); + + // when + runJob(trigger); + + // expect + Span parent = takeSpan(); + Span child = takeSpan(); + assertThat(parent.parentId()).isNull(); + assertThat(child.parentId()).isEqualTo(parent.id()); + } + + @Test + public void should_have_parent_and_child_span_when_trigger_job_data_was_created_with_differently_typed_map() + throws Exception { + // given + JobDataMap data = new JobDataMap(new HashMap()); + addSpanToJobData(data); + Trigger trigger = newTrigger().forJob(SUCCESSFUL_JOB_KEY).usingJobData(data) + .startNow().build(); + + // when + runJob(trigger); + + // expect + Span parent = takeSpan(); + Span child = takeSpan(); + assertThat(parent.parentId()).isNull(); + assertThat(child.parentId()).isEqualTo(parent.id()); + } + + void runJob(Trigger trigger) throws SchedulerException { + scheduler.scheduleJob(trigger); + completableJob.join(); + } + + Scheduler createScheduler(String name, int threadPoolSize) throws SchedulerException { + Properties config = new Properties(); + config.setProperty("org.quartz.scheduler.instanceName", name + "Scheduler"); + config.setProperty("org.quartz.scheduler.instanceId", "AUTO"); + config.setProperty("org.quartz.threadPool.threadCount", + Integer.toString(threadPoolSize)); + config.setProperty("org.quartz.threadPool.class", + "org.quartz.simpl.SimpleThreadPool"); + return new StdSchedulerFactory(config).getScheduler(); + } + + void addSpanToJobData(JobDataMap data) { + brave.Span span = tracing.tracer().nextSpan(); + try (SpanInScope spanInScope = tracing.tracer().withSpanInScope(span)) { + tracing.propagation() + .injector((Setter) StringKeyDirtyFlagMap::put) + .inject(tracing.currentTraceContext().get(), data); + } + finally { + span.finish(); + } + } + + Span takeSpan() throws InterruptedException { + Span result = spans.poll(); + assertThat(result).withFailMessage("Span was not reported, but was expected") + .isNotNull(); + return result; + } + + void requireNoSpan() throws InterruptedException { + Span result = spans.poll(); + assertThat(result).withFailMessage("Span was reported, but was not expected") + .isNull(); + } + + public static class CompleteableTriggerListener extends CompletableFuture + implements TriggerListener, JobListener { + + @Override + public String getName() { + return getClass().getName(); + } + + @Override + public void triggerFired(Trigger trigger, JobExecutionContext context) { + } + + @Override + public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { + return false; + } + + @Override + public void triggerMisfired(Trigger trigger) { + } + + @Override + public void triggerComplete(Trigger trigger, JobExecutionContext context, + CompletedExecutionInstruction triggerInstructionCode) { + complete(context.getResult()); + } + + @Override + public void jobToBeExecuted(JobExecutionContext context) { + } + + @Override + public void jobExecutionVetoed(JobExecutionContext context) { + complete(context.getResult()); + } + + @Override + public void jobWasExecuted(JobExecutionContext context, + JobExecutionException jobException) { + } + + } + + public static class VetoJobTriggerListener extends TriggerListenerSupport { + + @Override + public String getName() { + return getClass().getName(); + } + + @Override + public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { + return true; + } + + } + + public static class ContextModifyingJobListener extends JobListenerSupport { + + @Override + public String getName() { + return getClass().getName(); + } + + @Override + public void jobToBeExecuted(JobExecutionContext context) { + context.put(CONTEXT_SPAN_KEY, null); + context.put(CONTEXT_SPAN_IN_SCOPE_KEY, null); + } + + } + + public static class ExceptionalJob implements Job { + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + throw new RuntimeException("Intentional Exception"); + } + + } + + public static class SuccessfulJob implements Job { + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + } + + } + +}