KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > catalina > valves > FastCommonAccessLogValve


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 JavaDoc;
23 import java.io.File JavaDoc;
24 import java.io.FileWriter JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.PrintWriter JavaDoc;
27 import java.text.SimpleDateFormat JavaDoc;
28 import java.util.Calendar JavaDoc;
29 import java.util.Date JavaDoc;
30 import java.util.TimeZone JavaDoc;
31
32 import javax.servlet.ServletException JavaDoc;
33
34 import org.apache.catalina.Lifecycle;
35 import org.apache.catalina.LifecycleException;
36 import org.apache.catalina.LifecycleListener;
37 import org.apache.catalina.connector.Request;
38 import org.apache.catalina.connector.Response;
39 import org.apache.catalina.util.LifecycleSupport;
40 import org.apache.catalina.util.StringManager;
41
42
43 /**
44  * <p>Implementation of the <b>Valve</b> interface that generates a web server
45  * access log with the detailed line contents matching either the common or
46  * combined patterns. As an additional feature, automatic rollover of log files
47  * when the date changes is also supported.</p>
48  * <p>
49  * Conditional logging is also supported. This can be done with the
50  * <code>condition</code> property.
51  * If the value returned from ServletRequest.getAttribute(condition)
52  * yields a non-null value. The logging will be skipped.
53  * </p>
54  *
55  * @author Craig R. McClanahan
56  * @author Jason Brittain
57  * @author Remy Maucherat
58  * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
59  */

