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.jetty;
15  
16  import java.io.File;
17  import java.net.URL;
18  import java.util.HashMap;
19  import java.util.Iterator;
20  import java.util.List;
21  
22  import ch.qos.logback.core.status.InfoStatus;
23  import ch.qos.logback.core.util.FileUtil;
24  import ch.qos.logback.core.util.StatusPrinter;
25  
26  import org.eclipse.jetty.server.Request;
27  import org.eclipse.jetty.server.RequestLog;
28  import org.eclipse.jetty.server.Response;
29  
30  import ch.qos.logback.access.joran.JoranConfigurator;
31  import ch.qos.logback.access.spi.AccessEvent;
32  import ch.qos.logback.access.spi.IAccessEvent;
33  import ch.qos.logback.core.Appender;
34  import ch.qos.logback.core.ContextBase;
35  import ch.qos.logback.core.CoreConstants;
36  import ch.qos.logback.core.boolex.EventEvaluator;
37  import ch.qos.logback.core.filter.Filter;
38  import ch.qos.logback.core.joran.spi.JoranException;
39  import ch.qos.logback.core.spi.AppenderAttachable;
40  import ch.qos.logback.core.spi.AppenderAttachableImpl;
41  import ch.qos.logback.core.spi.FilterAttachable;
42  import ch.qos.logback.core.spi.FilterAttachableImpl;
43  import ch.qos.logback.core.spi.FilterReply;
44  import ch.qos.logback.core.status.ErrorStatus;
45  import ch.qos.logback.core.util.OptionHelper;
46  
47  /**
48   * This class is logback's implementation of jetty's RequestLog interface. <p>
49   * It can be seen as logback classic's LoggerContext. Appenders can be attached
50   * directly to RequestLogImpl and RequestLogImpl uses the same StatusManager as
51   * LoggerContext does. It also provides containers for properties. <p> To
52   * configure jetty in order to use RequestLogImpl, the following lines must be
53   * added to the jetty configuration file, namely <em>etc/jetty.xml</em>:
54   * <p/>
55   * <pre>
56   *    &lt;Ref id=&quot;requestLog&quot;&gt;
57   *      &lt;Set name=&quot;requestLog&quot;&gt;
58   *        &lt;New id=&quot;requestLogImpl&quot; class=&quot;ch.qos.logback.access.jetty.RequestLogImpl&quot;&gt;&lt;/New&gt;
59   *      &lt;/Set&gt;
60   *    &lt;/Ref&gt;
61   * </pre>
62   * <p/>
63   * By default, RequestLogImpl looks for a logback configuration file called
64   * logback-access.xml, in the same folder where jetty.xml is located, that is
65   * <em>etc/logback-access.xml</em>. The logback-access.xml file is slightly
66   * different than the usual logback classic configuration file. Most of it is
67   * the same: Appenders and Layouts are declared the exact same way. However,
68   * loggers elements are not allowed. <p> It is possible to put the logback
69   * configuration file anywhere, as long as it's path is specified. Here is
70   * another example, with a path to the logback-access.xml file.
71   * <p/>
72   * <pre>
73   *    &lt;Ref id=&quot;requestLog&quot;&gt;
74   *      &lt;Set name=&quot;requestLog&quot;&gt;
75   *        &lt;New id=&quot;requestLogImpl&quot; class=&quot;ch.qos.logback.access.jetty.RequestLogImpl&quot;&gt;&lt;/New&gt;
76   *          &lt;Set name=&quot;fileName&quot;&gt;path/to/logback.xml&lt;/Set&gt;
77   *      &lt;/Set&gt;
78   *    &lt;/Ref&gt;
79   * </pre>
80   * <p/>
81   * <p> Here is a sample logback-access.xml file that can be used right away:
82   * <p/>
83   * <pre>
84   *    &lt;configuration&gt;
85   *      &lt;appender name=&quot;STDOUT&quot; class=&quot;ch.qos.logback.core.ConsoleAppender&quot;&gt;
86   *        &lt;layout class=&quot;ch.qos.logback.access.PatternLayout&quot;&gt;
87   *          &lt;param name=&quot;Pattern&quot; value=&quot;%date %server %remoteIP %clientHost %user %requestURL&quot; /&gt;
88   *        &lt;/layout&gt;
89   *      &lt;/appender&gt;
90   *
91   *      &lt;appender-ref ref=&quot;STDOUT&quot; /&gt;
92   *    &lt;/configuration&gt;
93   * </pre>
94   * <p/>
95   * <p> Another configuration file, using SMTPAppender, could be:
96   * <p/>
97   * <pre>
98   *    &lt;configuration&gt;
99   *      &lt;appender name=&quot;SMTP&quot; class=&quot;ch.qos.logback.access.net.SMTPAppender&quot;&gt;
100  *        &lt;layout class=&quot;ch.qos.logback.access.PatternLayout&quot;&gt;
101  *          &lt;param name=&quot;pattern&quot; value=&quot;%remoteIP [%date] %requestURL %statusCode %bytesSent&quot; /&gt;
102  *        &lt;/layout&gt;
103  *        &lt;param name=&quot;From&quot; value=&quot;sender@domaine.org&quot; /&gt;
104  *        &lt;param name=&quot;SMTPHost&quot; value=&quot;mail.domain.org&quot; /&gt;
105  *         &lt;param name=&quot;Subject&quot; value=&quot;Last Event: %statusCode %requestURL&quot; /&gt;
106  *         &lt;param name=&quot;To&quot; value=&quot;server_admin@domain.org&quot; /&gt;
107  *      &lt;/appender&gt;
108  *      &lt;appender-ref ref=&quot;SMTP&quot; /&gt;
109  *    &lt;/configuration&gt;
110  * </pre>
111  *
112  * @author Ceki G&uuml;lc&uuml;
113  * @author S&eacute;bastien Pennec
114  */
115 public class RequestLogImpl extends ContextBase implements RequestLog, AppenderAttachable<IAccessEvent>, FilterAttachable<IAccessEvent> {
116 
117     public final static String DEFAULT_CONFIG_FILE = "etc" + File.separatorChar + "logback-access.xml";
118 
119     AppenderAttachableImpl<IAccessEvent> aai = new AppenderAttachableImpl<IAccessEvent>();
120     FilterAttachableImpl<IAccessEvent> fai = new FilterAttachableImpl<IAccessEvent>();
121     String fileName;
122     String resource;
123     boolean started = false;
124     boolean quiet = false;
125 
126     public RequestLogImpl() {
127         putObject(CoreConstants.EVALUATOR_MAP, new HashMap<String, EventEvaluator<?>>());
128     }
129 
130     @Override
131     public void log(Request jettyRequest, Response jettyResponse) {
132         JettyServerAdapter adapter = new JettyServerAdapter(jettyRequest, jettyResponse);
133         IAccessEvent accessEvent = new AccessEvent(jettyRequest, jettyResponse, adapter);
134         if (getFilterChainDecision(accessEvent) == FilterReply.DENY) {
135             return;
136         }
137         aai.appendLoopOnAppenders(accessEvent);
138     }
139 
140     private void addInfo(String msg) {
141         getStatusManager().add(new InfoStatus(msg, this));
142     }
143 
144     private void addError(String msg) {
145         getStatusManager().add(new ErrorStatus(msg, this));
146     }
147 
148     @Override
149     public void start() {
150         configure();
151         if (!isQuiet()) {
152             StatusPrinter.print(getStatusManager());
153         }
154         started = true;
155     }
156 
157     protected void configure() {
158         URL configURL = getConfigurationFileURL();
159         if (configURL != null) {
160             runJoranOnFile(configURL);
161         } else {
162             addError("Could not find configuration file for logback-access");
163         }
164     }
165 
166     protected URL getConfigurationFileURL() {
167         if (fileName != null) {
168             addInfo("Will use configuration file [" + fileName + "]");
169             File file = new File(fileName);
170             if (!file.exists())
171                 return null;
172             return FileUtil.fileToURL(file);
173         }
174         if (resource != null) {
175             addInfo("Will use configuration resource [" + resource + "]");
176             return this.getClass().getResource(resource);
177         }
178 
179         String jettyHomeProperty = OptionHelper.getSystemProperty("jetty.home");
180         String defaultConfigFile = DEFAULT_CONFIG_FILE;
181         if (!OptionHelper.isEmpty(jettyHomeProperty)) {
182             defaultConfigFile = jettyHomeProperty + File.separatorChar + DEFAULT_CONFIG_FILE;
183         } else {
184             addInfo("[jetty.home] system property not set.");
185         }
186         File file = new File(defaultConfigFile);
187         addInfo("Assuming default configuration file [" + defaultConfigFile + "]");
188         if (!file.exists())
189             return null;
190         return FileUtil.fileToURL(file);
191     }
192 
193     private void runJoranOnFile(URL configURL) {
194         try {
195             JoranConfigurator jc = new JoranConfigurator();
196             jc.setContext(this);
197             jc.doConfigure(configURL);
198             if (getName() == null) {
199                 setName("LogbackRequestLog");
200             }
201         } catch (JoranException e) {
202             // errors have been registered as status messages
203         }
204     }
205 
206     @Override
207     public void stop() {
208         aai.detachAndStopAllAppenders();
209         started = false;
210     }
211 
212     @Override
213     public boolean isRunning() {
214         return started;
215     }
216 
217     public void setFileName(String fileName) {
218         this.fileName = fileName;
219     }
220 
221     public void setResource(String resource) {
222         this.resource = resource;
223     }
224 
225     @Override
226     public boolean isStarted() {
227         return started;
228     }
229 
230     @Override
231     public boolean isStarting() {
232         return false;
233     }
234 
235     @Override
236     public boolean isStopping() {
237         return false;
238     }
239 
240     @Override
241     public boolean isStopped() {
242         return !started;
243     }
244 
245     @Override
246     public boolean isFailed() {
247         return false;
248     }
249 
250     public boolean isQuiet() {
251         return quiet;
252     }
253 
254     public void setQuiet(boolean quiet) {
255         this.quiet = quiet;
256     }
257 
258     @Override
259     public void addAppender(Appender<IAccessEvent> newAppender) {
260         aai.addAppender(newAppender);
261     }
262 
263     @Override
264     public Iterator<Appender<IAccessEvent>> iteratorForAppenders() {
265         return aai.iteratorForAppenders();
266     }
267 
268     @Override
269     public Appender<IAccessEvent> getAppender(String name) {
270         return aai.getAppender(name);
271     }
272 
273     @Override
274     public boolean isAttached(Appender<IAccessEvent> appender) {
275         return aai.isAttached(appender);
276     }
277 
278     @Override
279     public void detachAndStopAllAppenders() {
280         aai.detachAndStopAllAppenders();
281     }
282 
283     @Override
284     public boolean detachAppender(Appender<IAccessEvent> appender) {
285         return aai.detachAppender(appender);
286     }
287 
288     @Override
289     public boolean detachAppender(String name) {
290         return aai.detachAppender(name);
291     }
292 
293     @Override
294     public void addFilter(Filter<IAccessEvent> newFilter) {
295         fai.addFilter(newFilter);
296     }
297 
298     @Override
299     public void clearAllFilters() {
300         fai.clearAllFilters();
301     }
302 
303     @Override
304     public List<Filter<IAccessEvent>> getCopyOfAttachedFiltersList() {
305         return fai.getCopyOfAttachedFiltersList();
306     }
307 
308     @Override
309     public FilterReply getFilterChainDecision(IAccessEvent event) {
310         return fai.getFilterChainDecision(event);
311     }
312 
313     @Override
314     public void addLifeCycleListener(Listener listener) {
315         // we'll implement this when asked
316     }
317 
318     @Override
319     public void removeLifeCycleListener(Listener listener) {
320         // we'll implement this when asked
321     }
322 
323 }