View Javadoc

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