DataDog / dd-trace-dotnet

.NET Client Library for Datadog APM
https://docs.datadoghq.com/tracing/
Apache License 2.0
441 stars 138 forks source link

Instrument Quartz.Net #5153

Open TrymBeast opened 8 months ago

TrymBeast commented 8 months ago

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

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.

robbiedhickey commented 8 months ago

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);
              }
          }
      }
  }

2) 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;
         }
     }
}