blob: e11159f51abb1ff49196f873a6e682bb44f2f643 [file] [log] [blame]
////
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
////
= Web Applications
Nick Williams <nickwilliams@apache.org>; Matt Sicker <mattsicker@apache.org>
== Using Log4j 2 in Web Applications
You must take particular care when using Log4j or any other logging
framework within a Java EE web application. It's important for logging
resources to be properly cleaned up (database connections closed, files
closed, etc.) when the container shuts down or the web application is
undeployed. Because of the nature of class loaders within web
applications, Log4j resources cannot be cleaned up through normal means.
Log4j must be "started" when the web application deploys and "shut down"
when the web application undeploys. How this works varies depending on
whether your application is a link:#Servlet-3.0[Servlet 3.0 or newer] or
link:#Servlet-2.5[Servlet 2.5] web application.
In either case, you'll need to add the `log4j-web` module to your
deployment as detailed in the link:../maven-artifacts.html[Maven, Ivy,
and Gradle Artifacts] manual page.
NOTE: To avoid problems the Log4j shutdown hook will automatically be
disabled when the log4j-web jar is included.
[#Configuration]
== Configuration
Log4j allows the configuration file to be specified in web.xml using the
`log4jConfiguration` context parameter. Log4j will search for
configuration files by:
1. If a location is provided it will be searched for as a servlet
context resource. For example, if `log4jConfiguration` contains
"logging.xml" then Log4j will look for a file with that name in the root
directory of the web application.
2. If no location is defined Log4j will search for a file that starts
with "log4j2" in the WEB-INF directory. If more than one file is found,
and if a file that starts with "log4j2-name" is present, where name is
the name of the web application, then it will be used. Otherwise the
first file will be used.
3. The "normal" search sequence using the classpath and file URLs will
be used to locate the configuration file.
[#Servlet-3.0]
== Servlet 3.0 and Newer Web Applications
A Servlet 3.0 or newer web application is any `<web-app>` whose
`version` attribute has a value of "3.0" or higher. Of course, the
application must also be running in a compatible web container.
Some examples are:
- Tomcat 7.0 and higher
- GlassFish 3.0 and higher
- JBoss 7.0 and higher
- Oracle WebLogic 12c and higher
- IBM WebSphere 8.0 and higher.
=== The Short Story
Log4j 2 "just works" in Servlet 3.0 and newer web applications. It is
capable of automatically starting when the application deploys and
shutting down when the application undeploys. Thanks to the
https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContainerInitializer.html[`ServletContainerInitializer`]
API added to Servlet 3.0, the relevant `Filter` and
`ServletContextListener` classes can be registered dynamically on web
application startup.
NOTE: For performance reasons, containers often ignore
certain JARs known not to contain TLDs or `ServletContainerInitializer`
and do not scan them for web-fragments and initializers. Importantly,
Tomcat 7 <7.0.43 ignores all JAR files named log4j*.jar, which prevents
this feature from working. This has been fixed in Tomcat 7.0.43, Tomcat
8, and later. In Tomcat 7 <7.0.43 you will need to change
`catalina.properties` and remove "log4j*.jar" from the `jarsToSkip`
property. You may need to do something similar on other containers if
they skip scanning Log4j JAR files.
=== The Long Story
The Log4j 2 Web JAR file is a web-fragment configured to order before
any other web fragments in your application. It contains a
`ServletContainerInitializer`
(link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jServletContainerInitializer
.html[`Log4jServletContainerInitializer`])
that the container automatically discovers and initializes. This adds
the
link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jServletContextListener.html[`Log4jServletContextListener`]
and
link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jServletFilter.html[`Log4jServletFilter`]
to the `ServletContext`. These classes properly initialize and
deinitialize the Log4j configuration.
For some users, automatically starting Log4j is problematic or
undesirable. You can easily disable this feature using the
`isLog4jAutoInitializationDisabled` context parameter. Simply add it to
your deployment descriptor with the value "true" to disable
auto-initialization. You _must_ define the context parameter in
`web.xml`. If you set in programmatically, it will be too late for Log4j
to detect the setting.
[source,xml]
----
<context-param>
<param-name>isLog4jAutoInitializationDisabled</param-name>
<param-value>true</param-value>
</context-param>
----
Once you disable auto-initialization, you must initialize Log4j as you
would a link:#Servlet-2.5[Servlet 2.5 web application]. You must do so
in a way that this initialization happens before any other application
code (such as Spring Framework startup code) executes.
You can customize the behavior of the listener and filter using the
`log4jContextName`, `log4jConfiguration`, and/or
`isLog4jContextSelectorNamed` context parameters. Read more about this
in the link:#ContextParams[Context Parameters] section below. You _must
not_ manually configure the `Log4jServletContextListener` or
`Log4jServletFilter` in your deployment descriptor (`web.xml`) or in
another initializer or listener in a Servlet 3.0 or newer application
_unless you disable auto-initialization_ with
`isLog4jAutoInitializationDisabled`. Doing so will result in startup
errors and unspecified erroneous behavior.
[#Servlet-2.5]
== Servlet 2.5 Web Applications
A Servlet 2.5 web application is any `<web-app>` whose `version`
attribute has a value of "2.5." The `version` attribute is the only
thing that matters; even if the web application is running in a Servlet
3.0 or newer container, it is a Servlet 2.5 web application if the
`version` attribute is "2.5." Note that Log4j 2 does not support Servlet
2.4 and older web applications.
If you are using Log4j in a Servlet 2.5 web application, or if you have
disabled auto-initialization with the
`isLog4jAutoInitializationDisabled` context parameter, you _must_
configure the
link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jServletContextListener.html[`Log4jServletContextListener`]
and
link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jServletFilter.html[`Log4jServletFilter`]
in the deployment descriptor or programmatically. The filter should
match all requests of any type. The listener should be the very first
listener defined in your application, and the filter should be the very
first filter defined and mapped in your application. This is easily
accomplished using the following `web.xml` code:
[source,xml]
----
<listener>
<listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
</listener>
<filter>
<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>
<dispatcher>ASYNC</dispatcher><!-- Servlet 3.0 w/ disabled auto-initialization only; not supported in 2.5 -->
</filter-mapping>
----
You can customize the behavior of the listener and filter using the
`log4jContextName`, `log4jConfiguration`, and/or
`isLog4jContextSelectorNamed` context parameters. Read more about this
in the link:#ContextParams[Context Parameters] section below.
[#ContextParams]
== Context Parameters
By default, Log4j 2 uses the `ServletContext`'s
https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContext.html#getServletContextName()[context
name] as the `LoggerContext` name and uses the standard pattern for
locating the Log4j configuration file. There are three context
parameters that you can use to control this behavior. The first,
`isLog4jContextSelectorNamed`, specifies whether the context should be
selected using the
link:../log4j-core/apidocs/org/apache/logging/log4j/core/selector/JndiContextSelector.html[`JndiContextSelector`].
If `isLog4jContextSelectorNamed` is not specified or is anything other
than `true`, it is assumed to be `false`.
If `isLog4jContextSelectorNamed` is `true`, `log4jContextName` must be
specified or `display-name` must be specified in `web.xml`; otherwise,
the application will fail to start with an exception.
`log4jConfiguration` _should_ also be specified in this case, and must
be a valid URI for the configuration file; however, this parameter is
not required.
If `isLog4jContextSelectorNamed` is not `true`, `log4jConfiguration` may
optionally be specified and must be a valid URI or path to a
configuration file or start with "classpath:" to denote a configuration
file that can be found on the classpath. Without this parameter, Log4j
will use the standard mechanisms for locating the configuration file.
When specifying these context parameters, you must specify them in the
deployment descriptor (`web.xml`) even in a Servlet 3.0 or never
application. If you add them to the `ServletContext` within a listener,
Log4j will initialize before the context parameters are available and
they will have no effect. Here are some sample uses of these context
parameters.
=== Set the Logging Context Name to "myApplication"
[source,xml]
----
<context-param>
<param-name>log4jContextName</param-name>
<param-value>myApplication</param-value>
</context-param>
----
=== Set the Configuration Path/File/URI to "/etc/myApp/myLogging.xml"
[source,xml]
----
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>file:///etc/myApp/myLogging.xml</param-value>
</context-param>
----
=== Use the `JndiContextSelector`
[source,xml]
----
<context-param>
<param-name>isLog4jContextSelectorNamed</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>log4jContextName</param-name>
<param-value>appWithJndiSelector</param-value>
</context-param>
<context-param>
<param-name>log4jConfiguration</param-name>
<param-value>file:///D:/conf/myLogging.xml</param-value>
</context-param>
----
Note that in this case you must also set the "Log4jContextSelector"
system property to
"org.apache.logging.log4j.core.selector.JndiContextSelector".
[#WebLookup]
== Using Web Application Information During the Configuration
You may want to use information about the web application during
configuration. For example, you could embed the web application's
context path in the name of a Rolling File Appender. See WebLookup in
link:./lookups.html#WebLookup[Lookups] for more information.
[#JspLogging]
== JavaServer Pages Logging
You may use Log4j 2 within JSPs just as you would within any other Java
code. Simple obtain a `Logger` and call its methods to log events.
However, this requires you to use Java code within your JSPs, and some
development teams rightly are not comfortable with doing this. If you
have a dedicated user interface development team that is not familiar
with using Java, you may even have Java code disabled in your JSPs.
For this reason, Log4j 2 provides a JSP Tag Library that enables you to
log events without using any Java code. To read more about using this
tag library, link:../log4j-taglib/index.html[read the Log4j Tag Library
documentation.]
NOTE: As noted above, containers often ignore certain
JARs known not to contain TLDs and do not scan them for TLD files.
Importantly, Tomcat 7 <7.0.43 ignores all JAR files named log4j*.jar,
which prevents the JSP tag library from being automatically discovered.
This does not affect Tomcat 6.x and has been fixed in Tomcat 7.0.43,
Tomcat 8, and later. In Tomcat 7 <7.0.43 you will need to change
`catalina.properties` and remove "log4j*.jar" from the `jarsToSkip`
property. You may need to do something similar on other containers if
they skip scanning Log4j JAR files.
[#Async]
== 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.
[source,java]
----
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.
[source,java]
----
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
link:../log4j-web/apidocs/org/apache/logging/log4j/web/Log4jWebLifeCycle.html[`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.
[source,java]
----
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.
== Using the Servlet Appender
Log4j provides a Servlet Appender that uses the servlet context as the
log target. For example:
[source,xml]
----
<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.