Table of contents
1.
Introduction🎯
2.
Default Configuration⭕
2.1.
Some Noteworthy Points about These Configurations:✍✍
3.
Security Logging✅✅
4.
Using a Custom Application Loader💻
5.
Custom Configuration❓
5.1.
Using a Configuration File from the Project Source📃📃
5.2.
Using an External Configuration File⭕
5.3.
Using -Dlogger.resource
5.4.
Using -Dlogger.file
5.5.
Examples🎯
6.
Including Properties✅
7.
Akka Logging Configuration⭕
8.
Using A Custom Logging Framework💻
9.
Frequently Asked Questions
9.1.
Define Play framework for configuring logging.
9.2.
Which logging engine is used by Play?
9.3.
Define Logback in context of configuring logging.
9.4.
Play uses which code in level messages?
9.5.
Which property is exposed by logging framework by default?
10.
Conclusion
Last Updated: Mar 27, 2024
Easy

Configuring Logging

Author Ankit Kumar
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction🎯

Play is based on a stateless, lightweight, web-friendly architecture. Play is built on Akka, and it has minimal resource consumption. It is a developer-friendly framework and very versatile. We will study in detail configuring logging.

Most of the Java libraries are also supported in Play. 
 

configure logging

For logging, Play uses SLF4J, which is backed by logback and is used as the default logging engine. Logback is seen as a successor to log4j. Logback's design is highly versatile, allowing it to be used in a variety of situations. Logback is categorised into three modules: logback-core, logback-classic, and logback-access.Configuring Logging can be done in manyways.

Now, we will dive deeper into the topic of configuring logging.

Default Configuration⭕

The play uses the following default configuration when working in dev mode.

<!-- 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="com.gargoylesoftware.htmlunit.javascript" level="OFF" />
  <root level="WARN">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>


The following code is used in Play when in the default configuration:

<!-- 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">
    <!-- increases the default queue size -->
    <queueSize>512</queueSize>
    <!-- don't discard messages -->
    <discardingThreshold>0</discardingThreshold>
    <!-- block when queue is full -->
    <neverBlock>false</neverBlock>
    <appender-ref ref="STDOUT" />
  </appender>

  <logger name="play" level="INFO" />
  <logger name="com.gargoylesoftware.htmlunit.javascript" level="OFF" />
  <root level="WARN">
    <appender-ref ref="ASYNCSTDOUT" />
  </root>
  <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
</configuration>

Some Noteworthy Points about These Configurations:✍✍

imp points

1️⃣These default configurations simply provide a console logger that displays ten lines of an error stack trace.

2️⃣In level messages, Play employs ANSI colour codes by default.

3️⃣In production, the console logger is placed behind the logback AsyncAppender by default.

4️⃣ When your application ends, you must cleanly shut off logback to verify that logged messages have been handled by asynchronous appenders (including the TCP appender) and that background threads have been halted.You must also explicitly define DelayingShutdownHook: <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/

📍For adding a file in the logger, the following appender is needed to be added in   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>


Also, we can 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>


This was all in the default configuration, now we will see security logging.

Security Logging✅✅

security logging

For security-related activities in Play, a security marker has been introduced and failed security checks are now recorded at the WARN level with the security marker set. This guarantees that developers are constantly aware of why a certain request is failing, which is critical now that security filters are set by default in Play. 

The security marker also allows security failures to be triggered or filtered apart from conventional logging. For example, to deactivate all logging with the SECURITY marker set, add the following lines to the logback.xml file.

<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
    <Marker>SECURITY</Marker>
    <OnMatch>DENY</OnMatch>
</turboFilter>


Now, if any custom application loader is used, then the configuring logging configuration needs to be manually invoked, which we are going to see in the next section.

Using a Custom Application Loader💻

It should be noted that if you are using a custom application in configuring logging loader that does not extend the usual GuiceApplicationLoader (for example, when utilising compile-time dependency injection), you must manually run the LoggerConfigurator to pick up your specific settings. This is possible with the following code like this:

class MyApplicationLoaderWithInitialization extends ApplicationLoader {
  def load(context: Context) = {
    LoggerConfigurator(context.environment.classLoader).foreach {
      _.configure(context.environment, context.initialConfiguration, Map.empty)
    }
    new MyComponents(context).application
  }
}


Now, it’s time to check on the custom configuration types of configuring logging.

Custom Configuration❓

If we want to do the custom configuration, then we need to specify our own logback file.

Using a Configuration File from the Project Source📃📃

By providing the file conf/logback.xml, we may give a default logging setup.

Using an External Configuration File⭕

A configuration file can also be specified using a System property. This is especially handy in production scenarios because the configuration file may be managed independently of your application source.

Using -Dlogger.resource

A configuration file can be specified to load from the classpath:

$ start -Dlogger.resource=prod-logger.xml

Using -Dlogger.file

A configuration file can be specified to load from the file system

$ start -Dlogger.file=/opt/prod/logger.xml


Now, let’s see the examples.

Examples🎯

examples

Here's an example of a setup that uses both a rolling file appender and a separate appender for generating 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.home:-.}/logs/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="SECURITY_FILE" class="ch.qos.logback.core.FileAppender">
        <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
            <evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">
                <marker>SECURITY</marker>
            </evaluator>
            <OnMismatch>DENY</OnMismatch>
            <OnMatch>ACCEPT</OnMatch>
        </filter>
        <file>${application.home:-.}/logs/security.log</file>
        <encoder>
            <pattern>%date [%level] [%marker] 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>${application.home:-.}/logs/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"/>
        <appender-ref ref="SECURITY_FILE"/>
    </root>
</configuration>


This highlights the following helpful features: 

