§Configuring logging
Play uses SLF4J for logging, backed by Logback as its default logging engine. See the Logback documentation for details on configuration.
§Default configuration
In dev mode Play uses the following default configuration:
<!--
~ Copyright (C) 2009-2016 Lightbend Inc. <https://www.lightbend.com>
-->
<!-- The default logback configuration that Play uses in dev mode if no other configuration is provided -->
<configuration>
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
</encoder>
</appender>
<logger name="play" level="INFO" />
<logger name="application" level="DEBUG" />
<logger name="com.gargoylesoftware.htmlunit.javascript" level="OFF" />
<root level="WARN">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Play uses the following default configuration in production:
<!--
~ Copyright (C) 2009-2016 Lightbend Inc. <https://www.lightbend.com>
-->
<!-- The default logback configuration that Play uses if no other configuration is provided -->
<configuration>
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
</encoder>
</appender>
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="STDOUT" />
</appender>
<logger name="play" level="INFO" />
<logger name="application" level="INFO" />
<logger name="com.gargoylesoftware.htmlunit.javascript" level="OFF" />
<root level="WARN">
<appender-ref ref="ASYNCSTDOUT" />
</root>
</configuration>
A few things to note about these configurations:
- These default configs specify only a console logger which outputs only 10 lines of an exception stack trace.
- Play uses ANSI color codes by default in level messages.
- For production, the default config puts the console logger behind the logback AsyncAppender. For details on the performance implications on this, see this blog post.
To add a file logger, add the following appender to your conf/logback.xml
file:
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${application.home:-.}/logs/application.log</file>
<encoder>
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
</encoder>
</appender>
Optionally use the async appender to wrap the FileAppender
:
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
Add the necessary appender(s) to the root:
<root level="WARN">
<appender-ref ref="ASYNCFILE" />
<appender-ref ref="ASYNCSTDOUT" />
</root>
§Using a custom application loader
Note that when using a custom application loader that does not extend the default GuiceApplicationLoader
(for example when using compile-time dependency injection), the LoggerConfigurator
needs to be manually invoked to pick up your custom configuration. You can do this with code like the following:
class MyApplicationLoaderWithInitialization extends ApplicationLoader {
def load(context: Context) = {
LoggerConfigurator(context.environment.classLoader).foreach {
_.configure(context.environment)
}
new MyComponents(context).application
}
}
§Custom configuration
For any custom configuration, you will need to specify your own Logback configuration file.
§Using a configuration file from project source
You can provide a default logging configuration by providing a file conf/logback.xml
.
§Using an external configuration file
You can also specify a configuration file via a System property. This is particularly useful for production environments where the configuration file may be managed outside of your application source.
Note: The logging system gives top preference to configuration files specified by system properties, secondly to files in the
conf
directory, and lastly to the default. This allows you to customize your application’s logging configuration and still override it for specific environments or developer setups.
§Using -Dlogger.resource
Specify a configuration file to be loaded from the classpath:
$ start -Dlogger.resource=prod-logger.xml
§Using -Dlogger.file
Specify a configuration file to be loaded from the file system:
$ start -Dlogger.file=/opt/prod/logger.xml
§Examples
Here’s an example of configuration that uses a rolling file appender, as well as a seperate appender for outputting an access log:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${application.home:-.}/logs/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- Daily rollover with compression -->
<fileNamePattern>application-log-%d{yyyy-MM-dd}.gz</fileNamePattern>
<!-- keep 30 days worth of history -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss ZZZZ} [%level] from %logger in %thread - %message%n%xException</pattern>
</encoder>
</appender>
<appender name="ACCESS_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${application.home:-.}/logs/access.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover with compression -->
<fileNamePattern>access-log-%d{yyyy-MM-dd}.gz</fileNamePattern>
<!-- keep 1 week worth of history -->
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date{yyyy-MM-dd HH:mm:ss ZZZZ} %message%n</pattern>
<!-- this quadruples logging throughput -->
<immediateFlush>false</immediateFlush>
</encoder>
</appender>
<!-- additivity=false ensures access log data only goes to the access log -->
<logger name="access" level="INFO" additivity="false">
<appender-ref ref="ACCESS_FILE" />
</logger>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</configuration>
This demonstrates a few useful features:
- It uses
RollingFileAppender
which can help manage growing log files. - It writes log files to a directory external to the application so they aren’t affected by upgrades, etc.
- The
FILE
appender uses an expanded message format that can be parsed by third party log analytics providers such as Sumo Logic. - The
access
logger is routed to a separate log file using theACCESS_FILE
appender. - All loggers are set to a threshold of
INFO
which is a common choice for production logging.
§Akka logging configuration
Akka system logging can be done by changing the akka
logger to INFO.
<!-- Set logging for all Akka library classes to INFO -->
<logger name="akka" level="INFO" />
<!-- Set a specific actor to DEBUG -->
<logger name="actors.MyActor" level="DEBUG" />
You may also wish to configure an appender for the Akka loggers that includes useful properties such as thread and actor address. For more information about configuring Akka’s logging, including details on Logback and Slf4j integration, see the Akka documentation.
§Using a Custom Logging Framework
Play uses Logback by default, but it is possible to configure Play to use another logging framework as long as there is an SLF4J adapter for it. To do this, the PlayLogback
SBT plugin must be disabled using disablePlugins
:
lazy val root = (project in file("."))
.enablePlugins(PlayScala)
.disablePlugins(PlayLogback)
From there, a custom logging framework can be used. Here, Log4J 2 is used as an example.
libraryDependencies ++= Seq(
"org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.4.1",
"org.apache.logging.log4j" % "log4j-api" % "2.4.1",
"org.apache.logging.log4j" % "log4j-core" % "2.4.1"
)
Once the libraries and the SLF4J adapter are loaded, the log4j.configurationFile
system property can be set on the command line as usual.
If custom configuration depending on Play’s mode is required, you can do additional customization with the LoggerConfigurator
. To do this, add a logger-configurator.properties
to the classpath, with
play.logger.configurator=Log4J2LoggerConfigurator
And then extend LoggerConfigurator with any customizations:
import java.io.File
import java.net.URL
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.core._
import org.apache.logging.log4j.core.config.Configurator
import play.api.{Mode, Environment, LoggerConfigurator}
class Log4J2LoggerConfigurator extends LoggerConfigurator {
override def init(rootPath: File, mode: Mode.Mode): Unit = {
val properties = Map("application.home" -> rootPath.getAbsolutePath)
val resourceName = if (mode == Mode.Dev) "log4j2-dev.xml" else "log4j2.xml"
val resourceUrl = Option(this.getClass.getClassLoader.getResource(resourceName))
configure(properties, resourceUrl)
}
override def shutdown(): Unit = {
val context = LogManager.getContext().asInstanceOf[LoggerContext]
Configurator.shutdown(context)
}
override def configure(env: Environment): Unit = {
val properties = Map("application.home" -> env.rootPath.getAbsolutePath)
val resourceUrl = env.resource("log4j2.xml")
configure(properties, resourceUrl)
}
override def configure(properties: Map[String, String], config: Option[URL]): Unit = {
val context = LogManager.getContext(false).asInstanceOf[LoggerContext]
context.setConfigLocation(config.get.toURI)
}
}
Next: Configuring WS SSL