view src/org/eclipse/jetty/server/NCSARequestLog.java @ 808:b3176fd168bf

replace use of jetty.util.B64Code with java.util.Base64
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 08 Sep 2016 16:13:27 -0600
parents 3428c60d7cfc
children f8f7cb485c25
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();
            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;
        }
    }
}