Integrating with Jakarta EE

In a Jakarta EE environment, there are two possible approaches to logging:

  1. Each application can use their own copy of Log4j Core and include log4j-core in the WAR or EAR archive.

  2. Applications can also use a single copy of Log4j Core that must be installed globally on the application server.

While the first approach is the easiest to implement, it has some limitations:

Shared libraries

Log events emitted by each application and the libraries bundled with it will be handled by Log4j Core, but events related to the application emitted by a shared library (e.g. JPA implementation) will be handled by the application server. To diagnose a problem with the application, you might need to look into multiple log files.

Separate log files

Each application must use a different log file to prevent problems with concurrent access to the same file by multiple applications. Problems may arise, especially if a rolling file appender is used.

Lifecycle

Web applications have a different lifecycle from the application server. Additional care is required to stop Log4j Core when the application is stopped. See Integrating with web applications for more details.

The second approach requires changes to the configuration of the application server, but produces better results in terms of separating log events of different applications. See Sharing Log4j Core between Web Applications for more details.

Integrating with web applications

To avoid problems, some Log4j API and Log4j Core features are automatically disabled when running in a Jakarta EE environment. Most notably:

  • the usage of ThreadLocal for object pooling is disabled.

  • a web-safe implementation of ThreadContextMap is used.

  • JMX notifications are sent synchronously.

  • the JVM shutdown hook is disabled.

See log4j2.isWebapp for more details.

Using a logging implementation like Log4j Core in a Jakarta EE application requires particular care. Since the lifecycle of a container or web application is independent of the lifecycle of the JVM, it’s important for logging resources to be properly cleaned up (database connections closed, files closed, etc.) when the container or web application shuts down.

To properly synchronize the lifecycles of Log4j Core and Jakarta EE applications, an additional Log4j Web artifact is provided.

Installation

To install Log4j Web in your web application, you need to add it as a runtime dependency:

  • Maven

  • Gradle

We assume you use log4j-bom for dependency management.

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-jakarta-web</artifactId>
  <scope>runtime</scope>
</dependency>

We assume you use log4j-bom for dependency management.

runtimeOnly 'org.apache.logging.log4j:log4j-jakarta-web'
Click here if you are you using Jakarta EE 8 or any version of Java EE?

Jakarta EE 8 and all Java EE applications servers use the legacy javax package prefix instead of jakarta. If you are using those application servers, you should replace the dependencies above with:

  • Maven

  • Gradle

We assume you use log4j-bom for dependency management.

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-web</artifactId>
  <scope>runtime</scope>
</dependency>

We assume you use log4j-bom for dependency management.

runtimeOnly 'org.apache.logging.log4j:log4j-web'

If you are writing a Servlet 3.0 or later application, Apache Log4j Web will register a ServletContainerInitializer that takes care of configuring the Log4j lifecycle for you. Under the hood this will:

  • initialize Log4j Core with the correct configuration file.

  • register a Log4jServletContextListener to automatically shut down Log4j Core, when the application shuts down.

  • register a Log4jServletFilter to enable the web lookup.

While the Servlet Specification allows web fragments to automatically add context listeners, it does not give any guarantees regarding the order in which those listeners are executed (see Section 8.2.3).

If other context listeners in your application use logging, you need to make sure that Log4jServletContextListener is the last listener to be executed at shutdown. To do it, you must create a web.xml descriptor and add the Log4jServletContextListener explicitly as the first context listener:

Snippet from an example web.xml
<listener>
  <description>Handles Log4j Core lifecycle</description>
  <listener-class>
    org.apache.logging.log4j.web.Log4jServletContextListener
  </listener-class>
</listener>

Manual installation

If you are maintaining an older Servlet 2.5 (or earlier) application, or if you disabled the servlet container initializer.

Snippet from an example web.xml
<listener>
  <description>Handles Log4j Core lifecycle</description>
  <listener-class>
    org.apache.logging.log4j.web.Log4jServletContextListener
  </listener-class>
