View Javadoc

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