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     
135     public void setLogTimeZone(String tz) 
136     {
137         _logTimeZone = tz;
138     }
139     
140     public String getLogTimeZone()
141     {
142         return _logTimeZone;
143     }
144     
145     public void setRetainDays(int retainDays)
146     {
147         _retainDays = retainDays;
148     }
149     
150     public int getRetainDays()
151     {
152         return _retainDays;
153     }
154     
155     public void setExtended(boolean extended)
156     {
157         _extended = extended;
158     }
159     
160     public boolean isExtended() 
161     {
162         return _extended;
163     }
164     
165     public void setAppend(boolean append)
166     {
167         _append = append;
168     }
169     
170     public boolean isAppend()
171     {
172         return _append;
173     }
174     
175     public void setIgnorePaths(String[] ignorePaths) 
176     {
177         _ignorePaths = ignorePaths;
178     }
179     
180     public String[] getIgnorePaths()
181     {
182         return _ignorePaths;
183     }
184     
185     public void setLogCookies(boolean logCookies) 
186     {
187         _logCookies = logCookies;
188     }
189     
190     public boolean getLogCookies()
191     {
192         return _logCookies;
193     }
194 
195     public boolean getLogServer()
196     {
197         return _logServer;
198     }
199 
200     public void setLogServer(boolean logServer)
201     {
202         _logServer=logServer;
203     }
204     
205     public void setLogLatency(boolean logLatency) 
206     {
207         _logLatency = logLatency;
208     }
209     
210     public boolean getLogLatency()
211     {
212         return _logLatency;
213     }
214     
215     public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
216     {
217         _preferProxiedForAddress = preferProxiedForAddress;
218     }
219 
220     /* ------------------------------------------------------------ */
221     public void log(Request request, Response response)
222     {
223         if (!isStarted()) 
224             return;
225         
226         try 
227         {
228             if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
229                 return;
230             
231             if (_fileOut == null)
232                 return;
233 
234             Utf8StringBuffer u8buf;
235             StringBuffer buf;
236             synchronized(_writer)
237             {
238                 int size=_buffers.size();
239                 u8buf = size==0?new Utf8StringBuffer(160):(Utf8StringBuffer)_buffers.remove(size-1);
240                 buf = u8buf.getStringBuffer();
241             }
242             
243             synchronized(buf) // for efficiency until we can use StringBuilder
244             {
245                 if (_logServer)
246                 {
247                     buf.append(request.getServerName());
248                     buf.append(' ');
249                 }
250 
251                 String addr = null;
252                 if (_preferProxiedForAddress) 
253                 {
254                     addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
255                 }
256 
257                 if (addr == null) 
258                     addr = request.getRemoteAddr();
259 
260                 buf.append(addr);
261                 buf.append(" - ");
262                 String user = request.getRemoteUser();
263                 buf.append((user == null)? " - " : user);
264                 buf.append(" [");
265                 if (_logDateCache!=null)
266                     buf.append(_logDateCache.format(request.getTimeStamp()));
267                 else
268                     buf.append(request.getTimeStampBuffer().toString());
269                     
270                 buf.append("] \"");
271                 buf.append(request.getMethod());
272                 buf.append(' ');
273                 
274                 request.getUri().writeTo(u8buf);
275                 
276                 buf.append(' ');
277                 buf.append(request.getProtocol());
278                 buf.append("\" ");
279                 int status = response.getStatus();
280                 if (status<=0)
281                     status=404;
282                 buf.append((char)('0'+((status/100)%10)));
283                 buf.append((char)('0'+((status/10)%10)));
284                 buf.append((char)('0'+(status%10)));
285 
286 
287                 long responseLength=response.getContentCount();
288                 if (responseLength >=0)
289                 {
290                     buf.append(' ');
291                     if (responseLength > 99999)
292                         buf.append(Long.toString(responseLength));
293                     else 
294                     {
295                         if (responseLength > 9999)
296                             buf.append((char)('0' + ((responseLength / 10000)%10)));
297                         if (responseLength > 999)
298                             buf.append((char)('0' + ((responseLength /1000)%10)));
299                         if (responseLength > 99)
300                             buf.append((char)('0' + ((responseLength / 100)%10)));
301                         if (responseLength > 9)
302                             buf.append((char)('0' + ((responseLength / 10)%10)));
303                         buf.append((char)('0' + (responseLength)%10));
304                     }
305                     buf.append(' ');
306                 }
307                 else 
308                     buf.append(" - ");
309 
310             }
311 
312             if (!_extended && !_logCookies && !_logLatency)
313             {
314                 synchronized(_writer)
315                 {
316                     buf.append(StringUtil.__LINE_SEPARATOR);
317                     int l=buf.length();
318                     if (l>_copy.length)
319                         l=_copy.length;  
320                     buf.getChars(0,l,_copy,0); 
321                     _writer.write(_copy,0,l);
322                     _writer.flush();
323                     u8buf.reset();
324                     _buffers.add(u8buf); 
325                 }
326             }
327             else
328             {
329                 synchronized(_writer)
330                 {
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                     u8buf.reset();
337                     _buffers.add(u8buf); 
338 
339                     // TODO do outside synchronized scope
340                     if (_extended)
341                         logExtended(request, response, _writer);
342 
343                     // TODO do outside synchronized scope
344                     if (_logCookies)
345                     {
346                         Cookie[] cookies = request.getCookies(); 
347                         if (cookies == null || cookies.length == 0)
348                             _writer.write(" -");
349                         else
350                         {
351                             _writer.write(" \"");
352                             for (int i = 0; i < cookies.length; i++) 
353                             {
354                                 if (i != 0)
355                                     _writer.write(';');
356                                 _writer.write(cookies[i].getName());
357                                 _writer.write('=');
358                                 _writer.write(cookies[i].getValue());
359                             }
360                             _writer.write('\"');
361                         }
362                     }
363 
364                     if (_logLatency)
365                     {
366                         _writer.write(' ');
367                         _writer.write(TypeUtil.toString(System.currentTimeMillis() - request.getTimeStamp()));
368                     }
369 
370                     _writer.write(StringUtil.__LINE_SEPARATOR);
371                     _writer.flush();
372                 }
373             }
374         } 
375         catch (IOException e) 
376         {
377             Log.warn(e);
378         }
379         
380     }
381 
382     /* ------------------------------------------------------------ */
383     protected void logExtended(Request request, 
384                                Response response, 
385                                Writer writer) throws IOException 
386     {
387         String referer = request.getHeader(HttpHeaders.REFERER);
388         if (referer == null) 
389             writer.write("\"-\" ");
390         else 
391         {
392             writer.write('"');
393             writer.write(referer);
394             writer.write("\" ");
395         }
396         
397         String agent = request.getHeader(HttpHeaders.USER_AGENT);
398         if (agent == null)
399             writer.write("\"-\" ");
400         else
401         {
402             writer.write('"');
403             writer.write(agent);
404             writer.write('"');
405         }          
406     }
407 
408     /* ------------------------------------------------------------ */
409     protected void doStart() throws Exception
410     {
411         if (_logDateFormat!=null)
412         {       
413             _logDateCache = new DateCache(_logDateFormat, _logLocale);
414             _logDateCache.setTimeZoneID(_logTimeZone);
415         }
416         
417         if (_filename != null) 
418         {
419             _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null);
420             _closeOut = true;
421             Log.info("Opened "+getDatedFilename());
422         }
423         else 
424             _fileOut = System.err;
425         
426         _out = _fileOut;
427         
428         if (_ignorePaths != null && _ignorePaths.length > 0)
429         {
430             _ignorePathMap = new PathMap();
431             for (int i = 0; i < _ignorePaths.length; i++) 
432                 _ignorePathMap.put(_ignorePaths[i], _ignorePaths[i]);
433         }
434         else 
435             _ignorePathMap = null;
436         
437         _writer = new OutputStreamWriter(_out);
438         _buffers = new ArrayList();
439         _copy = new char[1024];
440         super.doStart();
441     }
442 
443     /* ------------------------------------------------------------ */
444     protected void doStop() throws Exception
445     {
446         super.doStop();
447         try {if (_writer != null) _writer.flush();} catch (IOException e) {Log.ignore(e);}
448         if (_out != null && _closeOut) 
449             try {_out.close();} catch (IOException e) {Log.ignore(e);}
450             
451         _out = null;
452         _fileOut = null;
453         _closeOut = false;
454         _logDateCache = null;
455         _writer = null;
456         _buffers = null;
457         _copy = null;
458     }
459 
460     /* ------------------------------------------------------------ */
461     /**
462      * @return the log File Date Format
463      */
464     public String getFilenameDateFormat()
465     {
466         return _filenameDateFormat;
467     }
468 
469     /* ------------------------------------------------------------ */
470     /** Set the log file date format.
471      * @see {@link RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)}
472      * @param logFileDateFormat the logFileDateFormat to pass to {@link RolloverFileOutputStream}
473      */
474     public void setFilenameDateFormat(String logFileDateFormat)
475     {
476         _filenameDateFormat=logFileDateFormat;
477     }
478 
479 }