</listener>
<filter>
  <description>Adds Log4j Core specific attributes to each request</description>
  <filter-name>log4jServletFilter</filter-name>
  <filter-class>org.apache.logging.log4j.web.Log4jServletFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>log4jServletFilter</filter-name>
  <url-pattern>/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
  <dispatcher>FORWARD</dispatcher>
  <dispatcher>INCLUDE</dispatcher>
  <dispatcher>ERROR</dispatcher>
  <!-- Servlet 3.0 with disabled auto-initialization; not supported in 2.5
  <dispatcher>ASYNC</dispatcher>
  -->
</filter-mapping>

Configuration

Log4j Web provides many configuration options to finely tune its installation. These configuration options should be specified as servlet context initialization parameters.

isLog4jAutoInitializationDisabled

Type

boolean

Default value

false

If set to true, the Log4jServletContainerInitializer will be disabled, which prevents the automatic registration of both the Log4jServletContextListener and Log4jServletFilter.

isLog4jAutoShutdownDisabled

Type

boolean

Default value

false

If set to true, the Log4jServletContextListener will not register a Log4jServletContextListener to handle the web application shut down.

log4j.stop.timeout.timeunit

Type

TimeUnit

Default value

SECONDS

Specifies the TimeUnit used for the shut-down delay.

log4j.stop.timeout

Type

long

Default value

30

It specifies the duration of the shut-down delay.

log4jContextName

Type

String

Default value

automatically computed

Used to specify the name of the logger context.

If JndiContextSelector is used, this parameter must be explicitly provided. Otherwise, the default value is:

  1. the servlet context name, if present,

  2. the servlet context path, including the leading /, otherwise.

isLog4jContextSelectorNamed

Type

boolean

Default value

false

Must be set to true to use the JNDI configuration.

log4jConfiguration

Type

URI

Default value

false

The location of a Log4j Core configuration file. If the provided value is not an absolute URI, Log4j interprets it as:

  1. the path to an existing servlet context resource,

  2. the path to an existing file,

  3. the path to a classpath resource.

If no value is provided:

  1. Log4j Web looks for a servlet context resource named /WEB-INF/log4j2-<contextName>.<extension>, where <contextName> is the name of the logger context,

  2. if no such file exists it looks for a servlet context resource named /WEB-INF/log4j2.<extension>,

  3. otherwise, it searches for a configuration file on the classpath using the usual automatic configuration procedure.

Asynchronous requests and threads

In order for the web lookup to work correctly, Log4j must be able to always identify the ServletContext used by the current thread. When standard requests, forwards, inclusions, and error resources are processed, the Log4jServletFilter binds the LoggerContext to the thread handling the request, and you don’t have to do anything.

The handling of asynchronous requests is however more tricky, since it allows you to execute code on threads that were not prepared by Log4jServletFilter. Such a situation occurs, for example, if your code was started using the AsyncContext.start(Runnable) method.

To successfully propagate the logger context along asynchronous calls, the WebLoggerContextUtils helper class is made available. Using this class you can either decorate a Runnable with method calls that bind the appropriate logger context to the thread:

Snippet from an example AsyncServlet.java
AsyncContext asyncContext = req.startAsync();
asyncContext.start(WebLoggerContextUtils.wrapExecutionContext(getServletContext(), () -> {
    // Put your logic here
}));

or, if more flexibility is required, you can apply the same logic by using Log4jWebSupport:

Snippet from an example AsyncServlet.java
AsyncContext asyncContext = req.startAsync();
Log4jWebSupport webSupport = WebLoggerContextUtils.getWebLifeCycle(getServletContext());
asyncContext.start(() -> {
    try {
        webSupport.setLoggerContext();
        // Put your logic here
    } finally {
        webSupport.clearLoggerContext();
    }
});

Logging in JavaServer Pages

The Log4j Tag library is planned to be removed in the next major release! If you are using this library, please get in touch with the Log4j maintainers using the official support channels.

To help users add logging statements to JavaServer Pages, Log4j provides a JSP tag library modeled after the Jakarta Commons Log Tag library. To use it, you need to add the following runtime dependency to your web application project:

  • Maven

  • Gradle

We assume you use log4j-bom for dependency management.

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-taglib</artifactId>
  <scope>runtime</scope>
</dependency>

We assume you use log4j-bom for dependency management.

