001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.access.jetty;
015
016import java.io.File;
017import java.net.URL;
018import java.util.HashMap;
019import java.util.Iterator;
020import java.util.List;
021
022import ch.qos.logback.core.status.InfoStatus;
023import ch.qos.logback.core.util.FileUtil;
024import ch.qos.logback.core.util.StatusPrinter;
025
026import org.eclipse.jetty.server.Request;
027import org.eclipse.jetty.server.RequestLog;
028import org.eclipse.jetty.server.Response;
029
030import ch.qos.logback.access.joran.JoranConfigurator;
031import ch.qos.logback.access.spi.AccessEvent;
032import ch.qos.logback.access.spi.IAccessEvent;
033import ch.qos.logback.core.Appender;
034import ch.qos.logback.core.ContextBase;
035import ch.qos.logback.core.CoreConstants;
036import ch.qos.logback.core.boolex.EventEvaluator;
037import ch.qos.logback.core.filter.Filter;
038import ch.qos.logback.core.joran.spi.JoranException;
039import ch.qos.logback.core.spi.AppenderAttachable;
040import ch.qos.logback.core.spi.AppenderAttachableImpl;
041import ch.qos.logback.core.spi.FilterAttachable;
042import ch.qos.logback.core.spi.FilterAttachableImpl;
043import ch.qos.logback.core.spi.FilterReply;
044import ch.qos.logback.core.status.ErrorStatus;
045import ch.qos.logback.core.util.OptionHelper;
046
047/**
048 * This class is logback's implementation of jetty's RequestLog interface. <p>
049 * It can be seen as logback classic's LoggerContext. Appenders can be attached
050 * directly to RequestLogImpl and RequestLogImpl uses the same StatusManager as
051 * LoggerContext does. It also provides containers for properties. <p> To
052 * configure jetty in order to use RequestLogImpl, the following lines must be
053 * added to the jetty configuration file, namely <em>etc/jetty.xml</em>:
054 * <p/>
055 * <pre>
056 *    &lt;Ref id=&quot;requestLog&quot;&gt;
057 *      &lt;Set name=&quot;requestLog&quot;&gt;
058 *        &lt;New id=&quot;requestLogImpl&quot; class=&quot;ch.qos.logback.access.jetty.RequestLogImpl&quot;&gt;&lt;/New&gt;
059 *      &lt;/Set&gt;
060 *    &lt;/Ref&gt;
061 * </pre>
062 * <p/>
063 * By default, RequestLogImpl looks for a logback configuration file called
064 * logback-access.xml, in the same folder where jetty.xml is located, that is
065 * <em>etc/logback-access.xml</em>. The logback-access.xml file is slightly
066 * different than the usual logback classic configuration file. Most of it is
067 * the same: Appenders and Layouts are declared the exact same way. However,
068 * loggers elements are not allowed. <p> It is possible to put the logback
069 * configuration file anywhere, as long as it's path is specified. Here is
070 * another example, with a path to the logback-access.xml file.
071 * <p/>
072 * <pre>
073 *    &lt;Ref id=&quot;requestLog&quot;&gt;
074 *      &lt;Set name=&quot;requestLog&quot;&gt;
075 *        &lt;New id=&quot;requestLogImpl&quot; class=&quot;ch.qos.logback.access.jetty.RequestLogImpl&quot;&gt;&lt;/New&gt;
076 *          &lt;Set name=&quot;fileName&quot;&gt;path/to/logback.xml&lt;/Set&gt;
077 *      &lt;/Set&gt;
078 *    &lt;/Ref&gt;
079 * </pre>
080 * <p/>
081 * <p> Here is a sample logback-access.xml file that can be used right away:
082 * <p/>
083 * <pre>
084 *    &lt;configuration&gt;
085 *      &lt;appender name=&quot;STDOUT&quot; class=&quot;ch.qos.logback.core.ConsoleAppender&quot;&gt;
086 *        &lt;layout class=&quot;ch.qos.logback.access.PatternLayout&quot;&gt;
087 *          &lt;param name=&quot;Pattern&quot; value=&quot;%date %server %remoteIP %clientHost %user %requestURL&quot; /&gt;
088 *        &lt;/layout&gt;
089 *      &lt;/appender&gt;
090 *
091 *      &lt;appender-ref ref=&quot;STDOUT&quot; /&gt;
092 *    &lt;/configuration&gt;
093 * </pre>
094 * <p/>
095 * <p> Another configuration file, using SMTPAppender, could be:
096 * <p/>
097 * <pre>
098 *    &lt;configuration&gt;
099 *      &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 */
115public 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}