60
61 public final class FastCommonAccessLogValve
62     extends ValveBase
63     implements Lifecycle {
64
65
66     // ----------------------------------------------------------- Constructors
67

68
69     /**
70      * Construct a new instance of this class with default property values.
71      */

72     public FastCommonAccessLogValve() {
73
74         super();
75         setPattern("common");
76
77
78     }
79
80
81     // ----------------------------------------------------- Instance Variables
82

83
84     /**
85      * The as-of date for the currently open log file, or a zero-length
86      * string if there is no open log file.
87      */

88     private String JavaDoc dateStamp = "";
89
90
91     /**
92      * The directory in which log files are created.
93      */

94     private String JavaDoc directory = "logs";
95
96
97     /**
98      * The descriptive information about this implementation.
99      */

100     protected static final String JavaDoc info =
101         "org.apache.catalina.valves.FastCommonAccessLogValve/1.0";
102
103
104     /**
105      * The lifecycle event support for this component.
106      */

107     protected LifecycleSupport lifecycle = new LifecycleSupport(this);
108
109
110     /**
111      * The set of month abbreviations for log messages.
112      */

113     protected static final String JavaDoc months[] =
114     { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
115       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
116
117
118     /**
119      * If the current log pattern is the same as the common access log
120      * format pattern, then we'll set this variable to true and log in
121      * a more optimal and hard-coded way.
122      */

123     private boolean common = false;
124
125
126     /**
127      * For the combined format (common, plus useragent and referer), we do
128      * the same
129      */

130     private boolean combined = false;
131
132
133     /**
134      * The pattern used to format our access log lines.
135      */

136     private String JavaDoc pattern = null;
137
138
139     /**
140      * The prefix that is added to log file filenames.
141      */

142     private String JavaDoc prefix = "access_log.";
143
144
145     /**
146      * Should we rotate our log file? Default is true (like old behavior)
147      */

148     private boolean rotatable = true;
149
150
151     /**
152      * The string manager for this package.
153      */

154     private StringManager sm =
155         StringManager.getManager(Constants.Package);
156
157
158     /**
159      * Has this component been started yet?
160      */

161     private boolean started = false;
162
163
164     /**
165      * The suffix that is added to log file filenames.
166      */

167     private String JavaDoc suffix = "";
168
169
170     /**
171      * The PrintWriter to which we are currently logging, if any.
172      */

173     private PrintWriter JavaDoc writer = null;
174
175
176     /**
177      * A date formatter to format a Date into a date in the format
178      * "yyyy-MM-dd".
179      */

180     private SimpleDateFormat JavaDoc dateFormatter = null;
181
182
183     /**
184      * A date formatter to format Dates into a day string in the format
185      * "dd".
186      */

187     private SimpleDateFormat JavaDoc dayFormatter = null;
188
189
190     /**
191      * A date formatter to format a Date into a month string in the format
192      * "MM".
193      */

194     private SimpleDateFormat JavaDoc monthFormatter = null;
195
196
197     /**
198      * A date formatter to format a Date into a year string in the format
199      * "yyyy".
200      */

201     private SimpleDateFormat JavaDoc yearFormatter = null;
202
203
204     /**
205      * A date formatter to format a Date into a time in the format
206      * "kk:mm:ss" (kk is a 24-hour representation of the hour).
207      */

208     private SimpleDateFormat JavaDoc timeFormatter = null;
209
210
211     /**
212      * The system timezone.
213      */

214     private TimeZone JavaDoc timezone = null;
215
216     
217     /**
218      * The time zone offset relative to GMT in text form when daylight saving
219      * is not in operation.
220      */

221     private String JavaDoc timeZoneNoDST = null;
222  
223     /**
224      * The time zone offset relative to GMT in text form when daylight saving
225      * is in operation.
226      */

227     private String JavaDoc timeZoneDST = null;
228
229
230     /**
231      * The system time when we last updated the Date that this valve
232      * uses for log lines.
233      */

234     private String JavaDoc currentDateString = null;
235     
236     
237     /**
238      * The instant where the date string was last updated.
239      */

240     private long currentDate = 0L;
241
242
243     /**
244      * When formatting log lines, we often use strings like this one (" ").
245      */

246     private String JavaDoc space = " ";
247
248
249     /**
250      * Resolve hosts.
251      */

252     private boolean resolveHosts = false;
253
254
255     /**
256      * Instant when the log daily rotation was last checked.
257      */

258     private long rotationLastChecked = 0L;
259
260
261     /**
262      * Are we doing conditional logging. default false.
263      */

264     private String JavaDoc condition = null;
265
266
267     /**
268      * Date format to place in log file name. Use at your own risk!
269      */

270     private String JavaDoc fileDateFormat = null;
271
272
273     // ------------------------------------------------------------- Properties
274

275
276     /**
277      * Return the directory in which we create log files.
278      */

279     public String JavaDoc getDirectory() {
280
281         return (directory);
282
283     }
284
285
286     /**
287      * Set the directory in which we create log files.
288      *
289      * @param directory The new log file directory
290      */

291     public void setDirectory(String JavaDoc directory) {
292
293         this.directory = directory;
294
295     }
296
297
298     /**
299      * Return descriptive information about this implementation.
300      */

301     public String JavaDoc getInfo() {
302
303         return (info);
304
305     }
306
307
308     /**
309      * Return the format pattern.
310      */

311     public String JavaDoc getPattern() {
312
313         return (this.pattern);
314
315     }
316
317
318     /**
319      * Set the format pattern, first translating any recognized alias.
320      *
321      * @param pattern The new pattern
322      */

323     public void setPattern(String JavaDoc pattern) {
324
325         if (pattern == null)
326             pattern = "";
327         if (pattern.equals(Constants.AccessLog.COMMON_ALIAS))
328             pattern = Constants.AccessLog.COMMON_PATTERN;
329         if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS))
330             pattern = Constants.AccessLog.COMBINED_PATTERN;
331         this.pattern = pattern;
332
333         if (this.pattern.equals(Constants.AccessLog.COMBINED_PATTERN))
334             combined = true;
335         else
336             combined = false;
337
338     }
339
340
341     /**
342      * Return the log file prefix.
343      */

344     public String JavaDoc getPrefix() {
345
346         return (prefix);
347
348     }
349
350
351     /**
352      * Set the log file prefix.
353      *
354      * @param prefix The new log file prefix
355      */

356     public void setPrefix(String JavaDoc prefix) {
357
358         this.prefix = prefix;
359
360     }
361
362
363     /**
364      * Should we rotate the logs
365      */

366     public boolean isRotatable() {
367
368         return rotatable;
369
370     }
371
372
373     /**
374      * Set the value is we should we rotate the logs
375      *
376      * @param rotatable true is we should rotate.
377      */

378     public void setRotatable(boolean rotatable) {
379
380         this.rotatable = rotatable;
381
382     }
383
384
385     /**
386      * Return the log file suffix.
387      */

