Logging is so important to diagnose UI bugs.
My first idea about logging for JavaFX was to build myself a small lightweight and powerful module to add log capability to JRebirth Framework.
But after some hundred of lines of code written, I realized that using a real logging library could be interesting and not too heavy to embed.
I choose to add dependency to slf4j-api : Simple Logging Facade 4 Java
This API is lightweight because the jar is near to 10 kb.
1 2 3 4 5 | < dependency > < groupid >org.slf4j</ groupid > < artifactid >slf4j-api</ artifactid > < version >1.7.5</ version > </ dependency > |
The slf4j api allow to declare logger for JRebirth Framework but doesn't provide any logger implementation.
By default all logs are rejected and you will have this message into the Java console at startup:
SLF4J: Failed to load class
"org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See
http://www.slf4j.org/codes.html#StaticLoggerBinder
for further details.
To avoid this error message to appear you can add the No-OPeration dependency:
1 2 3 4 5 | < dependency > < groupid >org.slf4j</ groupid > < artifactid >slf4j-nop</ artifactid > < version >1.7.5</ version > </ dependency > |
Personally I choose LOGback implementation because it represents a good compromise between performances and customization. It was finely integrated because it was written by the same team as slf4j.
LOGBack jars are quite heavy and weight more than 800 kb so it could be a problem for tiny applications. To use it you must add this dependency into your pom.xml :
1 2 3 4 5 | < dependency > < groupid >ch.qos.logback</ groupid > < artifactid >logback-classic</ artifactid > < version >1.0.13</ version > </ dependency > |
Then you must add a configuration file like this one into the application classpath:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <? xml version = "1.0" encoding = "UTF-8" ?> < configuration > < appender name = "FILE" class = "ch.qos.logback.core.FileAppender" > < file >JRebirthAnalyzer.log</ file > < encoder > < pattern >%date %level [%thread{6}] %logger{10} [%file:%line] %msg%n</ pattern > </ encoder > </ appender > < appender name = "STDOUT" class = "ch.qos.logback.core.ConsoleAppender" > < encoder > <!-- %level %msg%n --> < pattern >%level [%thread] %file:%line - %msg%n</ pattern > </ encoder > </ appender > < root level = "trace" > < appender-ref ref = "STDOUT" /> < appender-ref ref = "FILE" /> </ root > </ configuration > |
If you don't want to embed 800 kb of jar to provide logging feature you can use the simple logger provided by slf4j, it's only a 10 kb jar file.
You just have to add this dependency to your pom.xml:
1 2 3 4 5 | < dependency > < groupid >org.slf4j</ groupid > < artifactid >slf4j-simple</ artifactid > < version >1.7.5</ version > </ dependency > |
All JRebirth logs use an internationalized engine allowing us to provide extensible log messaging.
It could be seen as an overkill feature but it has some advantages:
Note that you can still use your logger basic features.
JRebirth provides its own LoggerFactory that you can initialize like this:
57 58 | /** The class logger. */ private static final JRLogger LOGGER = JRLoggerFactory.getLogger(JRebirthThread. class ); |
Then you can log your message like this:
108 111 162 177 280 | LOGGER.log(JTP_QUEUED, runnable.toString()); LOGGER.log(HPTP_QUEUED, runnable.toString()); LOGGER.error(BOOT_UP_ERROR, e); LOGGER.error(JIT_ERROR, e); LOGGER.log(SHUTDOWN_ERROR, e); |
All these log messages are store into an interface implemented by the class to let them accessible
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | public interface ConcurrentMessages extends MessageContainer { /** JRebirthThread. */ /** "Runnable submitted to JTP with hashCode={0}" . */ MessageItem JTP_QUEUED = create( new LogMessage( "jrebirth.concurrent.jtpQueued" , JRLevel.Trace, JRebirthMarkers.CONCURRENT)); /** "Runnable submitted to HPTP with hashCode={0}" . */ MessageItem HPTP_QUEUED = create( new LogMessage( "jrebirth.concurrent.hptpQueued" , JRLevel.Trace, JRebirthMarkers.CONCURRENT)); /** "An exception occurred during JRebirth BootUp" . */ MessageItem BOOT_UP_ERROR = create( new LogMessage( "jrebirth.concurrent.bootUpError" , JRLevel.Error, JRebirthMarkers.CONCURRENT)); /** "An exception occured into the JRebirth Thread". */ MessageItem JIT_ERROR = create( new LogMessage( "jrebirth.concurrent.jitError" , JRLevel.Error, JRebirthMarkers.CONCURRENT)); /** "An error occurred while shuting down the application ". */ MessageItem SHUTDOWN_ERROR = create( new LogMessage( "jrebirth.concurrent.shutdownError" , JRLevel.Error, JRebirthMarkers.CONCURRENT)); /** AbstractJrbRunnable. */ /** "Run> {0}". */ MessageItem RUN_IT = create( new LogMessage( "jrebirth.concurrent.runIt" , JRLevel.Trace, JRebirthMarkers.CONCURRENT)); /** "Thread error : {0} ". */ MessageItem THREAD_ERROR = create( new LogMessage( "jrebirth.concurrent.threadError" , JRLevel.Error, JRebirthMarkers.CONCURRENT)); /** JRebirthThreadPoolExecutor. */ /** "JTP returned an error" . */ MessageItem JTP_ERROR = create( new LogMessage( "jrebirth.concurrent.jtpError" , JRLevel.Error, JRebirthMarkers.CONCURRENT)); /** "JTP returned an error with rootCause =>". */ MessageItem JTP_ERROR_EXPLANATION = create( new LogMessage( "jrebirth.concurrent.jtpErrorExplanation" , JRLevel.Error, JRebirthMarkers.CONCURRENT)); /** "Future (hashcode={}) returned object : {0}". */ MessageItem FUTURE_DONE = create( new LogMessage( "jrebirth.concurrent.futureDone" , JRLevel.Trace, JRebirthMarkers.CONCURRENT)); } |