DataDog / dd-trace-dotnet

.NET Client Library for Datadog APM

Home Page:https://docs.datadoghq.com/tracing/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Instrument Quartz.Net

TrymBeast opened this issue · comments

Are you requesting automatic instrumentation for a framework or library? Please describe.

  • Framework or library name : Quartz.NET
  • Library type: Job Scheduling
  • Library version: 3.x

Is your feature request related to a problem? Please describe.
The problem that I'm facing is that I see SQL commands that I can't correlate with the quartz.net jobs that are being executed.

Describe the solution you'd like
Being able to see the execution of the quartz.net jobs in the traces and correlate with sql queries and http requests being made by the jobs.

This would be useful for our team as well. FWIW, here is a temporary solution that I am using to get some baseline telemetry.

  1. Create a job decorator to start a trace span and add any contextual metadata for the job
  /// <summary>
  /// A decorator to instrument job executions and track their runtime and other telemetry
  /// </summary>
  public class DatadogJobWrapper : IJob
  {
      private readonly IJob _innerJob;
      protected static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

      public DatadogJobWrapper(IJob innerJob)
      {
          _innerJob = innerJob;
      }

      public void Execute(JobExecutionContext context)
      {
          using (var scope = Tracer.Instance.StartActive("job"))
          {
              scope.Span.ResourceName = context.JobDetail.Name;

              if (context.NextFireTimeUtc.HasValue)
              {
                  scope.Span.SetTag("next_run_time", context.NextFireTimeUtc.Value.ToLocalTime().ToString());
              }

              if (context.PreviousFireTimeUtc.HasValue)
              {
                  scope.Span.SetTag("prev_run_time", context.PreviousFireTimeUtc.Value.ToLocalTime().ToString());
              }

              try
              {
                  // Execute the actual job
                  _innerJob.Execute(context);
              }
              catch (Exception ex)
              {
                  Log.Error($"{_innerJob.GetType().Name} job failed", ex);
                  scope.Span.SetException(ex);
              }
          }
      }
  }
  1. Update your job factory to wrap each instance with your new decorator:
 public class AutofacJobFactory : IJobFactory
 {
     private static readonly ILog Log = LoggerFactory.GetLogger<AutofacJobFactory>();
     private IComponentContext Context { get; set; }

     public AutofacJobFactory(IComponentContext context)
     {
         Context = context;
     }

     public IJob NewJob(TriggerFiredBundle bundle)
     {
         try
         {
             var job = (IJob)Context.Resolve(bundle.JobDetail.JobType);
             return new DatadogJobWrapper(job);
         }
         catch (Exception ex)
         {
             var type = bundle == null || bundle.JobDetail == null ? null : bundle.JobDetail.JobType;
             Log.Error(
                 string.Format("Failed to create IJob object of type '{0}'. ", type),
                 ex);
             throw;
         }
     }
}