1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.valves;
20
21
22 import java.io.BufferedWriter;
23 import java.io.File;
24 import java.io.FileWriter;
25 import java.io.IOException;
26 import java.io.PrintWriter;
27 import java.net.InetAddress;
28 import java.text.SimpleDateFormat;
29 import java.util.ArrayList;
30 import java.util.Calendar;
31 import java.util.Date;
32 import java.util.List;
33 import java.util.TimeZone;
34
35 import javax.servlet.ServletException;
36 import javax.servlet.http.Cookie;
37 import javax.servlet.http.HttpSession;
38
39 import org.apache.catalina.Lifecycle;
40 import org.apache.catalina.LifecycleException;
41 import org.apache.catalina.LifecycleListener;
42 import org.apache.catalina.connector.Request;
43 import org.apache.catalina.connector.Response;
44 import org.apache.catalina.util.LifecycleSupport;
45 import org.apache.catalina.util.StringManager;
46 import org.apache.coyote.RequestInfo;
47 import org.apache.juli.logging.Log;
48 import org.apache.juli.logging.LogFactory;
49
50
51 /**
52 * <p>Implementation of the <b>Valve</b> interface that generates a web server
53 * access log with the detailed line contents matching a configurable pattern.
54 * The syntax of the available patterns is similar to that supported by the
55 * Apache <code>mod_log_config</code> module. As an additional feature,
56 * automatic rollover of log files when the date changes is also supported.</p>
57 *
58 * <p>Patterns for the logged message may include constant text or any of the
59 * following replacement strings, for which the corresponding information
60 * from the specified Response is substituted:</p>
61 * <ul>
62 * <li><b>%a</b> - Remote IP address
63 * <li><b>%A</b> - Local IP address
64 * <li><b>%b</b> - Bytes sent, excluding HTTP headers, or '-' if no bytes
65 * were sent
66 * <li><b>%B</b> - Bytes sent, excluding HTTP headers
67 * <li><b>%h</b> - Remote host name
68 * <li><b>%H</b> - Request protocol
69 * <li><b>%l</b> - Remote logical username from identd (always returns '-')
70 * <li><b>%m</b> - Request method
71 * <li><b>%p</b> - Local port
72 * <li><b>%q</b> - Query string (prepended with a '?' if it exists, otherwise
73 * an empty string
74 * <li><b>%r</b> - First line of the request
75 * <li><b>%s</b> - HTTP status code of the response
76 * <li><b>%S</b> - User session ID
77 * <li><b>%t</b> - Date and time, in Common Log Format format
78 * <li><b>%u</b> - Remote user that was authenticated
79 * <li><b>%U</b> - Requested URL path
80 * <li><b>%v</b> - Local server name
81 * <li><b>%D</b> - Time taken to process the request, in millis
82 * <li><b>%T</b> - Time taken to process the request, in seconds
83 * <li><b>%I</b> - current Request thread name (can compare later with stacktraces)
84 * </ul>
85 * <p>In addition, the caller can specify one of the following aliases for
86 * commonly utilized patterns:</p>
87 * <ul>
88 * <li><b>common</b> - <code>%h %l %u %t "%r" %s %b</code>
89 * <li><b>combined</b> -
90 * <code>%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"</code>
91 * </ul>
92 *
93 * <p>
94 * There is also support to write information from the cookie, incoming
95 * header, the Session or something else in the ServletRequest.<br>
96 * It is modeled after the apache syntax:
97 * <ul>
98 * <li><code>%{xxx}i</code> for incoming headers
99 * <li><code>%{xxx}o</code> for outgoing response headers
100 * <li><code>%{xxx}c</code> for a specific cookie
101 * <li><code>%{xxx}r</code> xxx is an attribute in the ServletRequest
102 * <li><code>%{xxx}s</code> xxx is an attribute in the HttpSession
103 * </ul>
104 * </p>
105 *
106 * <p>
107 * Conditional logging is also supported. This can be done with the
108 * <code>condition</code> property.
109 * If the value returned from ServletRequest.getAttribute(condition)
110 * yields a non-null value. The logging will be skipped.
111 * </p>
112 *
113 * @author Craig R. McClanahan
114 * @author Jason Brittain
115 * @author Remy Maucherat
116 * @author Takayuki Kaneko
117 * @author Peter Rossbach
118 *
119 * @version $Revision: 575475 $ $Date: 2007-09-13 23:43:22 +0200 (jeu., 13 sept. 2007) $
120 */
121
122 public class AccessLogValve
123 extends ValveBase
124 implements Lifecycle {
125
126 private static Log log = LogFactory.getLog(AccessLogValve.class);
127
128 // ----------------------------------------------------- Instance Variables
129
130
131 /**
132 * The as-of date for the currently open log file, or a zero-length
133 * string if there is no open log file.
134 */
135 private String dateStamp = "";
136
137
138 /**
139 * The directory in which log files are created.
140 */
141 private String directory = "logs";
142
143
144 /**
145 * The descriptive information about this implementation.
146 */
147 protected static final String info =
148 "org.apache.catalina.valves.AccessLogValve/2.1";
149
150
151 /**
152 * The lifecycle event support for this component.
153 */
154 protected LifecycleSupport lifecycle = new LifecycleSupport(this);
155
156
157 /**
158 * The set of month abbreviations for log messages.
159 */
160 protected static final String months[] =
161 { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
162 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
163
164
165 /**
166 * enabled this component
167 */
168 protected boolean enabled = true;
169
170 /**
171 * The pattern used to format our access log lines.
172 */
173 protected String pattern = null;
174
175
176 /**
177 * The prefix that is added to log file filenames.
178 */
179 protected String prefix = "access_log.";
180
181
182 /**
183 * Should we rotate our log file? Default is true (like old behavior)
184 */
185 protected boolean rotatable = true;
186
187
188 /**
189 * Buffered logging.
190 */
191 private boolean buffered = true;
192
193
194 /**
195 * The string manager for this package.
196 */
197 protected StringManager sm =
198 StringManager.getManager(Constants.Package);
199
200
201 /**
202 * Has this component been started yet?
203 */
204 protected boolean started = false;
205
206
207 /**
208 * The suffix that is added to log file filenames.
209 */
210 protected String suffix = "";
211
212
213 /**
214 * The PrintWriter to which we are currently logging, if any.
215 */
216 protected PrintWriter writer = null;
217
218
219 /**
220 * A date formatter to format a Date into a date in the format
221 * "yyyy-MM-dd".
222 */
223 protected SimpleDateFormat fileDateFormatter = null;
224
225
226 /**
227 * A date formatter to format Dates into a day string in the format
228 * "dd".
229 */
230 private SimpleDateFormat dayFormatter = null;
231
232
233 /**
234 * A date formatter to format a Date into a month string in the format
235 * "MM".
236 */
237 private SimpleDateFormat monthFormatter = null;
238
239
240 /**
241 * A date formatter to format a Date into a year string in the format
242 * "yyyy".
243 */
244 private SimpleDateFormat yearFormatter = null;
245
246
247 /**
248 * A date formatter to format a Date into a time in the format
249 * "kk:mm:ss" (kk is a 24-hour representation of the hour).
250 */
251 private SimpleDateFormat timeFormatter = null;
252
253
254 /**
255 * The system timezone.
256 */
257 private TimeZone timezone = null;
258
259
260 /**
261 * The time zone offset relative to GMT in text form when daylight saving
262 * is not in operation.
263 */
264 private String timeZoneNoDST = null;
265
266
267 /**
268 * The time zone offset relative to GMT in text form when daylight saving
269 * is in operation.
270 */
271 private String timeZoneDST = null;
272
273
274 /**
275 * The current log file we are writing to. Helpful when checkExists
276 * is true.
277 */
278 protected File currentLogFile = null;
279
280 /**
281 * The system time when we last updated the Date that this valve
282 * uses for log lines.
283 */
284 private Date currentDate = null;
285
286 private long currentMillis = 0;
287
288
289 /**
290 * Resolve hosts.
291 */
292 private boolean resolveHosts = false;
293
294
295 /**
296 * Instant when the log daily rotation was last checked.
297 */
298 private long rotationLastChecked = 0L;
299
300 /**
301 * Do we check for log file existence? Helpful if an external
302 * agent renames the log file so we can automagically recreate it.
303 */
304 private boolean checkExists = false;
305
306
307 /**
308 * Are we doing conditional logging. default false.
309 */
310 protected String condition = null;
311
312
313 /**
314 * Date format to place in log file name. Use at your own risk!
315 */
316 protected String fileDateFormat = null;
317
318 /**
319 * Array of AccessLogElement, they will be used to make log message.
320 */
321 protected AccessLogElement[] logElements = null;
322
323 // ------------------------------------------------------------- Properties
324
325 /**
326 * @return Returns the enabled.
327 */
328 public boolean getEnabled() {
329 return enabled;
330 }
331
332 /**
333 * @param enabled
334 * The enabled to set.
335 */
336 public void setEnabled(boolean enabled) {
337 this.enabled = enabled;
338 }
339
340 /**
341 * Return the directory in which we create log files.
342 */
343 public String getDirectory() {
344 return (directory);
345 }
346
347
348 /**
349 * Set the directory in which we create log files.
350 *
351 * @param directory The new log file directory
352 */
353 public void setDirectory(String directory) {
354 this.directory = directory;
355 }
356
357
358 /**
359 * Return descriptive information about this implementation.
360 */
361 public String getInfo() {
362 return (info);
363 }
364
365
366 /**
367 * Return the format pattern.
368 */
369 public String getPattern() {
370 return (this.pattern);
371 }
372
373
374 /**
375 * Set the format pattern, first translating any recognized alias.
376 *
377 * @param pattern The new pattern
378 */
379 public void setPattern(String pattern) {
380 if (pattern == null)
381 pattern = "";
382 if (pattern.equals(Constants.AccessLog.COMMON_ALIAS))
383 pattern = Constants.AccessLog.COMMON_PATTERN;
384 if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS))
385 pattern = Constants.AccessLog.COMBINED_PATTERN;
386 this.pattern = pattern;
387 logElements = createLogElements();
388 }
389
390
391 /**
392 * Check for file existence before logging.
393 */
394 public boolean isCheckExists() {
395
396 return checkExists;
397
398 }
399
400
401 /**
402 * Set whether to check for log file existence before logging.
403 *
404 * @param checkExists true meaning to check for file existence.
405 */
406 public void setCheckExists(boolean checkExists) {
407
408 this.checkExists = checkExists;
409
410 }
411
412
413 /**
414 * Return the log file prefix.
415 */
416 public String getPrefix() {
417 return (prefix);
418 }
419
420
421 /**
422 * Set the log file prefix.
423 *
424 * @param prefix The new log file prefix
425 */
426 public void setPrefix(String prefix) {
427 this.prefix = prefix;
428 }
429
430
431 /**
432 * Should we rotate the logs
433 */
434 public boolean isRotatable() {
435 return rotatable;
436 }
437
438
439 /**
440 * Set the value is we should we rotate the logs
441 *
442 * @param rotatable true is we should rotate.
443 */
444 public void setRotatable(boolean rotatable) {
445 this.rotatable = rotatable;
446 }
447
448
449 /**
450 * Is the logging buffered
451 */
452 public boolean isBuffered() {
453 return buffered;
454 }
455
456
457 /**
458 * Set the value if the logging should be buffered
459 *
460 * @param buffered true if buffered.
461 */
462 public void setBuffered(boolean buffered) {
463 this.buffered = buffered;
464 }
465
466
467 /**
468 * Return the log file suffix.
469 */
470 public String getSuffix() {
471 return (suffix);
472 }
473
474
475 /**
476 * Set the log file suffix.
477 *
478 * @param suffix The new log file suffix
479 */
480 public void setSuffix(String suffix) {
481 this.suffix = suffix;
482 }
483
484
485 /**
486 * Set the resolve hosts flag.
487 *
488 * @param resolveHosts The new resolve hosts value
489 */
490 public void setResolveHosts(boolean resolveHosts) {
491 this.resolveHosts = resolveHosts;
492 }
493
494
495 /**
496 * Get the value of the resolve hosts flag.
497 */
498 public boolean isResolveHosts() {
499 return resolveHosts;
500 }
501
502
503 /**
504 * Return whether the attribute name to look for when
505 * performing conditional loggging. If null, every
506 * request is logged.
507 */
508 public String getCondition() {
509 return condition;
510 }
511
512
513 /**
514 * Set the ServletRequest.attribute to look for to perform
515 * conditional logging. Set to null to log everything.
516 *
517 * @param condition Set to null to log everything
518 */
519 public void setCondition(String condition) {
520 this.condition = condition;
521 }
522
523 /**
524 * Return the date format date based log rotation.
525 */
526 public String getFileDateFormat() {
527 return fileDateFormat;
528 }
529
530
531 /**
532 * Set the date format date based log rotation.
533 */
534 public void setFileDateFormat(String fileDateFormat) {
535 this.fileDateFormat = fileDateFormat;
536 }
537
538 // --------------------------------------------------------- Public Methods
539
540 /**
541 * Execute a periodic task, such as reloading, etc. This method will be
542 * invoked inside the classloading context of this container. Unexpected
543 * throwables will be caught and logged.
544 */
545 public void backgroundProcess() {
546 if (started && getEnabled() && writer != null && buffered) {
547 writer.flush();
548 }
549 }
550
551 /**
552 * Log a message summarizing the specified request and response, according
553 * to the format specified by the <code>pattern</code> property.
554 *
555 * @param request Request being processed
556 * @param response Response being processed
557 *
558 * @exception IOException if an input/output error has occurred
559 * @exception ServletException if a servlet error has occurred
560 */
561 public void invoke(Request request, Response response) throws IOException,
562 ServletException {
563
564 if (started && getEnabled()) {
565 // Pass this request on to the next valve in our pipeline
566 long t1 = System.currentTimeMillis();
567
568 getNext().invoke(request, response);
569
570 long t2 = System.currentTimeMillis();
571 long time = t2 - t1;
572
573 if (logElements == null || condition != null
574 && null != request.getRequest().getAttribute(condition)) {
575 return;
576 }
577
578 Date date = getDate();
579 StringBuffer result = new StringBuffer();
580
581 for (int i = 0; i < logElements.length; i++) {
582 logElements[i].addElement(result, date, request, response, time);
583 }
584
585 log(result.toString());
586 } else
587 getNext().invoke(request, response);
588 }
589
590
591 /**
592 * Rename the existing log file to something else. Then open the
593 * old log file name up once again. Intended to be called by a JMX
594 * agent.
595 *
596 *
597 * @param newFileName The file name to move the log file entry to
598 * @return true if a file was rotated with no error
599 */
600 public synchronized boolean rotate(String newFileName) {
601
602 if (currentLogFile != null) {
603 File holder = currentLogFile;
604 close();
605 try {
606 holder.renameTo(new File(newFileName));
607 } catch (Throwable e) {
608 log.error("rotate failed", e);
609 }
610
611 /* Make sure date is correct */
612 currentDate = new Date(System.currentTimeMillis());
613 dateStamp = fileDateFormatter.format(currentDate);
614
615 open();
616 return true;
617 } else {
618 return false;
619 }
620
621 }
622
623 // -------------------------------------------------------- Private Methods
624
625
626 /**
627 * Close the currently open log file (if any)
628 */
629 private synchronized void close() {
630 if (writer == null) {
631 return;
632 }
633 writer.flush();
634 writer.close();
635 writer = null;
636 dateStamp = "";
637 currentLogFile = null;
638 }
639
640
641 /**
642 * Log the specified message to the log file, switching files if the date
643 * has changed since the previous log call.
644 *
645 * @param message Message to be logged
646 */
647 public void log(String message) {
648 if (rotatable) {
649 // Only do a logfile switch check once a second, max.
650 long systime = System.currentTimeMillis();
651 if ((systime - rotationLastChecked) > 1000) {
652
653 // We need a new currentDate
654 currentDate = new Date(systime);
655 rotationLastChecked = systime;
656
657 // Check for a change of date
658 String tsDate = fileDateFormatter.format(currentDate);
659
660 // If the date has changed, switch log files
661 if (!dateStamp.equals(tsDate)) {
662 synchronized (this) {
663 if (!dateStamp.equals(tsDate)) {
664 close();
665 dateStamp = tsDate;
666 open();
667 }
668 }
669 }
670 }
671 }
672
673 /* In case something external rotated the file instead */
674 if (checkExists) {
675 synchronized (this) {
676 if (currentLogFile != null && !currentLogFile.exists()) {
677 try {
678 close();
679 } catch (Throwable e) {
680 log.info("at least this wasn't swallowed", e);
681 }
682
683 /* Make sure date is correct */
684 currentDate = new Date(System.currentTimeMillis());
685 dateStamp = fileDateFormatter.format(currentDate);
686
687 open();
688 }
689 }
690 }
691
692 // Log this message
693 if (writer != null) {
694 writer.println(message);
695 if (!buffered) {
696 writer.flush();
697 }
698 }
699
700 }
701
702
703 /**
704 * Return the month abbreviation for the specified month, which must
705 * be a two-digit String.
706 *
707 * @param month Month number ("01" .. "12").
708 */
709 private String lookup(String month) {
710 int index;
711 try {
712 index = Integer.parseInt(month) - 1;
713 } catch (Throwable t) {
714 index = 0; // Can not happen, in theory
715 }
716 return (months[index]);
717 }
718
719
720 /**
721 * Open the new log file for the date specified by <code>dateStamp</code>.
722 */
723 protected synchronized void open() {
724 // Create the directory if necessary
725 File dir = new File(directory);
726 if (!dir.isAbsolute())
727 dir = new File(System.getProperty("catalina.base"), directory);
728 dir.mkdirs();
729
730 // Open the current log file
731 try {
732 String pathname;
733 // If no rotate - no need for dateStamp in fileName
734 if (rotatable) {
735 pathname = dir.getAbsolutePath() + File.separator + prefix
736 + dateStamp + suffix;
737 } else {
738 pathname = dir.getAbsolutePath() + File.separator + prefix
739 + suffix;
740 }
741 writer = new PrintWriter(new BufferedWriter(new FileWriter(
742 pathname, true), 128000), false);
743
744 currentLogFile = new File(pathname);
745 } catch (IOException e) {
746 writer = null;
747 currentLogFile = null;
748 }
749 }
750
751 /**
752 * This method returns a Date object that is accurate to within one second.
753 * If a thread calls this method to get a Date and it's been less than 1
754 * second since a new Date was created, this method simply gives out the
755 * same Date again so that the system doesn't spend time creating Date
756 * objects unnecessarily.
757 *
758 * @return Date
759 */
760 private Date getDate() {
761 // Only create a new Date once per second, max.
762 long systime = System.currentTimeMillis();
763 if ((systime - currentMillis) > 1000) {
764 synchronized (this) {
765 if ((systime - currentMillis) > 1000) {
766 currentDate = new Date(systime);
767 currentMillis = systime;
768 }
769 }
770 }
771 return currentDate;
772 }
773
774
775 private String getTimeZone(Date date) {
776 if (timezone.inDaylightTime(date)) {
777 return timeZoneDST;
778 } else {
779 return timeZoneNoDST;
780 }
781 }
782
783
784 private String calculateTimeZoneOffset(long offset) {
785 StringBuffer tz = new StringBuffer();
786 if ((offset < 0)) {
787 tz.append("-");
788 offset = -offset;
789 } else {
790 tz.append("+");
791 }
792
793 long hourOffset = offset / (1000 * 60 * 60);
794 long minuteOffset = (offset / (1000 * 60)) % 60;
795
796 if (hourOffset < 10)
797 tz.append("0");
798 tz.append(hourOffset);
799
800 if (minuteOffset < 10)
801 tz.append("0");
802 tz.append(minuteOffset);
803
804 return tz.toString();
805 }
806
807
808 // ------------------------------------------------------ Lifecycle Methods
809
810
811 /**
812 * Add a lifecycle event listener to this component.
813 *
814 * @param listener The listener to add
815 */
816 public void addLifecycleListener(LifecycleListener listener) {
817 lifecycle.addLifecycleListener(listener);
818 }
819
820
821 /**
822 * Get the lifecycle listeners associated with this lifecycle. If this
823 * Lifecycle has no listeners registered, a zero-length array is returned.
824 */
825 public LifecycleListener[] findLifecycleListeners() {
826 return lifecycle.findLifecycleListeners();
827 }
828
829
830 /**
831 * Remove a lifecycle event listener from this component.
832 *
833 * @param listener The listener to add
834 */
835 public void removeLifecycleListener(LifecycleListener listener) {
836 lifecycle.removeLifecycleListener(listener);
837 }
838
839
840 /**
841 * Prepare for the beginning of active use of the public methods of this
842 * component. This method should be called after <code>configure()</code>,
843 * and before any of the public methods of the component are utilized.
844 *
845 * @exception LifecycleException if this component detects a fatal error
846 * that prevents this component from being used
847 */
848 public void start() throws LifecycleException {
849
850 // Validate and update our current component state
851 if (started)
852 throw new LifecycleException(sm
853 .getString("accessLogValve.alreadyStarted"));
854 lifecycle.fireLifecycleEvent(START_EVENT, null);
855 started = true;
856
857 // Initialize the timeZone, Date formatters, and currentDate
858 timezone = TimeZone.getDefault();
859 timeZoneNoDST = calculateTimeZoneOffset(timezone.getRawOffset());
860 Calendar calendar = Calendar.getInstance(timezone);
861 int offset = calendar.get(Calendar.DST_OFFSET);
862 timeZoneDST = calculateTimeZoneOffset(timezone.getRawOffset() + offset);
863
864 if (fileDateFormat == null || fileDateFormat.length() == 0)
865 fileDateFormat = "yyyy-MM-dd";
866 fileDateFormatter = new SimpleDateFormat(fileDateFormat);
867 fileDateFormatter.setTimeZone(timezone);
868 dayFormatter = new SimpleDateFormat("dd");
869 dayFormatter.setTimeZone(timezone);
870 monthFormatter = new SimpleDateFormat("MM");
871 monthFormatter.setTimeZone(timezone);
872 yearFormatter = new SimpleDateFormat("yyyy");
873 yearFormatter.setTimeZone(timezone);
874 timeFormatter = new SimpleDateFormat("HH:mm:ss");
875 timeFormatter.setTimeZone(timezone);
876 currentDate = new Date();
877 dateStamp = fileDateFormatter.format(currentDate);
878 open();
879 }
880
881
882 /**
883 * Gracefully terminate the active use of the public methods of this
884 * component. This method should be the last one called on a given
885 * instance of this component.
886 *
887 * @exception LifecycleException if this component detects a fatal error
888 * that needs to be reported
889 */
890 public void stop() throws LifecycleException {
891
892 // Validate and update our current component state
893 if (!started)
894 throw new LifecycleException(sm
895 .getString("accessLogValve.notStarted"));
896 lifecycle.fireLifecycleEvent(STOP_EVENT, null);
897 started = false;
898
899 close();
900 }
901
902 /**
903 * AccessLogElement writes the partial message into the buffer.
904 */
905 protected interface AccessLogElement {
906 public void addElement(StringBuffer buf, Date date, Request request,
907 Response response, long time);
908
909 }
910
911 /**
912 * write thread name - %I
913 */
914 protected class ThreadNameElement implements AccessLogElement {
915 public void addElement(StringBuffer buf, Date date, Request request,
916 Response response, long time) {
917 RequestInfo info = request.getCoyoteRequest().getRequestProcessor();
918 if(info != null) {
919 buf.append(info.getWorkerThreadName());
920 } else {
921 buf.append("-");
922 }
923 }
924 }
925
926 /**
927 * write local IP address - %A
928 */
929 protected class LocalAddrElement implements AccessLogElement {
930
931 private String value = null;
932
933 public void addElement(StringBuffer buf, Date date, Request request,
934 Response response, long time) {
935 if (value == null) {
936 synchronized (this) {
937 try {
938 value = InetAddress.getLocalHost().getHostAddress();
939 } catch (Throwable e) {
940 value = "127.0.0.1";
941 }
942 }
943 }
944 buf.append(value);
945 }
946 }
947
948 /**
949 * write remote IP address - %a
950 */
951 protected class RemoteAddrElement implements AccessLogElement {
952 public void addElement(StringBuffer buf, Date date, Request request,
953 Response response, long time) {
954 buf.append(request.getRemoteAddr());
955 }
956 }
957
958 /**
959 * write remote host name - %h
960 */
961 protected class HostElement implements AccessLogElement {
962 public void addElement(StringBuffer buf, Date date, Request request,
963 Response response, long time) {
964 buf.append(request.getRemoteHost());
965 }
966 }
967
968 /**
969 * write remote logical username from identd (always returns '-') - %l
970 */
971 protected class LogicalUserNameElement implements AccessLogElement {
972 public void addElement(StringBuffer buf, Date date, Request request,
973 Response response, long time) {
974 buf.append('-');
975 }
976 }
977
978 /**
979 * write request protocol - %H
980 */
981 protected class ProtocolElement implements AccessLogElement {
982 public void addElement(StringBuffer buf, Date date, Request request,
983 Response response, long time) {
984 buf.append(request.getProtocol());
985 }
986 }
987
988 /**
989 * write remote user that was authenticated (if any), else '-' - %u
990 */
991 protected class UserElement implements AccessLogElement {
992 public void addElement(StringBuffer buf, Date date, Request request,
993 Response response, long time) {
994 if (request != null) {
995 String value = request.getRemoteUser();
996 if (value != null) {
997 buf.append(value);
998 } else {
999 buf.append('-');
1000 }
1001 } else {
1002 buf.append('-');
1003 }
1004 }
1005 }
1006
1007 /**
1008 * write date and time, in Common Log Format - %t
1009 */
1010 protected class DateAndTimeElement implements AccessLogElement {
1011 private Date currentDate = new Date(0);
1012
1013 private String currentDateString = null;
1014
1015 public void addElement(StringBuffer buf, Date date, Request request,
1016 Response response, long time) {
1017 if (currentDate != date) {
1018 synchronized (this) {
1019 if (currentDate != date) {
1020 StringBuffer current = new StringBuffer(32);
1021 current.append('[');
1022 current.append(dayFormatter.format(date)); // Day
1023 current.append('/');
1024 current.append(lookup(monthFormatter.format(date))); // Month
1025 current.append('/');
1026 current.append(yearFormatter.format(date)); // Year
1027 current.append(':');
1028 current.append(timeFormatter.format(date)); // Time
1029 current.append(' ');
1030 current.append(getTimeZone(date)); // Timezone
1031 current.append(']');
1032 currentDateString = current.toString();
1033 currentDate = date;
1034 }
1035 }
1036 }
1037 buf.append(currentDateString);
1038 }
1039 }
1040
1041 /**
1042 * write first line of the request (method and request URI) - %r
1043 */
1044 protected class RequestElement implements AccessLogElement {
1045 public void addElement(StringBuffer buf, Date date, Request request,
1046 Response response, long time) {
1047 if (request != null) {
1048 buf.append(request.getMethod());
1049 buf.append(' ');
1050 buf.append(request.getRequestURI());
1051 if (request.getQueryString() != null) {
1052 buf.append('?');
1053 buf.append(request.getQueryString());
1054 }
1055 buf.append(' ');
1056 buf.append(request.getProtocol());
1057 } else {
1058 buf.append("- - ");
1059 }
1060 }
1061 }
1062
1063 /**
1064 * write HTTP status code of the response - %s
1065 */
1066 protected class HttpStatusCodeElement implements AccessLogElement {
1067 public void addElement(StringBuffer buf, Date date, Request request,
1068 Response response, long time) {
1069 if (response != null) {
1070 buf.append(response.getStatus());
1071 } else {
1072 buf.append('-');
1073 }
1074 }
1075 }
1076
1077 /**
1078 * write local port on which this request was received - %p
1079 */
1080 protected class LocalPortElement implements AccessLogElement {
1081 public void addElement(StringBuffer buf, Date date, Request request,
1082 Response response, long time) {
1083 buf.append(request.getServerPort());
1084 }
1085 }
1086
1087 /**
1088 * write bytes sent, excluding HTTP headers - %b, %B
1089 */
1090 protected class ByteSentElement implements AccessLogElement {
1091 private boolean conversion;
1092
1093 /**
1094 * if conversion is true, write '-' instead of 0 - %b
1095 */
1096 public ByteSentElement(boolean conversion) {
1097 this.conversion = conversion;
1098 }
1099
1100 public void addElement(StringBuffer buf, Date date, Request request,
1101 Response response, long time) {
1102 long length = response.getContentCountLong() ;
1103 if (length <= 0 && conversion) {
1104 buf.append('-');
1105 } else {
1106 buf.append(length);
1107 }
1108 }
1109 }
1110
1111 /**
1112 * write request method (GET, POST, etc.) - %m
1113 */
1114 protected class MethodElement implements AccessLogElement {
1115 public void addElement(StringBuffer buf, Date date, Request request,
1116 Response response, long time) {
1117 if (request != null) {
1118 buf.append(request.getMethod());
1119 }
1120 }
1121 }
1122
1123 /**
1124 * write time taken to process the request - %D, %T
1125 */
1126 protected class ElapsedTimeElement implements AccessLogElement {
1127 private boolean millis;
1128
1129 /**
1130 * if millis is true, write time in millis - %D
1131 * if millis is false, write time in seconds - %T
1132 */
1133 public ElapsedTimeElement(boolean millis) {
1134 this.millis = millis;
1135 }
1136
1137 public void addElement(StringBuffer buf, Date date, Request request,
1138 Response response, long time) {
1139 if (millis) {
1140 buf.append(time);
1141 } else {
1142 // second
1143 buf.append(time / 1000);
1144 buf.append('.');
1145 int remains = (int) (time % 1000);
1146 buf.append(remains / 100);
1147 remains = remains % 100;
1148 buf.append(remains / 10);
1149 buf.append(remains % 10);
1150 }
1151 }
1152 }
1153
1154 /**
1155 * write Query string (prepended with a '?' if it exists) - %q
1156 */
1157 protected class QueryElement implements AccessLogElement {
1158 public void addElement(StringBuffer buf, Date date, Request request,
1159 Response response, long time) {
1160 String query = null;
1161 if (request != null)
1162 query = request.getQueryString();
1163 if (query != null) {
1164 buf.append('?');
1165 buf.append(query);
1166 }
1167 }
1168 }
1169
1170 /**
1171 * write user session ID - %S
1172 */
1173 protected class SessionIdElement implements AccessLogElement {
1174 public void addElement(StringBuffer buf, Date date, Request request,
1175 Response response, long time) {
1176 if (request != null) {
1177 if (request.getSession(false) != null) {
1178 buf.append(request.getSessionInternal(false)
1179 .getIdInternal());
1180 } else {
1181 buf.append('-');
1182 }
1183 } else {
1184 buf.append('-');
1185 }
1186 }
1187 }
1188
1189 /**
1190 * write requested URL path - %U
1191 */
1192 protected class RequestURIElement implements AccessLogElement {
1193 public void addElement(StringBuffer buf, Date date, Request request,
1194 Response response, long time) {
1195 if (request != null) {
1196 buf.append(request.getRequestURI());
1197 } else {
1198 buf.append('-');
1199 }
1200 }
1201 }
1202
1203 /**
1204 * write local server name - %v
1205 */
1206 protected class LocalServerNameElement implements AccessLogElement {
1207 public void addElement(StringBuffer buf, Date date, Request request,
1208 Response response, long time) {
1209 buf.append(request.getServerName());
1210 }
1211 }
1212
1213 /**
1214 * write any string
1215 */
1216 protected class StringElement implements AccessLogElement {
1217 private String str;
1218
1219 public StringElement(String str) {
1220 this.str = str;
1221 }
1222
1223 public void addElement(StringBuffer buf, Date date, Request request,
1224 Response response, long time) {
1225 buf.append(str);
1226 }
1227 }
1228
1229 /**
1230 * write incoming headers - %{xxx}i
1231 */
1232 protected class HeaderElement implements AccessLogElement {
1233 private String header;
1234
1235 public HeaderElement(String header) {
1236 this.header = header;
1237 }
1238
1239 public void addElement(StringBuffer buf, Date date, Request request,
1240 Response response, long time) {
1241 buf.append(request.getHeader(header));
1242 }
1243 }
1244
1245 /**
1246 * write a specific cookie - %{xxx}c
1247 */
1248 protected class CookieElement implements AccessLogElement {
1249 private String header;
1250
1251 public CookieElement(String header) {
1252 this.header = header;
1253 }
1254
1255 public void addElement(StringBuffer buf, Date date, Request request,
1256 Response response, long time) {
1257 String value = "-";
1258 Cookie[] c = request.getCookies();
1259 if (c != null) {
1260 for (int i = 0; i < c.length; i++) {
1261 if (header.equals(c[i].getName())) {
1262 value = c[i].getValue();
1263 break;
1264 }
1265 }
1266 }
1267 buf.append(value);
1268 }
1269 }
1270
1271 /**
1272 * write a specific response header - %{xxx}o
1273 */
1274 protected class ResponseHeaderElement implements AccessLogElement {
1275 private String header;
1276
1277 public ResponseHeaderElement(String header) {
1278 this.header = header;
1279 }
1280
1281 public void addElement(StringBuffer buf, Date date, Request request,
1282 Response response, long time) {
1283 if (null != response) {
1284 String[] values = response.getHeaderValues(header);
1285 if(values.length > 0) {
1286 for (int i = 0; i < values.length; i++) {
1287 String string = values[i];
1288 buf.append(string) ;
1289 if(i+1<values.length)
1290 buf.append(",");
1291 }
1292 return ;
1293 }
1294 }
1295 buf.append("-");
1296 }
1297 }
1298
1299 /**
1300 * write an attribute in the ServletRequest - %{xxx}r
1301 */
1302 protected class RequestAttributeElement implements AccessLogElement {
1303 private String header;
1304
1305 public RequestAttributeElement(String header) {
1306 this.header = header;
1307 }
1308
1309 public void addElement(StringBuffer buf, Date date, Request request,
1310 Response response, long time) {
1311 Object value = null;
1312 if (request != null) {
1313 value = request.getAttribute(header);
1314 } else {
1315 value = "??";
1316 }
1317 if (value != null) {
1318 if (value instanceof String) {
1319 buf.append((String) value);
1320 } else {
1321 buf.append(value.toString());
1322 }
1323 } else {
1324 buf.append('-');
1325 }
1326 }
1327 }
1328
1329 /**
1330 * write an attribute in the HttpSession - %{xxx}s
1331 */
1332 protected class SessionAttributeElement implements AccessLogElement {
1333 private String header;
1334
1335 public SessionAttributeElement(String header) {
1336 this.header = header;
1337 }
1338
1339 public void addElement(StringBuffer buf, Date date, Request request,
1340 Response response, long time) {
1341 Object value = null;
1342 if (null != request) {
1343 HttpSession sess = request.getSession(false);
1344 if (null != sess)
1345 value = sess.getAttribute(header);
1346 } else {
1347 value = "??";
1348 }
1349 if (value != null) {
1350 if (value instanceof String) {
1351 buf.append((String) value);
1352 } else {
1353 buf.append(value.toString());
1354 }
1355 } else {
1356 buf.append('-');
1357 }
1358 }
1359 }
1360
1361
1362
1363
1364 /**
1365 * parse pattern string and create the array of AccessLogElement
1366 */
1367 protected AccessLogElement[] createLogElements() {
1368 List<AccessLogElement> list = new ArrayList<AccessLogElement>();
1369 boolean replace = false;
1370 StringBuffer buf = new StringBuffer();
1371 for (int i = 0; i < pattern.length(); i++) {
1372 char ch = pattern.charAt(i);
1373 if (replace) {
1374 /*
1375 * For code that processes {, the behavior will be ... if I do
1376 * not enounter a closing } - then I ignore the {
1377 */
1378 if ('{' == ch) {
1379 StringBuffer name = new StringBuffer();
1380 int j = i + 1;
1381 for (; j < pattern.length() && '}' != pattern.charAt(j); j++) {
1382 name.append(pattern.charAt(j));
1383 }
1384 if (j + 1 < pattern.length()) {
1385 /* the +1 was to account for } which we increment now */
1386 j++;
1387 list.add(createAccessLogElement(name.toString(),
1388 pattern.charAt(j)));
1389 i = j; /* Since we walked more than one character */
1390 } else {
1391 // D'oh - end of string - pretend we never did this
1392 // and do processing the "old way"
1393 list.add(createAccessLogElement(ch));
1394 }
1395 } else {
1396 list.add(createAccessLogElement(ch));
1397 }
1398 replace = false;
1399 } else if (ch == '%') {
1400 replace = true;
1401 list.add(new StringElement(buf.toString()));
1402 buf = new StringBuffer();
1403 } else {
1404 buf.append(ch);
1405 }
1406 }
1407 if (buf.length() > 0) {
1408 list.add(new StringElement(buf.toString()));
1409 }
1410 return (AccessLogElement[]) list.toArray(new AccessLogElement[0]);
1411 }
1412
1413 /**
1414 * create an AccessLogElement implementation which needs header string
1415 */
1416 private AccessLogElement createAccessLogElement(String header, char pattern) {
1417 switch (pattern) {
1418 case 'i':
1419 return new HeaderElement(header);
1420 case 'c':
1421 return new CookieElement(header);
1422 case 'o':
1423 return new ResponseHeaderElement(header);
1424 case 'r':
1425 return new RequestAttributeElement(header);
1426 case 's':
1427 return new SessionAttributeElement(header);
1428 default:
1429 return new StringElement("???");
1430 }
1431 }
1432
1433 /**
1434 * create an AccessLogElement implementation
1435 */
1436 private AccessLogElement createAccessLogElement(char pattern) {
1437 switch (pattern) {
1438 case 'a':
1439 return new RemoteAddrElement();
1440 case 'A':
1441 return new LocalAddrElement();
1442 case 'b':
1443 return new ByteSentElement(true);
1444 case 'B':
1445 return new ByteSentElement(false);
1446 case 'D':
1447 return new ElapsedTimeElement(true);
1448 case 'h':
1449 return new HostElement();
1450 case 'H':
1451 return new ProtocolElement();
1452 case 'l':
1453 return new LogicalUserNameElement();
1454 case 'm':
1455 return new MethodElement();
1456 case 'p':
1457 return new LocalPortElement();
1458 case 'q':
1459 return new QueryElement();
1460 case 'r':
1461 return new RequestElement();
1462 case 's':
1463 return new HttpStatusCodeElement();
1464 case 'S':
1465 return new SessionIdElement();
1466 case 't':
1467 return new DateAndTimeElement();
1468 case 'T':
1469 return new ElapsedTimeElement(false);
1470 case 'u':
1471 return new UserElement();
1472 case 'U':
1473 return new RequestURIElement();
1474 case 'v':
1475 return new LocalServerNameElement();
1476 case 'I':
1477 return new ThreadNameElement();
1478 default:
1479 return new StringElement("???" + pattern + "???");
1480 }
1481 }
1482 }