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