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