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