LoggingTriggerHistoryPlugin: Uncover the Secrets of Job Execution
To understand the significance of plugins, let's first consider the two primary abstractions in Quartz: jobs and triggers. A job represents a piece of code that you wish to schedule, while a trigger dictates when this code should be executed. You can associate multiple triggers with a single job, making it a flexible and powerful tool. Surprisingly, Quartz doesn't provide built-in logging or monitoring of executed jobs and triggers by default. Although an API is available, no logging is implemented, making it difficult to track job execution. To address this, you can add the following lines to your quartz.properties file:org.quartz.plugin.triggerHistory.class=org.quartz.plugins.history.LoggingTriggerHistoryPlugin org.quartz.plugin.triggerHistory.triggerFiredMessage=Trigger [{1}.{0}] fired job [{6}.{5}] scheduled at: {2, date, dd-MM-yyyy HH:mm:ss.SSS}, next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS} org.quartz.plugin.triggerHistory.triggerCompleteMessage=Trigger [{1}.{0}] completed firing job [{6}.{5}] with resulting trigger instruction code: {9}. Next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS} org.quartz.plugin.triggerHistory.triggerMisfiredMessage=Trigger [{1}.{0}] misfired job [{6}.{5}]. Should have fired at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}The first line loads the plugin class LoggingTriggerHistoryPlugin, while the remaining lines configure the plugin, customizing the logging messages. By adding these extra few lines, you can make debugging and monitoring significantly easier, as demonstrated in the example below:
LoggingTriggerHistoryPlugin | Trigger [Demo.Every-few-seconds] fired job [Demo.Print-message] scheduled at: 04-04-2012 23:23:47.036, next scheduled at: 04-04-2012 23:23:51.036//...job outputLoggingTriggerHistoryPlugin | Trigger [Demo.Every-few-seconds] completed firing job [Demo.Print-message] with resulting trigger instruction code: DO NOTHING. Next scheduled at: 04-04-2012 23:23:51.036
You now grasp the significance of assigning descriptive names to your triggers (Demo.Every-few-seconds) and jobs (Demo.Print-message), which greatly facilitates their identification.
LoggingJobHistoryPlugin
Another valuable plugin related to logging is worth exploring:
org.quartz.plugin.jobHistory.class=org.quartz.plugins.history.LoggingJobHistoryPluginorg.quartz.plugin.jobHistory.jobToBeFiredMessage=Job [{1}.{0}] scheduled to be fired by trigger [{4}.{3}], re-fire: {7}org.quartz.plugin.jobHistory.jobSuccessMessage=Job [{1}.{0}] execution completed successfully and reports: {8}org.quartz.plugin.jobHistory.jobFailedMessage=Job [{1}.{0}] execution failed with exception: {8}org.quartz.plugin.jobHistory.jobWasVetoedMessage=Job [{1}.{0}] was vetoed. It was to be fired by trigger [{4}.{3}] at: {2, date, dd-MM-yyyy HH:mm:ss.SSS}
The underlying principle is the same – plugin + extra configuration. For more details and possible placeholders, refer to the JavaDoc of LoggingJobHistoryPlugin. A quick glance at the logs reveals very descriptive output:
Trigger [Demo.Every-few-seconds] fired job [Demo.Print-message] scheduled at: 04-04-2012 23:34:53.739, next scheduled at: 04-04-2012 23:34:57.739Job [Demo.Print-message] to be fired by trigger [Demo.Every-few-seconds], re-fire: 0//...job outputJob [Demo.Print-message] execution complete and reports: nullTrigger [Demo.Every-few-seconds] completed firing job [Demo.Print-message] with resulting trigger instruction code: DO NOTHING. Next scheduled at: 04-04-2012 23:34:57.739
I find it puzzling that these plugins aren’t enabled by default. After all, if you don’t want such a verbose output, you can simply turn it off in your logging framework. Nevertheless, I believe it’s a good idea to have them in place when troubleshooting Quartz execution.
XMLSchedulingDataProcessorPlugin
This comprehensive plugin is particularly useful. It reads an XML file (by default named quartz_data.xml) containing jobs and triggers definitions and adds them to the scheduler. This is especially useful when you have a global job that you need to add once. The plugin can either update the existing jobs/triggers or ignore the XML file if they already exist – very useful when JDBCJobStore is used.
org.quartz.plugin.xmlScheduling.class=org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
In the aforementioned article, we manually added a job to the scheduler:
val trigger = newTrigger(). withIdentity("Every-few-seconds", "Demo"). withSchedule( simpleSchedule(). withIntervalInSeconds(4). repeatForever() ). build()val job = newJob(classOf[PrintMessageJob]). withIdentity("Print-message", "Demo"). usingJobData("msg", "Hello, world!"). build()scheduler.scheduleJob(job, trigger)
An alternative approach to achieve the same outcome is by configuring XML, which involves placing the quartz_data.xml file in your CLASSPATH as follows:
false true Every-few-seconds Demo Print-message Demo -1 4000 Print-message Demo com.blogspot.nurkiewicz.quartz.demo.PrintMessageJob msg Hello, World!
This XML file supports both simple and CRON triggers, and its structure is thoroughly documented using XML Schema.
Additionally, it is possible to reference XML files located in the file system and periodically scan them for changes (note the use of XMLSchedulingDataProcessorPlugin.setScanInterval()). Interestingly, Quartz utilizes its own scheduling mechanism for periodic scanning.
org.quartz.plugin.xmlScheduling.fileNames=/etc/quartz/system-jobs.xml,/home/johnny/my-jobs.xmlorg.quartz.plugin.xmlScheduling.scanInterval=60
ShutdownHookPlugin
Lastly, there is the ShutdownHookPlugin, a compact yet useful plugin that registers a shutdown hook in the JVM, enabling a gentle stop of the scheduler. However, it is advisable to disable cleanShutdown – if the system is already attempting to abruptly terminate the application (typically, scheduler shutdown is triggered by Spring via SchedulerFactoryBean) or the user presses Ctrl+C, waiting for currently running jobs seems ill-advised. After all, perhaps we are terminating the application because some jobs are running for too long or hanging?
org.quartz.plugin.shutdownHook.class=org.quartz.plugins.management.ShutdownHookPluginorg.quartz.plugin.shutdownHook.cleanShutdown=false
Evidently, Quartz boasts an array of captivating plugins. Although they lack comprehensive documentation, they operate exceptionally and prove to be a valuable asset to the scheduler.
The source code, incorporating these plugins, is accessible on GitHub.