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       &n