| // |
| // ======================================================================== |
| // 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; |
| } |
| } |
| } |