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