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