目次

Version 4, last updated by jeppenejsum at Nov 28 01:17 UTC

Note the text below refers to Lift 2.0-M3 and later. Some legacy logging code still exists but have been deprecated. This will be removed in a future release.

Logging is an important part of any application that has outgrown the toy stage. Often, logging is the only way to get insight into an application running in production.

Lift provides a Scala interface (in lift-common) to the SLF4J logging facade. As such, configuring logging in Lift is the same as in SLF4J, but a few helpers have been added.

Usage

The simplest way to start using logging is to mixin the Logger trait. This will provide direct access to methods for the different logging levels:

trait MyService extends Logger {
  info("Creating MyService at %s".format(now))
}

Note that there is no need to wrap the logger call with if (isInfoEnabled) since the string argument to the logger call is a by-name parameter and thus not evaluated if the loglevel is not active.

Be careful about mixing in the Logger trait with CometActor as some error methods conflict.

Sometimes you don’t want to pollute the namespace with all the Logger methods. In this case, you can get a nested logger if you mixin the Loggable trait:

trait MyService extends Loggable {
  logger.info("Creating MyService at %s".format(now))
}

The logger name

The default logger name is this.getClass.getName. This is usually fine for application level services. But for reusable types that will be used in different places it may make sense to name the logger differently. Either by using the static type:

package demoapp
trait MyService  {
  val logger = Logger(classOf[MyService]) // This will name the logger demoapp.MyService no matter where this trait is mixed in
  logger.info("Creating MyService at %s".format(now))
}

or by giving it an arbitrary name:

trait MyService  {
  val logger = Logger("audit.payments") // This will name the logger audit.payments no matter where this trait is mixed in
  logger.info("Creating MyService at %s".format(now))
}

Tracing function results

When coding in a functional style it sometimes ruins the readability if you have to break an expression into several intermediate steps (OTOH sometimes it helps :-) To remedy this, a trace method has been added that can be used to log a value (at the TRACE logging level) within an expression:

first map {f => user.firstName(trace("Extracted firstName",f))

Using a singleton for logging

Mixing in either Logger or Loggable to application components as described previously will create quite a few loggers, arranged in a hierarchy that resembles your package structure. This makes is easy to filter out irrelevant log messages at a granular level by changing the logging level of individual loggers. This happens at runtime, without changing any source code.

But if you for some reason don’t need this filtering you can create a single logger for use throughout the application:

object Log extends Logger
...
Log.info("log to the singleton logger")

Changing log levels at runtime

Lift includes a widget that can be used to change the log levels at runtime. More info can be found here

Configuration

Configuration is made at two levels:

  1. Selecting a backend. Since SLF4J is just a facade, an actual logging backend (such as Log4j or Logback) needs to be included in the application. This is done by adding the appropriate dependencies to your pom.xml. This step is required! (but see below for details) If you don’t include a logging backend in your application, it will fail to boot!
  2. Configuring the chosen logging backend. This includes selecting where to log (console, files, emails etc), what should be logged etc. This step is optional. A logging backend usually has got a default configuration process (ie. reading log4j.xml or logback.xml) that will be used of no other configuration is made.

Selecting a backend

Note the text below refers to Lift 2.0-M3 and later. Some legacy logging code still exists but have been deprecated. When this is removed in a future release, the configuration process (but not principle) will change.

To preserve backwards compatibility Log4j is currently the default logging backend in Lift. When the legacy logging code has been removed, this will change and Note the text below refers to Lift 2.0-M3 and later. Some legacy logging code still exists but have been deprecated. This will be removed in a future release.__

Using Log4j

No need to do anything atm cf. the above

Using Logback

Since log4j is included by default, you need to exclude this in your pom:

...
  <dependency>
    <groupId>net.liftweb</groupId>
    <artifactId>lift-webkit</artifactId>
    <version>2.0-SNAPSHOT</version>
    <exclusions>
      <exclusion>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
      </exclusion>
    </exclusions>
  </dependency>

Then add the Logback dependency:

...
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>0.9.18</version>
  </dependency>

Configuring the backend

If you’re only using lift-common, no backend configuration is made by default.

You can specify a function in Logger.setup that will be called before the first Logger is created. This can be used to configure the backend you have chosen.

Two helper objects are provided: Log4j and Logback. They can be used to configure the specified backend (which must be included in the pom) like this:

Logger.setup = Full(Log4j.withFile(pathToLog4jxml)) 

or

Logger.setup = Full(Logback.withFile(pathToLogbackxml)) 

Automatic run.mode dependent configuration

If you’re using lift-webkit, LiftRules contains a property, configureLogging, that can be used to specify how logging should be configured. Note this must be set before any Loggers are created

By default this is set to LoggingAutoConfigurer, which tries to guess (by loading specific classes) which of the Log4j or Logback backends are used. If a backend is found, it is configured with a file which should be named by the Lift property file naming conventions ( modeName.userName.hostName.) and ending in either log4j.xml, log4j.props or logback.xml.

Example with log4j.xml.

You can configure logging by providing a configuration files in src/main/resources/props/. There is a standard naming format that allows the configuration to be based on the run mode, user and host.

The format is:

modeName.hostName.userName.filename.extension

where:

  • modeName is omitted for development mode, but is otherwise one of: production, test, staging, pilot, profile or default.
  • hostName is optional
  • userName is optional
  • filename.extension is either log4j.xml or log4j.props

Lift searches for the the most specific file in the order. For example, on my machine (thunderbolt.lan), where I’m logged in as “richard” running in development mode, the search order is:

  • /props/richard.thunderbolt.lan.log4j.xml
  • /props/richard.log4j.xml
  • /props/thunderbolt.lan.log4j.xml
  • /props/default.log4j.xml
  • /richard.thunderbolt.lan.log4j.xml
  • /richard.log4j.xml
  • /thunderbolt.lan.log4j.xml
  • /default.log4j.xml

In production mode (-Drun.mode=production) the list looks like this:

  • /props/production.richard.thunderbolt.config.log4j.xml
  • /props/production.richard.log4j.xml
  • /props/production.thunderbolt.config.log4j.xml
  • /props/production.default.log4j.xml
  • /production.richard.thunderbolt.config.log4j.xml
  • /production.richard.log4j.xml
  • /production.thunderbolt.config.log4j.xml
  • /production.default.log4j.xml

The development file will usually only specify logging to the console and a rather verbose logging level for most components. When running in production, the file will usally specify logging to a file, rollover policy, maybe send email on errors etc. The loglevel will probably be set to error for most components.