Java Quartz Logging Example
1. Introduction
In this article, we will explore Java Quartz Logging application using two of the most widely used logging frameworks, Logback and Log4j. Quartz is a very popular open source job scheduling library that can be used in Java applications. The main concept of Quartz is that a scheduler holds a list of jobs that are triggered at specific times or repeatedly.
2. Project Setup
To run the code examples of this post, we will use the following technologies:
- Java 8
- Quartz 2.2.1
- SLF4J 1.7.26
- Logback 1.2.3
- Maven 3.3.3
- Eclipse 4.10.0
Additionally, we will add the libraries needed for Logback and Log4j in the following section.
3. Logging basics
Logging refers to the recording of activity. Logging is a common issue for development teams. Several frameworks ease and standardize the process of logging for the Java platform. The most commonly used logging frameworks for Java are Logback and Log4j. Logback is intended as a successor to the Log4j project due to the end of the support of the Log4j project. In this article we will use Log4j version 2, also known as Log4j2, which is an upgrade to Log4j that provides significant improvements over Log4j and provides many of the improvements available in Logback while fixing some inherent problems in Logback’s architecture.
4. Logback
Logback is one of the most widely used logging frameworks in the Java community. It offers a faster implementation than Log4j, provides more options for configuration, and more flexibility in archiving old log files. In the following section, we create a new maven project and add the libraries and configuration needed for Logback.
4.1 Create a new Maven Project
In Eclipse, create a new Maven Project and add the following dependencies to the pom.xml:
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javacodegeeks</groupId> <artifactId>java-quartz-logging-logback</artifactId> <version>0.0.1-SNAPSHOT</version> <description>Java Quartz Logging with Logback Example</description> <properties> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> </properties> <dependencies> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.26</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> </dependencies> </project>
4.2 Setup the Logback properties
In our maven project we add a logback.xml file under src/main/resources, in which we will configure the Logback properties:
logback.xml
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>%date{dd MMM yyyy HH:mm:ss.SSS} [%thread] %level %class - %msg%n</pattern> </layout> </appender> <root level="DEBUG"> <appender-ref ref="STDOUT" /> </root> </configuration>
In this very simple configuration, we create an appender named STDOUT and we add a pattern for the logs it will output. Then we add the STDOUT appender to the root level which is set to DEBUG mode. For detailed info about the configuration for Logback check this post.
5. Log4j
Log4j is a very old logging framework and was very popular until 2015 when the Apache team announced that they stopped the support of it. Its successor, Log4j2 is the updated version of Log4j library, which is a simple, flexible, and fast Java-based logging framework. In the following section, we create a new maven project and add the libraries and configuration needed for Log4j2.
5.1 Create a new Maven Project
In Eclipse, create a new Maven Project and add the following dependencies to the pom.xml:
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.javacodegeeks</groupId> <artifactId>java-quartz-logging-log4j</artifactId> <version>0.0.1-SNAPSHOT</version> <description>Java Quartz Logging with Log4j Example</description> <properties> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> </properties> <dependencies> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.26</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.8.2</version> </dependency> </dependencies> </project>
5.2 Setup the Log4j properties
In our maven project we add a log4j.properties file under src/main/resources, in which we will configure the Log4j properties:
log4j.properties
log4j.rootLogger=DEBUG,STDOUT log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout log4j.appender.STDOUT.layout.ConversionPattern=%d{dd MMM yyyy HH:mm:ss.SSS} [%t] %p %c - %m%n
The above configuration is similar to the configuration we added for Logback. We create an appender named STDOUT and we add a pattern for the logs it will output. Then we add the STDOUT appender to the root level which is set to DEBUG mode. For detailed info about the configuration for Log4j see this post.
6. Run the example
We have finished the configuration of Logback and Log4j properties. In this step, we will implement the code required to run a job. The code is exactly the same for both logging frameworks:
SimpleJob.java
public class SimpleJob implements Job { private final Logger log = LoggerFactory.getLogger(SimpleJob.class); public void execute(JobExecutionContext context) throws JobExecutionException { log.info("SimpleJob executed!"); } }
QuartzExample.java
public class QuartzExample { public void run() throws Exception { // create the scheduler SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); // define the job and tie it to the SimpleJob class JobDetail job = JobBuilder.newJob(SimpleJob.class) .withIdentity("myJob", "myGroup") .build(); // create the trigger and define its schedule to run every 3 seconds Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("myTrigger", "myGroup") .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(3) .repeatForever()) .build(); // add the job details to the scheduler and associate it with the trigger scheduler.scheduleJob(job, trigger); // start the scheduler scheduler.start(); // wait long enough to see the job execution Thread.sleep(5 * 1000); // shutdown the scheduler scheduler.shutdown(true); } public static void main(String[] args) throws Exception { // run the example QuartzExample example = new QuartzExample(); example.run(); } }
In the example above, we create the SimpleJob
job, a very simple job which only outputs a single line of code when it gets executed. In the QuartzExample
class, this job is added to the scheduler and set for execution every 3 seconds. Finally the scheduler shuts down after 8 seconds (see Thread.sleep(8 * 1000)). In the following section we will run the main method of QuartzExample
class for both Logback and Log4j and compare the output.
6.1 Logback output
The output we get when we run the main method of QuartzExample
for the Logback framework is:
Output
12:22:42,870 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml] 12:22:42,871 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy] 12:22:42,871 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/C:/Users/lkarageorgiou/Downloads/code/java-quartz-logging-logback/target/classes/logback.xml] 12:22:42,991 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set 12:22:42,992 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender] 12:22:43,005 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT] 12:22:43,069 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - This appender no longer admits a layout as a sub-component, set an encoder instead. 12:22:43,069 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - To ensure compatibility, wrapping your layout in LayoutWrappingEncoder. 12:22:43,069 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - See also http://logback.qos.ch/codes.html#layoutInsteadOfEncoder for details 12:22:43,070 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to DEBUG 12:22:43,070 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[ROOT] 12:22:43,070 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration. 12:22:43,072 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@3f102e87 - Registering current configuration as safe fallback point 21 May 2019 12:22:43.125 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor 21 May 2019 12:22:43.131 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main 21 May 2019 12:22:43.141 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl 21 May 2019 12:22:43.142 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.2.1 created. 21 May 2019 12:22:43.143 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized. 21 May 2019 12:22:43.144 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.1) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED' Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally. NOT STARTED. Currently in standby mode. Number of jobs executed: 0 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads. Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered. 21 May 2019 12:22:43.144 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties' 21 May 2019 12:22:43.144 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.2.1 21 May 2019 12:22:43.151 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started. 21 May 2019 12:22:43.152 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 21 May 2019 12:22:43.154 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.SimpleJobFactory - Producing instance of Job 'myGroup.myJob', class=com.javacodegeeks.SimpleJob 21 May 2019 12:22:43.155 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 21 May 2019 12:22:43.156 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob 21 May 2019 12:22:43.156 [DefaultQuartzScheduler_Worker-1] INFO com.javacodegeeks.SimpleJob - SimpleJob executed! 21 May 2019 12:22:44.142 [Timer-0] DEBUG org.quartz.utils.UpdateChecker - Checking for available updated version of Quartz... 21 May 2019 12:22:44.357 [Timer-0] DEBUG org.quartz.utils.UpdateChecker - Quartz version update check failed: Server returned HTTP response code: 403 for URL: http://www.terracotta.org/kit/reflector?kitID=quartz&pageID=update.properties&id=167774568&os-name=Windows+10&jvm-name=Java+HotSpot%28TM%29+64-Bit+Server+VM&jvm-version=1.8.0_77&platform=amd64&tc-version=2.2.1&tc-product=Quartz&source=Quartz&uptime-secs=1&patch=UNKNOWN 21 May 2019 12:22:46.149 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.SimpleJobFactory - Producing instance of Job 'myGroup.myJob', class=com.javacodegeeks.SimpleJob 21 May 2019 12:22:46.149 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 21 May 2019 12:22:46.149 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob 21 May 2019 12:22:46.149 [DefaultQuartzScheduler_Worker-2] INFO com.javacodegeeks.SimpleJob - SimpleJob executed! 21 May 2019 12:22:48.152 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down. 21 May 2019 12:22:48.152 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED paused. 21 May 2019 12:22:48.152 [main] DEBUG org.quartz.simpl.SimpleThreadPool - Shutting down threadpool... 21 May 2019 12:22:48.162 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.simpl.SimpleThreadPool$WorkerThread - WorkerThread is shut down. 21 May 2019 12:22:48.639 [DefaultQuartzScheduler_Worker-5] DEBUG org.quartz.simpl.SimpleThreadPool$WorkerThread - WorkerThread is shut down. 21 May 2019 12:22:48.639 [DefaultQuartzScheduler_Worker-7] DEBUG org.quartz.simpl.SimpleThreadPool$WorkerThread - WorkerThread is shut down. 21 May 2019 12:22:48.639 [DefaultQuartzScheduler_Worker-6] DEBUG org.quartz.simpl.SimpleThreadPool$WorkerThread - WorkerThread is shut down. 21 May 2019 12:22:48.639 [DefaultQuartzScheduler_Worker-9] DEBUG org.quartz.simpl.SimpleThreadPool$WorkerThread - WorkerThread is shut down. 21 May 2019 12:22:48.639 [DefaultQuartzScheduler_Worker-8] DEBUG org.quartz.simpl.SimpleThreadPool$WorkerThread - WorkerThread is shut down. 21 May 2019 12:22:48.639 [DefaultQuartzScheduler_Worker-10] DEBUG org.quartz.simpl.SimpleThreadPool$WorkerThread - WorkerThread is shut down. 21 May 2019 12:22:48.639 [DefaultQuartzScheduler_Worker-4] DEBUG org.quartz.simpl.SimpleThreadPool$WorkerThread - WorkerThread is shut down. 21 May 2019 12:22:48.640 [DefaultQuartzScheduler_Worker-3] DEBUG org.quartz.simpl.SimpleThreadPool$WorkerThread - WorkerThread is shut down. 21 May 2019 12:22:48.653 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.simpl.SimpleThreadPool$WorkerThread - WorkerThread is shut down. 21 May 2019 12:22:48.653 [main] DEBUG org.quartz.simpl.SimpleThreadPool - No executing jobs remaining, all threads stopped. 21 May 2019 12:22:48.653 [main] DEBUG org.quartz.simpl.SimpleThreadPool - Shutdown of threadpool complete. 21 May 2019 12:22:48.653 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutdown complete.
From the above output, in lines 1-13 we see that Logback is trying to find the properties file and configure the logs according to the logback.xml file. Until it configures the logging format we set, the pattern layout is the default.
6.2 Log4j output
Let’s now run the main method of QuartzExample
for the Log4j framework and compare the different logs with Logback:
Output
21 May 2019 12:23:49.767 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor 21 May 2019 12:23:49.771 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main 21 May 2019 12:23:49.782 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl 21 May 2019 12:23:49.783 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.2.1 created. 21 May 2019 12:23:49.784 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized. 21 May 2019 12:23:49.784 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.2.1) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED' Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally. NOT STARTED. Currently in standby mode. Number of jobs executed: 0 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads. Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered. 21 May 2019 12:23:49.784 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties' 21 May 2019 12:23:49.785 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.2.1 21 May 2019 12:23:49.790 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started. 21 May 2019 12:23:49.790 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 21 May 2019 12:23:49.792 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.javacodegeeks.SimpleJob 21 May 2019 12:23:49.794 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 21 May 2019 12:23:49.794 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob 21 May 2019 12:23:49.794 [DefaultQuartzScheduler_Worker-1] INFO com.javacodegeeks.SimpleJob - SimpleJob executed! 21 May 2019 12:23:50.783 [Timer-0] DEBUG org.quartz.utils.UpdateChecker - Checking for available updated version of Quartz... 21 May 2019 12:23:50.981 [Timer-0] DEBUG org.quartz.utils.UpdateChecker - Quartz version update check failed: Server returned HTTP response code: 403 for URL: http://www.terracotta.org/kit/reflector?kitID=quartz&pageID=update.properties&id=167774568&os-name=Windows+10&jvm-name=Java+HotSpot%28TM%29+64-Bit+Server+VM&jvm-version=1.8.0_77&platform=amd64&tc-version=2.2.1&tc-product=Quartz&source=Quartz&uptime-secs=1&patch=UNKNOWN 21 May 2019 12:23:52.788 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'myGroup.myJob', class=com.javacodegeeks.SimpleJob 21 May 2019 12:23:52.788 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 21 May 2019 12:23:52.788 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job myGroup.myJob 21 May 2019 12:23:52.788 [DefaultQuartzScheduler_Worker-2] INFO com.javacodegeeks.SimpleJob - SimpleJob executed! 21 May 2019 12:23:54.791 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down. 21 May 2019 12:23:54.791 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED paused. 21 May 2019 12:23:54.791 [main] DEBUG org.quartz.simpl.SimpleThreadPool - Shutting down threadpool... 21 May 2019 12:23:54.799 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.simpl.SimpleThreadPool - WorkerThread is shut down. 21 May 2019 12:23:55.276 [DefaultQuartzScheduler_Worker-5] DEBUG org.quartz.simpl.SimpleThreadPool - WorkerThread is shut down. 21 May 2019 12:23:55.276 [DefaultQuartzScheduler_Worker-10] DEBUG org.quartz.simpl.SimpleThreadPool - WorkerThread is shut down. 21 May 2019 12:23:55.277 [DefaultQuartzScheduler_Worker-9] DEBUG org.quartz.simpl.SimpleThreadPool - WorkerThread is shut down. 21 May 2019 12:23:55.277 [DefaultQuartzScheduler_Worker-6] DEBUG org.quartz.simpl.SimpleThreadPool - WorkerThread is shut down. 21 May 2019 12:23:55.277 [DefaultQuartzScheduler_Worker-8] DEBUG org.quartz.simpl.SimpleThreadPool - WorkerThread is shut down. 21 May 2019 12:23:55.276 [DefaultQuartzScheduler_Worker-4] DEBUG org.quartz.simpl.SimpleThreadPool - WorkerThread is shut down. 21 May 2019 12:23:55.276 [DefaultQuartzScheduler_Worker-7] DEBUG org.quartz.simpl.SimpleThreadPool - WorkerThread is shut down. 21 May 2019 12:23:55.276 [DefaultQuartzScheduler_Worker-3] DEBUG org.quartz.simpl.SimpleThreadPool - WorkerThread is shut down. 21 May 2019 12:23:55.290 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.simpl.SimpleThreadPool - WorkerThread is shut down. 21 May 2019 12:23:55.290 [main] DEBUG org.quartz.simpl.SimpleThreadPool - No executing jobs remaining, all threads stopped. 21 May 2019 12:23:55.291 [main] DEBUG org.quartz.simpl.SimpleThreadPool - Shutdown of threadpool complete. 21 May 2019 12:23:55.292 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutdown complete.
From the above output, we get no configuration logs for Log4j, compared to what we got for Logback. From line 1 we get the logs from Quartz and the layout pattern is exactly the same with Logback.
7. Closing thoughts
If we had to choose one framework over the other, then we had to compare which one is the best for our use case. You can read this detailed comparison of Logback vs Log4j. Both logging frameworks are widely used and would suit almost all use cases.
Finally, in order to properly diagnose and trace issues in applications that use Quartz, we should take logging very seriously. Any code that gets executed inside jobs, job listeners etc must be logged. In addition, Quartz has its own logs when an event occurs i.e. a scheduler gets created, a job gets executed etc. Those logs come from the org.quartz
package and should not be ignored by the logging properties we use.
8. Java Quartz Logging – Conclusion
In this article, we saw how to enable two of the most widely used logging frameworks, Logback and Log4j, to a Quartz application. We provided code examples and investigated the logs each logging framework outputs. Finally we saw the importance of logging in a Quartz application and provided a link for a detailed comparison of those two logging frameworks.
9. Download the Eclipse project
You can download the full source code of the above examples here: Java Quartz Logging Example