View Javadoc

    1   //========================================================================
    2   //Copyright 1997-2006 Mort Bay Consulting Pty. Ltd.
    3   //------------------------------------------------------------------------
    4   //Licensed under the Apache License, Version 2.0 (the "License");
    5   //you may not use this file except in compliance with the License.
    6   //You may obtain a copy of the License at
    7   //http://www.apache.org/licenses/LICENSE-2.0
    8   //Unless required by applicable law or agreed to in writing, software
    9   //distributed under the License is distributed on an "AS IS" BASIS,
    10  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  //See the License for the specific language governing permissions and
    12  //limitations under the License.
    13  //========================================================================
    14
    15  package org.mortbay.jetty;
    16
    17  import java.io.IOException;
    18  import java.io.OutputStream;
    19  import java.io.OutputStreamWriter;
    20  import java.io.Writer;
    21  import java.util.ArrayList;
    22  import java.util.Locale;
    23  import java.util.TimeZone;
    24
    25  import javax.servlet.http.Cookie;
    26
    27  import org.mortbay.component.AbstractLifeCycle;
    28  import org.mortbay.jetty.servlet.PathMap;
    29  import org.mortbay.log.Log;
    30  import org.mortbay.util.DateCache;
    31  import org.mortbay.util.RolloverFileOutputStream;
    32  import org.mortbay.util.StringUtil;
    33  import org.mortbay.util.TypeUtil;
    34  import org.mortbay.util.Utf8StringBuffer;
    35
    36  /**
    37   * This {@link RequestLog} implementation outputs logs in the pseudo-standard NCSA common log format.
    38   * Configuration options allow a choice between the standard Common Log Format (as used in the 3 log format)
    39   * and the Combined Log Format (single log format).
    40   * This log format can be output by most web servers, and almost all web log analysis software can understand
    41   *  these formats.
    42   * @author Greg Wilkins
    43   * @author Nigel Canonizado
    44   *
    45   * @org.apache.xbean.XBean element="ncsaLog"
    46   */
    47  public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
    48  {
    49      private String _filename;
    50      private boolean _extended;
    51      private boolean _append;
    52      private int _retainDays;
    53      private boolean _closeOut;
    54      private boolean _preferProxiedForAddress;
    55      private String _logDateFormat="dd/MMM/yyyy:HH:mm:ss Z";
    56      private String _filenameDateFormat = null;
    57      private Locale _logLocale = Locale.getDefault();
    58      private String _logTimeZone = "GMT";
    59      private String[] _ignorePaths;
    60      private boolean _logLatency = false;
    61      private boolean _logCookies = false;
    62      private boolean _logServer = false;
    63
    64      private transient OutputStream _out;
    65      private transient OutputStream _fileOut;
    66      private transient DateCache _logDateCache;
    67      private transient PathMap _ignorePathMap;
    68      private transient Writer _writer;
    69      private transient ArrayList _buffers;
    70      private transient char[] _copy;
    71
    72
    73      public NCSARequestLog()
    74      {
    75          _extended = true;
    76          _append = true;
    77          _retainDays = 31;
    78      }
    79
    80      /* ------------------------------------------------------------ */
    81      /**
    82       * @param filename The filename for the request log. This may be in the format expected by {@link RolloverFileOutputStream}
    83       */
    84      public NCSARequestLog(String filename)
    85      {
    86          _extended = true;
    87          _append = true;
    88          _retainDays = 31;
    89          setFilename(filename);
    90      }
    91
    92      /* ------------------------------------------------------------ */
    93      /**
    94       * @param filename The filename for the request log. This may be in the format expected by {@link RolloverFileOutputStream}
    95       */
    96      public void setFilename(String filename)
    97      {
    98          if (filename != null)
    99          {
    100             filename = filename.trim();
    101             if (filename.length() == 0)
    102                 filename = null;
    103         }
    104         _filename = filename;
    105     }
    106
    107     public String getFilename()
    108     {
    109         return _filename;
    110     }
    111
    112     public String getDatedFilename()
    113     {
    114         if (_fileOut instanceof RolloverFileOutputStream)
    115             return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
    116         return null;
    117     }
    118
    119     /* ------------------------------------------------------------ */
    120     /**
    121      * @param format Format for the timestamps in the log file.  If not set,
    122      * the pre-formated request timestamp is used.
    123      */
    124     public void setLogDateFormat(String format)
    125     {
    126         _logDateFormat = format;
    127     }
    128
    129     public String getLogDateFormat()
    130     {
    131         return _logDateFormat;
    132     }
    133
    134     public void setLogLocale(Locale logLocale)
    135     {
    136         _logLocale = logLocale;
    137     }
    138
    139     public Locale getLogLocale()
    140     {
    141         return _logLocale;
    142     }
    143
    144     public void setLogTimeZone(String tz)
    145     {
    146         _logTimeZone = tz;
    147     }
    148
    149     public String getLogTimeZone()
    150     {
    151         return _logTimeZone;
    152     }
    153
    154     public void setRetainDays(int retainDays)
    155     {
    156         _retainDays = retainDays;
    157     }
    158
    159     public int getRetainDays()
    160     {
    161         return _retainDays;
    162     }
    163
    164     public void setExtended(boolean extended)
    165     {
    166         _extended = extended;
    167     }
    168
    169     public boolean isExtended()
    170     {
    171         return _extended;
    172     }
    173
    174     public void setAppend(boolean append)
    175     {
    176         _append = append;
    177     }
    178
    179     public boolean isAppend()
    180     {
    181         return _append;
    182     }
    183
    184     public void setIgnorePaths(String[] ignorePaths)
    185     {
    186         _ignorePaths = ignorePaths;
    187     }
    188
    189     public String[] getIgnorePaths()
    190     {
    191         return _ignorePaths;
    192     }
    193
    194     public void setLogCookies(boolean logCookies)
    195     {
    196         _logCookies = logCookies;
    197     }
    198
    199     public boolean getLogCookies()
    200     {
    201         return _logCookies;
    202     }
    203
    204     public boolean getLogServer()
    205     {
    206         return _logServer;
    207     }
    208
    209     public void setLogServer(boolean logServer)
    210     {
    211         _logServer=logServer;
    212     }
    213
    214     public void setLogLatency(boolean logLatency)
    215     {
    216         _logLatency = logLatency;
    217     }
    218
    219     public boolean getLogLatency()
    220     {
    221         return _logLatency;
    222     }
    223
    224     public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
    225     {
    226         _preferProxiedForAddress = preferProxiedForAddress;
    227     }
    228
    229     /* ------------------------------------------------------------ */
    230     public void log(Request request, Response response)
    231     {
    232         if (!isStarted())
    233             return;
    234
    235         try
    236         {
    237             if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
    238                 return;
    239
    240             if (_fileOut == null)
    241                 return;
    242
    243             Utf8StringBuffer u8buf;
    244             StringBuffer buf;
    245             synchronized(_writer)
    246             {
    247                 int size=_buffers.size();
    248                 u8buf = size==0?new Utf8StringBuffer(160):(Utf8StringBuffer)_buffers.remove(size-1);
    249                 buf = u8buf.getStringBuffer();
    250             }
    251
    252             synchronized(buf) // for efficiency until we can use StringBuilder
    253             {
    254                 if (_logServer)
    255                 {
    256                     buf.append(request.getServerName());
    257                     buf.append(' ');
    258                 }
    259
    260                 String addr = null;
    261                 if (_preferProxiedForAddress)
    262                 {
    263                     addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
    264                 }
    265
    266                 if (addr == null)
    267                     addr = request.getRemoteAddr();
    268
    269                 buf.append(addr);
    270                 buf.append(" - ");
    271                 String user = request.getRemoteUser();
    272                 buf.append((user == null)? " - " : user);
    273                 buf.append(" [");
    274                 if (_logDateCache!=null)
    275                     buf.append(_logDateCache.format(request.getTimeStamp()));
    276                 else
    277                     buf.append(request.getTimeStampBuffer().toString());
    278
    279                 buf.append("] \"");
    280                 buf.append(request.getMethod());
    281                 buf.append(' ');
    282
    283                 request.getUri().writeTo(u8buf);
    284
    285                 buf.append(' ');
    286                 buf.append(request.getProtocol());
    287                 buf.append("\" ");
    288                 int status = response.getStatus();
    289                 if (status<=0)
    290                     status=404;
    291                 buf.append((char)('0'+((status/100)%10)));
    292                 buf.append((char)('0'+((status/10)%10)));
    293                 buf.append((char)('0'+(status%10)));
    294
    295
    296                 long responseLength=response.getContentCount();
    297                 if (responseLength >=0)
    298                 {
    299                     buf.append(' ');
    300                     if (responseLength > 99999)
    301                         buf.append(Long.toString(responseLength));
    302                     else
    303                     {
    304                         if (responseLength > 9999)
    305                             buf.append((char)('0' + ((responseLength / 10000)%10)));
    306                         if (responseLength > 999)
    307                             buf.append((char)('0' + ((responseLength /1000)%10)));
    308                         if (responseLength > 99)
    309                             buf.append((char)('0' + ((responseLength / 100)%10)));
    310                         if (responseLength > 9)
    311                             buf.append((char)('0' + ((responseLength / 10)%10)));
    312                         buf.append((char)('0' + (responseLength)%10));
    313                     }
    314                     buf.append(' ');
    315                 }
    316                 else
    317                     buf.append(" - ");
    318
    319             }
    320
    321             if (!_extended && !_logCookies && !_logLatency)
    322             {
    323                 synchronized(_writer)
    324                 {
    325                     buf.append(StringUtil.__LINE_SEPARATOR);
    326                     int l=buf.length();
    327                     if (l>_copy.length)
    328                         l=_copy.length;
    329                     buf.getChars(0,l,_copy,0);
    330                     _writer.write(_copy,0,l);
    331                     _writer.flush();
    332                     u8buf.reset();
    333                     _buffers.add(u8buf);
    334                 }
    335             }
    336             else
    337             {
    338                 synchronized(_writer)
    339                 {
    340                     int l=buf.length();
    341                     if (l>_copy.length)
    342                         l=_copy.length;
    343                     buf.getChars(0,l,_copy,0);
    344                     _writer.write(_copy,0,l);
    345                     u8buf.reset();
    346                     _buffers.add(u8buf);
    347
    348                     // TODO do outside synchronized scope
    349                     if (_extended)
    350                         logExtended(request, response, _writer);
    351
    352                     // TODO do outside synchronized scope
    353                     if (_logCookies)
    354                     {
    355                         Cookie[] cookies = request.getCookies();
    356                         if (cookies == null || cookies.length == 0)
    357                             _writer.write(" -");
    358                         else
    359                         {
    360                             _writer.write(" \"");
    361                             for (int i = 0; i < cookies.length; i++)
    362                             {
    363                                 if (i != 0)
    364                                     _writer.write(';');
    365                                 _writer.write(cookies[i].getName());
    366                                 _writer.write('=');
    367                                 _writer.write(cookies[i].getValue());
    368                             }
    369                             _writer.write('\"');
    370                         }
    371                     }
    372
    373                     if (_logLatency)
    374                     {
    375                         _writer.write(' ');
    376                         _writer.write(TypeUtil.toString(System.currentTimeMillis() - request.getTimeStamp()));
    377                     }
    378
    379                     _writer.write(StringUtil.__LINE_SEPARATOR);
    380                     _writer.flush();
    381                 }
    382             }
    383         }
    384         catch (IOException e)
    385         {
    386             Log.warn(e);
    387         }
    388
    389     }
    390
    391     /* ------------------------------------------------------------ */
    392     protected void logExtended(Request request,
    393                                Response response,
    394                                Writer writer) throws IOException
    395     {
    396         String referer = request.getHeader(HttpHeaders.REFERER);
    397         if (referer == null)
    398             writer.write("\"-\" ");
    399         else
    400         {
    401             writer.write('"');
    402             writer.write(referer);
    403             writer.write("\" ");
    404         }
    405
    406         String agent = request.getHeader(HttpHeaders.USER_AGENT);
    407         if (agent == null)
    408             writer.write("\"-\" ");
    409         else
    410         {
    411             writer.write('"');
    412             writer.write(agent);
    413             writer.write('"');
    414         }
    415     }
    416
    417     /* ------------------------------------------------------------ */
    418     protected void doStart() throws Exception
    419     {
    420         if (_logDateFormat!=null)
    421         {
    422             _logDateCache = new DateCache(_logDateFormat, _logLocale);
    423             _logDateCache.setTimeZoneID(_logTimeZone);
    424         }
    425
    426         if (_filename != null)
    427         {
    428             _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null);
    429             _closeOut = true;
    430             Log.info("Opened "+getDatedFilename());
    431         }
    432         else
    433             _fileOut = System.err;
    434
    435         _out = _fileOut;
    436
    437         if (_ignorePaths != null && _ignorePaths.length > 0)
    438         {
    439             _ignorePathMap = new PathMap();
    440             for (int i = 0; i < _ignorePaths.length; i++)
    441                 _ignorePathMap.put(_ignorePaths[i], _ignorePaths[i]);
    442         }
    443         else
    444             _ignorePathMap = null;
    445
    446         _writer = new OutputStreamWriter(_out);
    447         _buffers = new ArrayList();
    448         _copy = new char[1024];
    449         super.doStart();
    450     }
    451
    452     /* ------------------------------------------------------------ */
    453     protected void doStop() throws Exception
    454     {
    455         super.doStop();
    456         try {if (_writer != null) _writer.flush();} catch (IOException e) {Log.ignore(e);}
    457         if (_out != null && _closeOut)
    458             try {_out.close();} catch (IOException e) {Log.ignore(e);}
    459
    460         _out = null;
    461         _fileOut = null;
    462         _closeOut = false;
    463         _logDateCache = null;
    464         _writer = null;
    465         _buffers = null;
    466         _copy = null;
    467     }
    468
    469     /* ------------------------------------------------------------ */
    470     /**
    471      * @return the log File Date Format
    472      */
    473     public String getFilenameDateFormat()
    474     {
    475         return _filenameDateFormat;
    476     }
    477
    478     /* ------------------------------------------------------------ */
    479     /** Set the log file date format.
    480      * @see {@link RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)}
    481      * @param logFileDateFormat the logFileDateFormat to pass to {@link RolloverFileOutputStream}
    482      */
    483     public void setFilenameDateFormat(String logFileDateFormat)
    484     {
    485         _filenameDateFormat=logFileDateFormat;
    486     }
    487
    488 }