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