Author: nickwilliams
Date: Wed Jan 29 06:17:08 2014
New Revision: 1562365
URL: http://svn.apache.org/r1562365
Log:
Fixing LOG4J2-452 and LOG4J2-512 (Part 2-Documentation): Significantly
improving web application documentation
Modified:
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/web/Log4jWebInitializerImpl.java
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/web/Log4jWebSupport.java
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/web/Log4jWebInitializerImplTest.java
logging/log4j/log4j2/trunk/src/changes/changes.xml
logging/log4j/log4j2/trunk/src/site/site.xml
logging/log4j/log4j2/trunk/src/site/xdoc/manual/webapp.xml
Modified:
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/web/Log4jWebInitializerImpl.java
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/web/Log4jWebInitializerImpl.java?rev=1562365&r1=1562364&r2=1562365&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/web/Log4jWebInitializerImpl.java
(original)
+++
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/web/Log4jWebInitializerImpl.java
Wed Jan 29 06:17:08 2014
@@ -79,8 +79,10 @@ final class Log4jWebInitializerImpl impl
this.initialized = true;
this.name =
this.substitutor.replace(this.servletContext.getInitParameter(LOG4J_CONTEXT_NAME));
- final String location =
this.substitutor.replace(this.servletContext.getInitParameter(LOG4J_CONFIG_LOCATION));
- final boolean isJndi =
"true".equals(this.servletContext.getInitParameter(IS_LOG4J_CONTEXT_SELECTOR_NAMED));
+ final String location =
+
this.substitutor.replace(this.servletContext.getInitParameter(LOG4J_CONFIG_LOCATION));
+ final boolean isJndi =
+
"true".equalsIgnoreCase(this.servletContext.getInitParameter(IS_LOG4J_CONTEXT_SELECTOR_NAMED));
if (isJndi) {
this.initializeJndi(location);
@@ -179,6 +181,17 @@ final class Log4jWebInitializerImpl impl
ContextAnchor.THREAD_CONTEXT.remove();
}
+ @Override
+ public void wrapExecution(Runnable runnable) {
+ this.setLoggerContext();
+
+ try {
+ runnable.run();
+ } finally {
+ this.clearLoggerContext();
+ }
+ }
+
private ClassLoader getClassLoader() {
try {
// if container is Servlet 3.0, use its getClassLoader method
Modified:
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/web/Log4jWebSupport.java
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/web/Log4jWebSupport.java?rev=1562365&r1=1562364&r2=1562365&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/web/Log4jWebSupport.java
(original)
+++
logging/log4j/log4j2/trunk/log4j-core/src/main/java/org/apache/logging/log4j/core/web/Log4jWebSupport.java
Wed Jan 29 06:17:08 2014
@@ -56,7 +56,15 @@ public interface Log4jWebSupport {
void setLoggerContext();
/**
- * Clears the logger context set up in {@link #setLoggerContext()}.
+ * Clears the logger context set up in {@link #setLoggerContext}.
*/
void clearLoggerContext();
+
+ /**
+ * Sets the logger context by calling {@link #setLoggerContext}, executes
the runnable argument, then clears the
+ * logger context by calling {@link #clearLoggerContext}.
+ *
+ * @param runnable The runnable to execute wrapped with a configured
logger context
+ */
+ void wrapExecution(Runnable runnable);
}
Modified:
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/web/Log4jWebInitializerImplTest.java
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/web/Log4jWebInitializerImplTest.java?rev=1562365&r1=1562364&r2=1562365&view=diff
==============================================================================
---
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/web/Log4jWebInitializerImplTest.java
(original)
+++
logging/log4j/log4j2/trunk/log4j-core/src/test/java/org/apache/logging/log4j/core/web/Log4jWebInitializerImplTest.java
Wed Jan 29 06:17:08 2014
@@ -22,6 +22,7 @@ import javax.servlet.UnavailableExceptio
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.impl.ContextAnchor;
import org.easymock.Capture;
+import org.easymock.IAnswer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -417,4 +418,74 @@ public class Log4jWebInitializerImplTest
assertNull("The context should finally still be null.",
ContextAnchor.THREAD_CONTEXT.get());
}
+
+ @Test
+ public void testWrapExecutionWithNoParameters() throws Exception {
+ Capture<Object> loggerContextCapture = new Capture<Object>();
+
+
expect(this.servletContext.getInitParameter(Log4jWebSupport.LOG4J_CONTEXT_NAME)).andReturn(null);
+
expect(this.servletContext.getInitParameter(Log4jWebSupport.LOG4J_CONFIG_LOCATION)).andReturn(null);
+
expect(this.servletContext.getInitParameter(Log4jWebSupport.IS_LOG4J_CONTEXT_SELECTOR_NAMED))
+ .andReturn(null);
+
expect(this.servletContext.getServletContextName()).andReturn("helloWorld01");
+
this.servletContext.setAttribute(eq(Log4jWebSupport.CONTEXT_ATTRIBUTE),
capture(loggerContextCapture));
+ expectLastCall();
+
+ replay(this.servletContext);
+
+ assertNull("The context should be null.",
ContextAnchor.THREAD_CONTEXT.get());
+
+ this.initializer.initialize();
+
+ assertNotNull("The context attribute should not be null.",
loggerContextCapture.getValue());
+ assertTrue("The context attribute is not correct.",
+ loggerContextCapture.getValue() instanceof
org.apache.logging.log4j.spi.LoggerContext);
+ final org.apache.logging.log4j.spi.LoggerContext loggerContext =
+
(org.apache.logging.log4j.spi.LoggerContext)loggerContextCapture.getValue();
+
+ verify(this.servletContext);
+ reset(this.servletContext);
+
+ assertNull("The context should still be null.",
ContextAnchor.THREAD_CONTEXT.get());
+
+ Runnable runnable = createStrictMock(Runnable.class);
+ runnable.run();
+ expectLastCall().andAnswer(new IAnswer<Void>() {
+ @Override
+ public Void answer() {
+ final LoggerContext context =
ContextAnchor.THREAD_CONTEXT.get();
+ assertNotNull("The context should not be null.", context);
+ assertSame("The context is not correct.", loggerContext,
context);
+ return null;
+ }
+ });
+
+ replay(this.servletContext, runnable);
+
+ this.initializer.wrapExecution(runnable);
+
+ assertNull("The context should be null again.",
ContextAnchor.THREAD_CONTEXT.get());
+
+ verify(this.servletContext, runnable);
+ reset(this.servletContext);
+
+ this.servletContext.log(anyObject(String.class));
+ expectLastCall();
+ this.servletContext.removeAttribute(Log4jWebSupport.CONTEXT_ATTRIBUTE);
+ expectLastCall();
+
+ replay(this.servletContext);
+
+ this.initializer.deinitialize();
+
+ verify(this.servletContext);
+ reset(this.servletContext);
+ replay(this.servletContext);
+
+ assertNull("The context should again still be null.",
ContextAnchor.THREAD_CONTEXT.get());
+
+ this.initializer.setLoggerContext();
+
+ assertNull("The context should finally still be null.",
ContextAnchor.THREAD_CONTEXT.get());
+ }
}
Modified: logging/log4j/log4j2/trunk/src/changes/changes.xml
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/src/changes/changes.xml?rev=1562365&r1=1562364&r2=1562365&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/src/changes/changes.xml (original)
+++ logging/log4j/log4j2/trunk/src/changes/changes.xml Wed Jan 29 06:17:08 2014
@@ -21,7 +21,7 @@
</properties>
<body>
<release version="2.0-RC1" date="2014-MM-DD" description="Bug fixes and
enhancements">
- <action issue="LOG4J2-452" dev="nickwilliams" type="fix" due-to="">
+ <action issue="LOG4J2-452" dev="nickwilliams" type="fix">
Added a ServletContext attribute that, when set to "true", disables
Log4j's auto-initialization in
Servlet 3.0+ web applications.
</action>
Modified: logging/log4j/log4j2/trunk/src/site/site.xml
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/src/site/site.xml?rev=1562365&r1=1562364&r2=1562365&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/src/site/site.xml (original)
+++ logging/log4j/log4j2/trunk/src/site/site.xml Wed Jan 29 06:17:08 2014
@@ -65,8 +65,9 @@
<item name="Servlet 3.0 and Newer"
href="/manual/webapp.html#Servlet-3.0" />
<item name="Servlet 2.5" href="/manual/webapp.html#Servlet-2.5" />
<item name="Context Parameters"
href="/manual/webapp.html#ContextParams" />
- <item name="Configuration" href="/manual/webapp.html#WebLookup" />
+ <item name="Configuration Lookups"
href="/manual/webapp.html#WebLookup" />
<item name="JavaServer Pages Logging"
href="/manual/webapp.html#JspLogging" />
+ <item name="Asynchronous Requests" href="/manual/webapp.html#Async" />
</item>
<item name="Plugins" href="/manual/plugins.html" collapse="true">
Modified: logging/log4j/log4j2/trunk/src/site/xdoc/manual/webapp.xml
URL:
http://svn.apache.org/viewvc/logging/log4j/log4j2/trunk/src/site/xdoc/manual/webapp.xml?rev=1562365&r1=1562364&r2=1562365&view=diff
==============================================================================
--- logging/log4j/log4j2/trunk/src/site/xdoc/manual/webapp.xml (original)
+++ logging/log4j/log4j2/trunk/src/site/xdoc/manual/webapp.xml Wed Jan 29
06:17:08 2014
@@ -67,12 +67,30 @@
and deinitialize the Log4j configuration.
</p>
<p>
+ For some users, automatically starting Log4j is problematic or
undesirable. You can easily disable this
+ feature using the <code>isLog4jAutoInitializationDisabled</code>
context parameter. Simply add it to your
+ deployment descriptor with the value "true" to disable
auto-initialization. You <em>must</em> define the
+ context parameter in <code>web.xml</code>. If you set in
programmatically, it will be too late for Log4j
+ to detect the setting.
+ <pre class="prettyprint linenums"><![CDATA[ <context-param>
+ <param-name>isLog4jAutoInitializationDisabled</param-name>
+ <param-value>true</param-value>
+ </context-param>]]></pre>
+ </p>
+ <p>
+ Once you disable auto-initialization, you must initialize Log4j as
you would a
+ <a href="#Servlet-2.5">Servlet 2.5 web application</a>. You must do
so in a way that this initialization
+ happens before any other application code (such as Spring Framework
startup code) executes.
+ </p>
+ <p>
You can customize the behavior of the listener and filter using the
<code>log4jContextName</code>,
<code>log4jConfiguration</code>, and/or
<code>isLog4jContextSelectorNamed</code> context parameters. Read more
about this in the <a href="#ContextParams">Context Parameters</a>
section below. You <em>must not</em>
manually configure the <code>Log4jServletContextListener</code> or
<code>Log4jServletFilter</code> in your
deployment descriptor (<code>web.xml</code>) or in another
initializer or listener in a Servlet 3.0 or newer
- application. Doing so will result in startup errors and unspecified
erroneous behavior.
+ application <em>unless you disable auto-initialization</em> with
+ <code>isLog4jAutoInitializationDisabled</code>. Doing so will result
in startup errors and unspecified
+ erroneous behavior.
</p>
</subsection>
<a name="Servlet-2.5" />
@@ -85,14 +103,15 @@
applications.
</p>
<p>
- If you are using Log4j in a Servlet 2.5 web application, you
<em>must</em> configure the
+ If you are using Log4j in a Servlet 2.5 web application, or if you
have disabled auto-initialization with
+ the <code>isLog4jAutoInitializationDisabled</code> context
parameter, you <em>must</em> configure the
<a
href="../log4j-core/apidocs/org/apache/logging/log4j/core/web/Log4jServletContextListener.html"
>Log4jServletContextListener</a> and
<a
href="../log4j-core/apidocs/org/apache/logging/log4j/core/web/Log4jServletFilter.html"
- >Log4jServletFilter</a> in the deployment descriptor. The filter
should match all requests of any type. The
- listener should be the very first listener defined in the deployment
descriptor, and the filter should be the
- very first filter defined and mapped in the deployment descriptor.
This is easily accomplished using the
- following <code>web.xml</code> code:
+ >Log4jServletFilter</a> 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 <code>web.xml</code> code:
<pre class="prettyprint linenums"><![CDATA[ <listener>
<listener-class>org.apache.logging.log4j.core.web.Log4jServletContextListener</listener-class>
</listener>
@@ -108,6 +127,7 @@
<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>]]></pre>
</p>
<p>
@@ -178,11 +198,11 @@
</p>
</subsection>
<a name="WebLookup" />
- <subsection name="Using Web Application information during the
configuration">
+ <subsection name="Using Web Application Information During the
Configuration">
<p>
- 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 <a href="./lookups.html#WebLookup">Lookups</a>
for more information.
+ 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
+ <a href="./lookups.html#WebLookup">Lookups</a> for more information.
</p>
</subsection>
<a name="JspLogging" />
@@ -208,6 +228,97 @@
property. You may need to do something similar on other containers
if they skip scanning Log4j JAR files.
</p>
</subsection>
+ <a name="Async" />
+ <subsection name="Asynchronous Requests and Threads">
+ <p>
+ 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 <code>Log4jServletFilter</code> binds the
<code>LoggerContext</code> to the thread handling
+ the request. After request processing completes, the filter unbinds
the <code>LoggerContext</code> from the
+ thread.
+ </p>
+ <p>
+ Similarly, when an internal request is dispatched using a
<code>javax.servlet.AsyncContext</code>, the
+ <code>Log4jServletFilter</code> also binds the
<code>LoggerContext</code> to the thread handling the request
+ and unbinds it when request processing completes. However, this only
happens for requests <em>dispatched</em>
+ through the <code>AsyncContext</code>. There are other asynchronous
activities that can take place other than
+ internal dispatched requests.
+ </p>
+ <p>
+ For example, after starting an <code>AsyncContext</code> you could
start up a separate thread to process the
+ request in the background, possibly writing the response with the
<code>ServletOutputStream</code>. 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?
+ </p>
+ <p>
+ You may not need to do anything. If you didn't use the
<code>isLog4jContextSelectorNamed</code> context
+ parameter, there is no need to bind the <code>LoggerContext</code>
to the thread. Log4j can safely locate the
+ <code>LoggerContext</code> on its own. In these cases, the filter
provides only very modest performance
+ gains, and only when creating new <code>Logger</code>s. However, if
you <em>did</em> specify the
+ <code>isLog4jContextSelectorNamed</code> context parameter with the
value "true", you will need to manually
+ bind the <code>LoggerContext</code> to asynchronous threads.
Otherwise, Log4j will not be able to locate it.
+ </p>
+ <p>
+ Thankfully, Log4j provides a simple mechanism for binding the
<code>LoggerContext</code> to asynchronous
+ threads in these special circumstances. The key is to obtain a
+ <a
href="../log4j-core/apidocs/org/apache/logging/log4j/core/web/Log4jWebSupport.html"
+ >Log4jWebSupport</a> instance from the
<code>ServletContext</code> attributes, call its
+ <code>setLoggerContext</code> method as the very first line of code
in your asynchronous thread, and call
+ its <code>clearLoggerContext</code> 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 <code>Runnable</code> to the
<code>start</code> method.
+ <pre class="prettyprint linenums"><![CDATA[public class AsyncServlet
extends HttpServlet
+{
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse
response)
+ throws ServletException, IOException
+ {
+ final AsyncContext context = request.startAsync(request, response);
+ context.setTimeout(timeout);
+ context.start(new Runnable() {
+ @Override
+ public void run() {
+ Log4jWebSupport support =
(Log4jWebSupport)this.getServletContext()
+ .getAttribute(Log4jWebSupport.SUPPORT_ATTRIBUTE);
+ support.setLoggerContext();
+
+ try {
+ // miscellaneous asynchronous request handling
+ } finally {
+ support.clearLoggerContext();
+ }
+ }
+ });
+ }
+}]]></pre>
+ </p>
+ <p>
+ Note that you <em>must</em> call <code>clearLoggerContext</code>
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 <code>finally</code> block, which will always execute.
As a shortcut, you can use the
+ <code>wrapExecution</code> method, which takes care of setting the
context, executing the code you pass in,
+ and clearing the context in a finally black. This is especially
convenient when using Java 8 lambdas.
+ <pre class="prettyprint linenums"><![CDATA[public class AsyncServlet
extends HttpServlet
+{
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse
response)
+ throws ServletException, IOException
+ {
+ final AsyncContext context = request.startAsync(request, response);
+ context.setTimeout(timeout);
+ context.start(() => {
+ Log4jWebSupport support = (Log4jWebSupport)this.getServletContext()
+ .getAttribute(Log4jWebSupport.SUPPORT_ATTRIBUTE);
+ support.wrapExecution(() => {
+ // miscellaneous asynchronous request handling
+ });
+ });
+ }
+}]]></pre>
+ </p>
+ </subsection>
</section>
</body>