/**
* Copyright 2005-2011 Noelios Technologies.
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL 1.0 (the
* "Licenses"). You can select the license that you prefer but you may not use
* this file except in compliance with one of these Licenses.
*
* You can obtain a copy of the LGPL 3.0 license at
* http://www.opensource.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.opensource.org/licenses/lgpl-2.1.php
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.opensource.org/licenses/cddl1.php
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/
package org.restlet.service;
import java.util.logging.LogManager;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.engine.log.LogFilter;
import org.restlet.representation.Representation;
import org.restlet.resource.ClientResource;
import org.restlet.routing.Filter;
import org.restlet.routing.Template;
/**
* Service providing access logging service. The implementation is fully based
* on the standard logging mechanism introduced in JDK 1.4.<br>
* <br>
* The default access log format follows the <a
* href="http://www.w3.org/TR/WD-logfile.html"> W3C Extended Log File Format</a>
* with the following fields used: <br>
* <ol>
* <li>Date (YYYY-MM-DD)</li>
* <li>Time (HH:MM:SS)</li>
* <li>Client address (IP)</li>
* <li>Remote user identifier (see RFC 1413)</li>
* <li>Server address (IP)</li>
* <li>Server port</li>
* <li>Method (GET|POST|...)</li>
* <li>Resource reference path (including the leading slash)</li>
* <li>Resource reference query (excluding the leading question mark)</li>
* <li>Response status code</li>
* <li>Number of bytes sent</li>
* <li>Number of bytes received</li>
* <li>Time to serve the request (in milliseconds)</li>
* <li>Host reference</li>
* <li>Client agent name</li>
* <li>Referrer reference</li>
* </ol>
* <br>
* If you use <a href="http://www.analog.cx">Analog</a> to generate your log
* reports, and if you use the default log format, then you can simply specify
* this string as a value of the LOGFORMAT command:
* (%Y-%m-%d\t%h:%n:%j\t%S\t%u\t%j\t%j\t%j\t%r\t%q\t%c\t%b\t%j\t%T\t%v\t%B\t%f)<br>
* <br>
* For custom access log format, see the syntax to use and the list of available
* variable names in {@link org.restlet.routing.Template}. <br>
*
* @see <a href="http://wiki.restlet.org/docs_2.1/201-restlet.html">User Guide -
* Access logging</a>
* @see <a
* href="http://download.oracle.com/javase/1.5.0/docs/api/java/util/logging/package-summary.html">java.util.logging</a>
* @author Jerome Louvel
*/
public class LogService extends Service {
/** Indicates if the debugging mode is enabled. */
private volatile boolean debugging;
/** Indicates if the identity check (as specified by RFC1413) is enabled. */
private volatile boolean identityCheck;
/** The URI template of loggable resource references. */
private volatile Template loggableTemplate;
/** The access logger name. */
private volatile String loggerName;
/** The URI reference of the log properties. */
private volatile Reference logPropertiesRef;
/** The response log entry format. */
private volatile String responseLogFormat;
/** The response log template to use. */
protected volatile Template responseLogTemplate;
/**
* Constructor.
*/
public LogService() {
this(true);
}
/**
* Constructor.
*
* @param enabled
* True if the service has been enabled.
*/
public LogService(boolean enabled) {
super(enabled);
this.loggableTemplate = null;
this.loggerName = null;
this.responseLogFormat = null;
this.logPropertiesRef = null;
this.identityCheck = false;
}
@Override
public Filter createInboundFilter(Context context) {
return new LogFilter(context, this);
}
/**
* Format a log entry using the default IIS log format.
*
* @param response
* The response to log.
* @param duration
* The call duration (in milliseconds).
* @return The formatted log entry.
*/
protected String getDefaultResponseLogMessage(Response response,
int duration) {
StringBuilder sb = new StringBuilder();
Request request = response.getRequest();
if (isDebugging()) {
} else {
long currentTime = System.currentTimeMillis();
// Append the date of the request
sb.append(String.format("%tF", currentTime));
sb.append('\t');
// Append the time of the request
sb.append(String.format("%tT", currentTime));
sb.append('\t');
// Append the client IP address
String clientAddress = request.getClientInfo().getUpstreamAddress();
sb.append((clientAddress == null) ? "-" : clientAddress);
sb.append('\t');
// Append the user name (via IDENT protocol)
if (isIdentityCheck()) {
org.restlet.engine.log.IdentClient ic = new org.restlet.engine.log.IdentClient(
request.getClientInfo().getUpstreamAddress(), request
.getClientInfo().getPort(), response
.getServerInfo().getPort());
sb.append((ic.getUserIdentifier() == null) ? "-" : ic
.getUserIdentifier());
} else if ((request.getChallengeResponse() != null)
&& (request.getChallengeResponse().getIdentifier() != null)) {
sb.append(request.getChallengeResponse().getIdentifier());
} else {
sb.append('-');
}
sb.append('\t');
// Append the server IP address
String serverAddress = response.getServerInfo().getAddress();
sb.append((serverAddress == null) ? "-" : serverAddress);
sb.append('\t');
// Append the server port
Integer serverport = response.getServerInfo().getPort();
sb.append((serverport == null) ? "-" : serverport.toString());
sb.append('\t');
// Append the method name
String methodName = (request.getMethod() == null) ? "-" : request
.getMethod().getName();
sb.append((methodName == null) ? "-" : methodName);
// Append the resource path
sb.append('\t');
String resourcePath = (request.getResourceRef() == null) ? "-"
: request.getResourceRef().getPath();
sb.append((resourcePath == null) ? "-" : resourcePath);
// Append the resource query
sb.append('\t');
String resourceQuery = (request.getResourceRef() == null) ? "-"
: request.getResourceRef().getQuery();
sb.append((resourceQuery == null) ? "-" : resourceQuery);
// Append the status code
sb.append('\t');
sb.append((response.getStatus() == null) ? "-" : Integer
.toString(response.getStatus().getCode()));
// Append the returned size
sb.append('\t');
if (!response.isEntityAvailable()
|| Status.REDIRECTION_NOT_MODIFIED.equals(response
.getStatus())
|| Status.SUCCESS_NO_CONTENT.equals(response.getStatus())
|| Method.HEAD.equals(request.getMethod())) {
sb.append('0');
} else {
sb.append((response.getEntity().getSize() == -1) ? "-" : Long
.toString(response.getEntity().getSize()));
}
// Append the received size
sb.append('\t');
if (request.getEntity() == null) {
sb.append('0');
} else {
sb.append((request.getEntity().getSize() == -1) ? "-" : Long
.toString(request.getEntity().getSize()));
}
// Append the duration
sb.append('\t');
sb.append(duration);
// Append the host reference
sb.append('\t');
sb.append((request.getHostRef() == null) ? "-" : request
.getHostRef().toString());
// Append the agent name
sb.append('\t');
String agentName = request.getClientInfo().getAgent();
sb.append((agentName == null) ? "-" : agentName);
// Append the referrer
sb.append('\t');
sb.append((request.getReferrerRef() == null) ? "-" : request
.getReferrerRef().getIdentifier());
}
return sb.toString();
}
/**
* Returns the format used.
*
* @return The format used, or null if the default one is used.
* @see org.restlet.routing.Template for format syntax and variables.
* @deprecated Use the {@link #getResponseLogFormat()} method instead.
*/
@Deprecated
public String getLogFormat() {
return getResponseLogFormat();
}
/**
* Returns the URI template of loggable resource references. Returns null by
* default, meaning the all requests are loggable, independant of their
* target resource URI reference.
*
* @return The URI template of loggable resource references.
* @see Request#getResourceRef()
*/
public Template getLoggableTemplate() {
return loggableTemplate;
}
/**
* Returns the name of the JDK's logger to use when logging access calls.
* The default name will follow this pattern:
* "org.restlet.MyComponent.LogService", where "MyComponent" will correspond
* to the simple class name of your component subclass or to the base
* "Component" class.
*
* @return The name of the JDK's logger to use when logging access calls.
*/
public String getLoggerName() {
return this.loggerName;
}
/**
* Returns the URI reference of the log properties.
*
* @return The URI reference of the log properties.
*/
public Reference getLogPropertiesRef() {
return logPropertiesRef;
}
/**
* Returns the format used when logging responses.
*
* @return The format used, or null if the default one is used.
* @see org.restlet.routing.Template for format syntax and variables.
*/
public String getResponseLogFormat() {
return this.responseLogFormat;
}
/**
* Format an access log entry. If the log template property isn't provided,
* then a default IIS like format is used.
*
* @param response
* The response to log.
* @param duration
* The call duration.
* @return The formatted log entry.
*/
public String getResponseLogMessage(Response response, int duration) {
String result = null;
// Format the call into a log entry
if (this.responseLogTemplate != null) {
result = this.responseLogTemplate.format(response.getRequest(),
response);
} else {
result = getDefaultResponseLogMessage(response, duration);
}
return result;
}
/**
* Indicates if the debugging mode is enabled. False by default.
*
* @return True if the debugging mode is enabled.
*/
protected boolean isDebugging() {
return debugging;
}
/**
* Indicates if the identity check (as specified by RFC1413) is enabled.
* Default value is false.
*
* @return True if the identity check is enabled.
*/
public boolean isIdentityCheck() {
return this.identityCheck;
}
/**
* Indicates if the call should be logged during the processing chain. By
* default, it tries to match the request URI with the
* {@link #getLoggableTemplate()} URI template otherwise is returns true.
*
* @param request
* The request to log.
* @return True if the call should be logged during the processing chain.
*/
public boolean isLoggable(Request request) {
return (getLoggableTemplate() == null) ? true : getLoggableTemplate()
.match(request.getResourceRef().getTargetRef().toString()) > 0;
}
/**
* Indicates if the debugging mode is enabled.
*
* @param debugging
* True if the debugging mode is enabled.
*/
protected void setDebugging(boolean debugging) {
this.debugging = debugging;
}
/**
* Indicates if the identity check (as specified by RFC1413) is enabled.
*
* @param identityCheck
* True if the identity check is enabled.
*/
public void setIdentityCheck(boolean identityCheck) {
this.identityCheck = identityCheck;
}
/**
* Sets the format to use when logging responses. The default format matches
* the one of IIS 6.
*
* @param responseLogFormat
* The format to use when logging responses.
* @see org.restlet.routing.Template for format syntax and variables.
* @deprecated Use {@link #setResponseLogFormat(String)} instead.
*/
@Deprecated
public void setLogFormat(String responseLogFormat) {
setResponseLogFormat(responseLogFormat);
}
/**
* Sets the URI template of loggable resource references.
*
* @param loggableTemplateRef
* The URI template of loggable resource references.
* @see #setLoggableTemplate(Template)
*/
public void setLoggableTemplate(String loggableTemplateRef) {
if (loggableTemplateRef != null) {
this.loggableTemplate = new Template(loggableTemplateRef);
} else {
this.loggableTemplate = null;
}
}
/**
* Sets the URI template of loggable resource references.
*
* @param loggableTemplate
* The URI template of loggable resource references.
*/
public void setLoggableTemplate(Template loggableTemplate) {
this.loggableTemplate = loggableTemplate;
}
/**
* Sets the name of the JDK's logger to use when logging access calls.
*
* @param name
* The name of the JDK's logger to use when logging access calls.
*/
public void setLoggerName(String name) {
this.loggerName = name;
}
/**
* Sets the URI reference of the log properties.
*
* @param logPropertiesRef
* The URI reference of the log properties.
*/
public void setLogPropertiesRef(Reference logPropertiesRef) {
this.logPropertiesRef = logPropertiesRef;
}
/**
* Sets the URI reference of the log properties.
*
* @param logPropertiesUri
* The URI reference of the log properties.
*/
public void setLogPropertiesRef(String logPropertiesUri) {
setLogPropertiesRef(new Reference(logPropertiesUri));
}
/**
* Sets the format to use when logging responses. The default format matches
* the one of IIS 6.
*
* @param responseLogFormat
* The format to use when logging responses.
* @see org.restlet.routing.Template for format syntax and variables.
*/
public void setResponseLogFormat(String responseLogFormat) {
this.responseLogFormat = responseLogFormat;
}
/**
* Starts the log service by attempting to read the log properties if the
* {@link #getLogPropertiesRef()} returns a non null URI reference.
*/
@Override
public synchronized void start() throws Exception {
super.start();
this.responseLogTemplate = (getLogFormat() == null) ? null
: new Template(getLogFormat());
if (getLogPropertiesRef() != null) {
Representation logProperties = new ClientResource(getContext(),
getLogPropertiesRef()).get();
if (logProperties != null) {
LogManager.getLogManager().readConfiguration(
logProperties.getStream());
}
}
}
}