388     public String JavaDoc getSuffix() {
389
390         return (suffix);
391
392     }
393
394
395     /**
396      * Set the log file suffix.
397      *
398      * @param suffix The new log file suffix
399      */

400     public void setSuffix(String JavaDoc suffix) {
401
402         this.suffix = suffix;
403
404     }
405
406
407     /**
408      * Set the resolve hosts flag.
409      *
410      * @param resolveHosts The new resolve hosts value
411      */

412     public void setResolveHosts(boolean resolveHosts) {
413
414         this.resolveHosts = resolveHosts;
415
416     }
417
418
419     /**
420      * Get the value of the resolve hosts flag.
421      */

422     public boolean isResolveHosts() {
423
424         return resolveHosts;
425
426     }
427
428
429     /**
430      * Return whether the attribute name to look for when
431      * performing conditional loggging. If null, every
432      * request is logged.
433      */

434     public String JavaDoc getCondition() {
435
436         return condition;
437
438     }
439
440
441     /**
442      * Set the ServletRequest.attribute to look for to perform
443      * conditional logging. Set to null to log everything.
444      *
445      * @param condition Set to null to log everything
446      */

447     public void setCondition(String JavaDoc condition) {
448
449         this.condition = condition;
450
451     }
452
453     /**
454      * Return the date format date based log rotation.
455      */

456     public String JavaDoc getFileDateFormat() {
457         return fileDateFormat;
458     }
459
460
461     /**
462      * Set the date format date based log rotation.
463      */

464     public void setFileDateFormat(String JavaDoc fileDateFormat) {
465         this.fileDateFormat = fileDateFormat;
466     }
467
468     // --------------------------------------------------------- Public Methods
469

470
471     /**
472      * Execute a periodic task, such as reloading, etc. This method will be
473      * invoked inside the classloading context of this container. Unexpected
474      * throwables will be caught and logged.
475      */

476     public void backgroundProcess() {
477         if (writer != null)
478             writer.flush();
479     }
480
481
482     /**
483      * Log a message summarizing the specified request and response, according
484      * to the format specified by the <code>pattern</code> property.
485      *
486      * @param request Request being processed
487      * @param response Response being processed
488      *
489      * @exception IOException if an input/output error has occurred
490      * @exception ServletException if a servlet error has occurred
491      */

492     public void invoke(Request request, Response response)
493         throws IOException JavaDoc, ServletException JavaDoc {
494
495         // Pass this request on to the next valve in our pipeline
496
getNext().invoke(request, response);
497
498         if (condition!=null &&
499                 null!=request.getRequest().getAttribute(condition)) {
500             return;
501         }
502
503         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
504
505         // Check to see if we should log using the "common" access log pattern
506
String JavaDoc value = null;
507         
508         if (isResolveHosts())
509             result.append(request.getRemoteHost());
510         else
511             result.append(request.getRemoteAddr());
512         
513         result.append(" - ");
514         
515         value = request.getRemoteUser();
516         if (value == null)
517             result.append("- ");
518         else {
519             result.append(value);
520             result.append(space);
521         }
522         
523         result.append(getCurrentDateString());
524         
525         result.append(request.getMethod());
526         result.append(space);
527         result.append(request.getRequestURI());
528         if (request.getQueryString() != null) {
529             result.append('?');
530             result.append(request.getQueryString());
531         }
532         result.append(space);
533         result.append(request.getProtocol());
534         result.append("\" ");
535         
536         result.append(response.getStatus());
537         
538         result.append(space);
539         
540         int length = response.getContentCount();
541         
542         if (length <= 0)
543             value = "-";
544         else
545             value = "" + length;
546         result.append(value);
547         
548         if (combined) {
549             result.append(space);
550             result.append("\"");
551             String JavaDoc referer = request.getHeader("referer");
552             if(referer != null)
553                 result.append(referer);
554             else
555                 result.append("-");
556             result.append("\"");
557             
558             result.append(space);
559             result.append("\"");
560             String JavaDoc ua = request.getHeader("user-agent");
561             if(ua != null)
562                 result.append(ua);
563             else
564                 result.append("-");
565             result.append("\"");
566         }
567         
568         log(result.toString());
569         
570     }
571
572
573     // -------------------------------------------------------- Private Methods
574

