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.Locale;
21 import java.util.TimeZone;
22
23 import javax.servlet.http.Cookie;
24
25 import org.eclipse.jetty.http.HttpHeaders;
26 import org.eclipse.jetty.http.PathMap;
27 import org.eclipse.jetty.util.DateCache;
28 import org.eclipse.jetty.util.RolloverFileOutputStream;
29 import org.eclipse.jetty.util.StringUtil;
30 import org.eclipse.jetty.util.component.AbstractLifeCycle;
31 import org.eclipse.jetty.util.log.Log;
32
33 /**
34 * This {@link RequestLog} implementation outputs logs in the pseudo-standard
35 * NCSA common log format. Configuration options allow a choice between the
36 * standard Common Log Format (as used in the 3 log format) and the Combined Log
37 * Format (single log format). This log format can be output by most web
38 * servers, and almost all web log analysis software can understand these
39 * formats.
40 *
41 * @org.apache.xbean.XBean element="ncsaLog"
42 */
43
44 /* ------------------------------------------------------------ */
45 /**
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 private boolean _logDispatch = false;
64
65 private transient OutputStream _out;
66 private transient OutputStream _fileOut;
67 private transient DateCache _logDateCache;
68 private transient PathMap _ignorePathMap;
69 private transient Writer _writer;
70
71 /* ------------------------------------------------------------ */
72 /**
73 * Create request log object with default settings.
74 */
75 public NCSARequestLog()
76 {
77 _extended = true;
78 _append = true;
79 _retainDays = 31;
80 }
81
82 /* ------------------------------------------------------------ */
83 /**
84 * Create request log object with specified output file name.
85 *
86 * @param filename the file name for the request log.
87 * This may be in the format expected
88 * by {@link RolloverFileOutputStream}
89 */
90 public NCSARequestLog(String filename)
91 {
92 _extended = true;
93 _append = true;
94 _retainDays = 31;
95 setFilename(filename);
96 }
97
98 /* ------------------------------------------------------------ */
99 /**
100 * Set the output file name of the request log.
101 * The file name may be in the format expected by
102 * {@link RolloverFileOutputStream}.
103 *
104 * @param filename file name of the request log
105 *
106 */
107 public void setFilename(String filename)
108 {
109 if (filename != null)
110 {
111 filename = filename.trim();
112 if (filename.length() == 0)
113 filename = null;
114 }
115 _filename = filename;
116 }
117
118 /* ------------------------------------------------------------ */
119 /**
120 * Retrieve the output file name of the request log.
121 *
122 * @return file name of the request log
123 */
124 public String getFilename()
125 {
126 return _filename;
127 }
128
129 /* ------------------------------------------------------------ */
130 /**
131 * Retrieve the file name of the request log with the expanded
132 * date wildcard if the output is written to the disk using
133 * {@link RolloverFileOutputStream}.
134 *
135 * @return file name of the request log, or null if not applicable
136 */
137 public String getDatedFilename()
138 {
139 if (_fileOut instanceof RolloverFileOutputStream)
140 return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
141 return null;
142 }
143
144 /* ------------------------------------------------------------ */
145 /**
146 * Set the timestamp format for request log entries in the file.
147 * If this is not set, the pre-formated request timestamp is used.
148 *
149 * @param format timestamp format string
150 */
151 public void setLogDateFormat(String format)
152 {
153 _logDateFormat = format;
154 }
155
156 /* ------------------------------------------------------------ */
157 /**
158 * Retrieve the timestamp format string for request log entries.
159 *
160 * @return timestamp format string.
161 */
162 public String getLogDateFormat()
163 {
164 return _logDateFormat;
165 }
166
167 /* ------------------------------------------------------------ */
168 /**
169 * Set the locale of the request log.
170 *
171 * @param logLocale locale object
172 */
173 public void setLogLocale(Locale logLocale)
174 {
175 _logLocale = logLocale;
176 }
177
178 /* ------------------------------------------------------------ */
179 /**
180 * Retrieve the locale of the request log.
181 *
182 * @return locale object
183 */
184 public Locale getLogLocale()
185 {
186 return _logLocale;
187 }
188
189 /* ------------------------------------------------------------ */
190 /**
191 * Set the timezone of the request log.
192 *
193 * @param tz timezone string
194 */
195 public void setLogTimeZone(String tz)
196 {
197 _logTimeZone = tz;
198 }
199
200 /* ------------------------------------------------------------ */
201 /**
202 * Retrieve the timezone of the request log.
203 *
204 * @return timezone string
205 */
206 public String getLogTimeZone()
207 {
208 return _logTimeZone;
209 }
210
211 /* ------------------------------------------------------------ */
212 /**
213 * Set the number of days before rotated log files are deleted.
214 *
215 * @param retainDays number of days to keep a log file
216 */
217 public void setRetainDays(int retainDays)
218 {
219 _retainDays = retainDays;
220 }
221
222 /* ------------------------------------------------------------ */
223 /**
224 * Retrieve the number of days before rotated log files are deleted.
225 *
226 * @return number of days to keep a log file
227 */
228 public int getRetainDays()
229 {
230 return _retainDays;
231 }
232
233 /* ------------------------------------------------------------ */
234 /**
235 * Set the extended request log format flag.
236 *
237 * @param extended true - log the extended request information,
238 * false - do not log the extended request information
239 */
240 public void setExtended(boolean extended)
241 {
242 _extended = extended;
243 }
244
245 /* ------------------------------------------------------------ */
246 /**
247 * Retrieve the extended request log format flag.
248 *
249 * @return value of the flag
250 */
251 public boolean isExtended()
252 {
253 return _extended;
254 }
255
256 /* ------------------------------------------------------------ */
257 /**
258 * Set append to log flag.
259 *
260 * @param append true - request log file will be appended after restart,
261 * false - request log file will be overwritten after restart
262 */
263 public void setAppend(boolean append)
264 {
265 _append = append;
266 }
267
268 /* ------------------------------------------------------------ */
269 /**
270 * Retrieve append to log flag.
271 *
272 * @return value of the flag
273 */
274 public boolean isAppend()
275 {
276 return _append;
277 }
278
279 /* ------------------------------------------------------------ */
280 /**
281 * Set request paths that will not be logged.
282 *
283 * @param ignorePaths array of request paths
284 */
285 public void setIgnorePaths(String[] ignorePaths)
286 {
287 _ignorePaths = ignorePaths;
288 }
289
290 /* ------------------------------------------------------------ */
291 /**
292 * Retrieve the request paths that will not be logged.
293 *
294 * @return array of request paths
295 */
296 public String[] getIgnorePaths()
297 {
298 return _ignorePaths;
299 }
300
301 /* ------------------------------------------------------------ */
302 /**
303 * Controls logging of the request cookies.
304 *
305 * @param logCookies true - values of request cookies will be logged,
306 * false - values of request cookies will not be logged
307 */
308 public void setLogCookies(boolean logCookies)
309 {
310 _logCookies = logCookies;
311 }
312
313 /* ------------------------------------------------------------ */
314 /**
315 * Retrieve log cookies flag
316 *
317 * @return value of the flag
318 */
319 public boolean getLogCookies()
320 {
321 return _logCookies;
322 }
323
324 /* ------------------------------------------------------------ */
325 /**
326 * Controls logging of the request hostname.
327 *
328 * @param logServer true - request hostname will be logged,
329 * false - request hostname will not be logged
330 */
331 public void setLogServer(boolean logServer)
332 {
333 _logServer = logServer;
334 }
335
336 /* ------------------------------------------------------------ */
337 /**
338 * Retrieve log hostname flag.
339 *
340 * @return value of the flag
341 */
342 public boolean getLogServer()
343 {
344 return _logServer;
345 }
346
347 /* ------------------------------------------------------------ */
348 /**
349 * Controls logging of request processing time.
350 *
351 * @param logLatency true - request processing time will be logged
352 * false - request processing time will not be logged
353 */
354 public void setLogLatency(boolean logLatency)
355 {
356 _logLatency = logLatency;
357 }
358
359 /* ------------------------------------------------------------ */
360 /**
361 * Retrieve log request processing time flag.
362 *
363 * @return value of the flag
364 */
365 public boolean getLogLatency()
366 {
367 return _logLatency;
368 }
369
370 /* ------------------------------------------------------------ */
371 /**
372 * Controls whether the actual IP address of the connection or
373 * the IP address from the X-Forwarded-For header will be logged.
374 *
375 * @param preferProxiedForAddress true - IP address from header will be logged,
376 * false - IP address from the connection will be logged
377 */
378 public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
379 {
380 _preferProxiedForAddress = preferProxiedForAddress;
381 }
382
383 /* ------------------------------------------------------------ */
384 /**
385 * Retrieved log X-Forwarded-For IP address flag.
386 *
387 * @return value of the flag
388 */
389 public boolean getPreferProxiedForAddress()
390 {
391 return _preferProxiedForAddress;
392 }
393
394 /* ------------------------------------------------------------ */
395 /**
396 * Set the log file name date format.
397 * @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
398 *
399 * @param logFileDateFormat format string that is passed to {@link RolloverFileOutputStream}
400 */
401 public void setFilenameDateFormat(String logFileDateFormat)
402 {
403 _filenameDateFormat = logFileDateFormat;
404 }
405
406 /* ------------------------------------------------------------ */
407 /**
408 * Retrieve the file name date format string.
409 *
410 * @return the log File Date Format
411 */
412 public String getFilenameDateFormat()
413 {
414 return _filenameDateFormat;
415 }
416
417 /* ------------------------------------------------------------ */
418 /**
419 * Controls logging of the request dispatch time
420 *
421 * @param value true - request dispatch time will be logged
422 * false - request dispatch time will not be logged
423 */
424 public void setLogDispatch(boolean value)
425 {
426 _logDispatch = value;
427 }
428
429 /* ------------------------------------------------------------ */
430 /**
431 * Retrieve request dispatch time logging flag
432 *
433 * @return value of the flag
434 */
435 public boolean isLogDispatch()
436 {
437 return _logDispatch;
438 }
439
440 /* ------------------------------------------------------------ */
441 /**
442 * Writes the request and response information to the output stream.
443 *
444 * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response)
445 */
446 public void log(Request request, Response response)
447 {
448 try
449 {
450 if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
451 return;
452
453 if (_fileOut == null)
454 return;
455
456 StringBuilder buf= new StringBuilder(256);
457
458 if (_logServer)
459 {
460 buf.append(request.getServerName());
461 buf.append(' ');
462 }
463
464 String addr = null;
465 if (_preferProxiedForAddress)
466 {
467 addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
468 }
469
470 if (addr == null)
471 addr = request.getRemoteAddr();
472
473 buf.append(addr);
474 buf.append(" - ");
475 Authentication authentication=request.getAuthentication();
476 if (authentication instanceof Authentication.User)
477 buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
478 else
479 buf.append(" - ");
480
481 buf.append(" [");
482 if (_logDateCache != null)
483 buf.append(_logDateCache.format(request.getTimeStamp()));
484 else
485 buf.append(request.getTimeStampBuffer().toString());
486
487 buf.append("] \"");
488 buf.append(request.getMethod());
489 buf.append(' ');
490 buf.append(request.getUri().toString());
491 buf.append(' ');
492 buf.append(request.getProtocol());
493 buf.append("\" ");
494 if (request.getAsyncContinuation().isInitial())
495 {
496 int status = response.getStatus();
497 if (status <= 0)
498 status = 404;
499 buf.append((char)('0' + ((status / 100) % 10)));
500 buf.append((char)('0' + ((status / 10) % 10)));
501 buf.append((char)('0' + (status % 10)));
502 }
503 else
504 buf.append("Async");
505
506 long responseLength = response.getContentCount();
507 if (responseLength >= 0)
508 {
509 buf.append(' ');
510 if (responseLength > 99999)
511 buf.append(responseLength);
512 else
513 {
514 if (responseLength > 9999)
515 buf.append((char)('0' + ((responseLength / 10000) % 10)));
516 if (responseLength > 999)
517 buf.append((char)('0' + ((responseLength / 1000) % 10)));
518 if (responseLength > 99)
519 buf.append((char)('0' + ((responseLength / 100) % 10)));
520 if (responseLength > 9)
521 buf.append((char)('0' + ((responseLength / 10) % 10)));
522 buf.append((char)('0' + (responseLength) % 10));
523 }
524 buf.append(' ');
525 }
526 else
527 buf.append(" - ");
528
529
530 if (_extended)
531 logExtended(request, response, buf);
532
533 if (_logCookies)
534 {
535 Cookie[] cookies = request.getCookies();
536 if (cookies == null || cookies.length == 0)
537 buf.append(" -");
538 else
539 {
540 buf.append(" \"");
541 for (int i = 0; i < cookies.length; i++)
542 {
543 if (i != 0)
544 buf.append(';');
545 buf.append(cookies[i].getName());
546 buf.append('=');
547 buf.append(cookies[i].getValue());
548 }
549 buf.append('\"');
550 }
551 }
552
553 if (_logDispatch || _logLatency)
554 {
555 long now = System.currentTimeMillis();
556
557 if (_logDispatch)
558 {
559 long d = request.getDispatchTime();
560 buf.append(' ');
561 buf.append(now - (d==0 ? request.getTimeStamp():d));
562 }
563
564 if (_logLatency)
565 {
566 buf.append(' ');
567 buf.append(now - request.getTimeStamp());
568 }
569 }
570
571 buf.append(StringUtil.__LINE_SEPARATOR);
572 String log = buf.toString();
573 synchronized(this)
574 {
575 if (_writer==null)
576 return;
577 _writer.write(log);
578 _writer.flush();
579 }
580 }
581 catch (IOException e)
582 {
583 Log.warn(e);
584 }
585
586 }
587
588 /* ------------------------------------------------------------ */
589 /**
590 * Writes extended request and response information to the output stream.
591 *
592 * @param request request object
593 * @param response response object
594 * @param b StringBuilder to write to
595 * @throws IOException
596 */
597 protected void logExtended(Request request,
598 Response response,
599 StringBuilder b) throws IOException
600 {
601 String referer = request.getHeader(HttpHeaders.REFERER);
602 if (referer == null)
603 b.append("\"-\" ");
604 else
605 {
606 b.append('"');
607 b.append(referer);
608 b.append("\" ");
609 }
610
611 String agent = request.getHeader(HttpHeaders.USER_AGENT);
612 if (agent == null)
613 b.append("\"-\" ");
614 else
615 {
616 b.append('"');
617 b.append(agent);
618 b.append('"');
619 }
620 }
621
622 /* ------------------------------------------------------------ */
623 /**
624 * Set up request logging and open log file.
625 *
626 * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
627 */
628 @Override
629 protected void doStart() throws Exception
630 {
631 if (_logDateFormat != null)
632 {
633 _logDateCache = new DateCache(_logDateFormat,_logLocale);
634 _logDateCache.setTimeZoneID(_logTimeZone);
635 }
636
637 if (_filename != null)
638 {
639 _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null);
640 _closeOut = true;
641 Log.info("Opened " + getDatedFilename());
642 }
643 else
644 _fileOut = System.err;
645
646 _out = _fileOut;
647
648 if (_ignorePaths != null && _ignorePaths.length > 0)
649 {
650 _ignorePathMap = new PathMap();
651 for (int i = 0; i < _ignorePaths.length; i++)
652 _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]);
653 }
654 else
655 _ignorePathMap = null;
656
657 _writer = new OutputStreamWriter(_out);
658 super.doStart();
659 }
660
661 /* ------------------------------------------------------------ */
662 /**
663 * Close the log file and perform cleanup.
664 *
665 * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
666 */
667 @Override
668 protected void doStop() throws Exception
669 {
670 synchronized (this)
671 {
672 super.doStop();
673 try
674 {
675 if (_writer != null)
676 _writer.flush();
677 }
678 catch (IOException e)
679 {
680 Log.ignore(e);
681 }
682 if (_out != null && _closeOut)
683 try
684 {
685 _out.close();
686 }
687 catch (IOException e)
688 {
689 Log.ignore(e);
690 }
691
692 _out = null;
693 _fileOut = null;
694 _closeOut = false;
695 _logDateCache = null;
696 _writer = null;
697 }
698 }
699 }