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