Unhandled Exception in Quartz.NET job



Logging undhandled exceptions is usually done by adding a delegate to System.AppDomain.CurrentDomain.UnhandledException:

System.AppDomain.CurrentDomain.UnhandledException += (sender,
                                                      eventArgs) =>
{
  var exception = eventArgs.ExceptionObject as System.Exception;

  // TODO add logging
};

This did also apply to the execution of Quartz.IJob implementations - but at some point Quartz.NET wrapped the execution of jobs with a try/catch, which breaks the bubbling of the exception to the AppDomain’s level.

Nevertheless, you don’t have to give up hope, there are some solutions available, based on adding a Quartz.IJobListener to the scheduler.

Quartz.Plugin.History.LoggingJobHistoryPlugin

The most convenient approach is to add a Quartz.Plugin.History.LoggingJobHistoryPlugin instance to the scheduler, which forwards any events of the trigger to Common.Logging:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="common">
      <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
    </sectionGroup>
    <section name="quartz" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  </configSections>
  <common>
    <logging>
      <!-- TODO configure logging here -->
    </logging>
  </common>
  <quartz>
    <!-- TODO configure other quartz settings here -->
    <add key="quartz.plugin.triggerHistory.type" value="Quartz.Plugin.History.LoggingJobHistoryPlugin, Quartz" />
  </quartz>
</configuration>

Custom Quartz.IJobListener implementation

Another approach is to create a custom job listener:

public class ExceptionOccuredJobListener : Quartz.IJobListener
{
  public virtual void JobToBeExecuted(Quartz.IJobExecutionContext context)
  {
    var message = $"Job {context.JobDetail.JobType} to be executed";

    // TODO add logging
  }

  public virtual void JobExecutionVetoed(Quartz.IJobExecutionContext context)
  {
    var message = $"Job {context.JobDetail.JobType} vetoed";

    // TODO add logging
  }

  public virtual void JobWasExecuted(Quartz.IJobExecutionContext context,
                                     Quartz.JobExecutionException jobException)
  {
    var exception = jobException?.GetBaseException();
    if (exception != null)
    {
      var message = $"Job {context.JobDetail.JobType} threw an exception";

      // TODO add logging
    }
  }

  public virtual string Name => "ExceptionOccuredJobListener";
}

This listener can easily be added like:

var exceptionOccuredJobListener = new ExceptionOccuredJobListener();
scheduler.ListenerManager.AddJobListener(exceptionOccuredJobListener);