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