blob: 5bcf73d99968343592e4e09ee5960e5029718847 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Locale;
import java.util.TimeZone;
import javax.servlet.http.Cookie;
import org.eclipse.jetty.http.HttpHeaders;
import org.eclipse.jetty.http.PathMap;
import org.eclipse.jetty.util.DateCache;
import org.eclipse.jetty.util.RolloverFileOutputStream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* This {@link RequestLog} implementation outputs logs in the pseudo-standard
* NCSA common log format. Configuration options allow a choice between the
* standard Common Log Format (as used in the 3 log format) and the Combined Log
* Format (single log format). This log format can be output by most web
* servers, and almost all web log analysis software can understand these
* formats.
*
* @org.apache.xbean.XBean element="ncsaLog"
*/
/* ------------------------------------------------------------ */
/**
*/
public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
{
private static final Logger LOG = Log.getLogger(NCSARequestLog.class);
private static ThreadLocal<StringBuilder> _buffers = new ThreadLocal<StringBuilder>()
{
@Override
protected StringBuilder initialValue()
{
return new StringBuilder(256);
}
};
private String _filename;
private boolean _extended;
private boolean _append;
private int _retainDays;
private boolean _closeOut;
private boolean _preferProxiedForAddress;
private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
private String _filenameDateFormat = null;
private Locale _logLocale = Locale.getDefault();
private String _logTimeZone = "GMT";
private String[] _ignorePaths;
private boolean _logLatency = false;
private boolean _logCookies = false;
private boolean _logServer = false;
private boolean _logDispatch = false;
private transient OutputStream _out;
private transient OutputStream _fileOut;
private transient DateCache _logDateCache;
private transient PathMap _ignorePathMap;
private transient Writer _writer;
/* ------------------------------------------------------------ */
/**
* Create request log object with default settings.
*/
public NCSARequestLog()
{
_extended = true;
_append = true;
_retainDays = 31;
}
/* ------------------------------------------------------------ */
/**
* Create request log object with specified output file name.
*
* @param filename the file name for the request log.
* This may be in the format expected
* by {@link RolloverFileOutputStream}
*/
public NCSARequestLog(String filename)
{
_extended = true;
_append = true;
_retainDays = 31;
setFilename(filename);
}
/* ------------------------------------------------------------ */
/**
* Set the output file name of the request log.
* The file name may be in the format expected by
* {@link RolloverFileOutputStream}.
*
* @param filename file name of the request log
*
*/
public void setFilename(String filename)
{
if (filename != null)
{
filename = filename.trim();
if (filename.length() == 0)
filename = null;
}
_filename = filename;
}
/* ------------------------------------------------------------ */
/**
* Retrieve the output file name of the request log.
*
* @return file name of the request log
*/
public String getFilename()
{
return _filename;
}
/* ------------------------------------------------------------ */
/**
* Retrieve the file name of the request log with the expanded
* date wildcard if the output is written to the disk using
* {@link RolloverFileOutputStream}.
*
* @return file name of the request log, or null if not applicable
*/
public String getDatedFilename()
{
if (_fileOut instanceof RolloverFileOutputStream)
return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
return null;
}
/* ------------------------------------------------------------ */
/**
* Set the timestamp format for request log entries in the file.
* If this is not set, the pre-formated request timestamp is used.
*
* @param format timestamp format string
*/
public void setLogDateFormat(String format)
{
_logDateFormat = format;
}
/* ------------------------------------------------------------ */
/**
* Retrieve the timestamp format string for request log entries.
*
* @return timestamp format string.
*/
public String getLogDateFormat()
{
return _logDateFormat;
}
/* ------------------------------------------------------------ */
/**
* Set the locale of the request log.
*
* @param logLocale locale object
*/
public void setLogLocale(Locale logLocale)
{
_logLocale = logLocale;
}
/* ------------------------------------------------------------ */
/**
* Retrieve the locale of the request log.
*
* @return locale object
*/
public Locale getLogLocale()
{
return _logLocale;
}
/* ------------------------------------------------------------ */
/**
* Set the timezone of the request log.
*
* @param tz timezone string
*/
public void setLogTimeZone(String tz)
{
_logTimeZone = tz;
}
/* ------------------------------------------------------------ */
/**
* Retrieve the timezone of the request log.
*
* @return timezone string
*/
public String getLogTimeZone()
{
return _logTimeZone;
}
/* ------------------------------------------------------------ */
/**
* Set the number of days before rotated log files are deleted.
*
* @param retainDays number of days to keep a log file
*/
public void setRetainDays(int retainDays)
{
_retainDays = retainDays;
}
/* ------------------------------------------------------------ */
/**
* Retrieve the number of days before rotated log files are deleted.
*
* @return number of days to keep a log file
*/
public int getRetainDays()
{
return _retainDays;
}
/* ------------------------------------------------------------ */
/**
* Set the extended request log format flag.
*
* @param extended true - log the extended request information,
* false - do not log the extended request information
*/
public void setExtended(boolean extended)
{
_extended = extended;
}
/* ------------------------------------------------------------ */
/**
* Retrieve the extended request log format flag.
*
* @return value of the flag
*/
public boolean isExtended()
{
return _extended;
}
/* ------------------------------------------------------------ */
/**
* Set append to log flag.
*
* @param append true - request log file will be appended after restart,
* false - request log file will be overwritten after restart
*/
public void setAppend(boolean append)
{
_append = append;
}
/* ------------------------------------------------------------ */
/**
* Retrieve append to log flag.
*
* @return value of the flag
*/
public boolean isAppend()
{
return _append;
}
/* ------------------------------------------------------------ */
/**
* Set request paths that will not be logged.
*
* @param ignorePaths array of request paths
*/
public void setIgnorePaths(String[] ignorePaths)
{
_ignorePaths = ignorePaths;
}
/* ------------------------------------------------------------ */
/**
* Retrieve the request paths that will not be logged.
*
* @return array of request paths
*/
public String[] getIgnorePaths()
{
return _ignorePaths;
}
/* ------------------------------------------------------------ */
/**
* Controls logging of the request cookies.
*
* @param logCookies true - values of request cookies will be logged,
* false - values of request cookies will not be logged
*/
public void setLogCookies(boolean logCookies)
{
_logCookies = logCookies;
}
/* ------------------------------------------------------------ */
/**
* Retrieve log cookies flag
*
* @return value of the flag
*/
public boolean getLogCookies()
{
return _logCookies;
}
/* ------------------------------------------------------------ */
/**
* Controls logging of the request hostname.
*
* @param logServer true - request hostname will be logged,
* false - request hostname will not be logged
*/
public void setLogServer(boolean logServer)
{
_logServer = logServer;
}
/* ------------------------------------------------------------ */
/**
* Retrieve log hostname flag.
*
* @return value of the flag
*/
public boolean getLogServer()
{
return _logServer;
}
/* ------------------------------------------------------------ */
/**
* Controls logging of request processing time.
*
* @param logLatency true - request processing time will be logged
* false - request processing time will not be logged
*/
public void setLogLatency(boolean logLatency)
{
_logLatency = logLatency;
}
/* ------------------------------------------------------------ */
/**
* Retrieve log request processing time flag.
*
* @return value of the flag
*/
public boolean getLogLatency()
{
return _logLatency;
}
/* ------------------------------------------------------------ */
/**
* Controls whether the actual IP address of the connection or
* the IP address from the X-Forwarded-For header will be logged.
*
* @param preferProxiedForAddress true - IP address from header will be logged,
* false - IP address from the connection will be logged
*/
public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
{
_preferProxiedForAddress = preferProxiedForAddress;
}
/* ------------------------------------------------------------ */
/**
* Retrieved log X-Forwarded-For IP address flag.
*
* @return value of the flag
*/
public boolean getPreferProxiedForAddress()
{
return _preferProxiedForAddress;
}
/* ------------------------------------------------------------ */
/**
* Set the log file name date format.
* @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
*
* @param logFileDateFormat format string that is passed to {@link RolloverFileOutputStream}
*/
public void setFilenameDateFormat(String logFileDateFormat)
{
_filenameDateFormat = logFileDateFormat;
}
/* ------------------------------------------------------------ */
/**
* Retrieve the file name date format string.
*
* @return the log File Date Format
*/
public String getFilenameDateFormat()
{
return _filenameDateFormat;
}
/* ------------------------------------------------------------ */
/**
* Controls logging of the request dispatch time
*
* @param value true - request dispatch time will be logged
* false - request dispatch time will not be logged
*/
public void setLogDispatch(boolean value)
{
_logDispatch = value;
}
/* ------------------------------------------------------------ */
/**
* Retrieve request dispatch time logging flag
*
* @return value of the flag
*/
public boolean isLogDispatch()
{
return _logDispatch;
}
/* ------------------------------------------------------------ */
/**
* Writes the request and response information to the output stream.
*
* @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response)
*/
public void log(Request request, Response response)
{
try
{
if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
return;
if (_fileOut == null)
return;
StringBuilder buf= _buffers.get();
buf.setLength(0);
if (_logServer)
{
buf.append(request.getServerName());
buf.append(' ');
}
String addr = null;
if (_preferProxiedForAddress)
{
addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
}
if (addr == null)
addr = request.getRemoteAddr();
buf.append(addr);
buf.append(" - ");
Authentication authentication=request.getAuthentication();
if (authentication instanceof Authentication.User)
buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
else
buf.append(" - ");
buf.append(" [");
if (_logDateCache != null)
buf.append(_logDateCache.format(request.getTimeStamp()));
else
buf.append(request.getTimeStampBuffer().toString());
buf.append("] \"");
buf.append(request.getMethod());
buf.append(' ');
buf.append(request.getUri().toString());
buf.append(' ');
buf.append(request.getProtocol());
buf.append("\" ");
if (request.getAsyncContinuation().isInitial())
{
int status = response.getStatus();
if (status <= 0)
status = 404;
buf.append((char)('0' + ((status / 100) % 10)));
buf.append((char)('0' + ((status / 10) % 10)));
buf.append((char)('0' + (status % 10)));
}
else
buf.append("Async");
long responseLength = response.getContentCount();
if (responseLength >= 0)
{
buf.append(' ');
if (responseLength > 99999)
buf.append(responseLength);
else
{
if (responseLength > 9999)
buf.append((char)('0' + ((responseLength / 10000) % 10)));
if (responseLength > 999)
buf.append((char)('0' + ((responseLength / 1000) % 10)));
if (responseLength > 99)
buf.append((char)('0' + ((responseLength / 100) % 10)));
if (responseLength > 9)
buf.append((char)('0' + ((responseLength / 10) % 10)));
buf.append((char)('0' + (responseLength) % 10));
}
buf.append(' ');
}
else
buf.append(" - ");
if (_extended)
logExtended(request, response, buf);
if (_logCookies)
{
Cookie[] cookies = request.getCookies();
if (cookies == null || cookies.length == 0)
buf.append(" -");
else
{
buf.append(" \"");
for (int i = 0; i < cookies.length; i++)
{
if (i != 0)
buf.append(';');
buf.append(cookies[i].getName());
buf.append('=');
buf.append(cookies[i].getValue());
}
buf.append('\"');
}
}
if (_logDispatch || _logLatency)
{
long now = System.currentTimeMillis();
if (_logDispatch)
{
long d = request.getDispatchTime();
buf.append(' ');
buf.append(now - (d==0 ? request.getTimeStamp():d));
}
if (_logLatency)
{
buf.append(' ');
buf.append(now - request.getTimeStamp());
}
}
buf.append(StringUtil.__LINE_SEPARATOR);
String log = buf.toString();
write(log);
}
catch (IOException e)
{
LOG.warn(e);
}
}
/* ------------------------------------------------------------ */
protected void write(String log) throws IOException
{
synchronized(this)
{
if (_writer==null)
return;
_writer.write(log);
_writer.flush();
}
}
/* ------------------------------------------------------------ */
/**
* Writes extended request and response information to the output stream.
*
* @param request request object
* @param response response object
* @param b StringBuilder to write to
* @throws IOException
*/
protected void logExtended(Request request,
Response response,
StringBuilder b) throws IOException
{
String referer = request.getHeader(HttpHeaders.REFERER);
if (referer == null)
b.append("\"-\" ");
else
{
b.append('"');
b.append(referer);
b.append("\" ");
}
String agent = request.getHeader(HttpHeaders.USER_AGENT);
if (agent == null)
b.append("\"-\" ");
else
{
b.append('"');
b.append(agent);
b.append('"');
}
}
/* ------------------------------------------------------------ */
/**
* Set up request logging and open log file.
*
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
*/
@Override
protected synchronized void doStart() throws Exception
{
if (_logDateFormat != null)
{
_logDateCache = new DateCache(_logDateFormat,_logLocale);
_logDateCache.setTimeZoneID(_logTimeZone);
}
if (_filename != null)
{
_fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null);
_closeOut = true;
LOG.info("Opened " + getDatedFilename());
}
else
_fileOut = System.err;
_out = _fileOut;
if (_ignorePaths != null && _ignorePaths.length > 0)
{
_ignorePathMap = new PathMap();
for (int i = 0; i < _ignorePaths.length; i++)
_ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]);
}
else
_ignorePathMap = null;
synchronized(this)
{
_writer = new OutputStreamWriter(_out);
}
super.doStart();
}
/* ------------------------------------------------------------ */
/**
* Close the log file and perform cleanup.
*
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
*/
@Override
protected void doStop() throws Exception
{
synchronized (this)
{
super.doStop();
try
{
if (_writer != null)
_writer.flush();
}
catch (IOException e)
{
LOG.ignore(e);
}
if (_out != null && _closeOut)
try
{
_out.close();
}
catch (IOException e)
{
LOG.ignore(e);
}
_out = null;
_fileOut = null;
_closeOut = false;
_logDateCache = null;
_writer = null;
}
}
}