575
576     /**
577      * Close the currently open log file (if any)
578      */

579     private synchronized void close() {
580
581         if (writer == null)
582             return;
583         writer.flush();
584         writer.close();
585         writer = null;
586         dateStamp = "";
587
588     }
589
590
591     /**
592      * Log the specified message to the log file, switching files if the date
593      * has changed since the previous log call.
594      *
595      * @param message Message to be logged
596      */

597     public void log(String JavaDoc message) {
598
599         // Log this message
600
if (writer != null) {
601             writer.println(message);
602         }
603
604     }
605
606
607     /**
608      * Return the month abbreviation for the specified month, which must
609      * be a two-digit String.
610      *
611      * @param month Month number ("01" .. "12").
612      */

613     private String JavaDoc lookup(String JavaDoc month) {
614
615         int index;
616         try {
617             index = Integer.parseInt(month) - 1;
618         } catch (Throwable JavaDoc t) {
619             index = 0; // Can not happen, in theory
620
}
621         return (months[index]);
622
623     }
624
625
626     /**
627      * Open the new log file for the date specified by <code>dateStamp</code>.
628      */

629     private synchronized void open() {
630
631         // Create the directory if necessary
632
File JavaDoc dir = new File JavaDoc(directory);
633         if (!dir.isAbsolute())
634             dir = new File JavaDoc(System.getProperty("catalina.base"), directory);
635         dir.mkdirs();
636
637         // Open the current log file
638
try {
639             String JavaDoc pathname;
640             // If no rotate - no need for dateStamp in fileName
641
if (rotatable){
642                 pathname = dir.getAbsolutePath() + File.separator +
643                             prefix + dateStamp + suffix;
644             } else {
645                 pathname = dir.getAbsolutePath() + File.separator +
646                             prefix + suffix;
647             }
648             writer = new PrintWriter JavaDoc(new BufferedWriter JavaDoc
649                     (new FileWriter JavaDoc(pathname, true), 128000), false);
650         } catch (IOException JavaDoc e) {
651             writer = null;
652         }
653
654     }
655
656
657     /**
658      * This method returns a Date object that is accurate to within one
659      * second. If a thread calls this method to get a Date and it's been
660      * less than 1 second since a new Date was created, this method
661      * simply gives out the same Date again so that the system doesn't
662      * spend time creating Date objects unnecessarily.
663      *
664      * @return Date
665      */

666     private String JavaDoc getCurrentDateString() {
667         // Only create a new Date once per second, max.
668
long systime = System.currentTimeMillis();
669         if ((systime - currentDate) > 1000) {
670             synchronized (this) {
671                 // We don't care about being exact here: if an entry does get
672
// logged as having happened during the previous second
673
// it will not make any difference
674
if ((systime - currentDate) > 1000) {
675
676                     // Format the new date
677
Date JavaDoc date = new Date JavaDoc();
678                     StringBuffer JavaDoc result = new StringBuffer JavaDoc(32);
679                     result.append("[");
680                     // Day
681
result.append(dayFormatter.format(date));
682                     result.append('/');
683                     // Month
684
result.append(lookup(monthFormatter.format(date)));
685                     result.append('/');
686                     // Year
687
result.append(yearFormatter.format(date));
688                     result.append(':');
689                     // Time
690
result.append(timeFormatter.format(date));
691                     result.append(space);
692                     // Time zone
693
result.append(getTimeZone(date));
694                     result.append("] \"");
695                     
696                     // Check for log rotation
697
if (rotatable) {
698                         // Check for a change of date
699
String JavaDoc tsDate = dateFormatter.format(date);
700                         // If the date has changed, switch log files
701
if (!dateStamp.equals(tsDate)) {
702                             synchronized (this) {
703                                 if (!dateStamp.equals(tsDate)) {
704                                     close();
705                                     dateStamp = tsDate;
706                                     open();
707                                 }
708                             }
709                         }
710                     }
711                     
712                     currentDateString = result.toString();
713                     currentDate = date.getTime();
714                 }
715             }
716         }
717         return currentDateString;
718     }
719
720
721     private String JavaDoc getTimeZone(Date JavaDoc date) {
722         if (timezone.inDaylightTime(date)) {
723             return timeZoneDST;
724         } else {
725             return timeZoneNoDST;
726         }
727     }
728     
729     
730     private String JavaDoc calculateTimeZoneOffset(long offset) {
731         StringBuffer JavaDoc tz = new StringBuffer JavaDoc();
732         if ((offset<0)) {
733             tz.append("-");
734             offset = -offset;
735         } else {
736             tz.append("+");
737         }
738
739         long hourOffset = offset/(1000*60*60);
740         long minuteOffset = (offset/(1000*60)) % 60;
741
742         if (hourOffset<10)
743             tz.append("0");
744         tz.append(hourOffset);
745
746         if (minuteOffset<10)
747             tz.append("0");
748         tz.append(minuteOffset);
749
750         return tz.toString();
751     }
752
753
754     // ------------------------------------------------------ Lifecycle Methods
755

