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