runtimeOnly 'org.apache.logging.log4j:log4j-taglib'

and add the following declaration to your JSP pages:

<%@ taglib prefix="log4j" uri="http://logging.apache.org/log4j/tld/log" %>

The Log4j Taglib component is deprecated and is scheduled for removal in Log4j 3.

Currently, it only works with JavaServer Pages 2.3 and previous releases, and no version compatible with Jakarta Server Pages 3.0 is available.

The Log4j Taglib library defines a tag for most Logger methods, including:

  • simple and parameterized log statements:

    Snippet from an example taglib.jsp
    <log4j:debug message="Simple message"/>
    <log4j:info message="Hello {}!" p0="${param.who}"/>
    <log4j:warn message="Message with marker" marker="${requestScope.marker}"/>
  • flow tracing statements:

    Snippet from an example taglib.jsp
    <log4j:entry p0="${param.who}"/>
    <log4j:exit/>
  • catching and throwing statements:

    Snippet from an example taglib.jsp
    <c:catch var="exception">
        <%= 5 / 0 %>
    </c:catch>
    <c:if test="${exception != null}">
        <log4j:catching exception="${exception}"/>
    </c:if>
  • tags to test the current log level:

    <log4j:ifEnabled level="INFO">
        <code>INFO</code> is enabled.
    </log4j:ifEnabled>
  • tags to set the name of the logger used:

    Snippet from an example taglib.jsp
    <log4j:setLogger logger="example.jsp"/>
  • a dump tag that prints the contents of a JSP scope:

    Snippet from an example taglib.jsp
    <log4j:dump scope="request"/>

Application server specific notes

WildFly

WildFly implicitly adds a shared copy of log4j-api to each web application deployment. This copy of log4j-api is configured to forward all events to WildFly’s centralized logging system and does not use the copy of Log4j Core bundled with the web application.

To use Log4j Core, you need to set the add-logging-api-dependencies attribute of the logging subsystem to false. See WildFly documentation for more details.

Sharing Log4j Core between Web Applications

Since Log4j Core supports multiple logger contexts, it is possible to share a single instance of Log4j Core without losing the ability to configure logging for each application separately.

Sharing Log4j Core has two main advantages:

  • You can send log statements from multiple applications to the same log file. Under the hood, Log4j Core will use a single manager per file, which will serialize concurrent access from multiple applications.

  • You can capture log statements issued by other shared libraries, so you don’t have to look for them in the global application server log.

Setup

To share Log4j Core between applications, you need to share at least these two JAR files:

Since sharing libraries between applications is not part of the Jakarta EE standard, the instructions are specific to each application server:

  • GlassFish

  • Jetty

  • OpenLiberty

  • Payara

  • Tomcat

  • WildFly

In GlassFish, you can add those libraries to the common classloader. See GlassFish documentation for more details.

Recent versions of Jetty have a logging-log4j2 module that can be easily enabled to share Log4j Core between applications and to use Log4j Core for the Jetty server itself. See Jetty Modules documentation for more details.

In OpenLiberty, you can add Log4j as a global library. See OpenLiberty documentation for more details.

In Tomcat, you can use the common classloader. See Tomcat classloader documentation for more details.

You can install Log4j as a global module or in a global directory. See WildFly EE Application Deployment documentation for more details.

Check also the WildFly note above.

Web application classloaders (see Servlet Specification 10.7.2 ) use a "parent last" delegation strategy, but prevent application from overriding implementation classes provided by the container.

If you share Log4j between applications and the applications themselves contain Log4j Core, the logging behavior depends on the application server. Some application servers will use the shared instance (e.g., WildFly), while others will use the application instance (e.g., Tomcat). There are two solutions to this problem:

  • you can remove Log4j from the WAR or EAR archive:

    • Maven

    • Gradle

    You can declare the scope of all Log4j libraries as provided.

    You can add log4j-api to the providedCompile configuration, while log4j-core to the providedRuntime configuration. See the Gradle WAR plugin for more details.

  • you can use an application-server-specific configuration option to delegate the loading of Log4j API to the parent classloader.

Log separation

