Griffon And org.slf4j (Logback)

Blog » Griffon And org.slf4j (Logback)

Posted on 1259524684|%A: %d %B, %Y|agohover

Griffon and org.slf4j

I prefer to use slf4j over commons logging and Logback over log4j. Support for slf4j and Logback in Griffon application will be described in this post. Source code (Griffon project structure) can be found here.

Steps covered in this Griffon and org.slf4j example

This example will use (or extend) Griffon application from Internationalization in Griffon using stringtomap post. See application structure first.

Add required jar files

  • copy slf4j api jar file (slf4j-api-1.5.6.jar) to ./simplemenu/lib directory
  • copy Logback jar files (logback-core-0.9.8.jar,logback-access-0.9.8.jar and logback-classic-0.9.8.jar) to ./simplemenu/lib directory
  • copy janino jar file (janino.jar) to ./simplemenu/lib directory (needed by Logback)

Note: version numbers of jar files may be different (numbers in text reflects version numbers available at the time of writing this post)

Add Logback configuration file to the Griffon application

Create ./simplemenu/griffon-app/resources/logback.xml file and fill it with following content (see Logback documentation):

<configuration>
  <appender name="FILE_LOG"
    class="ch.qos.logback.core.FileAppender" >
    <Append>false</Append>
    <file>simplemenu.log</file>
    <layout class="ch.qos.logback.classic.PatternLayout">      
      <Pattern>%d{HH:mm:ss.SSS} [%thread]  %-5level %logger{36}  - %msg%n</Pattern>
    </layout>    
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>ERROR</level>        
        <OnMatch>DENY</OnMatch>
       <OnMismatch>NEUTRAL</OnMismatch>
     </filter>  
     <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
      <evaluator name="testngEval">
        <expression>logger.getName().contains("testng")</expression>
      </evaluator>
      <OnMismatch>ACCEPT</OnMismatch>
      <OnMatch>DENY</OnMatch>
    </filter>      
  </appender>
 
  <appender name="FILE_ERR"
    class="ch.qos.logback.core.FileAppender">
    <Append>false</Append>
    <file>simplemenu.err</file>
    <layout class="ch.qos.logback.classic.PatternLayout">
      <!--Pattern>
        %date %level [%thread] %logger{10} [%file : %line] %msg%n
      </Pattern-->
      <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file] - %msg%n</Pattern>
    </layout>
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>ERROR</level>
      <OnMismatch>DENY</OnMismatch>
      <OnMatch>ACCEPT</OnMatch>
    </filter>      
  </appender>
 
  <appender name="STDOUT"
    class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
      <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
    </layout>
  </appender>
 
  <root>
    <level value="trace" />
    <appender-ref ref="FILE_LOG" />
    <appender-ref ref="FILE_ERR" />
    <appender-ref ref="STDOUT" />      
  </root>    
</configuration>

In this Logback configuration file there are defined two file appenders (one for errors, other for logging) and one console appender. All appenders are enabled (appender can be disabled by commenting its line in <root> element).

Initialize logging for Griffon classes

To initialize logging in Griffon MVC classes, the approach described in this post can be used. Create ./simplemenu/griffon-app/conf/Events.groovy file and add following content to it:

import org.slf4j.LoggerFactory
onNewInstance = {klass, type, instance ->  
    instance.metaClass.logger = LoggerFactory.getLogger(klass);
}

Whenever Griffon MVC class is instantiated (model, view or controller), new org.slf4j.Logger instance is injected to the class instance. After that, you can use logger in your Griffon MVC instances.

Edit controller class ./simplemenu/griffon-app/controllers/SimplemenuController.groovy and add logging to mvcGroupInit method.

class SimplemenuController {
    // these will be injected by Griffon
    def model
    def view
 
    void mvcGroupInit(Map args) {
       logger.info("==> mvcGroupInit {}", args)
        // this method is called after model and view are injected
       logger.info("<== mvcGroupInit")
    }
 
    def open = {
    println 'Open action!'   
    }
 
    def exit = {
    println 'Exit action!'   
        System.exit(0)
    }
}

Run Griffon application with griffon run-app and you should see following output:

