From fbf49b2cc61a42305b0a44a3518e6058154f47f7 Mon Sep 17 00:00:00 2001 From: hailin0 Date: Tue, 8 Dec 2020 10:04:14 +0800 Subject: [PATCH] Change the operation name of quartz-scheduler plugin (#5934) --- CHANGES.md | 1 + .../JobExecuteStateMethodInterceptor.java | 56 +++++++++++++++++++ .../quartz/JobRunShellMethodInterceptor.java | 6 +- ...JobRunShellInterceptorInstrumentation.java | 32 +++++++++-- .../setup/service-agent/java-agent/README.md | 2 +- .../config/expectedData.yaml | 32 ++++++++++- .../quartzscheduler/job/ExceptionJob.java | 32 +++++++++++ .../quartzscheduler/job/QuartzConfig.java | 21 ++++++- 8 files changed, 170 insertions(+), 12 deletions(-) create mode 100644 apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/JobExecuteStateMethodInterceptor.java create mode 100644 test/plugin/scenarios/quartz-scheduler-2.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/quartzscheduler/job/ExceptionJob.java diff --git a/CHANGES.md b/CHANGES.md index 228bf47c8..1d2ce2a1a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Release Notes. * Chore: adapt `create_source_release.sh` to make it runnable on Linux. #### Java Agent +* The operation name of quartz-scheduler plugin, has been changed as the `quartz-scheduler/${className}` format. * Fix jdk-http and okhttp-3.x plugin did not overwrite the old trace header. #### OAP-Backend diff --git a/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/JobExecuteStateMethodInterceptor.java b/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/JobExecuteStateMethodInterceptor.java new file mode 100644 index 000000000..e42c45a12 --- /dev/null +++ b/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/JobExecuteStateMethodInterceptor.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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.apache.skywalking.apm.plugin.quartz; + +import org.apache.skywalking.apm.agent.core.context.ContextManager; +import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor; +import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult; +import org.quartz.JobExecutionException; +import org.quartz.core.JobRunShell; + +import java.lang.reflect.Method; + +/** + * Intercept method of {@link JobRunShell#notifyJobListenersComplete(org.quartz.JobExecutionContext, org.quartz.JobExecutionException)}. + * record the quartz job execute exception. + */ +public class JobExecuteStateMethodInterceptor implements InstanceMethodsAroundInterceptor { + + @Override + public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { + if (ContextManager.isActive()) { + JobExecutionException exception = (JobExecutionException) allArguments[1]; + if (exception != null) { + AbstractSpan span = ContextManager.activeSpan(); + span.log(exception); + } + } + } + + @Override + public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Object ret) throws Throwable { + return ret; + } + + @Override + public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, Throwable t) { + } +} diff --git a/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/JobRunShellMethodInterceptor.java b/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/JobRunShellMethodInterceptor.java index 465a7fe63..213c93cad 100644 --- a/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/JobRunShellMethodInterceptor.java +++ b/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/JobRunShellMethodInterceptor.java @@ -40,6 +40,7 @@ import java.util.Map; public class JobRunShellMethodInterceptor implements InstanceMethodsAroundInterceptor { private static final AbstractTag JOB_GROUP = Tags.ofKey("jobGroup"); + private static final AbstractTag JOB_NAME = Tags.ofKey("jobName"); private static final AbstractTag JOB_DATA_MAP = Tags.ofKey("jobDataMap"); private static final String EMPTY_JOB_DATA_MAP_STRING = Collections.emptyMap().toString(); @@ -48,14 +49,15 @@ public class JobRunShellMethodInterceptor implements InstanceMethodsAroundInterc public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class[] argumentsTypes, MethodInterceptResult result) throws Throwable { JobDetail jobDetail = (JobDetail) objInst.getSkyWalkingDynamicField(); - String jobName = jobDetail.getKey().getName(); String jobGroup = jobDetail.getKey().getGroup(); - String operationName = ComponentsDefine.QUARTZ_SCHEDULER.getName() + "/" + jobName; + String jobName = jobDetail.getKey().getName(); + String operationName = ComponentsDefine.QUARTZ_SCHEDULER.getName() + "/" + jobDetail.getJobClass().getName(); AbstractSpan span = ContextManager.createLocalSpan(operationName); span.setComponent(ComponentsDefine.QUARTZ_SCHEDULER); Tags.LOGIC_ENDPOINT.set(span, Tags.VAL_LOCAL_SPAN_AS_LOGIC_ENDPOINT); span.tag(JOB_GROUP, jobGroup == null ? "" : jobGroup); + span.tag(JOB_NAME, jobName == null ? "" : jobName); span.tag(JOB_DATA_MAP, getJobDataMapString(jobDetail)); } diff --git a/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/define/JobRunShellInterceptorInstrumentation.java b/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/define/JobRunShellInterceptorInstrumentation.java index 6cfad9264..6a098df76 100644 --- a/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/define/JobRunShellInterceptorInstrumentation.java +++ b/apm-sniffer/optional-plugins/quartz-scheduler-2.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/quartz/define/JobRunShellInterceptorInstrumentation.java @@ -26,22 +26,25 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInst import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch; import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.isPrivate; import static org.apache.skywalking.apm.agent.core.plugin.match.NameMatch.byName; /** - * Enhance {@link org.quartz.core.JobRunShell} instance and intercept {@link org.quartz.core.JobRunShell#run()} method, - * this method is a unified entrance of execute schedule job. + * Enhance {@link org.quartz.core.JobRunShell} instance and intercept {@link org.quartz.core.JobRunShell#run()},{@link org.quartz.core.JobRunShell#notifyJobListenersComplete(org.quartz.JobExecutionContext, org.quartz.JobExecutionException)} methods, + * this class is a unified entrance of execute schedule job. * * @see org.apache.skywalking.apm.plugin.quartz.JobRunShellConstructorInterceptor * @see org.apache.skywalking.apm.plugin.quartz.JobRunShellMethodInterceptor + * @see org.apache.skywalking.apm.plugin.quartz.JobExecuteStateMethodInterceptor */ public class JobRunShellInterceptorInstrumentation extends ClassInstanceMethodsEnhancePluginDefine { public static final String CONSTRUCTOR_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.quartz.JobRunShellConstructorInterceptor"; - public static final String METHOD_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.quartz.JobRunShellMethodInterceptor"; + public static final String JOB_EXECUTE_METHOD_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.quartz.JobRunShellMethodInterceptor"; + public static final String JOB_EXECUTE_STATE_METHOD_INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.quartz.JobExecuteStateMethodInterceptor"; public static final String ENHANC_CLASS = "org.quartz.core.JobRunShell"; @Override @@ -81,7 +84,26 @@ public class JobRunShellInterceptorInstrumentation extends ClassInstanceMethodsE @Override public String getMethodsInterceptor() { - return METHOD_INTERCEPTOR_CLASS; + return JOB_EXECUTE_METHOD_INTERCEPTOR_CLASS; + } + + @Override + public boolean isOverrideArgs() { + return false; + } + }, + new InstanceMethodsInterceptPoint() { + @Override + public ElementMatcher getMethodsMatcher() { + return named("notifyJobListenersComplete") + .and(isPrivate()) + .and(takesArguments(2)) + .and(takesArgument(1, named("org.quartz.JobExecutionException"))); + } + + @Override + public String getMethodsInterceptor() { + return JOB_EXECUTE_STATE_METHOD_INTERCEPTOR_CLASS; } @Override diff --git a/docs/en/setup/service-agent/java-agent/README.md b/docs/en/setup/service-agent/java-agent/README.md index 144fee322..db886c59d 100755 --- a/docs/en/setup/service-agent/java-agent/README.md +++ b/docs/en/setup/service-agent/java-agent/README.md @@ -192,7 +192,7 @@ The following logic endpoints are added automatically by plugins. 1. Spring's ScheduledMethodRunnable jobs are logic endpoints. The name format is `SpringScheduled`/`${className}`/`${methodName}`. 1. Apache ShardingSphere ElasticJob's jobs are logic endpoints. The name format is `ElasticJob`/`${jobName}`. 1. XXLJob's jobs are logic endpoints. The name formats include `xxl-job`/`MethodJob`/`${className}`.`${methodName}`, `xxl-job`/`ScriptJob`/`${GlueType}`/`id`/`${jobId}`, and `xxl-job`/`SimpleJob`/`${className}`. -1. Quartz(optional plugin)'s jobs are logic endpoints. the name format is `quartz-scheduler`/`${jobName}`. +1. Quartz(optional plugin)'s jobs are logic endpoints. the name format is `quartz-scheduler`/`${className}`. User could use the SkyWalking's application toolkits to add the tag into the local span to label the span as a logic endpoint in the analysis stage. The tag is, key=`x-le` and value = `{"logic-span":true}`. diff --git a/test/plugin/scenarios/quartz-scheduler-2.x-scenario/config/expectedData.yaml b/test/plugin/scenarios/quartz-scheduler-2.x-scenario/config/expectedData.yaml index ae649d7d9..7deb74cb6 100644 --- a/test/plugin/scenarios/quartz-scheduler-2.x-scenario/config/expectedData.yaml +++ b/test/plugin/scenarios/quartz-scheduler-2.x-scenario/config/expectedData.yaml @@ -35,7 +35,7 @@ segmentItems: - {key: url, value: 'http://localhost:8080/quartz-scheduler-2.x-scenario/case/call'} - {key: http.method, value: GET} refs: - - {parentEndpoint: quartz-scheduler/DemoJob, networkAddress: 'localhost:8080', refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} + - {parentEndpoint: quartz-scheduler/org.apache.skywalking.apm.testcase.quartzscheduler.job.DemoJob, networkAddress: 'localhost:8080', refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not null, parentService: not null, traceId: not null} - segmentId: not null spans: - operationName: /quartz-scheduler-2.x-scenario/case/call @@ -53,7 +53,7 @@ segmentItems: tags: - {key: http.method, value: GET} - {key: url, value: 'http://localhost:8080/quartz-scheduler-2.x-scenario/case/call'} - - operationName: quartz-scheduler/DemoJob + - operationName: quartz-scheduler/org.apache.skywalking.apm.testcase.quartzscheduler.job.DemoJob operationId: 0 parentSpanId: -1 spanId: 0 @@ -68,4 +68,30 @@ segmentItems: tags: - {key: x-le, value: '{"logic-span":true}'} - {key: jobGroup, value: 'DemoJobGroup'} - - {key: jobDataMap, value: '{param1=test}'} \ No newline at end of file + - {key: jobName, value: 'DemoJob'} + - {key: jobDataMap, value: '{param1=test}'} + - segmentId: not null + spans: + - operationName: quartz-scheduler/org.apache.skywalking.apm.testcase.quartzscheduler.job.ExceptionJob + operationId: 0 + parentSpanId: -1 + spanId: 0 + spanLayer: Unknown + startTime: not null + endTime: not null + componentId: 97 + isError: true + spanType: Local + peer: '' + skipAnalysis: false + tags: + - {key: x-le, value: '{"logic-span":true}'} + - {key: jobGroup, value: 'ExceptionJobGroup'} + - {key: jobName, value: 'ExceptionJob'} + - {key: jobDataMap, value: '{param1=test}'} + logs: + - logEvent: + - {key: event, value: error} + - {key: error.kind, value: org.quartz.JobExecutionException} + - {key: message, value: not null} + - {key: stack, value: not null} \ No newline at end of file diff --git a/test/plugin/scenarios/quartz-scheduler-2.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/quartzscheduler/job/ExceptionJob.java b/test/plugin/scenarios/quartz-scheduler-2.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/quartzscheduler/job/ExceptionJob.java new file mode 100644 index 000000000..6cb29c3c9 --- /dev/null +++ b/test/plugin/scenarios/quartz-scheduler-2.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/quartzscheduler/job/ExceptionJob.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * + * http://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.apache.skywalking.apm.testcase.quartzscheduler.job; + +import lombok.extern.slf4j.Slf4j; +import org.quartz.Job; +import org.quartz.JobExecutionContext; + +@Slf4j +public class ExceptionJob implements Job { + + @Override + public void execute(JobExecutionContext jobExecutionContext) { + throw new RuntimeException("execute job exception"); + } +} diff --git a/test/plugin/scenarios/quartz-scheduler-2.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/quartzscheduler/job/QuartzConfig.java b/test/plugin/scenarios/quartz-scheduler-2.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/quartzscheduler/job/QuartzConfig.java index e4d2e48a2..4ca979e6d 100644 --- a/test/plugin/scenarios/quartz-scheduler-2.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/quartzscheduler/job/QuartzConfig.java +++ b/test/plugin/scenarios/quartz-scheduler-2.x-scenario/src/main/java/org/apache/skywalking/apm/testcase/quartzscheduler/job/QuartzConfig.java @@ -38,12 +38,15 @@ public class QuartzConfig { Map.Entry demoJobConfig = demoJobConfig(); scheduler.scheduleJob(demoJobConfig.getKey(), demoJobConfig.getValue()); + Map.Entry exceptionJobConfig = exceptionJobConfig(); + scheduler.scheduleJob(exceptionJobConfig.getKey(), exceptionJobConfig.getValue()); + return scheduler; } private Map.Entry demoJobConfig() throws ParseException { JobDetail demoJobDetail = JobBuilder.newJob(DemoJob.class) - .withIdentity("DemoJob","DemoJobGroup") + .withIdentity("DemoJob", "DemoJobGroup") .usingJobData("param1", "test") .storeDurably() .build(); @@ -56,4 +59,20 @@ public class QuartzConfig { return new AbstractMap.SimpleEntry(demoJobDetail, demoJobTrigger); } + + private Map.Entry exceptionJobConfig() throws ParseException { + JobDetail exceptionJobDetail = JobBuilder.newJob(ExceptionJob.class) + .withIdentity("ExceptionJob", "ExceptionJobGroup") + .usingJobData("param1", "test") + .storeDurably() + .build(); + + Trigger exceptionJobTrigger = TriggerBuilder.newTrigger() + .forJob(exceptionJobDetail) + .startNow() + .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")) + .build(); + + return new AbstractMap.SimpleEntry(exceptionJobDetail, exceptionJobTrigger); + } } \ No newline at end of file