When using a shared instance of Log4j Core, you might be interested in identifying the application associated with a given log event. Log4j Core provides a mechanism to split all Logger instances into logging domains called LoggerContexts. You have therefore two ways to separate log events:

  1. You can create a separate logger context for each web application and one context for the common libraries. See Multiple logger contexts for more details.

  2. You can also use a single logger context for all log events, but use lookups to add context data to your log events. See Single logger context for more details.

These two approaches deliver similar results for log events generated by the web applications themselves or the libraries bundled in the WAR or EAR archive.

Differences between these approaches appear in the handling of shared libraries. There are two kinds of shared libraries:

  1. Shared libraries that use static Logger fields. These libraries will always use the same logger context, which will not be one of the per-application contexts.

    This kind includes all the shared libraries, which were not written with Jakarta EE in mind.

  2. Shared libraries that use instance Logger fields. These libraries will use the logger context associated with the web application that uses them.

    Application server implementations usually use instance Logger fields.

Since the first kind of libraries is more common, counterintuitively the Single logger context approach will usually give better results than the Multiple logger contexts approach.

Single logger context

By default, Log4j Core creates a separate logger context per classloader. To use a single logger context, you need to set the log4j2.contextSelector system property to:

In this approach, you must use lookups to register the application that generated a log event. The most useful lookups in this case are:

Web lookup

It does not require any setup, but it is available only after Log4jServletFilter has been executed. Some log events pertinent to a web application can be unmarked. See web lookup for more information.

JNDI lookup

It covers a larger part of the handling of a request, but it requires additional setup to export the name of the application via JNDI. See JNDI lookup for more information.

When using a single logger context, you choose between:

  • Logging all events to a single appender. We strongly recommend using a structured layout (e.g., JSON Template Layout) with an additional field capturing the Servlet context name. This would allow separation of application logs by filtering on the context name. The following example demonstrates this scheme using a Socket Appender writing to Elasticsearch:

    • XML

    • JSON

    • YAML

    • Properties

    <File name="GLOBAL" fileName="logs/global.log">
      <JsonTemplateLayout>
        <EventTemplateAdditionalField key="contextName"
                                      value="$${web:contextName}"/>
      </JsonTemplateLayout>
    </File>
    "File": {
      "name": "GLOBAL",
      "fileName": "logs/global.log",
      "JsonTemplateLayout": {
        "EventTemplateAdditionalField": {
          "key": "contextName",
          "value": "$${web:contextName}"
        }
      }
    },
    File:
      name: "GLOBAL"
      fileName: "logs/global.log"
      JsonTemplateLayout:
        EventTemplateAdditionalField:
          key: "contextName",
          value: "$${web:contextName"
    appender.0.type = File
    appender.0.name = GLOBAL
    appender.0.fileName = logs/global
    appender.0.layout.type = JsonTemplateLayout
    appender.0.layout.0.type = EventTemplateAdditionalField
    appender.0.layout.0.key = contextName
    appender.0.layout.0.value = $${web:contextName}
  • Logging events to a separate appender for each application. In this case, you can use routing appender to separate the events. This kind of configuration might be used on the development server together with the human-friendly Pattern Layout:

    • XML

    • JSON

    • YAML

    • Properties

    <Routing name="ROUTING">
      <Routes pattern="$${web:contextName:-common}">
        <Route>
          <File name="${web:contextName:-common}"
                fileName="logs/${web:contextName:-common}.log">
            <PatternLayout pattern="%d [%t] %-5p %c - %m%n"/>
          </File>
        </Route>
      </Routes>
    </Routing>
    "Routing": {
      "name": "ROUTING",
      "Routes": {
        "pattern": "$${web:contextName:-common}",
        "Route": {
          "File": {
            "name": "${web:contextName:-common}",
            "fileName": "logs/${web:contextName:-common}.log",
            "PatternLayout": {
              "pattern": "d [%t] %-5p %c - %m%n"
            }
          }
        }
      }
    }
    Routing:
      name: "ROUTING"
      Routes:
        pattern: "$${web:contextName:-common}"
        File:
          name: "${web:contextName:-common}"
          fileName: "logs/${web:contextName:-common}.log"
          PatternLayout:
            pattern: "%d [%t] %-5p %c - %m%n"
    appender.1.type = Routing
    appender.1.name = ROUTING
    appender.1.route.type = Routes
    appender.1.route.pattern = $${web:contextName:-common}
    appender.1.route.0.type = Route
    appender.1.route.0.appender.type = File
    appender.1.route.0.appender.name = ${web:contextName:-common}
    appender.1.route.0.appender.fileName = logs/${web:contextName:-common}.log
    appender.1.route.0.appender.layout.type = PatternLayout
    appender.1.route.0.appender.layout.pattern = %d [%t] %-5p %c - %m%n

