1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.web;
18
19 import java.net.URI;
20 import java.net.URL;
21 import java.text.SimpleDateFormat;
22 import java.util.Arrays;
23 import java.util.Date;
24 import java.util.Map;
25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.concurrent.TimeUnit;
27
28 import javax.servlet.ServletContext;
29
30 import org.apache.logging.log4j.LogManager;
31 import org.apache.logging.log4j.core.AbstractLifeCycle;
32 import org.apache.logging.log4j.core.LoggerContext;
33 import org.apache.logging.log4j.core.async.AsyncLoggerContext;
34 import org.apache.logging.log4j.core.config.Configurator;
35 import org.apache.logging.log4j.core.impl.ContextAnchor;
36 import org.apache.logging.log4j.core.impl.Log4jContextFactory;
37 import org.apache.logging.log4j.core.lookup.Interpolator;
38 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
39 import org.apache.logging.log4j.core.selector.ContextSelector;
40 import org.apache.logging.log4j.core.selector.NamedContextSelector;
41 import org.apache.logging.log4j.core.util.Loader;
42 import org.apache.logging.log4j.core.util.NetUtils;
43 import org.apache.logging.log4j.core.util.SetUtils;
44 import org.apache.logging.log4j.spi.LoggerContextFactory;
45 import org.apache.logging.log4j.util.LoaderUtil;
46
47
48
49
50 final class Log4jWebInitializerImpl extends AbstractLifeCycle implements Log4jWebLifeCycle {
51
52 private static final String WEB_INF = "/WEB-INF/";
53
54 static {
55 if (Loader.isClassAvailable("org.apache.logging.log4j.core.web.JNDIContextFilter")) {
56 throw new IllegalStateException("You are using Log4j 2 in a web application with the old, extinct "
57 + "log4j-web artifact. This is not supported and could cause serious runtime problems. Please"
58 + "remove the log4j-web JAR file from your application.");
59 }
60 }
61
62 private final Map<String, String> map = new ConcurrentHashMap<>();
63 private final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator(map));
64 private final ServletContext servletContext;
65
66 private String name;
67 private NamedContextSelector namedContextSelector;
68 private LoggerContext loggerContext;
69
70 private Log4jWebInitializerImpl(final ServletContext servletContext) {
71 this.servletContext = servletContext;
72 this.map.put("hostName", NetUtils.getLocalHostname());
73 }
74
75
76
77
78
79
80
81
82
83
84
85 protected static Log4jWebInitializerImpl initialize(final ServletContext servletContext) {
86 final Log4jWebInitializerImpl initializer = new Log4jWebInitializerImpl(servletContext);
87 servletContext.setAttribute(SUPPORT_ATTRIBUTE, initializer);
88 return initializer;
89 }
90
91 @Override
92 public synchronized void start() {
93 if (this.isStopped() || this.isStopping()) {
94 throw new IllegalStateException("Cannot start this Log4jWebInitializerImpl after it was stopped.");
95 }
96
97
98 if (this.isInitialized()) {
99 super.setStarting();
100
101 this.name = this.substitutor.replace(this.servletContext.getInitParameter(LOG4J_CONTEXT_NAME));
102 final String location = this.substitutor.replace(this.servletContext
103 .getInitParameter(LOG4J_CONFIG_LOCATION));
104 final boolean isJndi = "true".equalsIgnoreCase(this.servletContext
105 .getInitParameter(IS_LOG4J_CONTEXT_SELECTOR_NAMED));
106
107 if (isJndi) {
108 this.initializeJndi(location);
109 } else {
110 this.initializeNonJndi(location);
111 }
112 if (this.loggerContext instanceof AsyncLoggerContext) {
113 ((AsyncLoggerContext) this.loggerContext).setUseThreadLocals(false);
114 }
115
116 this.servletContext.setAttribute(CONTEXT_ATTRIBUTE, this.loggerContext);
117 super.setStarted();
118 }
119 }
120
121 private void initializeJndi(final String location) {
122 final URI configLocation = getConfigURI(location);
123
124 if (this.name == null) {
125 throw new IllegalStateException("A log4jContextName context parameter is required");
126 }
127
128 LoggerContext context;
129 final LoggerContextFactory factory = LogManager.getFactory();
130 if (factory instanceof Log4jContextFactory) {
131 final ContextSelector selector = ((Log4jContextFactory) factory).getSelector();
132 if (selector instanceof NamedContextSelector) {
133 this.namedContextSelector = (NamedContextSelector) selector;
134 context = this.namedContextSelector.locateContext(this.name, this.servletContext, configLocation);
135 ContextAnchor.THREAD_CONTEXT.set(context);
136 if (context.isInitialized()) {
137 context.start();
138 }
139 ContextAnchor.THREAD_CONTEXT.remove();
140 } else {
141 LOGGER.warn("Potential problem: Selector is not an instance of NamedContextSelector.");
142 return;
143 }
144 } else {
145 LOGGER.warn("Potential problem: LoggerContextFactory is not an instance of Log4jContextFactory.");
146 return;
147 }
148 this.loggerContext = context;
149 LOGGER.debug("Created logger context for [{}] using [{}].", this.name, context.getClass().getClassLoader());
150 }
151
152 private void initializeNonJndi(final String location) {
153 if (this.name == null) {
154 this.name = this.servletContext.getServletContextName();
155 LOGGER.debug("Using the servlet context name \"{}\".", this.name);
156 }
157 if (this.name == null) {
158 this.name = this.servletContext.getContextPath();
159 LOGGER.debug("Using the servlet context context-path \"{}\".", this.name);
160 }
161
162 if (this.name == null && location == null) {
163 LOGGER.error("No Log4j context configuration provided. This is very unusual.");
164 this.name = new SimpleDateFormat("yyyyMMdd_HHmmss.SSS").format(new Date());
165 }
166
167 final URI uri = getConfigURI(location);
168 this.loggerContext = Configurator.initialize(this.name, this.getClassLoader(), uri, this.servletContext);
169 }
170
171 private URI getConfigURI(final String location) {
172 try {
173 String configLocation = location;
174 if (configLocation == null) {
175 final String[] paths = SetUtils.prefixSet(servletContext.getResourcePaths(WEB_INF), WEB_INF + "log4j2");
176 LOGGER.debug("getConfigURI found resource paths {} in servletContext at [{}]", Arrays.toString(paths), WEB_INF);
177 if (paths.length == 1) {
178 configLocation = paths[0];
179 } else if (paths.length > 1) {
180 final String prefix = WEB_INF + "log4j2-" + this.name + ".";
181 boolean found = false;
182 for (final String str : paths) {
183 if (str.startsWith(prefix)) {
184 configLocation = str;
185 found = true;
186 break;
187 }
188 }
189 if (!found) {
190 configLocation = paths[0];
191 }
192 }
193 }
194 if (configLocation != null) {
195 final URL url = servletContext.getResource(configLocation);
196 if (url != null) {
197 final URI uri = url.toURI();
198 LOGGER.debug("getConfigURI found resource [{}] in servletContext at [{}]", uri, configLocation);
199 return uri;
200 }
201 }
202 } catch (final Exception ex) {
203
204 }
205 if (location != null) {
206 try {
207 final URI correctedFilePathUri = NetUtils.toURI(location);
208 LOGGER.debug("getConfigURI found [{}] in servletContext at [{}]", correctedFilePathUri, location);
209 return correctedFilePathUri;
210 } catch (final Exception e) {
211 LOGGER.error("Unable to convert configuration location [{}] to a URI", location, e);
212 }
213 }
214 return null;
215 }
216
217 @Override
218 public synchronized boolean stop(final long timeout, final TimeUnit timeUnit) {
219 if (!this.isStarted() && !this.isStopped()) {
220 throw new IllegalStateException("Cannot stop this Log4jWebInitializer because it has not started.");
221 }
222
223
224 if (this.isStarted()) {
225 this.setStopping();
226 if (this.loggerContext != null) {
227 LOGGER.debug("Removing LoggerContext for [{}].", this.name);
228 this.servletContext.removeAttribute(CONTEXT_ATTRIBUTE);
229 if (this.namedContextSelector != null) {
230 this.namedContextSelector.removeContext(this.name);
231 }
232 this.loggerContext.stop(timeout, timeUnit);
233 this.loggerContext.setExternalContext(null);
234 this.loggerContext = null;
235 }
236 this.setStopped();
237 }
238 return super.stop(timeout, timeUnit);
239 }
240
241 @Override
242 public void setLoggerContext() {
243 if (this.loggerContext != null) {
244 ContextAnchor.THREAD_CONTEXT.set(this.loggerContext);
245 }
246 }
247
248 @Override
249 public void clearLoggerContext() {
250 ContextAnchor.THREAD_CONTEXT.remove();
251 }
252
253 @Override
254 public void wrapExecution(final Runnable runnable) {
255 this.setLoggerContext();
256
257 try {
258 runnable.run();
259 } finally {
260 this.clearLoggerContext();
261 }
262 }
263
264 private ClassLoader getClassLoader() {
265 try {
266
267
268
269 return this.servletContext.getClassLoader();
270 } catch (final Throwable ignore) {
271
272 return LoaderUtil.getThreadContextClassLoader();
273 }
274 }
275
276 }