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