View Javadoc

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