View Javadoc

1   // ========================================================================
2   // Copyright (c) 1997-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses.
12  // ========================================================================
13  
14  package org.eclipse.jetty.server;
15  
16  import java.io.IOException;
17  import java.io.OutputStream;
18  import java.io.OutputStreamWriter;
19  import java.io.Writer;
20  import java.util.ArrayList;
21  import java.util.Locale;
22  import java.util.TimeZone;
23  import javax.servlet.http.Cookie;
24  
25  import org.eclipse.jetty.http.HttpHeaders;
26  import org.eclipse.jetty.http.PathMap;
27  import org.eclipse.jetty.util.DateCache;
28  import org.eclipse.jetty.util.RolloverFileOutputStream;
29  import org.eclipse.jetty.util.StringUtil;
30  import org.eclipse.jetty.util.Utf8StringBuilder;
31  import org.eclipse.jetty.util.component.AbstractLifeCycle;
32  import org.eclipse.jetty.util.log.Log;
33  
34  /**
35   * This {@link RequestLog} implementation outputs logs in the pseudo-standard
36   * NCSA common log format. Configuration options allow a choice between the
37   * standard Common Log Format (as used in the 3 log format) and the Combined Log
38   * Format (single log format). This log format can be output by most web
39   * servers, and almost all web log analysis software can understand these
40   * formats.
41   *
42   * @org.apache.xbean.XBean element="ncsaLog"
43   */
44  public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
45  {
46      private String _filename;
47      private boolean _extended;
48      private boolean _append;
49      private int _retainDays;
50      private boolean _closeOut;
51      private boolean _preferProxiedForAddress;
52      private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
53      private String _filenameDateFormat = null;
54      private Locale _logLocale = Locale.getDefault();
55      private String _logTimeZone = "GMT";
56      private String[] _ignorePaths;
57      private boolean _logLatency = false;
58      private boolean _logCookies = false;
59      private boolean _logServer = false;
60  
61      private transient OutputStream _out;
62      private transient OutputStream _fileOut;
63      private transient DateCache _logDateCache;
64      private transient PathMap _ignorePathMap;
65      private transient Writer _writer;
66      private transient ArrayList _buffers;
67      private transient char[] _copy;
68  
69      public NCSARequestLog()
70      {
71          _extended = true;
72          _append = true;
73          _retainDays = 31;
74      }
75  
76      /* ------------------------------------------------------------ */
77      /**
78       * @param filename
79       *                The filename for the request log. This may be in the
80       *                format expected by {@link RolloverFileOutputStream}
81       */
82      public NCSARequestLog(String filename)
83      {
84          _extended = true;
85          _append = true;
86          _retainDays = 31;
87          setFilename(filename);
88      }
89  
90      /* ------------------------------------------------------------ */
91      /**
92       * @param filename
93       *                The filename for the request log. This may be in the
94       *                format expected by {@link RolloverFileOutputStream}
95       */
96      public void setFilename(String filename)
97      {
98          if (filename != null)
99          {
100             filename = filename.trim();
101             if (filename.length() == 0)
102                 filename = null;
103         }
104         _filename = filename;
105     }
106 
107     public String getFilename()
108     {
109         return _filename;
110     }
111 
112     public String getDatedFilename()
113     {
114         if (_fileOut instanceof RolloverFileOutputStream)
115             return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
116         return null;
117     }
118 
119     /* ------------------------------------------------------------ */
120     /**
121      * @param format
122      *                Format for the timestamps in the log file. If not set, the
123      *                pre-formated request timestamp is used.
124      */
125     public void setLogDateFormat(String format)
126     {
127         _logDateFormat = format;
128     }
129 
130     public String getLogDateFormat()
131     {
132         return _logDateFormat;
133     }
134 
135     public void setLogLocale(Locale logLocale)
136     {
137         _logLocale = logLocale;
138     }
139 
140     public Locale getLogLocale()
141     {
142         return _logLocale;
143     }
144 
145     public void setLogTimeZone(String tz)
146     {
147         _logTimeZone = tz;
148     }
149 
150     public String getLogTimeZone()
151     {
152         return _logTimeZone;
153     }
154 
155     public void setRetainDays(int retainDays)
156     {
157         _retainDays = retainDays;
158     }
159 
160     public int getRetainDays()
161     {
162         return _retainDays;
163     }
164 
165     public void setExtended(boolean extended)
166     {
167         _extended = extended;
168     }
169 
170     public boolean isExtended()
171     {
172         return _extended;
173     }
174 
175     public void setAppend(boolean append)
176     {
177         _append = append;
178     }
179 
180     public boolean isAppend()
181     {
182         return _append;
183     }
184 
185     public void setIgnorePaths(String[] ignorePaths)
186     {
187         _ignorePaths = ignorePaths;
188     }
189 
190     public String[] getIgnorePaths()
191     {
192         return _ignorePaths;
193     }
194 
195     public void setLogCookies(boolean logCookies)
196     {
197         _logCookies = logCookies;
198     }
199 
200     public boolean getLogCookies()
201     {
202         return _logCookies;
203     }
204 
205     public boolean getLogServer()
206     {
207         return _logServer;
208     }
209 
210     public void setLogServer(boolean logServer)
211     {
212         _logServer = logServer;
213     }
214 
215     public void setLogLatency(boolean logLatency)
216     {
217         _logLatency = logLatency;
218     }
219 
220     public boolean getLogLatency()
221     {
222         return _logLatency;
223     }
224 
225     public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
226     {
227         _preferProxiedForAddress = preferProxiedForAddress;
228     }
229 
230     /* ------------------------------------------------------------ */
231     public void log(Request request, Response response)
232     {
233         if (!isStarted())
234             return;
235 
236         try
237         {
238             if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
239                 return;
240 
241             if (_fileOut == null)
242                 return;
243 
244             Utf8StringBuilder u8buf;
245             StringBuilder buf;
246             synchronized(_writer)
247             {
248                 int size=_buffers.size();
249                 u8buf = size==0?new Utf8StringBuilder(160):(Utf8StringBuilder)_buffers.remove(size-1);
250                 buf = u8buf.getStringBuilder();
251             }
252 
253             if (_logServer)
254             {
255                 buf.append(request.getServerName());
256                 buf.append(' ');
257             }
258 
259             String addr = null;
260             if (_preferProxiedForAddress)
261             {
262                 addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
263             }
264 
265             if (addr == null)
266                 addr = request.getRemoteAddr();
267 
268             buf.append(addr);
269             buf.append(" - ");
270             Authentication authentication=request.getAuthentication();
271             if (authentication instanceof Authentication.User)
272                 buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
273             else
274                 buf.append(" - ");
275 
276             buf.append(" [");
277             if (_logDateCache != null)
278                 buf.append(_logDateCache.format(request.getTimeStamp()));
279             else
280                 buf.append(request.getTimeStampBuffer().toString());
281 
282             buf.append("] \"");
283             buf.append(request.getMethod());
284             buf.append(' ');
285 
286             request.getUri().writeTo(u8buf);
287 
288             buf.append(' ');
289             buf.append(request.getProtocol());
290             buf.append("\" ");
291             if (request.getAsyncContinuation().isInitial())
292             {
293                 int status = response.getStatus();
294                 if (status <= 0)
295                     status = 404;
296                 buf.append((char)('0' + ((status / 100) % 10)));
297                 buf.append((char)('0' + ((status / 10) % 10)));
298                 buf.append((char)('0' + (status % 10)));
299             }
300             else
301                 buf.append("Async");
302 
303             long responseLength = response.getContentCount();
304             if (responseLength >= 0)
305             {
306                 buf.append(' ');
307                 if (responseLength > 99999)
308                     buf.append(responseLength);
309                 else
310                 {
311                     if (responseLength > 9999)
312                         buf.append((char)('0' + ((responseLength / 10000) % 10)));
313                     if (responseLength > 999)
314                         buf.append((char)('0' + ((responseLength / 1000) % 10)));
315                     if (responseLength > 99)
316                         buf.append((char)('0' + ((responseLength / 100) % 10)));
317                     if (responseLength > 9)
318                         buf.append((char)('0' + ((responseLength / 10) % 10)));
319                     buf.append((char)('0' + (responseLength) % 10));
320                 }
321                 buf.append(' ');
322             }
323             else
324                 buf.append(" - ");
325 
326             if (!_extended && !_logCookies && !_logLatency)
327 	    {
328                 synchronized(_writer)
329 		{
330                     buf.append(StringUtil.__LINE_SEPARATOR);
331                     int l=buf.length();
332                     if (l>_copy.length)
333                         l=_copy.length;
334                     buf.getChars(0,l,_copy,0);
335                     _writer.write(_copy,0,l);
336                     _writer.flush();
337                     u8buf.reset();
338                     _buffers.add(u8buf);
339                 }
340             }
341             else
342             {
343                 synchronized(_writer)
344                 {
345                     int l=buf.length();
346                     if (l>_copy.length)
347                         l=_copy.length;
348                     buf.getChars(0,l,_copy,0);
349                     _writer.write(_copy,0,l);
350                     u8buf.reset();
351                     _buffers.add(u8buf);
352 
353                     // TODO do outside synchronized scope
354                     if (_extended)
355                         logExtended(request, response, _writer);
356 
357                     // TODO do outside synchronized scope
358                     if (_logCookies)
359                     {
360                         Cookie[] cookies = request.getCookies();
361                         if (cookies == null || cookies.length == 0)
362                             _writer.write(" -");
363                         else
364                         {
365                             _writer.write(" \"");
366                             for (int i = 0; i < cookies.length; i++)
367                             {
368                                 if (i != 0)
369                                     _writer.write(';');
370                                 _writer.write(cookies[i].getName());
371                                 _writer.write('=');
372                                 _writer.write(cookies[i].getValue());
373                             }
374                             _writer.write('\"');
375                         }
376                     }
377 
378                     if (_logLatency)
379                     {
380                         _writer.write(' ');
381                         _writer.write(Long.toString(System.currentTimeMillis() - request.getTimeStamp()));
382                     }
383 
384                     _writer.write(StringUtil.__LINE_SEPARATOR);
385                     _writer.flush();
386                 }
387             }
388         }
389         catch (IOException e)
390         {
391             Log.warn(e);
392         }
393 
394     }
395 
396     /* ------------------------------------------------------------ */
397     protected void logExtended(Request request,
398                                Response response,
399                                Writer writer) throws IOException
400     {
401         String referer = request.getHeader(HttpHeaders.REFERER);
402         if (referer == null)
403             writer.write("\"-\" ");
404         else
405         {
406             writer.write('"');
407             writer.write(referer);
408             writer.write("\" ");
409         }
410 
411         String agent = request.getHeader(HttpHeaders.USER_AGENT);
412         if (agent == null)
413             writer.write("\"-\" ");
414         else
415         {
416             writer.write('"');
417             writer.write(agent);
418             writer.write('"');
419         }
420     }
421 
422     /* ------------------------------------------------------------ */
423     @Override
424     protected void doStart() throws Exception
425     {
426         if (_logDateFormat != null)
427         {
428             _logDateCache = new DateCache(_logDateFormat,_logLocale);
429             _logDateCache.setTimeZoneID(_logTimeZone);
430         }
431 
432         if (_filename != null)
433         {
434             _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null);
435             _closeOut = true;
436             Log.info("Opened " + getDatedFilename());
437         }
438         else
439             _fileOut = System.err;
440 
441         _out = _fileOut;
442 
443         if (_ignorePaths != null && _ignorePaths.length > 0)
444         {
445             _ignorePathMap = new PathMap();
446             for (int i = 0; i < _ignorePaths.length; i++)
447                 _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]);
448         }
449         else
450             _ignorePathMap = null;
451 
452         _writer = new OutputStreamWriter(_out);
453         _buffers = new ArrayList();
454         _copy = new char[1024];
455         super.doStart();
456     }
457 
458     /* ------------------------------------------------------------ */
459     @Override
460     protected void doStop() throws Exception
461     {
462         super.doStop();
463         try
464         {
465             if (_writer != null)
466                 _writer.flush();
467         }
468         catch (IOException e)
469         {
470             Log.ignore(e);
471         }
472         if (_out != null && _closeOut)
473             try
474             {
475                 _out.close();
476             }
477             catch (IOException e)
478             {
479                 Log.ignore(e);
480             }
481 
482         _out = null;
483         _fileOut = null;
484         _closeOut = false;
485         _logDateCache = null;
486         _writer = null;
487         _buffers = null;
488         _copy = null;
489     }
490 
491     /* ------------------------------------------------------------ */
492     /**
493      * @return the log File Date Format
494      */
495     public String getFilenameDateFormat()
496     {
497         return _filenameDateFormat;
498     }
499 
500     /* ------------------------------------------------------------ */
501     /**
502      * Set the log file date format.
503      *
504      * @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
505      * @param logFileDateFormat
506      *                the logFileDateFormat to pass to
507      *                {@link RolloverFileOutputStream}
508      */
509     public void setFilenameDateFormat(String logFileDateFormat)
510     {
511         _filenameDateFormat = logFileDateFormat;
512     }
513 
514 }