Fork me on GitHub

To log or not to log !

Logging is so important to diagnose UI bugs.

Logging

My first idea about logging for JavaFX apps 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.

Logging Facade

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.

4
5
6
7
8
<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. So you need to add another dependency to choose the implementation you want to use.

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.

There are 3 implementations available:

  • NOP
  • LOGback
  • SimpleLogger

To avoid this error message to appear you can add the No-OPeration dependency, it will log nothing but avoid the previous error message:

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>

JRebirth Logs

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:

  • Provide localized log message
  • Manage log level activation automatically
  • Parameterize log level, to increase or decrease it by customization
  • Disable Message resolution for high performance

Note that you can still use your logger basic features.

Short UML Diagram:

Logging Class Diagram

JRebirth provides its own LoggerFactory that you can initialize like this:

54
55
/** The class logger. */
private static final JRLogger LOGGER = JRLoggerFactory.getLogger(JRebirthThread.class);

Then you can log your message like this:

105
108
159
174
273
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 occurred into the JRebirth Thread". */
    MessageItem JIT_ERROR = create(new LogMessage("jrebirth.concurrent.jitError", JRLevel.Error, JRebirthMarkers.CONCURRENT));
  
    /** "An error occurred while shutting 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));
  
}