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