/**
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2011, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.access.jetty;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import ch.qos.logback.core.util.StatusPrinter;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Response;
import ch.qos.logback.access.joran.JoranConfigurator;
import ch.qos.logback.access.spi.AccessEvent;
import ch.qos.logback.access.spi.IAccessEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ContextBase;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.spi.AppenderAttachable;
import ch.qos.logback.core.spi.AppenderAttachableImpl;
import ch.qos.logback.core.spi.FilterAttachable;
import ch.qos.logback.core.spi.FilterAttachableImpl;
import ch.qos.logback.core.spi.FilterReply;
import ch.qos.logback.core.status.ErrorStatus;
import ch.qos.logback.core.status.WarnStatus;
import ch.qos.logback.core.util.OptionHelper;
/**
* This class is logback's implementation of jetty's RequestLog interface. <p>
* It can be seen as logback classic's LoggerContext. Appenders can be attached
* directly to RequestLogImpl and RequestLogImpl uses the same StatusManager as
* LoggerContext does. It also provides containers for properties. <p> To
* configure jetty in order to use RequestLogImpl, the following lines must be
* added to the jetty configuration file, namely <em>etc/jetty.xml</em>:
* <p/>
* <pre>
* <Ref id="requestLog">
* <Set name="requestLog">
* <New id="requestLogImpl" class="ch.qos.logback.access.jetty.RequestLogImpl"></New>
* </Set>
* </Ref>
* </pre>
* <p/>
* By default, RequestLogImpl looks for a logback configuration file called
* logback-access.xml, in the same folder where jetty.xml is located, that is
* <em>etc/logback-access.xml</em>. The logback-access.xml file is slightly
* different than the usual logback classic configuration file. Most of it is
* the same: Appenders and Layouts are declared the exact same way. However,
* loggers elements are not allowed. <p> It is possible to put the logback
* configuration file anywhere, as long as it's path is specified. Here is
* another example, with a path to the logback-access.xml file.
* <p/>
* <pre>
* <Ref id="requestLog">
* <Set name="requestLog">
* <New id="requestLogImpl" class="ch.qos.logback.access.jetty.RequestLogImpl"></New>
* <Set name="fileName">path/to/logback.xml</Set>
* </Set>
* </Ref>
* </pre>
* <p/>
* <p> Here is a sample logback-access.xml file that can be used right away:
* <p/>
* <pre>
* <configuration>
* <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
* <layout class="ch.qos.logback.access.PatternLayout">
* <param name="Pattern" value="%date %server %remoteIP %clientHost %user %requestURL" />
* </layout>
* </appender>
*
* <appender-ref ref="STDOUT" />
* </configuration>
* </pre>
* <p/>
* <p> Another configuration file, using SMTPAppender, could be:
* <p/>
* <pre>
* <configuration>
* <appender name="SMTP" class="ch.qos.logback.access.net.SMTPAppender">
* <layout class="ch.qos.logback.access.PatternLayout">
* <param name="pattern" value="%remoteIP [%date] %requestURL %statusCode %bytesSent" />
* </layout>
* <param name="From" value="sender@domaine.org" />
* <param name="SMTPHost" value="mail.domain.org" />
* <param name="Subject" value="Last Event: %statusCode %requestURL" />
* <param name="To" value="server_admin@domain.org" />
* </appender>
* <appender-ref ref="SMTP" />
* </configuration>
* </pre>
*
* @author Ceki Gülcü
* @author Sébastien Pennec
*/
public class RequestLogImpl extends ContextBase implements RequestLog,
AppenderAttachable<IAccessEvent>, FilterAttachable<IAccessEvent> {
public final static String DEFAULT_CONFIG_FILE = "etc" + File.separatorChar
+ "logback-access.xml";
AppenderAttachableImpl<IAccessEvent> aai = new AppenderAttachableImpl<IAccessEvent>();
FilterAttachableImpl<IAccessEvent> fai = new FilterAttachableImpl<IAccessEvent>();
String fileName;
boolean started = false;
boolean quiet = false;
public RequestLogImpl() {
putObject(CoreConstants.EVALUATOR_MAP, new HashMap());
}
public void log(Request jettyRequest, Response jettyResponse) {
JettyServerAdapter adapter = new JettyServerAdapter(jettyRequest,
jettyResponse);
IAccessEvent accessEvent = new AccessEvent(jettyRequest, jettyResponse,
adapter);
if (getFilterChainDecision(accessEvent) == FilterReply.DENY) {
return;
}
aai.appendLoopOnAppenders(accessEvent);
}
public void start() {
if (fileName == null) {
String jettyHomeProperty = OptionHelper.getSystemProperty("jetty.home");
if (OptionHelper.isEmpty(jettyHomeProperty)) {
getStatusManager().add(
new WarnStatus("[jetty.home] system property not set.", this));
fileName = DEFAULT_CONFIG_FILE;
} else {
fileName = jettyHomeProperty + File.separatorChar + DEFAULT_CONFIG_FILE;
}
getStatusManager().add(
new WarnStatus("fileName property not set. Assuming [" + fileName
+ "]", this));
}
File configFile = new File(fileName);
if (configFile.exists()) {
runJoranOnFile(configFile);
} else {
getStatusManager().add(
new ErrorStatus("Could not find logback-access configuration file [" + fileName + "]", this));
}
if (!isQuiet()) {
StatusPrinter.print(getStatusManager());
}
started = true;
}
private void runJoranOnFile(File configFile) {
try {
JoranConfigurator jc = new JoranConfigurator();
jc.setContext(this);
jc.doConfigure(configFile);
if (getName() == null) {
setName("LogbackRequestLog");
}
} catch (JoranException e) {
// errors have been registered as status messages
}
}
public void stop() {
aai.detachAndStopAllAppenders();
started = false;
}
public boolean isRunning() {
return started;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public boolean isStarted() {
return started;
}
public boolean isStarting() {
return false;
}
public boolean isStopping() {
return false;
}
public boolean isStopped() {
return !started;
}
public boolean isFailed() {
return false;
}
public boolean isQuiet() {
return quiet;
}
public void setQuiet(boolean quiet) {
this.quiet = quiet;
}
public void addAppender(Appender<IAccessEvent> newAppender) {
aai.addAppender(newAppender);
}
public Iterator<Appender<IAccessEvent>> iteratorForAppenders() {
return aai.iteratorForAppenders();
}
public Appender<IAccessEvent> getAppender(String name) {
return aai.getAppender(name);
}
public boolean isAttached(Appender<IAccessEvent> appender) {
return aai.isAttached(appender);
}
public void detachAndStopAllAppenders() {
aai.detachAndStopAllAppenders();
}
public boolean detachAppender(Appender<IAccessEvent> appender) {
return aai.detachAppender(appender);
}
public boolean detachAppender(String name) {
return aai.detachAppender(name);
}
public void addFilter(Filter<IAccessEvent> newFilter) {
fai.addFilter(newFilter);
}
public void clearAllFilters() {
fai.clearAllFilters();
}
public List<Filter<IAccessEvent>> getCopyOfAttachedFiltersList() {
return fai.getCopyOfAttachedFiltersList();
}
public FilterReply getFilterChainDecision(IAccessEvent event) {
return fai.getFilterChainDecision(event);
}
public void addLifeCycleListener(Listener listener) {
// we'll implement this when asked
}
public void removeLifeCycleListener(Listener listener) {
// we'll implement this when asked
}
}