package com.twitter.common.net.http;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.common.annotations.VisibleForTesting;
import org.mortbay.component.AbstractLifeCycle;
import org.mortbay.jetty.HttpHeaders;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.RequestLog;
import org.mortbay.jetty.Response;
import org.mortbay.util.DateCache;
import com.twitter.common.util.Clock;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A request logger that borrows formatting code from {@link org.mortbay.jetty.NCSARequestLog},
* but removes unneeded features (writing to file) and logging to java.util.logging.
*/
public class RequestLogger extends AbstractLifeCycle implements RequestLog {
private static final Logger LOG = Logger.getLogger(RequestLogger.class.getName());
private final Clock clock;
private final LogSink sink;
private final DateCache logDateCache;
interface LogSink {
boolean isLoggable(Level level);
void log(Level level, String messagge);
}
RequestLogger() {
this(Clock.SYSTEM_CLOCK, new LogSink() {
@Override
public boolean isLoggable(Level level) {
return LOG.isLoggable(level);
}
@Override public void log(Level level, String message) {
LOG.log(level, message);
}
});
}
@VisibleForTesting
RequestLogger(Clock clock, LogSink sink) {
this.clock = checkNotNull(clock);
this.sink = checkNotNull(sink);
logDateCache = new DateCache("dd/MMM/yyyy:HH:mm:ss Z", Locale.getDefault());
logDateCache.setTimeZoneID("GMT");
}
private String formatEntry(Request request, Response response) {
StringBuilder buf = new StringBuilder();
buf.append(request.getServerName());
buf.append(' ');
String addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
if (addr == null) {
addr = request.getRemoteAddr();
}
buf.append(addr);
buf.append(" [");
buf.append(logDateCache.format(request.getTimeStamp()));
buf.append("] \"");
buf.append(request.getMethod());
buf.append(' ');
buf.append(request.getUri().toString());
buf.append(' ');
buf.append(request.getProtocol());
buf.append("\" ");
buf.append(response.getStatus());
buf.append(' ');
buf.append(response.getContentCount());
buf.append(' ');
String referer = request.getHeader(HttpHeaders.REFERER);
if (referer == null) {
buf.append("\"-\" ");
} else {
buf.append('"');
buf.append(referer);
buf.append("\" ");
}
String agent = request.getHeader(HttpHeaders.USER_AGENT);
if (agent == null) {
buf.append("\"-\" ");
} else {
buf.append('"');
buf.append(agent);
buf.append('"');
}
buf.append(' ');
buf.append(clock.nowMillis() - request.getTimeStamp());
return buf.toString();
}
@Override
public void log(Request request, Response response) {
int statusCategory = response.getStatus() / 100;
Level level = ((statusCategory == 2) || (statusCategory == 3)) ? Level.FINE : Level.INFO;
if (!sink.isLoggable(level)) {
return;
}
sink.log(level, formatEntry(request, response));
}
}