756
757     /**
758      * Add a lifecycle event listener to this component.
759      *
760      * @param listener The listener to add
761      */

762     public void addLifecycleListener(LifecycleListener listener) {
763
764         lifecycle.addLifecycleListener(listener);
765
766     }
767
768
769     /**
770      * Get the lifecycle listeners associated with this lifecycle. If this
771      * Lifecycle has no listeners registered, a zero-length array is returned.
772      */

773     public LifecycleListener[] findLifecycleListeners() {
774
775         return lifecycle.findLifecycleListeners();
776
777     }
778
779
780     /**
781      * Remove a lifecycle event listener from this component.
782      *
783      * @param listener The listener to add
784      */

785     public void removeLifecycleListener(LifecycleListener listener) {
786
787         lifecycle.removeLifecycleListener(listener);
788
789     }
790
791
792     /**
793      * Prepare for the beginning of active use of the public methods of this
794      * component. This method should be called after <code>configure()</code>,
795      * and before any of the public methods of the component are utilized.
796      *
797      * @exception LifecycleException if this component detects a fatal error
798      * that prevents this component from being used
799      */

800     public void start() throws LifecycleException {
801
802         // Validate and update our current component state
803
if (started)
804             throw new LifecycleException
805                 (sm.getString("accessLogValve.alreadyStarted"));
806         lifecycle.fireLifecycleEvent(START_EVENT, null);
807         started = true;
808
809         // Initialize the timeZone, Date formatters, and currentDate
810
timezone = TimeZone.getDefault();
811         timeZoneNoDST = calculateTimeZoneOffset(timezone.getRawOffset());
812         Calendar JavaDoc calendar = Calendar.getInstance(timezone);
813         int offset = calendar.get(Calendar.DST_OFFSET);
814         timeZoneDST = calculateTimeZoneOffset(timezone.getRawOffset()+offset);
815         
816         if (fileDateFormat==null || fileDateFormat.length()==0)
817             fileDateFormat = "yyyy-MM-dd";
818         dateFormatter = new SimpleDateFormat JavaDoc(fileDateFormat);
819         dateFormatter.setTimeZone(timezone);
820         dayFormatter = new SimpleDateFormat JavaDoc("dd");
821         dayFormatter.setTimeZone(timezone);
822         monthFormatter = new SimpleDateFormat JavaDoc("MM");
823         monthFormatter.setTimeZone(timezone);
824         yearFormatter = new SimpleDateFormat JavaDoc("yyyy");
825         yearFormatter.setTimeZone(timezone);
826         timeFormatter = new SimpleDateFormat JavaDoc("HH:mm:ss");
827         timeFormatter.setTimeZone(timezone);
828         currentDateString = getCurrentDateString();
829         dateStamp = dateFormatter.format(new Date JavaDoc());
830
831         open();
832
833     }
834
835
836     /**
837      * Gracefully terminate the active use of the public methods of this
838      * component. This method should be the last one called on a given
839      * instance of this component.
840      *
841      * @exception LifecycleException if this component detects a fatal error
842      * that needs to be reported
843      */

844     public void stop() throws LifecycleException {
845
846         // Validate and update our current component state
847
if (!started)
848             throw new LifecycleException
849                 (sm.getString("accessLogValve.notStarted"));
850         lifecycle.fireLifecycleEvent(STOP_EVENT, null);
851         started = false;
852
853         close();
854
855     }
856 }
857
Popular Tags