Log4j Jakarta Web
Using Log4j Core in web applications
Using a logging backend 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.
The purpose of the log4j-jakarta-web
is to register the appropriate hooks into a container’s lifecycle.
To avoid problems, some Log4j API and Log4j Core features are automatically disabled, when running in a Jakarta EE environment. Most notably:
|
Installation
In order to install log4j-jakarta-web
in your Web application, you need to add it as runtime dependency:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jakarta-web</artifactId>
<version>3.0.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
Lifecycle hooks and configuration
In a Jakarta EE 9 environment, Log4j Jakarta Web automatically registers a ServletContainerInitializer that takes care of the Log4j Core startup, configuration and shutdown. The initializer can be configured through these servlet context initialization parameters, which must be specified in a web.xml
deployment descriptor to be effective:
Parameter | Default | Description |
---|---|---|
|
|
If set to |
|
|
If set to |
|
|
The TimeUnit used to express the shutdown timeout. |
|
|
The shutdown timeout. |
|
Specifies the name of the logger context to use. It must be explicitly specified if The default value is:
|
|
|
|
Must be set to |
|
The location of a Log4j Core configuration file. If no value is provided:
|
The Log4j Jakarta Web JAR file is a web-fragment configured to run before any other web fragments in your application. While this guarantees that Log4j Core starts before all other initializers and listeners, the Servlet 5.0 Specification (cf. section 8.2.3) does not give guarantees that Log4j Core will shutdown after all servlet context listeners. If the shutdown order is important, users must create a
|
JNDI configuration
Log4j Core allows the usage of JNDI to coordinate the usage of logger contexts in a Jakarta EE application server. In order to use this feature, you need to:
-
Install the
log4j-jndi
module. -
Switch to the
JndiContextSelector
, by setting thelog4j2.*.LoggerContext.selector
Log4j property toorg.apache.logging.log4j.jndi.selector.JndiContextSelector
, -
For security reasons you need to enable the selector, by setting the
log4j2.*.JNDI.contextSelector
Log4j property totrue
, -
Each web application needs to configure the servlet context parameter
isLog4jContextSelectorNamed
totrue
and provide a value for thelog4jContextName
servlet context parameter andjava:comp/env/log4j/context-name
JNDI environment entry:
<web-app>
<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>
</web-app>
Web lookup
Log4j Jakarta Web also provides a ${web:…}
lookup
Key | Description |
---|---|
|
Value of the |
|
The first fragment of the servlet context path. |
|
The entire servlet context path. |
|
Value of the |
|
The major version of the Servlet specification supported by the application. |
|
The minor version of the Servlet specification supported by the application. |
|
Value of the |
|
Value of the |
|
The major version of the Servlet specification supported by the server. |
|
The minor version of the Servlet specification supported by the server. |
|
Value of the |
|
HTTP request method. |
|
Value of the |
|
Servlet request principal. |
|
Servlet request remote address. |
|
Servlet request remote host. |
|
Servlet request remote port. |
|
Servlet request URI. |
|
Servlet request URL. |
|
The root directory of the servlet context. |
|
The server info. |
|
The servlet context name. |
|
Value of the |
|
Servlet session id. |
|
The value of |
Asynchronous Requests and Threads
The handling of asynchronous requests is tricky, and regardless of Servlet container version or configuration Log4j cannot handle everything automatically.
When standard requests, forwards, includes, and error resources are processed, the Log4jServletFilter
binds the LoggerContext
to the thread handling the request.
After request processing completes, the filter unbinds the LoggerContext
from the thread.
Similarly, when an internal request is dispatched using a
javax.servlet.AsyncContext
, the Log4jServletFilter
also binds the
LoggerContext
to the thread handling the request and unbinds it when
request processing completes. However, this only happens for requests
dispatched through the AsyncContext
. There are other asynchronous
activities that can take place other than internal dispatched requests.
For example, after starting an AsyncContext
you could start up a
separate thread to process the request in the background, possibly
writing the response with the ServletOutputStream
. Filters cannot
intercept the execution of this thread. Filters also cannot intercept
threads that you start in the background during non-asynchronous
requests. This is true whether you use a brand new thread or a thread
borrowed from a thread pool. So what can you do for these special
threads?
You may not need to do anything. If you didn’t use the
isLog4jContextSelectorNamed
context parameter, there is no need to
bind the LoggerContext
to the thread. Log4j can safely locate the
LoggerContext
on its own. In these cases, the filter provides only
very modest performance gains, and only when creating new Logger
instances.
However, if you did specify the isLog4jContextSelectorNamed
context
parameter with the value "true", you will need to manually bind the
LoggerContext
to asynchronous threads. Otherwise, Log4j will not be
able to locate it.
Thankfully, Log4j provides a simple mechanism for binding the
LoggerContext
to asynchronous threads in these special circumstances.
The simplest way to do this is to wrap the Runnable
instance that is
passed to the AsyncContext.start()
method.
import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.web.WebLoggerContextUtils;
public class TestAsyncServlet extends HttpServlet {
@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
final AsyncContext asyncContext = req.startAsync();
asyncContext.start(WebLoggerContextUtils.wrapExecutionContext(this.getServletContext(), new Runnable() {
@Override
public void run() {
final Logger logger = LogManager.getLogger(TestAsyncServlet.class);
logger.info("Hello, servlet!");
}
}));
}
@Override
protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
final AsyncContext asyncContext = req.startAsync();
asyncContext.start(new Runnable() {
@Override
public void run() {
final Log4jWebSupport webSupport =
WebLoggerContextUtils.getWebLifeCycle(TestAsyncServlet.this.getServletContext());
webSupport.setLoggerContext();
// do stuff
webSupport.clearLoggerContext();
}
});
}
}
This can be slightly more convenient when using Java 1.8 and lambda functions as demonstrated below.
import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.web.WebLoggerContextUtils;
public class TestAsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
final AsyncContext asyncContext = req.startAsync();
asyncContext.start(WebLoggerContextUtils.wrapExecutionContext(this.getServletContext(), () -> {
final Logger logger = LogManager.getLogger(TestAsyncServlet.class);
logger.info("Hello, servlet!");
}));
}
}
Alternatively, you can obtain the
Log4jWebLifeCycle
instance from the ServletContext
attributes, call its
setLoggerContext
method as the very first line of code in your
asynchronous thread, and call its clearLoggerContext
method as the
very last line of code in your asynchronous thread. The following code
demonstrates this. It uses the container thread pool to execute
asynchronous request processing, passing an anonymous inner Runnable
to the start
method.
import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.web.Log4jWebLifeCycle;
import org.apache.logging.log4j.web.WebLoggerContextUtils;
public class TestAsyncServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
final AsyncContext asyncContext = req.startAsync();
asyncContext.start(new Runnable() {
@Override
public void run() {
final Log4jWebLifeCycle webLifeCycle =
WebLoggerContextUtils.getWebLifeCycle(TestAsyncServlet.this.getServletContext());
webLifeCycle.setLoggerContext();
try {
final Logger logger = LogManager.getLogger(TestAsyncServlet.class);
logger.info("Hello, servlet!");
} finally {
webLifeCycle.clearLoggerContext();
}
}
});
}
}
Note that you must call clearLoggerContext
once your thread is
finished processing. Failing to do so will result in memory leaks. If
using a thread pool, it can even disrupt the logging of other web
applications in your container. For that reason, the example here shows
clearing the context in a finally
block, which will always execute.
Servlet Appender
Log4j Jakarta Web provides a Servlet Appender that uses the servlet context as the log target. For example:
<Configuration status="WARN" name="ServletTest">
<Appenders>
<Servlet name="Servlet">
<PatternLayout pattern="%m%n%ex{none}"/>
</Servlet>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Servlet"/>
</Root>
</Loggers>
</Configuration>
To avoid double logging of exceptions to the servlet context, you must
use %ex{none}
in your PatternLayout
as shown in the example. The
exception will be omitted from the message text but it is passed to the
servlet context as the actual Throwable
object.