View Javadoc

1   // ========================================================================
2   // Copyright (c) 1997-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses.
12  // ========================================================================
13  
14  package org.eclipse.jetty.server;
15  
16  import java.io.IOException;
17  import java.io.OutputStream;
18  import java.io.OutputStreamWriter;
19  import java.io.Writer;
20  import java.util.Locale;
21  import java.util.TimeZone;
22  
23  import javax.servlet.http.Cookie;
24  
25  import org.eclipse.jetty.http.HttpHeaders;
26  import org.eclipse.jetty.http.PathMap;
27  import org.eclipse.jetty.util.DateCache;
28  import org.eclipse.jetty.util.RolloverFileOutputStream;
29  import org.eclipse.jetty.util.StringUtil;
30  import org.eclipse.jetty.util.component.AbstractLifeCycle;
31  import org.eclipse.jetty.util.log.Log;
32  import org.eclipse.jetty.util.log.Logger;
33  
34  /**
35   * This {@link RequestLog} implementation outputs logs in the pseudo-standard
36   * NCSA common log format. Configuration options allow a choice between the
37   * standard Common Log Format (as used in the 3 log format) and the Combined Log
38   * Format (single log format). This log format can be output by most web
39   * servers, and almost all web log analysis software can understand these
40   * formats.
41   *
42   * @org.apache.xbean.XBean element="ncsaLog"
43   */
44  
45  /* ------------------------------------------------------------ */
46  /**
47   */
48  public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
49  {
50      private static final Logger LOG = Log.getLogger(NCSARequestLog.class);
51  
52      private String _filename;
53      private boolean _extended;
54      private boolean _append;
55      private int _retainDays;
56      private boolean _closeOut;
57      private boolean _preferProxiedForAddress;
58      private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
59      private String _filenameDateFormat = null;
60      private Locale _logLocale = Locale.getDefault();
61      private String _logTimeZone = "GMT";
62      private String[] _ignorePaths;
63      private boolean _logLatency = false;
64      private boolean _logCookies = false;
65      private boolean _logServer = false;
66      private boolean _logDispatch = false;
67  
68      private transient OutputStream _out;
69      private transient OutputStream _fileOut;
70      private transient DateCache _logDateCache;
71      private transient PathMap _ignorePathMap;
72      private transient Writer _writer;
73  
74      /* ------------------------------------------------------------ */
75      /**
76       * Create request log object with default settings.
77       */
78      public NCSARequestLog()
79      {
80          _extended = true;
81          _append = true;
82          _retainDays = 31;
83      }
84  
85      /* ------------------------------------------------------------ */
86      /**
87       * Create request log object with specified output file name.
88       * 
89       * @param filename the file name for the request log.
90       *                 This may be in the format expected
91       *                 by {@link RolloverFileOutputStream}
92       */
93      public NCSARequestLog(String filename)
94      {
95          _extended = true;
96          _append = true;
97          _retainDays = 31;
98          setFilename(filename);
99      }
100 
101     /* ------------------------------------------------------------ */
102     /**
103      * Set the output file name of the request log.
104      * The file name may be in the format expected by
105      * {@link RolloverFileOutputStream}.
106      * 
107      * @param filename file name of the request log
108      *                
109      */
110     public void setFilename(String filename)
111     {
112         if (filename != null)
113         {
114             filename = filename.trim();
115             if (filename.length() == 0)
116                 filename = null;
117         }
118         _filename = filename;
119     }
120 
121     /* ------------------------------------------------------------ */
122     /**
123      * Retrieve the output file name of the request log.
124      * 
125      * @return file name of the request log
126      */
127     public String getFilename()
128     {
129         return _filename;
130     }
131 
132     /* ------------------------------------------------------------ */
133     /**
134      * Retrieve the file name of the request log with the expanded
135      * date wildcard if the output is written to the disk using
136      * {@link RolloverFileOutputStream}.
137      * 
138      * @return file name of the request log, or null if not applicable
139      */
140     public String getDatedFilename()
141     {
142         if (_fileOut instanceof RolloverFileOutputStream)
143             return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
144         return null;
145     }
146 
147     /* ------------------------------------------------------------ */
148     /**
149      * Set the timestamp format for request log entries in the file.
150      * If this is not set, the pre-formated request timestamp is used.
151      * 
152      * @param format timestamp format string 
153      */
154     public void setLogDateFormat(String format)
155     {
156         _logDateFormat = format;
157     }
158 
159     /* ------------------------------------------------------------ */
160     /**
161      * Retrieve the timestamp format string for request log entries.
162      * 
163      * @return timestamp format string.
164      */
165     public String getLogDateFormat()
166     {
167         return _logDateFormat;
168     }
169 
170     /* ------------------------------------------------------------ */
171     /**
172      * Set the locale of the request log.
173      * 
174      * @param logLocale locale object
175      */
176     public void setLogLocale(Locale logLocale)
177     {
178         _logLocale = logLocale;
179     }
180 
181     /* ------------------------------------------------------------ */
182     /**
183      * Retrieve the locale of the request log.
184      * 
185      * @return locale object
186      */
187     public Locale getLogLocale()
188     {
189         return _logLocale;
190     }
191 
192     /* ------------------------------------------------------------ */
193     /**
194      * Set the timezone of the request log.
195      * 
196      * @param tz timezone string
197      */
198     public void setLogTimeZone(String tz)
199     {
200         _logTimeZone = tz;
201     }
202 
203     /* ------------------------------------------------------------ */
204     /**
205      * Retrieve the timezone of the request log.
206      * 
207      * @return timezone string
208      */
209     public String getLogTimeZone()
210     {
211         return _logTimeZone;
212     }
213 
214     /* ------------------------------------------------------------ */
215     /**
216      * Set the number of days before rotated log files are deleted.
217      * 
218      * @param retainDays number of days to keep a log file
219      */
220     public void setRetainDays(int retainDays)
221     {
222         _retainDays = retainDays;
223     }
224 
225     /* ------------------------------------------------------------ */
226     /**
227      * Retrieve the number of days before rotated log files are deleted.
228      * 
229      * @return number of days to keep a log file
230      */
231     public int getRetainDays()
232     {
233         return _retainDays;
234     }
235 
236     /* ------------------------------------------------------------ */
237     /**
238      * Set the extended request log format flag.
239      * 
240      * @param extended true - log the extended request information,
241      *                 false - do not log the extended request information
242      */
243     public void setExtended(boolean extended)
244     {
245         _extended = extended;
246     }
247 
248     /* ------------------------------------------------------------ */
249     /**
250      * Retrieve the extended request log format flag.
251      * 
252      * @return value of the flag
253      */
254     public boolean isExtended()
255     {
256         return _extended;
257     }
258 
259     /* ------------------------------------------------------------ */
260     /**
261      * Set append to log flag.
262      * 
263      * @param append true - request log file will be appended after restart,
264      *               false - request log file will be overwritten after restart
265      */
266     public void setAppend(boolean append)
267     {
268         _append = append;
269     }
270 
271     /* ------------------------------------------------------------ */
272     /**
273      * Retrieve append to log flag.
274      * 
275      * @return value of the flag
276      */
277     public boolean isAppend()
278     {
279         return _append;
280     }
281 
282     /* ------------------------------------------------------------ */
283     /**
284      * Set request paths that will not be logged.
285      * 
286      * @param ignorePaths array of request paths
287      */
288     public void setIgnorePaths(String[] ignorePaths)
289     {
290         _ignorePaths = ignorePaths;
291     }
292 
293     /* ------------------------------------------------------------ */
294     /**
295      * Retrieve the request paths that will not be logged.
296      * 
297      * @return array of request paths
298      */
299     public String[] getIgnorePaths()
300     {
301         return _ignorePaths;
302     }
303 
304     /* ------------------------------------------------------------ */
305     /**
306      * Controls logging of the request cookies.
307      * 
308      * @param logCookies true - values of request cookies will be logged,
309      *                   false - values of request cookies will not be logged
310      */
311     public void setLogCookies(boolean logCookies)
312     {
313         _logCookies = logCookies;
314     }
315 
316     /* ------------------------------------------------------------ */
317     /**
318      * Retrieve log cookies flag
319      * 
320      * @return value of the flag
321      */
322     public boolean getLogCookies()
323     {
324         return _logCookies;
325     }
326 
327     /* ------------------------------------------------------------ */
328     /**
329      * Controls logging of the request hostname.
330      * 
331      * @param logServer true - request hostname will be logged,
332      *                  false - request hostname will not be logged
333      */
334     public void setLogServer(boolean logServer)
335     {
336         _logServer = logServer;
337     }
338 
339     /* ------------------------------------------------------------ */
340     /**
341      * Retrieve log hostname flag.
342      * 
343      * @return value of the flag
344      */
345     public boolean getLogServer()
346     {
347         return _logServer;
348     }
349 
350     /* ------------------------------------------------------------ */
351     /**
352      * Controls logging of request processing time.
353      * 
354      * @param logLatency true - request processing time will be logged
355      *                   false - request processing time will not be logged
356      */
357     public void setLogLatency(boolean logLatency)
358     {
359         _logLatency = logLatency;
360     }
361 
362     /* ------------------------------------------------------------ */
363     /**
364      * Retrieve log request processing time flag.
365      * 
366      * @return value of the flag
367      */
368     public boolean getLogLatency()
369     {
370         return _logLatency;
371     }
372 
373     /* ------------------------------------------------------------ */
374     /**
375      * Controls whether the actual IP address of the connection or
376      * the IP address from the X-Forwarded-For header will be logged.
377      * 
378      * @param preferProxiedForAddress true - IP address from header will be logged,
379      *                                false - IP address from the connection will be logged
380      */
381     public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
382     {
383         _preferProxiedForAddress = preferProxiedForAddress;
384     }
385     
386     /* ------------------------------------------------------------ */
387     /**
388      * Retrieved log X-Forwarded-For IP address flag.
389      * 
390      * @return value of the flag
391      */
392     public boolean getPreferProxiedForAddress()
393     {
394         return _preferProxiedForAddress;
395     }
396 
397     /* ------------------------------------------------------------ */
398     /**
399      * Set the log file name date format.
400      * @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
401      * 
402      * @param logFileDateFormat format string that is passed to {@link RolloverFileOutputStream}
403      */
404     public void setFilenameDateFormat(String logFileDateFormat)
405     {
406         _filenameDateFormat = logFileDateFormat;
407     }
408 
409     /* ------------------------------------------------------------ */
410     /**
411      * Retrieve the file name date format string.
412      * 
413      * @return the log File Date Format
414      */
415     public String getFilenameDateFormat()
416     {
417         return _filenameDateFormat;
418     }
419 
420     /* ------------------------------------------------------------ */
421     /** 
422      * Controls logging of the request dispatch time
423      * 
424      * @param value true - request dispatch time will be logged
425      *              false - request dispatch time will not be logged
426      */
427     public void setLogDispatch(boolean value)
428     {
429         _logDispatch = value;
430     }
431 
432     /* ------------------------------------------------------------ */
433     /**
434      * Retrieve request dispatch time logging flag
435      * 
436      * @return value of the flag
437      */
438     public boolean isLogDispatch()
439     {
440         return _logDispatch;
441     }
442 
443     /* ------------------------------------------------------------ */
444     /**
445      * Writes the request and response information to the output stream.
446      * 
447      * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response)
448      */
449     public void log(Request request, Response response)
450     {
451         try
452         {
453             if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
454                 return;
455 
456             if (_fileOut == null)
457                 return;
458 
459             StringBuilder buf= new StringBuilder(256);
460 
461             if (_logServer)
462             {
463                 buf.append(request.getServerName());
464                 buf.append(' ');
465             }
466 
467             String addr = null;
468             if (_preferProxiedForAddress)
469             {
470                 addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
471             }
472 
473             if (addr == null)
474                 addr = request.getRemoteAddr();
475 
476             buf.append(addr);
477             buf.append(" - ");
478             Authentication authentication=request.getAuthentication();
479             if (authentication instanceof Authentication.User)
480                 buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
481             else
482                 buf.append(" - ");
483 
484             buf.append(" [");
485             if (_logDateCache != null)
486                 buf.append(_logDateCache.format(request.getTimeStamp()));
487             else
488                 buf.append(request.getTimeStampBuffer().toString());
489 
490             buf.append("] \"");
491             buf.append(request.getMethod());
492             buf.append(' ');
493             buf.append(request.getUri().toString());
494             buf.append(' ');
495             buf.append(request.getProtocol());
496             buf.append("\" ");
497             if (request.getAsyncContinuation().isInitial())
498             {
499                 int status = response.getStatus();
500                 if (status <= 0)
501                     status = 404;
502                 buf.append((char)('0' + ((status / 100) % 10)));
503                 buf.append((char)('0' + ((status / 10) % 10)));
504                 buf.append((char)('0' + (status % 10)));
505             }
506             else
507                 buf.append("Async");
508 
509             long responseLength = response.getContentCount();
510             if (responseLength >= 0)
511             {
512                 buf.append(' ');
513                 if (responseLength > 99999)
514                     buf.append(responseLength);
515                 else
516                 {
517                     if (responseLength > 9999)
518                         buf.append((char)('0' + ((responseLength / 10000) % 10)));
519                     if (responseLength > 999)
520                         buf.append((char)('0' + ((responseLength / 1000) % 10)));
521                     if (responseLength > 99)
522                         buf.append((char)('0' + ((responseLength / 100) % 10)));
523                     if (responseLength > 9)
524                         buf.append((char)('0' + ((responseLength / 10) % 10)));
525                     buf.append((char)('0' + (responseLength) % 10));
526                 }
527                 buf.append(' ');
528             }
529             else
530                 buf.append(" - ");
531 
532             
533             if (_extended)
534                 logExtended(request, response, buf);
535 
536             if (_logCookies)
537             {
538                 Cookie[] cookies = request.getCookies();
539                 if (cookies == null || cookies.length == 0)
540                     buf.append(" -");
541                 else
542                 {
543                     buf.append(" \"");
544                     for (int i = 0; i < cookies.length; i++)
545                     {
546                         if (i != 0)
547                             buf.append(';');
548                         buf.append(cookies[i].getName());
549                         buf.append('=');
550                         buf.append(cookies[i].getValue());
551                     }
552                     buf.append('\"');
553                 }
554             }
555 
556             if (_logDispatch || _logLatency)
557             {
558                 long now = System.currentTimeMillis();
559 
560                 if (_logDispatch)
561                 {   
562                     long d = request.getDispatchTime();
563                     buf.append(' ');
564                     buf.append(now - (d==0 ? request.getTimeStamp():d));
565                 }
566 
567                 if (_logLatency)
568                 {
569                     buf.append(' ');
570                     buf.append(now - request.getTimeStamp());
571                 }
572             }
573 
574             buf.append(StringUtil.__LINE_SEPARATOR);
575             String log = buf.toString();
576             synchronized(this)
577             {
578                 if (_writer==null)
579                     return;
580                 _writer.write(log);
581                 _writer.flush();
582             }
583         }
584         catch (IOException e)
585         {
586             LOG.warn(e);
587         }
588 
589     }
590 
591     /* ------------------------------------------------------------ */
592     /**
593      * Writes extended request and response information to the output stream.
594      * 
595      * @param request request object
596      * @param response response object
597      * @param b StringBuilder to write to
598      * @throws IOException
599      */
600     protected void logExtended(Request request,
601                                Response response,
602                                StringBuilder b) throws IOException
603     {
604         String referer = request.getHeader(HttpHeaders.REFERER);
605         if (referer == null)
606             b.append("\"-\" ");
607         else
608         {
609             b.append('"');
610             b.append(referer);
611             b.append("\" ");
612         }
613 
614         String agent = request.getHeader(HttpHeaders.USER_AGENT);
615         if (agent == null)
616             b.append("\"-\" ");
617         else
618         {
619             b.append('"');
620             b.append(agent);
621             b.append('"');
622         }
623     }
624 
625     /* ------------------------------------------------------------ */
626     /**
627      * Set up request logging and open log file.
628      * 
629      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
630      */
631     @Override
632     protected void doStart() throws Exception
633     {
634         if (_logDateFormat != null)
635         {
636             _logDateCache = new DateCache(_logDateFormat,_logLocale);
637             _logDateCache.setTimeZoneID(_logTimeZone);
638         }
639 
640         if (_filename != null)
641         {
642             _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null);
643             _closeOut = true;
644             LOG.info("Opened " + getDatedFilename());
645         }
646         else
647             _fileOut = System.err;
648 
649         _out = _fileOut;
650 
651         if (_ignorePaths != null && _ignorePaths.length > 0)
652         {
653             _ignorePathMap = new PathMap();
654             for (int i = 0; i < _ignorePaths.length; i++)
655                 _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]);
656         }
657         else
658             _ignorePathMap = null;
659 
660         _writer = new OutputStreamWriter(_out);
661         super.doStart();
662     }
663 
664     /* ------------------------------------------------------------ */
665     /**
666      * Close the log file and perform cleanup.
667      * 
668      * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
669      */
670     @Override
671     protected void doStop() throws Exception
672     {
673         synchronized (this)
674         {
675             super.doStop();
676             try
677             {
678                 if (_writer != null)
679                     _writer.flush();
680             }
681             catch (IOException e)
682             {
683                 LOG.ignore(e);
684             }
685             if (_out != null && _closeOut)
686                 try
687                 {
688                     _out.close();
689                 }
690                 catch (IOException e)
691                 {
692                     LOG.ignore(e);
693                 }
694 
695             _out = null;
696             _fileOut = null;
697             _closeOut = false;
698             _logDateCache = null;
699             _writer = null;
700         }
701     }
702 }