view src/org/eclipse/jetty/server/NCSARequestLog.java @ 814:95cbe23a96fb

minor
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 09 Sep 2016 10:37:37 -0600
parents f8f7cb485c25
children 07c82fabc46b
line wrap: on
line source

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