23:26:14.224 [main] INFO SimplemenuController - ==> mvcGroupInit {app=griffon.application.SwingApplication@1332109, mvcType=simplemenu, mvcName=simplemenu, model=SimplemenuModel@1408325, controller=SimplemenuController@11ce2ad, view=SimplemenuView@1bde3d2, builder=griffon.builder.UberBuilder@1b80d9b}
23:26:14.230 [main] INFO SimplemenuController - <== mvcGroupInit

logger.info statements are logged to the standard output and to ./simplemenu/staging/simplemenu.log and ./simplemenu/staging/simplemenu.err files (all appenders are enabled).

Logging support to non Griffon classes

Instances of non Griffon classes do not get logger member automatically injected. It has to initialized (instantiated) in class source code. Create following Groovy class:

./simplemenu/src/main/Hello.groovy

class Hello {
    def sayHello() {
     println "Hello"
    }
}

Modify controller (./simplemenu/griffon-app/controllers/SimplemenuController.groovy) in a way that it uses this class:

class SimplemenuController {
    // these will be injected by Griffon
    def model
    def view
 
    void mvcGroupInit(Map args) {
       logger.info("==> mvcGroupInit {}", args)
       new Hello().sayHello()      
        // this method is called after model and view are injected
       logger.info("<== mvcGroupInit")
    }
 
    def open = {
    println 'Open action!'   
    }
 
    def exit = {
    println 'Exit action!'   
        System.exit(0)
    }
}

Run Griffon application with griffon run-app and you should see following output (Hello is printed):

23:43:46.449 [main] INFO SimplemenuController - ==> mvcGroupInit {app=griffon.application.SwingApplication@10ab78a, mvcType=simplemenu, mvcName=simplemenu, model=SimplemenuModel@117b450, controller=SimplemenuController@eb840f, view=SimplemenuView@98062f, builder=griffon.builder.UberBuilder@be0446}
Hello
23:43:46.459 [main] INFO SimplemenuController - <== mvcGroupInit

Try to use logger in the Hello class:

class Hello {
    def sayHello() {
     logger.info("==> sayHello")
     println "Hello"
     logger.info("<== sayHello")
    }
}

If you run application again, you will get exception telling that logger is not defined for Hello class:

groovy.lang.MissingPropertyException: No such property: logger for class: Hello
at Hello.sayHello(Hello.groovy:3)
at Hello$sayHello.call(Unknown Source)

Add logger initialization to the Hello class:

import org.slf4j.LoggerFactory
class Hello {
    def sayHello() {
     logger.info("==> sayHello")
     println "Hello"
     logger.info("<== sayHello")
    }
    def static logger =  LoggerFactory.getLogger(Hello);
}

Note: In the example, we have chosen to use static scope for the logger (reused in all instances). Griffon MVC classes inject non-static logger. (Currently I do not know if it is possible to inject static logger to Griffon MVC classes as well.) In most cases Griffon MVC classes are instantiated only once (or not very often), so ti does not really matter if logger is static or not.

If you run application, you can see logger in Hello class is working (logging) now:

23:50:20.592 [main] INFO SimplemenuController - ==> mvcGroupInit {app=griffon.application.SwingApplication@3e1d25, mvcType=simplemenu, mvcName=simplemenu, model=SimplemenuModel@4178d0, controller=SimplemenuController@cee41f, view=SimplemenuView@1eb717e, builder=griffon.builder.UberBuilder@681db8}
23:50:20.602 [main] INFO Hello - ==> sayHello
Hello
23:50:20.603 [main] INFO Hello - <== sayHello
23:50:20.603 [main] INFO SimplemenuController - <== mvcGroupInit

Summary

It is easy to add support for slf4j and Logback to Griffon. Once you use slf4j, you can easily switch between different logging implementations (services) like (Logback, log4j, java logging). All you have to do is to add required jar files and provide configuration specific for chosen logging service.

I would like to improve this example in a way that it does not use XML file for Logback configuration, but configuration is built with Groovy XmlMarkup builder or XmlStreamingMarkupBuilder. In addition, it may be useful to be able to specify location of Logback configuration (not to have it in application jar file).

Leave a comment

Add a New Comment
or Sign in as Wikidot user
(will not be published)
- +
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License