KickJava   Java API By Example, From Geeks To Geeks.

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


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.File JavaDoc;
23 import java.io.FileWriter JavaDoc;
24 import java.io.IOException JavaDoc;
25 import java.io.PrintWriter JavaDoc;
26 import java.net.InetAddress JavaDoc;
27 import java.net.URLEncoder JavaDoc;
28 import java.text.DecimalFormat JavaDoc;
29 import java.text.SimpleDateFormat JavaDoc;
30 import java.util.Date JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.LinkedList JavaDoc;
33 import java.util.TimeZone JavaDoc;
34
35 import javax.servlet.ServletException JavaDoc;
36 import javax.servlet.http.Cookie JavaDoc;
37 import javax.servlet.http.HttpSession JavaDoc;
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.ServerInfo;
46 import org.apache.catalina.util.StringManager;
47 import org.apache.commons.logging.Log;
48 import org.apache.commons.logging.LogFactory;
49
50
51
52 /**
53  * An implementation of the W3c Extended Log File Format. See
54  * http://www.w3.org/TR/WD-logfile.html for more information about the format.
55  *
56  * The following fields are supported:
57  * <ul>
58  * <li><code>c-dns</code>: Client hostname</li>
59  * <li><code>c-ip</code>: Client ip address</li>
60  * <li><code>bytes</code>: bytes served</li>
61  * <li><code>cs-method</code>: request method</li>
62  * <li><code>cs-uri</code>: The full uri requested</li>
63  * <li><code>cs-uri-query</code>: The query string</li>
64  * <li><code>cs-uri-stem</code>: The uri without query string</li>
65  * <li><code>date</code>: The date in yyyy-mm-dd format for GMT</li>
66  * <li><code>s-dns</code>: The server dns entry </li>
67  * <li><code>s-ip</code>: The server ip address</li>
68  * <li><code>cs(XXX)</code>: The value of header XXX from client to server</li>
69  * <li><code>sc(XXX)</code>: The value of header XXX from server to client </li>
70  * <li><code>sc-status</code>: The status code</li>
71  * <li><code>time</code>: Time the request was served</li>
72  * <li><code>time-taken</code>: Time (in seconds) taken to serve the request</li>
73  * <li><code>x-A(XXX)</code>: Pull XXX attribute from the servlet context </li>
74  * <li><code>x-C(XXX)</code>: Pull the first cookie of the name XXX </li>
75  * <li><code>x-R(XXX)</code>: Pull XXX attribute from the servlet request </li>
76  * <li><code>x-S(XXX)</code>: Pull XXX attribute from the session </li>
77  * <li><code>x-P(...)</code>: Call request.getParameter(...)
78  * and URLencode it. Helpful to capture
79  * certain POST parameters.
80  * </li>
81  * <li>For any of the x-H(...) the following method will be called from the
82  * HttpServletRequestObject </li>
83  * <li><code>x-H(authType)</code>: getAuthType </li>
84  * <li><code>x-H(characterEncoding)</code>: getCharacterEncoding </li>
85  * <li><code>x-H(contentLength)</code>: getContentLength </li>
86  * <li><code>x-H(locale)</code>: getLocale</li>
87  * <li><code>x-H(protocol)</code>: getProtocol </li>
88  * <li><code>x-H(remoteUser)</code>: getRemoteUser</li>
89  * <li><code>x-H(requestedSessionId)</code>: getGequestedSessionId</li>
90  * <li><code>x-H(requestedSessionIdFromCookie)</code>:
91  * isRequestedSessionIdFromCookie </li>
92  * <li><code>x-H(requestedSessionIdValid)</code>:
93  * isRequestedSessionIdValid</li>
94  * <li><code>x-H(scheme)</code>: getScheme</li>
95  * <li><code>x-H(secure)</code>: isSecure</li>
96  * </ul>
97  *
98  *
99  *
100  * <p>
101  * Log rotation can be on or off. This is dictated by the rotatable
102  * property.
103  * </p>
104  *
105  * <p>
106  * For UNIX users, another field called <code>checkExists</code>is also
107  * available. If set to true, the log file's existence will be checked before
108  * each logging. This way an external log rotator can move the file
109  * somewhere and tomcat will start with a new file.
110  * </p>
111  *
112  * <p>
113  * For JMX junkies, a public method called </code>rotate</code> has
114  * been made available to allow you to tell this instance to move
115  * the existing log file to somewhere else start writing a new log file.
116  * </p>
117  *
118  * <p>
119  * Conditional logging is also supported. This can be done with the
120  * <code>condition</code> property.
121  * If the value returned from ServletRequest.getAttribute(condition)
122  * yields a non-null value. The logging will be skipped.
123  * </p>
124  *
125  * <p>
126  * For extended attributes coming from a getAttribute() call,
127  * it is you responsibility to ensure there are no newline or
128  * control characters.
129  * </p>
130  *
131  *
132  * @author Tim Funk
133  * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
134  */