1️⃣It employs RollingFileAppender, which can aid in the management of expanding log files. 

2️⃣It saves log files to a directory outside of the program so that they are not impacted by upgrades, etc. 

3️⃣The FILE appender employs an enhanced message format that third-party log analytics providers such as Sumo Logic can interpret. 

Using the ACCESS FILE appender, the access logger is sent to a different log file. 

Using the EvaluatorFilter and the OnMarkerEvaluator, all log messages with the "SECURITY" marker attached are recorded to the security.log file. 

All loggers are set to an INFO threshold, which is a common setting for production logging.

Including Properties✅

Only the property application.home is exposed by default to the logging framework, which means that files can be accessed relative to the Play application.

<file>${application.home:-}/example.log</file>


We can include Play.logger.includeConfigProperties=true if we wish to refer to attributes that are defined in the application.conf file. All configuration-defined attributes will be accessible to the logger during application startup:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>context = ${my.property.defined.in.application.conf} %message%n</pattern>
    </encoder>
</appender>

Akka Logging Configuration⭕

Log in can be done in the Akka system by changing akka logger to INFO by the following code.

<!-- 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" />


We can configure an appender for the Akka loggers to include useful properties such as actor address and threads.

Using A Custom Logging Framework💻

Play by default uses Logback, but if another logging framework is supported via an SLF4J adapter, it may be configured to use that one instead. To do this, disablePlugins must be used to disable the PlayLogback sbt plugin.

lazy val root = (project in file("."))
  .enablePlugins(PlayScala)
  .disablePlugins(PlayLogback)


Now, we can use a custom logging framework. For example, here we use Log4J.

libraryDependencies ++= Seq(
  "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.17.0",
  "org.apache.logging.log4j" % "log4j-api" % "2.17.0",
  "org.apache.logging.log4j" % "log4j-core" % "2.17.0"
)


Once the SLF4J adapter and libraries are loaded, the command line can be used to set the system property log4j.configurationFile.

For more customisation we can do that with LoggerConfigurator. Add logger-configurator.properties to classpath, with the following code:

play.logger.configurator=Log4J2LoggerConfigurator


Add any modifications if we wish, then extend LoggerConfigurator:

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.slf4j.ILoggerFactory;
import play.Environment;
import play.LoggerConfigurator;
import play.Mode;
import play.api.PlayException;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.*;
import org.apache.logging.log4j.core.config.Configurator;


public class JavaLog4JLoggerConfigurator implements LoggerConfigurator {


  private ILoggerFactory factory;


  @Override
  public void init(File rootPath, Mode mode) {
    Map<String, String> properties = new HashMap<>();
    properties.put("application.home", rootPath.getAbsolutePath());


    String resourceName = "log4j2.xml";
    URL resourceUrl = this.getClass().getClassLoader().getResource(resourceName);
    configure(properties, Optional.ofNullable(resourceUrl));
  }


  @Override
  public void configure(Environment env) {
    Map<String, String> properties =
        LoggerConfigurator.generateProperties(env, ConfigFactory.empty(), Collections.emptyMap());
    URL resourceUrl = env.resource("log4j2.xml");
    configure(properties, Optional.ofNullable(resourceUrl));
  }


  @Override
  public void configure(
      Environment env, Config configuration, Map<String, String> optionalProperties) {
    // LoggerConfigurator.generateProperties enables play.logger.includeConfigProperties=true
    Map<String, String> properties =
        LoggerConfigurator.generateProperties(env, configuration, optionalProperties);
    URL resourceUrl = env.resource("log4j2.xml");
    configure(properties, Optional.ofNullable(resourceUrl));
  }


  @Override
  public void configure(Map<String, String> properties, Optional<URL> config) {
    try {
      LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
      loggerContext.setConfigLocation(config.get().toURI());


      factory = org.slf4j.impl.StaticLoggerBinder.getSingleton().getLoggerFactory();
    } catch (URISyntaxException ex) {
      throw new PlayException(
          "log4j2.xml resource was not found",
          "Could not parse the location for log4j2.xml resource",
          ex);
    }
  }


  @Override
  public ILoggerFactory loggerFactory() {
    return factory;
  }


  @Override
  public void shutdown() {
    LoggerContext loggerContext = (LoggerContext) LogManager.getContext();
    Configurator.shutdown(loggerContext);
  }
}

 

Frequently Asked Questions

Define Play framework for configuring logging.

Play is based on a stateless, lightweight, web-friendly architecture. Play is built on Akka, it has minimal resource consumption. It is a developer-friendly framework and very versatile. Most of the Java libraries are also supported in Play. 

Which logging engine is used by Play?

 The Play uses SLF4J, which is backed by logback.

Define Logback in context of configuring logging.

Logback is seen as a successor to log4j. Logback's design is highly versatile, allowing it to be used in a variety of situations. Logback is categorised into three modules: logback-core, logback-classic, and logback-access.

Play uses which code in level messages?

In level messages, Play employs ANSI colour codes by default.

Which property is exposed by logging framework by default?

The property application.home is exposed by default to the logging framework.

Conclusion

In this article, we extensively discussed the configuring logging in Play. We started with the introduction of the Play. Then we saw the default configuration and learnt about security logging, custom logging and custom configuration. Furthermore,we saw properties utilisation and custom logging framework.

 You can refer to other similar articles as well

Now you must be curious after knowing about web2py and how simple it is to learn. You can visit Basics of Python with Data Structures and Algorithms and Free Python Foundation with DS and Algo and start your journey.

Nevertheless, you may consider our paid courses to give your career an edge over others!

Do upvote our blogs if you find them helpful and engaging!

Happy Learning Ninja! 🥷

Live masterclass