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.Locale;
22  import java.util.TimeZone;
23  
24  import javax.servlet.http.Cookie;
25  
26  import org.mortbay.component.AbstractLifeCycle;
27  import org.mortbay.jetty.servlet.PathMap;
28  import org.mortbay.log.Log;
29  import org.mortbay.util.DateCache;
30  import org.mortbay.util.RolloverFileOutputStream;
31  import org.mortbay.util.StringUtil;
32  import org.mortbay.util.TypeUtil;
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   * @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  
70      public NCSARequestLog()
71      {
72          _extended = true;
73          _append = true;
74          _retainDays = 31;
75      }
76  
77      /* ------------------------------------------------------------ */
78      /**
79       * @param filename
80       *                The filename for the request log. This may be in the
81       *                format expected by {@link RolloverFileOutputStream}
82       */
83      public NCSARequestLog(String filename)
84      {
85          _extended = true;
86          _append = true;
87          _retainDays = 31;
88          setFilename(filename);
89      }
90  
91      /* ------------------------------------------------------------ */
92      /**
93       * @param filename
94       *                The filename for the request log. This may be in the
95       *                format expected by {@link RolloverFileOutputStream}
96       */
97      public void setFilename(String filename)
98      {
99          if (filename != null)
100         {
101             filename = filename.trim();
102             if (filename.length() == 0)
103                 filename = null;
104         }
105         _filename = filename;
106     }
107 
108     public String getFilename()
109     {
110         return _filename;
111     }
112 
113     public String getDatedFilename()
114     {
115         if (_fileOut instanceof RolloverFileOutputStream)
116             return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
117         return null;
118     }
119 
120     /* ------------------------------------------------------------ */
121     /**
122      * @param format
123      *                Format for the timestamps in the log file. If not set, the
124      *                pre-formated request timestamp is used.
125      */
126     public void setLogDateFormat(String format)
127     {
128         _logDateFormat = format;
129     }
130 
131     public String getLogDateFormat()
132     {
133         return _logDateFormat;
134     }
135 
136     public void setLogTimeZone(String tz)
137     {
138         _logTimeZone = tz;
139     }
140 
141     public String getLogTimeZone()
142     {
143         return _logTimeZone;
144     }
145 
146     public void setRetainDays(int retainDays)
147     {
148         _retainDays = retainDays;
149     }
150 
151     public int getRetainDays()
152     {
153         return _retainDays;
154     }
155 
156     public void setExtended(boolean extended)
157     {
158         _extended = extended;
159     }
160 
161     public boolean isExtended()
162     {
163         return _extended;
164     }
165 
166     public void setAppend(boolean append)
167     {
168         _append = append;
169     }
170 
171     public boolean isAppend()
172     {
173         return _append;
174     }
175 
176     public void setIgnorePaths(String[] ignorePaths)
177     {
178         _ignorePaths = ignorePaths;
179     }
180 
181     public String[] getIgnorePaths()
182     {
183         return _ignorePaths;
184     }
185 
186     public void setLogCookies(boolean logCookies)
187     {
188         _logCookies = logCookies;
189     }
190 
191     public boolean getLogCookies()
192     {
193         return _logCookies;
194     }
195 
196     public boolean getLogServer()
197     {
198         return _logServer;
199     }
200 
201     public void setLogServer(boolean logServer)
202     {
203         _logServer = logServer;
204     }
205 
206     public void setLogLatency(boolean logLatency)
207     {
208         _logLatency = logLatency;
209     }
210 
211     public boolean getLogLatency()
212     {
213         return _logLatency;
214     }
215 
216     public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
217     {
218         _preferProxiedForAddress = preferProxiedForAddress;
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             StringBuilder buf = new StringBuilder(160);
235             String log = null;
236             if (_logServer)
237             {
238                 buf.append(request.getServerName());
239                 buf.append(' ');
240             }
241 
242             String addr = null;
243             if (_preferProxiedForAddress)
244             {
245                 addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
246             }
247 
248             if (addr == null)
249                 addr = request.getRemoteAddr();
250 
251             buf.append(addr);
252             buf.append(" - ");
253             String user = request.getRemoteUser();
254             buf.append((user == null)?" - ":user);
255             buf.append(" [");
256             if (_logDateCache != null)
257                 buf.append(_logDateCache.format(request.getTimeStamp()));
258             else
259                 buf.append(request.getTimeStampBuffer().toString());
260 
261             buf.append("] \"");
262             buf.append(request.getMethod());
263             buf.append(' ');
264             buf.append(request.getUri());
265             buf.append(' ');
266             buf.append(request.getProtocol());
267             buf.append("\" ");
268             if (request.isSuspended())
269                 buf.append("SUS");
270             else
271             {
272                 int status = response.getStatus();
273                 if (status <= 0)
274                     status = 404;
275                 buf.append((char)('0' + ((status / 100) % 10)));
276                 buf.append((char)('0' + ((status / 10) % 10)));
277                 buf.append((char)('0' + (status % 10)));
278             }
279 
280             long responseLength = response.getContentCount();
281             if (responseLength >= 0)
282             {
283                 buf.append(' ');
284                 if (responseLength > 99999)
285                     buf.append(Long.toString(responseLength));
286                 else
287                 {
288                     if (responseLength > 9999)
289                         buf.append((char)('0' + ((responseLength / 10000) % 10)));
290                     if (responseLength > 999)
291                         buf.append((char)('0' + ((responseLength / 1000) % 10)));
292                     if (responseLength > 99)
293                         buf.append((char)('0' + ((responseLength / 100) % 10)));
294                     if (responseLength > 9)
295                         buf.append((char)('0' + ((responseLength / 10) % 10)));
296                     buf.append((char)('0' + (responseLength) % 10));
297                 }
298                 buf.append(' ');
299             }
300             else
301                 buf.append(" - ");
302 
303             log = buf.toString();
304 
305             synchronized (_writer)
306             {
307                 _writer.write(log);
308                 if (_extended)
309                     logExtended(request,response,_writer);
310 
311                 if (_logCookies)
312                 {
313                     Cookie[] cookies = request.getCookies();
314                     if (cookies == null || cookies.length == 0)
315                         _writer.write(" -");
316                     else
317                     {
318                         _writer.write(" \"");
319                         for (int i = 0; i < cookies.length; i++)
320                         {
321                             if (i != 0)
322                                 _writer.write(';');
323                             _writer.write(cookies[i].getName());
324                             _writer.write('=');
325                             _writer.write(cookies[i].getValue());
326                         }
327                         _writer.write("\"");
328                     }
329                 }
330 
331                 if (_logLatency)
332                 {
333                     _writer.write(" ");
334                     _writer.write(TypeUtil.toString(System.currentTimeMillis() - request.getTimeStamp()));
335                 }
336 
337                 _writer.write(StringUtil.__LINE_SEPARATOR);
338                 _writer.flush();
339 
340             }
341         }
342         catch (IOException e)
343         {
344             Log.warn(e);
345         }
346 
347     }
348 
349     protected void logExtended(Request request, Response response, Writer writer) throws IOException
350     {
351         String referer = request.getHeader(HttpHeaders.REFERER);
352         if (referer == null)
353             writer.write("\"-\" ");
354         else
355         {
356             writer.write('"');
357             writer.write(referer);
358             writer.write("\" ");
359         }
360 
361         String agent = request.getHeader(HttpHeaders.USER_AGENT);
362         if (agent == null)
363             writer.write("\"-\" ");
364         else
365         {
366             writer.write('"');
367             writer.write(agent);
368             writer.write('"');
369         }
370     }
371 
372     protected void doStart() throws Exception
373     {
374         if (_logDateFormat != null)
375         {
376             _logDateCache = new DateCache(_logDateFormat,_logLocale);
377             _logDateCache.setTimeZoneID(_logTimeZone);
378         }
379 
380         if (_filename != null)
381         {
382             _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null);
383             _closeOut = true;
384             Log.info("Opened " + getDatedFilename());
385         }
386         else
387             _fileOut = System.err;
388 
389         _out = _fileOut;
390 
391         if (_ignorePaths != null && _ignorePaths.length > 0)
392         {
393             _ignorePathMap = new PathMap();
394             for (int i = 0; i < _ignorePaths.length; i++)
395                 _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]);
396         }
397         else
398             _ignorePathMap = null;
399 
400         _writer = new OutputStreamWriter(_out);
401         super.doStart();
402     }
403 
404     protected void doStop() throws Exception
405     {
406         super.doStop();
407         try
408         {
409             if (_writer != null)
410                 _writer.flush();
411         }
412         catch (IOException e)
413         {
414             Log.ignore(e);
415         }
416         if (_out != null && _closeOut)
417             try
418             {
419                 _out.close();
420             }
421             catch (IOException e)
422             {
423                 Log.ignore(e);
424             }
425 
426         _out = null;
427         _fileOut = null;
428         _closeOut = false;
429         _logDateCache = null;
430         _writer = null;
431     }
432 
433     /* ------------------------------------------------------------ */
434     /**
435      * @return the log File Date Format
436      */
437     public String getFilenameDateFormat()
438     {
439         return _filenameDateFormat;
440     }
441 
442     /* ------------------------------------------------------------ */
443     /**
444      * Set the log file date format.
445      * 
446      * @see {@link RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)}
447      * @param logFileDateFormat
448      *                the logFileDateFormat to pass to
449      *                {@link RolloverFileOutputStream}
450      */
451     public void setFilenameDateFormat(String logFileDateFormat)
452     {
453         _filenameDateFormat = logFileDateFormat;
454     }
455 
456 }