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