1
2
3
4
5
6
7
8
9
10
11
12
13
14 package ch.qos.logback.access.tomcat;
15
16 import java.io.File;
17 import java.io.IOException;
18 import java.net.MalformedURLException;
19 import java.net.URL;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.concurrent.ExecutorService;
26 import java.util.concurrent.ScheduledExecutorService;
27 import java.util.concurrent.ScheduledFuture;
28 import java.util.concurrent.locks.ReentrantLock;
29
30 import ch.qos.logback.access.common.AccessConstants;
31 import ch.qos.logback.access.common.joran.JoranConfigurator;
32 import ch.qos.logback.access.common.spi.AccessEvent;
33 import ch.qos.logback.access.common.spi.IAccessEvent;
34 import ch.qos.logback.access.common.util.AccessCommonVersionUtil;
35 import ch.qos.logback.core.spi.ConfigurationEvent;
36 import ch.qos.logback.core.spi.ConfigurationEventListener;
37 import ch.qos.logback.core.util.CoreVersionUtil;
38 import ch.qos.logback.core.util.VersionUtil;
39 import jakarta.servlet.ServletContext;
40 import jakarta.servlet.ServletException;
41
42 import org.apache.catalina.Lifecycle;
43 import org.apache.catalina.LifecycleException;
44 import org.apache.catalina.LifecycleListener;
45 import org.apache.catalina.LifecycleState;
46 import org.apache.catalina.connector.Request;
47 import org.apache.catalina.connector.Response;
48 import org.apache.catalina.valves.ValveBase;
49
50 import ch.qos.logback.core.Appender;
51 import ch.qos.logback.core.BasicStatusManager;
52 import ch.qos.logback.core.Context;
53 import ch.qos.logback.core.CoreConstants;
54 import ch.qos.logback.core.LifeCycleManager;
55 import ch.qos.logback.core.boolex.EventEvaluator;
56 import ch.qos.logback.core.filter.Filter;
57 import ch.qos.logback.core.joran.spi.JoranException;
58 import ch.qos.logback.core.spi.AppenderAttachable;
59 import ch.qos.logback.core.spi.AppenderAttachableImpl;
60 import ch.qos.logback.core.spi.FilterAttachable;
61 import ch.qos.logback.core.spi.FilterAttachableImpl;
62 import ch.qos.logback.core.spi.FilterReply;
63 import ch.qos.logback.core.spi.LifeCycle;
64 import ch.qos.logback.core.spi.LogbackLock;
65 import ch.qos.logback.core.spi.SequenceNumberGenerator;
66 import ch.qos.logback.core.status.ErrorStatus;
67 import ch.qos.logback.core.status.InfoStatus;
68 import ch.qos.logback.core.status.OnConsoleStatusListener;
69 import ch.qos.logback.core.status.Status;
70 import ch.qos.logback.core.status.StatusManager;
71 import ch.qos.logback.core.status.WarnStatus;
72 import ch.qos.logback.core.util.ExecutorServiceUtil;
73 import ch.qos.logback.core.util.Loader;
74 import ch.qos.logback.core.util.OptionHelper;
75 import ch.qos.logback.core.util.StatusListenerConfigHelper;
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 public class LogbackValve extends ValveBase
93 implements Lifecycle, Context, AppenderAttachable<IAccessEvent>, FilterAttachable<IAccessEvent> {
94
95 public final static String DEFAULT_FILENAME = "logback-access.xml";
96 public final static String DEFAULT_CONFIG_FILE = "conf" + File.separatorChar + DEFAULT_FILENAME;
97 final static String CATALINA_BASE_KEY = "catalina.base";
98 final static String CATALINA_HOME_KEY = "catalina.home";
99
100 private final LifeCycleManager lifeCycleManager = new LifeCycleManager();
101
102 private long birthTime = System.currentTimeMillis();
103
104 ReentrantLock configurationLock = new ReentrantLock();
105
106 final private List<ConfigurationEventListener> configurationEventListenerList = new ArrayList<>();
107
108
109 private String name;
110 StatusManager sm = new BasicStatusManager();
111
112
113
114 Map<String, String> propertyMap = new HashMap<String, String>();
115 Map<String, Object> objectMap = new HashMap<String, Object>();
116 private FilterAttachableImpl<IAccessEvent> fai = new FilterAttachableImpl<IAccessEvent>();
117
118 AppenderAttachableImpl<IAccessEvent> aai = new AppenderAttachableImpl<IAccessEvent>();
119 String filenameOption;
120 boolean quiet;
121 boolean started;
122 boolean alreadySetLogbackStatusManager = false;
123 private SequenceNumberGenerator sequenceNumberGenerator;
124
125 private ScheduledExecutorService scheduledExecutorService;
126
127 public LogbackValve() {
128 putObject(CoreConstants.EVALUATOR_MAP, new HashMap<String, EventEvaluator<?>>());
129 }
130
131 public boolean isStarted() {
132 return started;
133 }
134
135 @Override
136 public void startInternal() throws LifecycleException {
137
138 versionCheck();
139
140 scheduledExecutorService = ExecutorServiceUtil.newScheduledExecutorService();
141
142 String filename;
143
144 if (filenameOption != null) {
145 filename = filenameOption;
146 } else {
147 addInfo("filename property not set. Assuming [" + DEFAULT_CONFIG_FILE + "]");
148 filename = DEFAULT_CONFIG_FILE;
149 }
150
151
152
153
154 File configFile = searchForConfigFileTomcatProperty(filename, CATALINA_BASE_KEY);
155 if (configFile == null) {
156 configFile = searchForConfigFileTomcatProperty(filename, CATALINA_HOME_KEY);
157 }
158
159 URL resourceURL;
160 if (configFile != null)
161 resourceURL = fileToUrl(configFile);
162 else
163 resourceURL = searchAsResource(filename);
164
165 if (resourceURL != null) {
166 configureAsResource(resourceURL);
167 } else {
168 addWarn("Failed to find valid logback-access configuration file.");
169 }
170
171 if (!quiet) {
172 StatusListenerConfigHelper.addOnConsoleListenerInstance(this, new OnConsoleStatusListener());
173 }
174
175 started = true;
176 setState(LifecycleState.STARTING);
177 }
178
179 static String LOGBACK_ACCESS_TOMCAT_NAME = "logback-access-tomcat";
180 static String LOGBACK_ACCESS_COMMON_NAME = "logback-access-common";
181 static String LOGBACK_CORE_NAME = "logback-core";
182
183 private void versionCheck() {
184 try {
185 String coreVersion = CoreVersionUtil.getCoreVersionBySelfDeclaredProperties();
186 String accessCommonVersion = AccessCommonVersionUtil.getAccessCommonVersionBySelfDeclaredProperties();
187 VersionUtil.checkForVersionEquality(this, this.getClass(), accessCommonVersion, LOGBACK_ACCESS_TOMCAT_NAME, LOGBACK_ACCESS_COMMON_NAME);
188 VersionUtil.compareExpectedAndFoundVersion(this, coreVersion, AccessConstants.class, accessCommonVersion, LOGBACK_ACCESS_COMMON_NAME, LOGBACK_CORE_NAME);
189 } catch(NoClassDefFoundError e) {
190 addWarn("Missing ch.logback.core.util.VersionUtil class on classpath. The version of logback-core is probably earlier than 1.5.25.");
191 } catch(NoSuchMethodError e) {
192 addWarn(e.getMessage() + ". The version of logback-core is probably earlier than 1.5.26.");
193 }
194 }
195
196 private URL fileToUrl(File configFile) {
197 try {
198 return configFile.toURI().toURL();
199 } catch (MalformedURLException e) {
200 throw new IllegalStateException("File to URL conversion failed", e);
201 }
202 }
203
204 private URL searchAsResource(String filename) {
205 URL result = Loader.getResource(filename, getClass().getClassLoader());
206 if (result != null)
207 addInfo("Found [" + filename + "] as a resource.");
208 else
209 addInfo("Could NOT find [" + filename + "] as a resource.");
210 return result;
211 }
212
213 private File searchForConfigFileTomcatProperty(String filename, String propertyKey) {
214 String propertyValue = OptionHelper.getSystemProperty(propertyKey);
215 String candidatePath = propertyValue + File.separatorChar + filename;
216 if (propertyValue == null) {
217 addInfo("System property \"" + propertyKey + "\" is not set. Skipping configuration file search with ${"
218 + propertyKey + "} path prefix.");
219 return null;
220 }
221 File candidateFile = new File(candidatePath);
222 if (candidateFile.exists()) {
223 addInfo("Found configuration file [" + candidatePath + "] using property \"" + propertyKey + "\"");
224 return candidateFile;
225 } else {
226 addInfo("Could NOT find configuration file [" + candidatePath + "] using property \"" + propertyKey + "\"");
227 return null;
228 }
229 }
230
231 public void addStatus(Status status) {
232 StatusManager sm = getStatusManager();
233 if (sm != null) {
234 sm.add(status);
235 }
236 }
237
238 public void addInfo(String msg) {
239 addStatus(new InfoStatus(msg, this));
240 }
241
242 public void addWarn(String msg) {
243 addStatus(new WarnStatus(msg, this));
244 }
245
246 public void addError(String msg, Throwable t) {
247 addStatus(new ErrorStatus(msg, this, t));
248 }
249
250 private void configureAsResource(URL resourceURL) {
251 try {
252 JoranConfigurator jc = new JoranConfigurator();
253 jc.setContext(this);
254 jc.doConfigure(resourceURL);
255 addInfo("Done configuring");
256 } catch (JoranException e) {
257 addError("Failed to configure LogbackValve", e);
258 }
259 }
260
261 public String getFilename() {
262 return filenameOption;
263 }
264
265 public void setFilename(String filename) {
266 this.filenameOption = filename;
267 }
268
269 public boolean isQuiet() {
270 return quiet;
271 }
272
273 public void setQuiet(boolean quiet) {
274 this.quiet = quiet;
275 }
276
277 @Override
278 public void invoke(Request request, Response response) throws IOException, ServletException {
279 try {
280 if (!alreadySetLogbackStatusManager) {
281 alreadySetLogbackStatusManager = true;
282 org.apache.catalina.Context tomcatContext = request.getContext();
283 if (tomcatContext != null) {
284 ServletContext sc = tomcatContext.getServletContext();
285 if (sc != null) {
286 sc.setAttribute(AccessConstants.LOGBACK_STATUS_MANAGER_KEY, getStatusManager());
287 }
288 }
289 }
290
291 getNext().invoke(request, response);
292
293 TomcatServerAdapter adapter = new TomcatServerAdapter(request, response);
294 IAccessEvent accessEvent = new AccessEvent(this, request, response, adapter);
295
296 addThreadName(accessEvent);
297
298 if (getFilterChainDecision(accessEvent) == FilterReply.DENY) {
299 return;
300 }
301
302
303 aai.appendLoopOnAppenders(accessEvent);
304 } finally {
305 request.removeAttribute(AccessConstants.LOGBACK_STATUS_MANAGER_KEY);
306 }
307 }
308
309 private void addThreadName(IAccessEvent accessEvent) {
310 try {
311 final String threadName = Thread.currentThread().getName();
312 if (threadName != null) {
313 accessEvent.setThreadName(threadName);
314 }
315 } catch (Exception ignored) {
316 }
317 }
318
319 @Override
320 protected void stopInternal() throws LifecycleException {
321 started = false;
322 setState(LifecycleState.STOPPING);
323 lifeCycleManager.reset();
324 if (scheduledExecutorService != null) {
325 ExecutorServiceUtil.shutdown(scheduledExecutorService);
326 scheduledExecutorService = null;
327 }
328 }
329
330 @Override
331 public void addAppender(Appender<IAccessEvent> newAppender) {
332 aai.addAppender(newAppender);
333 }
334
335 @Override
336 public Iterator<Appender<IAccessEvent>> iteratorForAppenders() {
337 return aai.iteratorForAppenders();
338 }
339
340 @Override
341 public Appender<IAccessEvent> getAppender(String name) {
342 return aai.getAppender(name);
343 }
344
345 @Override
346 public boolean isAttached(Appender<IAccessEvent> appender) {
347 return aai.isAttached(appender);
348 }
349
350 @Override
351 public void detachAndStopAllAppenders() {
352 aai.detachAndStopAllAppenders();
353
354 }
355
356 @Override
357 public boolean detachAppender(Appender<IAccessEvent> appender) {
358 return aai.detachAppender(appender);
359 }
360
361 @Override
362 public boolean detachAppender(String name) {
363 return aai.detachAppender(name);
364 }
365
366 public String getInfo() {
367 return "Logback's implementation of ValveBase";
368 }
369
370
371 @Override
372 public StatusManager getStatusManager() {
373 return sm;
374 }
375
376 public Map<String, String> getPropertyMap() {
377 return propertyMap;
378 }
379
380 @Override
381 public void addSubstitutionProperty(String key, String value) {
382 this.propertyMap.put(key, value);
383 }
384
385 @Override
386 public void putProperty(String key, String val) {
387 this.propertyMap.put(key, val);
388 }
389
390 @Override
391 public String getProperty(String key) {
392 return (String) this.propertyMap.get(key);
393 }
394
395 @Override
396 public Map<String, String> getCopyOfPropertyMap() {
397 return new HashMap<String, String>(this.propertyMap);
398 }
399
400 @Override
401 public Object getObject(String key) {
402 return objectMap.get(key);
403 }
404
405 @Override
406 public void putObject(String key, Object value) {
407 objectMap.put(key, value);
408 }
409
410 @Override
411 public void addFilter(Filter<IAccessEvent> newFilter) {
412 fai.addFilter(newFilter);
413 }
414
415 @Override
416 public void clearAllFilters() {
417 fai.clearAllFilters();
418 }
419
420 @Override
421 public List<Filter<IAccessEvent>> getCopyOfAttachedFiltersList() {
422 return fai.getCopyOfAttachedFiltersList();
423 }
424
425 @Override
426 public FilterReply getFilterChainDecision(IAccessEvent event) {
427 return fai.getFilterChainDecision(event);
428 }
429
430 @Override
431 public ExecutorService getExecutorService() {
432 return getScheduledExecutorService();
433 }
434
435 @Override
436 public String getName() {
437 return name;
438 }
439
440 @Override
441 public void setName(String name) {
442 if (this.name != null) {
443 throw new IllegalStateException("LogbackValve has been already given a name");
444 }
445 this.name = name;
446 }
447
448 @Override
449 public long getBirthTime() {
450 return birthTime;
451 }
452
453 @Override
454 public ReentrantLock getConfigurationLock() {
455 return configurationLock;
456 }
457
458 @Override
459 public void register(LifeCycle component) {
460 lifeCycleManager.register(component);
461 }
462
463
464
465 @Override
466 public void addLifecycleListener(LifecycleListener arg0) {
467
468 }
469
470 @Override
471 public LifecycleListener[] findLifecycleListeners() {
472 return new LifecycleListener[0];
473 }
474
475 @Override
476 public void removeLifecycleListener(LifecycleListener arg0) {
477
478 }
479
480 @Override
481 public String toString() {
482 StringBuilder sb = new StringBuilder(this.getClass().getName());
483 sb.append('[');
484 sb.append(getName());
485 sb.append(']');
486 return sb.toString();
487 }
488
489 @Override
490 public ScheduledExecutorService getScheduledExecutorService() {
491 return scheduledExecutorService;
492 }
493
494 @Override
495 public void addScheduledFuture(ScheduledFuture<?> scheduledFuture) {
496 throw new UnsupportedOperationException();
497 }
498
499 public SequenceNumberGenerator getSequenceNumberGenerator() {
500 return sequenceNumberGenerator;
501 }
502
503 public void setSequenceNumberGenerator(SequenceNumberGenerator sequenceNumberGenerator) {
504 this.sequenceNumberGenerator = sequenceNumberGenerator;
505 }
506
507
508 @Override
509 public void addConfigurationEventListener(ConfigurationEventListener listener) {
510 configurationEventListenerList.add(listener);
511 }
512
513 @Override
514 public void fireConfigurationEvent(ConfigurationEvent configurationEvent) {
515 configurationEventListenerList.forEach( l -> l.listen(configurationEvent));
516 }
517 }