135
136 public class ExtendedAccessLogValve
137     extends ValveBase
138     implements Lifecycle {
139
140
141     // ----------------------------------------------------------- Constructors
142

143
144     /**
145      * Construct a new instance of this class with default property values.
146      */

147     public ExtendedAccessLogValve() {
148
149         super();
150
151
152     }
153
154
155     // ----------------------------------------------------- Instance Variables
156
private static Log log = LogFactory.getLog(ExtendedAccessLogValve.class);
157
158
159     /**
160      * The descriptive information about this implementation.
161      */

162     protected static final String JavaDoc info =
163         "org.apache.catalina.valves.ExtendedAccessLogValve/1.0";
164
165
166     /**
167      * The lifecycle event support for this component.
168      */

169     protected LifecycleSupport lifecycle = new LifecycleSupport(this);
170
171
172
173     /**
174      * The string manager for this package.
175      */

176     private StringManager sm =
177         StringManager.getManager(Constants.Package);
178
179
180     /**
181      * Has this component been started yet?
182      */

183     private boolean started = false;
184
185
186     /**
187      * The as-of date for the currently open log file, or a zero-length
188      * string if there is no open log file.
189      */

190     private String JavaDoc dateStamp = "";
191
192
193     /**
194      * The PrintWriter to which we are currently logging, if any.
195      */

196     private PrintWriter JavaDoc writer = null;
197
198
199     /**
200      * The formatter for the date contained in the file name.
201      */

202     private SimpleDateFormat JavaDoc fileDateFormatter = null;
203
204
205     /**
206      * A date formatter to format a Date into a date in the format
207      * "yyyy-MM-dd".
208      */

209     private SimpleDateFormat JavaDoc dateFormatter = null;
210
211
212     /**
213      * A date formatter to format a Date into a time in the format
214      * "kk:mm:ss" (kk is a 24-hour representation of the hour).
215      */

216     private SimpleDateFormat JavaDoc timeFormatter = null;
217
218
219     /**
220      * Time taken formatter for 3 decimal places.
221      */

222      private DecimalFormat JavaDoc timeTakenFormatter = null;
223
224
225     /**
226      * My ip address. Look it up once and remember it. Dump this if we can
227      * determine another reliable way to get server ip address since this
228      * server may have many ip's.
229      */

230     private String JavaDoc myIpAddress = null;
231
232
233     /**
234      * My dns name. Look it up once and remember it. Dump this if we can
235      * determine another reliable way to get server name address since this
236      * server may have many ip's.
237      */

238     private String JavaDoc myDNSName = null;
239
240
241     /**
242      * Holder for all of the fields to log after the pattern is decoded.
243      */

244     private FieldInfo[] fieldInfos;
245
246
247     /**
248      * The current log file we are writing to. Helpful when checkExists
249      * is true.
250      */

251     private File JavaDoc currentLogFile = null;
252
253
254
255     /**
256      * The system time when we last updated the Date that this valve
257      * uses for log lines.
258      */

259     private Date JavaDoc currentDate = null;
260
261
262     /**
263      * Instant when the log daily rotation was last checked.
264      */

265     private long rotationLastChecked = 0L;
266
267
268     /**
269      * The directory in which log files are created.
270      */

271     private String JavaDoc directory = "logs";
272
273
274     /**
275      * The pattern used to format our access log lines.
276      */

277     private String JavaDoc pattern = null;
278
279
280     /**
281      * The prefix that is added to log file filenames.
282      */

283     private String JavaDoc prefix = "access_log.";
284
285
286     /**
287      * Should we rotate our log file? Default is true (like old behavior)
288      */

289     private boolean rotatable = true;
290
291
292     /**
293      * The suffix that is added to log file filenames.
294      */

295     private String JavaDoc suffix = "";
296
297
298     /**
299      * Are we doing conditional logging. default false.
300      */

301     private String JavaDoc condition = null;
302
303
304     /**
305      * Do we check for log file existence? Helpful if an external
306      * agent renames the log file so we can automagically recreate it.
307      */

308     private boolean checkExists = false;
309
310
311     /**
312      * Date format to place in log file name. Use at your own risk!
313      */

314     private String JavaDoc fileDateFormat = null;
315
316
317     // ------------------------------------------------------------- Properties
318

319
320     /**
321      * Return the directory in which we create log files.
322      */

323     public String JavaDoc getDirectory() {
324
325         return (directory);
326
327     }
328
329
330     /**
331      * Set the directory in which we create log files.
332      *
333      * @param directory The new log file directory
334      */

335     public void setDirectory(String JavaDoc directory) {
336
337         this.directory = directory;
338
339     }
340
341
342     /**
343      * Return descriptive information about this implementation.
344      */

345     public String JavaDoc getInfo() {
346
347         return (info);
348
349     }
350
351
352     /**
353      * Return the format pattern.
354      */

355     public String JavaDoc getPattern() {
356
357         return (this.pattern);
358
359     }
360
361
362     /**
363      * Set the format pattern, first translating any recognized alias.
364      *
365      * @param pattern The new pattern pattern
366      */

367     public void setPattern(String JavaDoc pattern) {
368
369         FieldInfo[] f= decodePattern(pattern);
370         if (f!=null) {
371             this.pattern = pattern;
372             this.fieldInfos = f;
373         }
374     }
375
376
377     /**
378      * Return the log file prefix.
379      */

380     public String JavaDoc getPrefix() {
381
382         return (prefix);
383
384     }
385
386
387     /**
388      * Set the log file prefix.
389      *
390      * @param prefix The new log file prefix
391      */

392     public void setPrefix(String JavaDoc prefix) {
393
394         this.prefix = prefix;
395
396     }
397
398
399     /**
400      * Return true if logs are automatically rotated.
401      */

402     public boolean isRotatable() {
403
404         return rotatable;
405
406     }
407
408
409     /**
410      * Set the value is we should we rotate the logs
411      *
412      * @param rotatable true is we should rotate.
413      */

414     public void setRotatable(boolean rotatable) {
415
416         this.rotatable = rotatable;
417
418     }
419
420
421     /**
422      * Return the log file suffix.
423      */

424     public String JavaDoc getSuffix() {
425
426         return (suffix);
427
428     }
429
430
431     /**
432      * Set the log file suffix.
433      *
434      * @param suffix The new log file suffix
435      */

436     public void setSuffix(String JavaDoc suffix) {
437
438         this.suffix = suffix;
439
440     }
441
442
443
444     /**
445      * Return whether the attribute name to look for when
446      * performing conditional loggging. If null, every
447      * request is logged.
448      */

449     public String JavaDoc getCondition() {
450
451         return condition;
452
453     }
454
455
456     /**
457      * Set the ServletRequest.attribute to look for to perform
458      * conditional logging. Set to null to log everything.
459      *
460      * @param condition Set to null to log everything
461      */

462     public void setCondition(String JavaDoc condition) {
463
464         this.condition = condition;
465
466     }
467
468
469
470     /**
471      * Check for file existence before logging.
472      */

473     public boolean isCheckExists() {
474
475         return checkExists;
476
477     }
478
479
480     /**
481      * Set whether to check for log file existence before logging.
482      *
483      * @param checkExists true meaning to check for file existence.
484      */

485     public void setCheckExists(boolean checkExists) {
486
487         this.checkExists = checkExists;
488
489     }
490
491
492     /**
493      * Return the date format date based log rotation.
494      */

495     public String JavaDoc getFileDateFormat() {
496         return fileDateFormat;
497     }
498
499
500     /**
501      * Set the date format date based log rotation.
502      */

503     public void setFileDateFormat(String JavaDoc fileDateFormat) {
504         this.fileDateFormat = fileDateFormat;
505     }
506
507
508     // --------------------------------------------------------- Public Methods
509

510
511     /**
512      * Log a message summarizing the specified request and response, according
513      * to the format specified by the <code>pattern</code> property.
514      *
515      * @param request Request being processed
516      * @param response Response being processed
517      *
518      * @exception IOException if an input/output error has occurred
519      * @exception ServletException if a servlet error has occurred
520      */

521     public void invoke(Request request, Response response)
522         throws IOException JavaDoc, ServletException JavaDoc {
523
524         // Pass this request on to the next valve in our pipeline
525
long endTime;
526         long runTime;
527         long startTime=System.currentTimeMillis();
528
529         getNext().invoke(request, response);
530
531         endTime = System.currentTimeMillis();
532         runTime = endTime-startTime;
533
534         if (fieldInfos==null || condition!=null &&
535               null!=request.getRequest().getAttribute(condition)) {
536             return;
537         }
538
539
540         Date JavaDoc date = getDate(endTime);
541         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
542
543         for (int i=0; fieldInfos!=null && i<fieldInfos.length; i++) {
544             switch(fieldInfos[i].type) {
545                 case FieldInfo.DATA_CLIENT:
546                     if (FieldInfo.FIELD_IP==fieldInfos[i].location)
547                         result.append(request.getRequest().getRemoteAddr());
548                     else if (FieldInfo.FIELD_DNS==fieldInfos[i].location)
549                         result.append(request.getRequest().getRemoteHost());
550                     else
551                         result.append("?WTF?"); /* This should never happen! */
552                     break;
553                 case FieldInfo.DATA_SERVER:
554                     if (FieldInfo.FIELD_IP==fieldInfos[i].location)
555                         result.append(myIpAddress);
556                     else if (FieldInfo.FIELD_DNS==fieldInfos[i].location)
557                         result.append(myDNSName);
558                     else
559                         result.append("?WTF?"); /* This should never happen! */
560                     break;
561                 case FieldInfo.DATA_REMOTE:
562                     result.append('?'); /* I don't know how to handle these! */
563                     break;
564                 case FieldInfo.DATA_CLIENT_TO_SERVER:
565                     result.append(getClientToServer(fieldInfos[i], request));
566                     break;
567                 case FieldInfo.DATA_SERVER_TO_CLIENT:
568                     result.append(getServerToClient(fieldInfos[i], response));
569                     break;
570                 case FieldInfo.DATA_SERVER_TO_RSERVER:
571                     result.append('-');
572                     break;
573                 case FieldInfo.DATA_RSERVER_TO_SERVER:
574                     result.append('-');
575                     break;
576                 case FieldInfo.DATA_APP_SPECIFIC:
577                     result.append(getAppSpecific(fieldInfos[i], request));
578                     break;
579                 case FieldInfo.DATA_SPECIAL:
580                     if (FieldInfo.SPECIAL_DATE==fieldInfos[i].location)
581                         result.append(dateFormatter.format(date));
582                     else if (FieldInfo.SPECIAL_TIME_TAKEN==fieldInfos[i].location)
583                         result.append(timeTakenFormatter.format(runTime/1000d));
584                     else if (FieldInfo.SPECIAL_TIME==fieldInfos[i].location)
585                         result.append(timeFormatter.format(date));
586                     else if (FieldInfo.SPECIAL_BYTES==fieldInfos[i].location) {
587                         int length = response.getContentCount();
588                         if (length > 0)
589                             result.append(length);
590                         else
591                             result.append("-");
592                     } else if (FieldInfo.SPECIAL_CACHED==fieldInfos[i].location)
593                         result.append('-'); /* I don't know how to evaluate this! */
594                     else
595                         result.append("?WTF?"); /* This should never happen! */
596                     break;
597                 default:
598                     result.append("?WTF?"); /* This should never happen! */
599             }
600
601             if (fieldInfos[i].postWhiteSpace!=null) {
602                 result.append(fieldInfos[i].postWhiteSpace);
603             }
604         }
605         log(result.toString(), date);
606
607     }
608
609
610     /**
611      * Rename the existing log file to something else. Then open the
612      * old log file name up once again. Intended to be called by a JMX
613      * agent.
614      *
615      *
616      * @param newFileName The file name to move the log file entry to
617      * @return true if a file was rotated with no error
618      */

619     public synchronized boolean rotate(String JavaDoc newFileName) {
620
621         if (currentLogFile!=null) {
622             File JavaDoc holder = currentLogFile;
623             close();
624             try {
625                 holder.renameTo(new File JavaDoc(newFileName));
626             } catch(Throwable JavaDoc e){
627                 log.error("rotate failed", e);
628             }
629
630             /* Make sure date is correct */
631             currentDate = new Date JavaDoc(System.currentTimeMillis());
632             dateStamp = fileDateFormatter.format(currentDate);
633
634             open();
635             return true;
636         } else {
637             return false;
638         }
639
640     }
641
642     // -------------------------------------------------------- Private Methods
643

644
645     /**
646      * Return the client to server data.
647      * @param fieldInfo The field to decode.
648      * @param request The object we pull data from.
649      * @return The appropriate value.
650      */

651      private String JavaDoc getClientToServer(FieldInfo fieldInfo, Request request) {
652
653         switch(fieldInfo.location) {
654             case FieldInfo.FIELD_METHOD:
655                 return request.getMethod();
656             case FieldInfo.FIELD_URI:
657                 if (null==request.getQueryString())
658                     return request.getRequestURI();
659                 else
660                     return request.getRequestURI() + "?" + request.getQueryString();
661             case FieldInfo.FIELD_URI_STEM:
662                 return request.getRequestURI();
663             case FieldInfo.FIELD_URI_QUERY:
664                 if (null==request.getQueryString())
665                     return "-";
666                 return request.getQueryString();
667             case FieldInfo.FIELD_HEADER:
668                 return wrap(request.getHeader(fieldInfo.value));
669             default:
670                 ;
671         }
672
673         return "-";
674
675     }
676
677
678     /**
679      * Return the server to client data.
680      * @param fieldInfo The field to decode.
681      * @param response The object we pull data from.
682      * @return The appropriate value.
683      */

684     private String JavaDoc getServerToClient(FieldInfo fieldInfo, Response response) {
685         switch(fieldInfo.location) {
686             case FieldInfo.FIELD_STATUS:
687                 return "" + response.getStatus();
688             case FieldInfo.FIELD_COMMENT:
689                 return "?"; /* Not coded yet*/
690             case FieldInfo.FIELD_HEADER:
691                 return wrap(response.getHeader(fieldInfo.value));
692             default:
693                 ;
694         }
695
696         return "-";
697
698     }
699
700
701     /**
702      * Get app specific data.
703      * @param fieldInfo The field to decode
704      * @param request Where we will pull the data from.
705      * @return The appropriate value
706      */

707     private String JavaDoc getAppSpecific(FieldInfo fieldInfo, Request request) {
708
709         switch(fieldInfo.xType) {
710             case FieldInfo.X_PARAMETER:
711                 return wrap(urlEncode(request.getParameter(fieldInfo.value)));
712             case FieldInfo.X_REQUEST:
713                 return wrap(request.getAttribute(fieldInfo.value));
714             case FieldInfo.X_SESSION:
715                 HttpSession JavaDoc session = null;
716                 if (request!=null){
717                     session = request.getSession(false);
718                     if (session!=null)
719                         return wrap(session.getAttribute(fieldInfo.value));
720                 }
721                 break;
722             case FieldInfo.X_COOKIE:
723                 Cookie JavaDoc[] c = request.getCookies();
724                 for (int i=0; c != null && i < c.length; i++){
725                     if (fieldInfo.value.equals(c[i].getName())){
726                         return wrap(c[i].getValue());
727                     }
728                  }
729             case FieldInfo.X_APP:
730                 return wrap(request.getContext().getServletContext()
731                                 .getAttribute(fieldInfo.value));
732             case FieldInfo.X_SERVLET_REQUEST:
733                 if (fieldInfo.location==FieldInfo.X_LOC_AUTHTYPE) {
734                     return wrap(request.getAuthType());
735                 } else if (fieldInfo.location==FieldInfo.X_LOC_REMOTEUSER) {
736                     return wrap(request.getRemoteUser());
737                 } else if (fieldInfo.location==
738                             FieldInfo.X_LOC_REQUESTEDSESSIONID) {
739                     return wrap(request.getRequestedSessionId());
740                 } else if (fieldInfo.location==
741                             FieldInfo.X_LOC_REQUESTEDSESSIONIDFROMCOOKIE) {
742                     return wrap(""+request.isRequestedSessionIdFromCookie());
743                 } else if (fieldInfo.location==
744                             FieldInfo.X_LOC_REQUESTEDSESSIONIDVALID) {
745                     return wrap(""+request.isRequestedSessionIdValid());
746                 } else if (fieldInfo.location==FieldInfo.X_LOC_CONTENTLENGTH) {
747                     return wrap(""+request.getContentLength());
748                 } else if (fieldInfo.location==
749                             FieldInfo.X_LOC_CHARACTERENCODING) {
750                     return wrap(request.getCharacterEncoding());
751                 } else if (fieldInfo.location==FieldInfo.X_LOC_LOCALE) {
752                     return wrap(request.getLocale());
753                 } else if (fieldInfo.location==FieldInfo.X_LOC_PROTOCOL) {
754                     return wrap(request.getProtocol());
755                 } else if (fieldInfo.location==FieldInfo.X_LOC_SCHEME) {
756                     return wrap(request.getScheme());
757                 } else if (fieldInfo.location==FieldInfo.X_LOC_SECURE) {
758                     return wrap(""+request.isSecure());
759                 }
760                 break;
761             default:
762                 ;
763         }
764
765         return "-";
766
767     }
768
769
770     /**
771      * urlEncode the given string. If null or empty, return null.
772      */

773     private String JavaDoc urlEncode(String JavaDoc value) {
774         if (null==value || value.length()==0) {
775             return null;
776         }
777         return URLEncoder.encode(value);
778     }
779
780
781     /**
782      * Wrap the incoming value into quotes and escape any inner
783      * quotes with double quotes.
784      *
785      * @param value - The value to wrap quotes around
786      * @return '-' if empty of null. Otherwise, toString() will
787      * be called on the object and the value will be wrapped
788      * in quotes and any quotes will be escaped with 2
789      * sets of quotes.
790      */

791     private String JavaDoc wrap(Object JavaDoc value) {
792
793         String JavaDoc svalue;
794         // Does the value contain a " ? If so must encode it
795
if (value==null || "-".equals(value))
796             return "-";
797
798
799         try {
800             svalue = value.toString();
801             if ("".equals(svalue))
802                 return "-";
803         } catch(Throwable JavaDoc e){
804             /* Log error */
805             return "-";
806         }
807
808         /* Wrap all quotes in double quotes. */
809         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(svalue.length()+2);
810         buffer.append('"');
811         int i=0;
812         while (i<svalue.length()) {
813             int j = svalue.indexOf('"', i);
814             if (j==-1) {
815                 buffer.append(svalue.substring(i));
816                 i=svalue.length();
817             } else {
818                 buffer.append(svalue.substring(i, j+1));
819                 buffer.append('"');
820                 i=j+2;
821             }
822         }
823
824         buffer.append('"');
825         return buffer.toString();
826
827     }
828
829
830     /**
831      * Close the currently open log file (if any)
832      */

833     private synchronized void close() {
834
835         if (writer == null)
836             return;
837         writer.flush();
838         writer.close();
839         writer = null;
840         currentLogFile = null;
841
842     }
843
844
845     /**
846      * Log the specified message to the log file, switching files if the date
847      * has changed since the previous log call.
848      *
849      * @param message Message to be logged
850      * @param date the current Date object (so this method doesn't need to
851      * create a new one)
852      */

853     private void log(String JavaDoc message, Date JavaDoc date) {
854
855         if (rotatable){
856             // Only do a logfile switch check once a second, max.
857
long systime = System.currentTimeMillis();
858             if ((systime - rotationLastChecked) > 1000) {
859
860                 // We need a new currentDate
861
currentDate = new Date JavaDoc(systime);
862                 rotationLastChecked = systime;
863
864                 // Check for a change of date
865
String JavaDoc tsDate = fileDateFormatter.format(currentDate);
866
867                 // If the date has changed, switch log files
868
if (!dateStamp.equals(tsDate)) {
869                     synchronized (this) {
870                         if (!dateStamp.equals(tsDate)) {
871                             close();
872                             dateStamp = tsDate;
873                             open();
874                         }
875                     }
876                 }
877             }
878         }
879
880         /* In case something external rotated the file instead */
881         if (checkExists){
882             synchronized (this) {
883                 if (currentLogFile!=null && !currentLogFile.exists()) {
884                     try {
885                         close();
886                     } catch (Throwable JavaDoc e){
887                         log.info("at least this wasn't swallowed", e);
888                     }
889
890                     /* Make sure date is correct */
891                     currentDate = new Date JavaDoc(System.currentTimeMillis());
892                     dateStamp = fileDateFormatter.format(currentDate);
893
894                     open();
895                 }
896             }
897         }
898
899         // Log this message
900
if (writer != null) {
901             writer.println(message);
902         }
903
904     }
905
906
907     /**
908      * Open the new log file for the date specified by <code>dateStamp</code>.
909      */

910     private synchronized void open() {
911
912         // Create the directory if necessary
913
File JavaDoc dir = new File JavaDoc(directory);
914         if (!dir.isAbsolute())
915             dir = new File JavaDoc(System.getProperty("catalina.base"), directory);
916         dir.mkdirs();
917
918         // Open the current log file
919
try {
920             String JavaDoc pathname;
921
922             // If no rotate - no need for dateStamp in fileName
923
if (rotatable){
924                 pathname = dir.getAbsolutePath() + File.separator +
925                             prefix + dateStamp + suffix;
926             } else {
927                 pathname = dir.getAbsolutePath() + File.separator +
928                             prefix + suffix;
929             }
930
931             currentLogFile = new File JavaDoc(pathname);
932             writer = new PrintWriter JavaDoc(new FileWriter JavaDoc(pathname, true), true);
933             if (currentLogFile.length()==0) {
934                 writer.println("#Fields: " + pattern);
935                 writer.println("#Version: 1.0");
936                 writer.println("#Software: " + ServerInfo.getServerInfo());
937             }
938
939
940         } catch (IOException JavaDoc e) {
941             writer = null;
942             currentLogFile = null;
943         }
944
945     }
946
947
948     /**
949      * This method returns a Date object that is accurate to within one
950      * second. If a thread calls this method to get a Date and it's been
951      * less than 1 second since a new Date was created, this method
952      * simply gives out the same Date again so that the system doesn't
953      * spend time creating Date objects unnecessarily.
954      */

955     private Date JavaDoc getDate(long systime) {
956         /* Avoid extra call to System.currentTimeMillis(); */
957         if (0==systime) {
958             systime = System.currentTimeMillis();
959         }
960
961         // Only create a new Date once per second, max.
962
if ((systime - currentDate.getTime()) > 1000) {
963             currentDate.setTime(systime);
964         }
965
966         return currentDate;
967
968     }
969
970
971     // ------------------------------------------------------ Lifecycle Methods
972

973
974     /**
975      * Add a lifecycle event listener to this component.
976      *
977      * @param listener The listener to add
978      */

979     public void addLifecycleListener(LifecycleListener listener) {
980
981         lifecycle.addLifecycleListener(listener);
982
983     }
984
985
986     /**
987      * Get the lifecycle listeners associated with this lifecycle. If this
988      * Lifecycle has no listeners registered, a zero-length array is returned.
989      */

990     public LifecycleListener[] findLifecycleListeners() {
991
992         return lifecycle.findLifecycleListeners();
993
994     }
995
996
997     /**
998      * Remove a lifecycle event listener from this component.
999      *
1000     * @param listener The listener to add
1001     */

1002    public void removeLifecycleListener(LifecycleListener listener) {
1003
1004        lifecycle.removeLifecycleListener(listener);
1005
1006    }
1007
1008
1009    /**
1010     * Prepare for the beginning of active use of the public methods of this
1011     * component. This method should be called after <code>configure()</code>,
1012     * and before any of the public methods of the component are utilized.
1013     *
1014     * @exception LifecycleException if this component detects a fatal error
1015     * that prevents this component from being used
1016     */

1017    public void start() throws LifecycleException {
1018
1019        // Validate and update our current component state
1020
if (started)
1021            throw new LifecycleException
1022                (sm.getString("extendedAccessLogValve.alreadyStarted"));
1023        lifecycle.fireLifecycleEvent(START_EVENT, null);
1024        started = true;
1025
1026        // Initialize the timeZone, Date formatters, and currentDate
1027
TimeZone JavaDoc tz = TimeZone.getTimeZone("GMT");
1028        dateFormatter = new SimpleDateFormat JavaDoc("yyyy-MM-dd");
1029        dateFormatter.setTimeZone(tz);
1030        timeFormatter = new SimpleDateFormat JavaDoc("HH:mm:ss");
1031        timeFormatter.setTimeZone(tz);
1032        currentDate = new Date JavaDoc(System.currentTimeMillis());
1033        if (fileDateFormat==null || fileDateFormat.length()==0)
1034            fileDateFormat = "yyyy-MM-dd";
1035        fileDateFormatter = new SimpleDateFormat JavaDoc(fileDateFormat);
1036        dateStamp = fileDateFormatter.format(currentDate);
1037        timeTakenFormatter = new DecimalFormat JavaDoc("0.000");
1038
1039        /* Everybody say ick ... ick */
1040        try {
1041            InetAddress JavaDoc inetAddress = InetAddress.getLocalHost();
1042            myIpAddress = inetAddress.getHostAddress();
1043            myDNSName = inetAddress.getHostName();
1044        } catch(Throwable JavaDoc e){
1045            myIpAddress="127.0.0.1";
1046            myDNSName="localhost";
1047        }
1048
1049        open();
1050
1051    }
1052
1053
1054    /**
1055     * Gracefully terminate the active use of the public methods of this
1056     * component. This method should be the last one called on a given
1057     * instance of this component.
1058     *
1059     * @exception LifecycleException if this component detects a fatal error
1060     * that needs to be reported
1061     */

1062    public void stop() throws LifecycleException {
1063
1064        // Validate and update our current component state
1065
if (!started)
1066            throw new LifecycleException
1067                (sm.getString("extendedAccessLogValve.notStarted"));
1068        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
1069        started = false;
1070
1071        close();
1072
1073    }
1074
1075
1076    /**
1077     * Decode the given pattern. Is public so a pattern may
1078     * allows to be validated.
1079     * @param fields The pattern to decode
1080     * @return null on error. Otherwise array of decoded fields
1081     */

1082    public FieldInfo[] decodePattern(String JavaDoc fields) {
1083
1084        if (log.isDebugEnabled())
1085            log.debug("decodePattern, fields=" + fields);
1086
1087        LinkedList JavaDoc list = new LinkedList JavaDoc();
1088
1089        //Ignore leading whitespace.
1090
int i=0;
1091        for (;i<fields.length() && Character.isWhitespace(fields.charAt(i));i++);
1092
1093        if (i>=fields.length()) {
1094            log.info("fields was just empty or whitespace");
1095            return null;
1096        }
1097
1098        int j;
1099        while(i<fields.length()) {
1100            if (log.isDebugEnabled())
1101                log.debug("fields.substring(i)=" + fields.substring(i));
1102
1103            FieldInfo currentFieldInfo = new FieldInfo();
1104
1105
1106            if (fields.startsWith("date",i)) {
1107                currentFieldInfo.type = FieldInfo.DATA_SPECIAL;
1108                currentFieldInfo.location = FieldInfo.SPECIAL_DATE;
1109                i+="date".length();
1110            } else if (fields.startsWith("time-taken",i)) {
1111                currentFieldInfo.type = FieldInfo.DATA_SPECIAL;
1112                currentFieldInfo.location = FieldInfo.SPECIAL_TIME_TAKEN;
1113                i+="time-taken".length();
1114            } else if (fields.startsWith("time",i)) {
1115                currentFieldInfo.type = FieldInfo.DATA_SPECIAL;
1116                currentFieldInfo.location = FieldInfo.SPECIAL_TIME;
1117                i+="time".length();
1118            } else if (fields.startsWith("bytes",i)) {
1119                currentFieldInfo.type = FieldInfo.DATA_SPECIAL;
1120                currentFieldInfo.location = FieldInfo.SPECIAL_BYTES;
1121                i+="bytes".length();
1122            } else if (fields.startsWith("cached",i)) {
1123                currentFieldInfo.type = FieldInfo.DATA_SPECIAL;
1124                currentFieldInfo.location = FieldInfo.SPECIAL_CACHED;
1125                i+="cached".length();
1126            } else if (fields.startsWith("c-ip",i)) {
1127                currentFieldInfo.type = FieldInfo.DATA_CLIENT;
1128                currentFieldInfo.location = FieldInfo.FIELD_IP;
1129                i+="c-ip".length();
1130            } else if (fields.startsWith("c-dns",i)) {
1131                currentFieldInfo.type = FieldInfo.DATA_CLIENT;
1132                currentFieldInfo.location = FieldInfo.FIELD_DNS;
1133                i+="c-dns".length();
1134            } else if (fields.startsWith("s-ip",i)) {
1135                currentFieldInfo.type = FieldInfo.DATA_SERVER;
1136                currentFieldInfo.location = FieldInfo.FIELD_IP;
1137                i+="s-ip".length();
1138            } else if (fields.startsWith("s-dns",i)) {
1139                currentFieldInfo.type = FieldInfo.DATA_SERVER;
1140                currentFieldInfo.location = FieldInfo.FIELD_DNS;
1141                i+="s-dns".length();
1142            } else if (fields.startsWith("cs",i)) {
1143                i = decode(fields, i+2, currentFieldInfo,
1144                            FieldInfo.DATA_CLIENT_TO_SERVER);
1145                if (i<0)
1146                    return null;
1147            } else if (fields.startsWith("sc",i)) {
1148                i = decode(fields, i+2, currentFieldInfo,
1149                            FieldInfo.DATA_SERVER_TO_CLIENT);
1150                if (i<0)
1151                    return null;
1152            } else if (fields.startsWith("sr",i)) {
1153                i = decode(fields, i+2, currentFieldInfo,
1154                            FieldInfo.DATA_SERVER_TO_RSERVER);
1155                if (i<0)
1156                    return null;
1157            } else if (fields.startsWith("rs",i)) {
1158                i = decode(fields, i+2, currentFieldInfo,
1159                            FieldInfo.DATA_RSERVER_TO_SERVER);
1160                if (i<0)
1161                    return null;
1162            } else if (fields.startsWith("x",i)) {
1163                i = decodeAppSpecific(fields, i, currentFieldInfo);
1164            } else {
1165                // Unable to decode ...
1166
log.error("unable to decode with rest of chars being: " +
1167                            fields.substring(i));
1168                return null;
1169            }
1170
1171            // By this point we should have the field, get the whitespace
1172
j=i;
1173            for (;j<fields.length() && Character.isWhitespace(fields.charAt(j));j++);
1174
1175            if (j>=fields.length()) {
1176                if (j==i) {
1177                    // Special case - end of string
1178
currentFieldInfo.postWhiteSpace = "";
1179                } else {
1180                    currentFieldInfo.postWhiteSpace = fields.substring(i);
1181                    i=j;
1182                }
1183            } else {
1184                currentFieldInfo.postWhiteSpace = fields.substring(i,j);
1185                i=j;
1186            }
1187
1188            list.add(currentFieldInfo);
1189        }
1190
1191        i=0;
1192        FieldInfo[] f = new FieldInfo[list.size()];
1193        for (Iterator JavaDoc k = list.iterator(); k.hasNext();)
1194             f[i++] = (FieldInfo)k.next();
1195
1196        if (log.isDebugEnabled())
1197            log.debug("finished decoding with length of: " + i);
1198
1199        return f;
1200    }
1201
1202    /**
1203     * Decode the cs or sc fields.
1204     * Returns negative on error.
1205     *
1206     * @param fields The pattern to decode
1207     * @param i The string index where we are decoding.
1208     * @param fieldInfo Where to store the results
1209     * @param type The type we are decoding.
1210     * @return -1 on error. Otherwise the new String index.
1211     */

1212    private int decode(String JavaDoc fields, int i, FieldInfo fieldInfo, short type) {
1213
1214        if (fields.startsWith("-status",i)) {
1215            fieldInfo.location = FieldInfo.FIELD_STATUS;
1216            i+="-status".length();
1217        } else if (fields.startsWith("-comment",i)) {
1218            fieldInfo.location = FieldInfo.FIELD_COMMENT;
1219            i+="-comment".length();
1220        } else if (fields.startsWith("-uri-query",i)) {
1221            fieldInfo.location = FieldInfo.FIELD_URI_QUERY;
1222            i+="-uri-query".length();
1223        } else if (fields.startsWith("-uri-stem",i)) {
1224            fieldInfo.location = FieldInfo.FIELD_URI_STEM;
1225            i+="-uri-stem".length();
1226        } else if (fields.startsWith("-uri",i)) {
1227            fieldInfo.location = FieldInfo.FIELD_URI;
1228            i+="-uri".length();
1229        } else if (fields.startsWith("-method",i)) {
1230            fieldInfo.location = FieldInfo.FIELD_METHOD;
1231            i+="-method".length();
1232        } else if (fields.startsWith("(",i)) {
1233            fieldInfo.location = FieldInfo.FIELD_HEADER;
1234            i++; /* Move past the ( */
1235            int j = fields.indexOf(')', i);
1236            if (j==-1) { /* Not found */
1237                log.error("No closing ) found for in decode");
1238                return -1;
1239            }
1240            fieldInfo.value = fields.substring(i,j);
1241            i=j+1; // Move pointer past ) */
1242
} else {
1243            log.error("The next characters couldn't be decoded: " + fields.substring(i));
1244            return -1;
1245        }
1246
1247        fieldInfo.type = type;
1248        return i;
1249
1250    }
1251
1252
1253    /**
1254      * Decode app specific log entry.
1255      *
1256      * Special fields are of the form:
1257      * x-C(...) - For cookie
1258      * x-A(...) - Value in servletContext
1259      * x-S(...) - Value in session
1260      * x-R(...) - Value in servletRequest
1261      * @param fields The pattern to decode
1262      * @param i The string index where we are decoding.
1263      * @param fieldInfo Where to store the results
1264      * @return -1 on error. Otherwise the new String index.
1265      */

1266    private int decodeAppSpecific(String JavaDoc fields, int i, FieldInfo fieldInfo) {
1267
1268        fieldInfo.type = FieldInfo.DATA_APP_SPECIFIC;
1269        /* Move past 'x-' */
1270        i+=2;
1271
1272        if (i>=fields.length()) {
1273            log.error("End of line reached before decoding x- param");
1274            return -1;
1275        }
1276
1277        switch(fields.charAt(i)) {
1278            case 'A':
1279                fieldInfo.xType = FieldInfo.X_APP;
1280                break;
1281            case 'C':
1282                fieldInfo.xType = FieldInfo.X_COOKIE;
1283                break;
1284            case 'R':
1285                fieldInfo.xType = FieldInfo.X_REQUEST;
1286                break;
1287            case 'S':
1288                fieldInfo.xType = FieldInfo.X_SESSION;
1289                break;
1290            case 'H':
1291                fieldInfo.xType = FieldInfo.X_SERVLET_REQUEST;
1292                break;
1293            case 'P':
1294                fieldInfo.xType = FieldInfo.X_PARAMETER;
1295                break;
1296            default:
1297                return -1;
1298        }
1299
1300        /* test that next char is a ( */
1301        if (i+1!=fields.indexOf('(',i)) {
1302            log.error("x param in wrong format. Needs to be 'x-#(...)' read the docs!");
1303            return -1;
1304        }
1305        i+=2; /* Move inside of the () */
1306
1307        /* Look for ending ) and return error if not found. */
1308        int j = fields.indexOf(')',i);
1309        if (j==-1) {
1310            log.error("x param in wrong format. No closing ')'!");
1311            return -1;
1312        }
1313
1314        fieldInfo.value = fields.substring(i,j);
1315
1316        if (fieldInfo.xType == FieldInfo.X_SERVLET_REQUEST) {
1317            if ("authType".equals(fieldInfo.value)){
1318                fieldInfo.location = FieldInfo.X_LOC_AUTHTYPE;
1319            } else if ("remoteUser".equals(fieldInfo.value)){
1320                fieldInfo.location = FieldInfo.X_LOC_REMOTEUSER;
1321            } else if ("requestedSessionId".equals(fieldInfo.value)){
1322                fieldInfo.location = FieldInfo.X_LOC_REQUESTEDSESSIONID;
1323            } else if ("requestedSessionIdFromCookie".equals(fieldInfo.value)){
1324                fieldInfo.location = FieldInfo.X_LOC_REQUESTEDSESSIONIDFROMCOOKIE;
1325            } else if ("requestedSessionIdValid".equals(fieldInfo.value)){
1326                fieldInfo.location = FieldInfo.X_LOC_REQUESTEDSESSIONIDVALID;
1327            } else if ("contentLength".equals(fieldInfo.value)){
1328                fieldInfo.location = FieldInfo.X_LOC_CONTENTLENGTH;
1329            } else if ("characterEncoding".equals(fieldInfo.value)){
1330                fieldInfo.location = FieldInfo.X_LOC_CHARACTERENCODING;
1331            } else if ("locale".equals(fieldInfo.value)){
1332                fieldInfo.location = FieldInfo.X_LOC_LOCALE;
1333            } else if ("protocol".equals(fieldInfo.value)){
1334                fieldInfo.location = FieldInfo.X_LOC_PROTOCOL;
1335            } else if ("scheme".equals(fieldInfo.value)){
1336                fieldInfo.location = FieldInfo.X_LOC_SCHEME;
1337            } else if ("secure".equals(fieldInfo.value)){
1338                fieldInfo.location = FieldInfo.X_LOC_SECURE;
1339            } else {
1340                log.error("x param for servlet request, couldn't decode value: " +
1341                            fieldInfo.location);
1342                return -1;
1343            }
1344        }
1345
1346        return j+1;
1347
1348    }
1349
1350
1351}
1352
1353/**
1354 * A simple helper for decoding the pattern.
1355 */