Multiple logger contexts

Since Log4j Core uses ClassLoaderContextSelector by default, no configuration is needed to achieve multiple logger contexts in your application server: the classes of each classloader will use the logger context associated with the classloader.

To provide a different configuration file for each logger context, you can add files named log4j2<contextName>.xml to the classpath of your application server.

See log4jContextName and log4jConfiguration for more details.

Associating logger contexts to classloaders has, however, some limitations: shared libraries will not be able to use the per-application logger contexts. To overcome this limitation, Log4j Core provides an alternative algorithm to determine the right logger context to choose: JNDI lookups.

JNDI context selector

Application servers set up the correct JNDI context as soon as they determine which application will handle a request. Log4j Core allows the usage of JNDI to coordinate the usage of logger contexts in a Jakarta EE application server. To use this feature, you need to:

  1. Set the log4j2.contextSelector Log4j configuration property to org.apache.logging.log4j.core.selector.JndiContextSelector,

  2. For security reasons you need to enable the selector, by setting the log4j2.enableJndiContextSelector Log4j configuration property to true,

  3. Each web application needs to configure the servlet context parameter isLog4jContextSelectorNamed to true and provide a value for the log4jContextName servlet context parameter and java:comp/env/log4j/context-name JNDI environment entry:

    Snippet from an example web.xml
    <context-param>
      <param-name>isLog4jContextSelectorNamed</param-name>
      <param-value>true</param-value>
    </context-param>
    <context-param>
      <param-name>log4jContextName</param-name>
      <param-value>your_application_name</param-value>
    </context-param>
    <env-entry>
      <env-entry-name>log4j/context-name</env-entry-name>
      <env-entry-value>your_application_name</env-entry-value>
      <env-entry-type>java.lang.String</env-entry-type>
    </env-entry>

Replacing the application server logging subsystem

Some application servers allow administrators to replace the default logging subsystem of the application server with Log4j Core. Known instructions are listed in the section. If your application server is not listed here, check the documentation of the application server.

Tomcat

Tomcat uses a modified version of Apache Commons Logging called Tomcat JULI as the internal logging system. Tomcat JULI uses java.util.logging as default logging implementation, but since Tomcat 8.5 you can replace it with a different backend.

To use Log4j Core as logging backend, you need to modify the system classloader of the server. Assuming $CATALINA_BASE is the main directory of your Tomcat instance you need to:

  1. Create a $CATALINA_BASE/log4j folder to contain Log4j dependencies,

  2. Download the following JAR files into $CATALINA_BASE/log4j:

  3. Add a Log4j Core configuration file called either log4j2.xml or log4j2-tomcat.xml to the $CATALINA_BASE/log4j folder.

  4. Modify the system classloader classpath to include all the JAR files and the $CATALINA_BASE/log4j folder itself. If you are starting Tomcat using the scripts in $CATALINA_HOME/bin, you can do it by creating a $CATALINA_BASE/bin/setenv.sh file with content:

    CLASSPATH="$CATALINA_HOME/log4j/*:$CATALINA_HOME/log4j/"

    Windows users can modify the classpath using the Procrun monitor application GUI application. The application is traditionally located in $CATALINA_HOME/bin/tomcat<n>w.exe, where <n> is the major version number of Tomcat.

Jetty

In recent Jetty versions you just need to enable the logging-log4j2 module. See Jetty Modules documentation for more details.

On Jetty 9.x or earlier you need to:

  1. Add the following JAR files to Jetty’s classpath:

  2. Set the system property org.eclipse.jetty.util.log.class to org.apache.logging.log4j.appserver.jetty.Log4j2Logger