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