1356class FieldInfo {
1357    /*
1358       The goal of the constants listed below is to make the construction of the log
1359       entry as quick as possible via numerci decodings of the methods to call instead
1360       of performing many String comparisons on each logging request.
1361    */

1362
1363    /* Where the data is located. */
1364    static final short DATA_CLIENT = 0;
1365    static final short DATA_SERVER = 1;
1366    static final short DATA_REMOTE = 2;
1367    static final short DATA_CLIENT_TO_SERVER = 3;
1368    static final short DATA_SERVER_TO_CLIENT = 4;
1369    static final short DATA_SERVER_TO_RSERVER = 5; /* Here to honor the spec. */
1370    static final short DATA_RSERVER_TO_SERVER = 6; /* Here to honor the spec. */
1371    static final short DATA_APP_SPECIFIC = 7;
1372    static final short DATA_SPECIAL = 8;
1373
1374    /* The type of special fields. */
1375    static final short SPECIAL_DATE = 1;
1376    static final short SPECIAL_TIME_TAKEN = 2;
1377    static final short SPECIAL_TIME = 3;
1378    static final short SPECIAL_BYTES = 4;
1379    static final short SPECIAL_CACHED = 5;
1380
1381    /* Where to pull the data for prefixed values */
1382    static final short FIELD_IP = 1;
1383    static final short FIELD_DNS = 2;
1384    static final short FIELD_STATUS = 3;
1385    static final short FIELD_COMMENT = 4;
1386    static final short FIELD_METHOD = 5;
1387    static final short FIELD_URI = 6;
1388    static final short FIELD_URI_STEM = 7;
1389    static final short FIELD_URI_QUERY = 8;
1390    static final short FIELD_HEADER = 9;
1391
1392
1393    /* Application Specific parameters */
1394    static final short X_REQUEST = 1; /* For x app specific */
1395    static final short X_SESSION = 2; /* For x app specific */
1396    static final short X_COOKIE = 3; /* For x app specific */
1397    static final short X_APP = 4; /* For x app specific */
1398    static final short X_SERVLET_REQUEST = 5; /* For x app specific */
1399    static final short X_PARAMETER = 6; /* For x app specific */
1400
1401    static final short X_LOC_AUTHTYPE = 1;
1402    static final short X_LOC_REMOTEUSER = 2;
1403    static final short X_LOC_REQUESTEDSESSIONID = 3;
1404    static final short X_LOC_REQUESTEDSESSIONIDFROMCOOKIE = 4;
1405    static final short X_LOC_REQUESTEDSESSIONIDVALID = 5;
1406    static final short X_LOC_CONTENTLENGTH = 6;
1407    static final short X_LOC_CHARACTERENCODING = 7;
1408    static final short X_LOC_LOCALE = 8;
1409    static final short X_LOC_PROTOCOL = 9;
1410    static final short X_LOC_SCHEME = 10;
1411    static final short X_LOC_SECURE = 11;
1412
1413
1414
1415    /** The field type */
1416    short type;
1417
1418    /** Where to pull the data from? Icky variable name. */
1419    short location;
1420
1421    /** The x- specific place to pull the data from. */
1422    short xType;
1423
1424    /** The field value if needed. Needed for headers and app specific. */
1425    String JavaDoc value;
1426
1427    /** Any white space after this field? Put it here. */
1428    String JavaDoc postWhiteSpace = null;
1429
1430}
1431
Popular Tags