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 package org.apache.catalina.valves;
18
19
20 import java.io.CharArrayWriter;
21 import java.io.IOException;
22 import java.net.InetAddress;
23 import java.text.SimpleDateFormat;
24 import java.util.ArrayList;
25 import java.util.Date;
26 import java.util.Enumeration;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.TimeZone;
33 import java.util.concurrent.atomic.AtomicBoolean;
34
35 import javax.servlet.RequestDispatcher;
36 import javax.servlet.ServletException;
37 import javax.servlet.http.Cookie;
38 import javax.servlet.http.HttpSession;
39
40 import org.apache.catalina.AccessLog;
41 import org.apache.catalina.Globals;
42 import org.apache.catalina.LifecycleException;
43 import org.apache.catalina.LifecycleState;
44 import org.apache.catalina.Session;
45 import org.apache.catalina.connector.ClientAbortException;
46 import org.apache.catalina.connector.Request;
47 import org.apache.catalina.connector.Response;
48 import org.apache.catalina.util.TLSUtil;
49 import org.apache.coyote.ActionCode;
50 import org.apache.coyote.RequestInfo;
51 import org.apache.juli.logging.Log;
52 import org.apache.juli.logging.LogFactory;
53 import org.apache.tomcat.util.ExceptionUtils;
54 import org.apache.tomcat.util.collections.SynchronizedStack;
55 import org.apache.tomcat.util.net.IPv6Utils;
56
57
58 /**
59  * <p>Abstract implementation of the <b>Valve</b> interface that generates a web
60  * server access log with the detailed line contents matching a configurable
61  * pattern. The syntax of the available patterns is similar to that supported by
62  * the <a href="https://httpd.apache.org/">Apache HTTP Server</a>
63  * <code>mod_log_config</code> module.</p>
64  *
65  * <p>Patterns for the logged message may include constant text or any of the
66  * following replacement strings, for which the corresponding information
67  * from the specified Response is substituted:</p>
68  * <ul>
69  * <li><b>%a</b> - Remote IP address
70  * <li><b>%A</b> - Local IP address
71  * <li><b>%b</b> - Bytes sent, excluding HTTP headers, or '-' if no bytes
72  *     were sent
73  * <li><b>%B</b> - Bytes sent, excluding HTTP headers
74  * <li><b>%h</b> - Remote host name (or IP address if
75  * <code>enableLookups</code> for the connector is false)
76  * <li><b>%H</b> - Request protocol
77  * <li><b>%l</b> - Remote logical username from identd (always returns '-')
78  * <li><b>%m</b> - Request method
79  * <li><b>%p</b> - Local port
80  * <li><b>%q</b> - Query string (prepended with a '?' if it exists, otherwise
81  *     an empty string
82  * <li><b>%r</b> - First line of the request
83  * <li><b>%s</b> - HTTP status code of the response
84  * <li><b>%S</b> - User session ID
85  * <li><b>%t</b> - Date and time, in Common Log Format format
86  * <li><b>%u</b> - Remote user that was authenticated
87  * <li><b>%U</b> - Requested URL path
88  * <li><b>%v</b> - Local server name
89  * <li><b>%D</b> - Time taken to process the request, in millis
90  * <li><b>%T</b> - Time taken to process the request, in seconds
91  * <li><b>%F</b> - Time taken to commit the response, in millis
92  * <li><b>%I</b> - current Request thread name (can compare later with stacktraces)
93  * <li><b>%X</b> - Connection status when response is completed:
94  *   <ul>
95  *   <li><code>X</code> = Connection aborted before the response completed.</li>
96  *   <li><code>+</code> = Connection may be kept alive after the response is sent.</li>
97  *   <li><code>-</code> = Connection will be closed after the response is sent.</li>
98  *   </ul>
99  * </ul>
100  * <p>In addition, the caller can specify one of the following aliases for
101  * commonly utilized patterns:</p>
102  * <ul>
103  * <li><b>common</b> - <code>%h %l %u %t "%r" %s %b</code>
104  * <li><b>combined</b> -
105  *   <code>%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"</code>
106  * </ul>
107  *
108  * <p>
109  * There is also support to write information from the cookie, incoming
110  * header, the Session or something else in the ServletRequest.<br>
111  * It is modeled after the
112  * <a href="https://httpd.apache.org/">Apache HTTP Server</a> log configuration
113  * syntax:</p>
114  * <ul>
115  * <li><code>%{xxx}i</code> for incoming headers
116  * <li><code>%{xxx}o</code> for outgoing response headers
117  * <li><code>%{xxx}c</code> for a specific cookie
118  * <li><code>%{xxx}r</code> xxx is an attribute in the ServletRequest
119  * <li><code>%{xxx}s</code> xxx is an attribute in the HttpSession
120  * <li><code>%{xxx}t</code> xxx is an enhanced SimpleDateFormat pattern
121  * (see Configuration Reference document for details on supported time patterns)
122  * </ul>
123  *
124  * <p>
125  * Conditional logging is also supported. This can be done with the
126  * <code>conditionUnless</code> and <code>conditionIf</code> properties.
127  * If the value returned from ServletRequest.getAttribute(conditionUnless)
128  * yields a non-null value, the logging will be skipped.
129  * If the value returned from ServletRequest.getAttribute(conditionIf)
130  * yields the null value, the logging will be skipped.
131  * The <code>condition</code> attribute is synonym for
132  * <code>conditionUnless</code> and is provided for backwards compatibility.
133  * </p>
134  *
135  * <p>
136  * For extended attributes coming from a getAttribute() call,
137  * it is you responsibility to ensure there are no newline or
138  * control characters.
139  * </p>
140  *
141  * @author Craig R. McClanahan
142  * @author Jason Brittain
143  * @author Remy Maucherat
144  * @author Takayuki Kaneko
145  * @author Peter Rossbach
146  */

147 public abstract class AbstractAccessLogValve extends ValveBase implements AccessLog {
148
149     private static final Log log = LogFactory.getLog(AbstractAccessLogValve.class);
150
151     /**
152      * The list of our time format types.
153      */

154     private enum FormatType {
155         CLF, SEC, MSEC, MSEC_FRAC, SDF
156     }
157
158     /**
159      * The list of our port types.
160      */

161     private enum PortType {
162         LOCAL, REMOTE
163     }
164
165     //------------------------------------------------------ Constructor
166     public AbstractAccessLogValve() {
167         super(true);
168     }
169
170     // ----------------------------------------------------- Instance Variables
171
172
173     /**
174      * enabled this component
175      */

176     protected boolean enabled = true;
177
178      /**
179      * Use IPv6 canonical representation format as defined by RFC 5952.
180      */

181     private boolean ipv6Canonical = false;
182
183     /**
184      * The pattern used to format our access log lines.
185      */

186     protected String pattern = null;
187
188     /**
189      * The size of our global date format cache
190      */

191     private static final int globalCacheSize = 300;
192
193     /**
194      * The size of our thread local date format cache
195      */

196     private static final int localCacheSize = 60;
197
198     /**
199      * <p>Cache structure for formatted timestamps based on seconds.</p>
200      *
201      * <p>The cache consists of entries for a consecutive range of
202      * seconds. The length of the range is configurable. It is
203      * implemented based on a cyclic buffer. New entries shift the range.</p>
204      *
205      * <p>There is one cache for the CLF format (the access log standard
206      * format) and a HashMap of caches for additional formats used by
207      * SimpleDateFormat.</p>
208      *
209      * <p>Although the cache supports specifying a locale when retrieving a
210      * formatted timestamp, each format will always use the locale given
211      * when the format was first used. New locales can only be used for new formats.
212      * The CLF format will always be formatted using the locale
213      * <code>en_US</code>.</p>
214      *
215      * <p>The cache is not threadsafe. It can be used without synchronization
216      * via thread local instances, or with synchronization as a global cache.</p>
217      *
218      * <p>The cache can be created with a parent cache to build a cache hierarchy.
219      * Access to the parent cache is threadsafe.</p>
220      *
221      * <p>This class uses a small thread local first level cache and a bigger
222      * synchronized global second level cache.</p>
223      */

224     protected static class DateFormatCache {
225
226         protected class Cache {
227
228             /* CLF log format */
229             private static final String cLFFormat = "dd/MMM/yyyy:HH:mm:ss Z";
230
231             /* Second used to retrieve CLF format in most recent invocation */
232             private long previousSeconds = Long.MIN_VALUE;
233             /* Value of CLF format retrieved in most recent invocation */
234             private String previousFormat = "";
235
236             /* First second contained in cache */
237             private long first = Long.MIN_VALUE;
238             /* Last second contained in cache */
239             private long last = Long.MIN_VALUE;
240             /* Index of "first" in the cyclic cache */
241             private int offset = 0;
242             /* Helper object to be able to call SimpleDateFormat.format(). */
243             private final Date currentDate = new Date();
244
245             protected final String cache[];
246             private SimpleDateFormat formatter;
247             private boolean isCLF = false;
248
249             private Cache parent = null;
250
251             private Cache(Cache parent) {
252                 this(null, parent);
253             }
254
255             private Cache(String format, Cache parent) {
256                 this(format, null, parent);
257             }
258
259             private Cache(String format, Locale loc, Cache parent) {
260                 cache = new String[cacheSize];
261                 for (int i = 0; i < cacheSize; i++) {
262                     cache[i] = null;
263                 }
264                 if (loc == null) {
265                     loc = cacheDefaultLocale;
266                 }
267                 if (format == null) {
268                     isCLF = true;
269                     format = cLFFormat;
270                     formatter = new SimpleDateFormat(format, Locale.US);
271                 } else {
272                     formatter = new SimpleDateFormat(format, loc);
273                 }
274                 formatter.setTimeZone(TimeZone.getDefault());
275                 this.parent = parent;
276             }
277
278             private String getFormatInternal(long time) {
279
280                 long seconds = time / 1000;
281
282                 /* First step: if we have seen this timestamp
283                    during the previous call, and we need CLF, return the previous value. */

284                 if (seconds == previousSeconds) {
285                     return previousFormat;
286                 }
287
288                 /* Second step: Try to locate in cache */
289                 previousSeconds = seconds;
290                 int index = (offset + (int)(seconds - first)) % cacheSize;
291                 if (index < 0) {
292                     index += cacheSize;
293                 }
294                 if (seconds >= first && seconds <= last) {
295                     if (cache[index] != null) {
296                         /* Found, so remember for next call and return.*/
297                         previousFormat = cache[index];
298                         return previousFormat;
299                     }
300
301                 /* Third step: not found in cache, adjust cache and add item */
302                 } else if (seconds >= last + cacheSize || seconds <= first - cacheSize) {
303                     first = seconds;
304                     last = first + cacheSize - 1;
305                     index = 0;
306                     offset = 0;
307                     for (int i = 1; i < cacheSize; i++) {
308                         cache[i] = null;
309                     }
310                 } else if (seconds > last) {
311                     for (int i = 1; i < seconds - last; i++) {
312                         cache[(index + cacheSize - i) % cacheSize] = null;
313                     }
314                     first = seconds - (cacheSize - 1);
315                     last = seconds;
316                     offset = (index + 1) % cacheSize;
317                 } else if (seconds < first) {
318                     for (int i = 1; i < first - seconds; i++) {
319                         cache[(index + i) % cacheSize] = null;
320                     }
321                     first = seconds;
322                     last = seconds + (cacheSize - 1);
323                     offset = index;
324                 }
325
326                 /* Last step: format new timestamp either using
327                  * parent cache or locally. */

328                 if (parent != null) {
329                     synchronized(parent) {
330                         previousFormat = parent.getFormatInternal(time);
331                     }
332                 } else {
333                     currentDate.setTime(time);
334                     previousFormat = formatter.format(currentDate);
335                     if (isCLF) {
336                         StringBuilder current = new StringBuilder(32);
337                         current.append('[');
338                         current.append(previousFormat);
339                         current.append(']');
340                         previousFormat = current.toString();
341                     }
342                 }
343                 cache[index] = previousFormat;
344                 return previousFormat;
345             }
346         }
347
348         /* Number of cached entries */
349         private int cacheSize = 0;
350
351         private final Locale cacheDefaultLocale;
352         private final DateFormatCache parent;
353         protected final Cache cLFCache;
354         private final Map<String, Cache> formatCache = new HashMap<>();
355
356         protected DateFormatCache(int size, Locale loc, DateFormatCache parent) {
357             cacheSize = size;
358             cacheDefaultLocale = loc;
359             this.parent = parent;
360             Cache parentCache = null;
361             if (parent != null) {
362                 synchronized(parent) {
363                     parentCache = parent.getCache(nullnull);
364                 }
365             }
366             cLFCache = new Cache(parentCache);
367         }
368
369         private Cache getCache(String format, Locale loc) {
370             Cache cache;
371             if (format == null) {
372                 cache = cLFCache;
373             } else {
374                 cache = formatCache.get(format);
375                 if (cache == null) {
376                     Cache parentCache = null;
377                     if (parent != null) {
378                         synchronized(parent) {
379                             parentCache = parent.getCache(format, loc);
380                         }
381                     }
382                     cache = new Cache(format, loc, parentCache);
383                     formatCache.put(format, cache);
384                 }
385             }
386             return cache;
387         }
388
389         public String getFormat(long time) {
390             return cLFCache.getFormatInternal(time);
391         }
392
393         public String getFormat(String format, Locale loc, long time) {
394             return getCache(format, loc).getFormatInternal(time);
395         }
396     }
397
398     /**
399      * Global date format cache.
400      */

401     private static final DateFormatCache globalDateCache =
402             new DateFormatCache(globalCacheSize, Locale.getDefault(), null);
403
404     /**
405      * Thread local date format cache.
406      */

407     private static final ThreadLocal<DateFormatCache> localDateCache =
408             new ThreadLocal<DateFormatCache>() {
409         @Override
410         protected DateFormatCache initialValue() {
411             return new DateFormatCache(localCacheSize, Locale.getDefault(), globalDateCache);
412         }
413     };
414
415
416     /**
417      * The system time when we last updated the Date that this valve
418      * uses for log lines.
419      */

420     private static final ThreadLocal<Date> localDate =
421             new ThreadLocal<Date>() {
422         @Override
423         protected Date initialValue() {
424             return new Date();
425         }
426     };
427
428     /**
429      * Are we doing conditional logging. default null.
430      * It is the value of <code>conditionUnless</code> property.
431      */

432     protected String condition = null;
433
434     /**
435      * Are we doing conditional logging. default null.
436      * It is the value of <code>conditionIf</code> property.
437      */

438     protected String conditionIf = null;
439
440     /**
441      * Name of locale used to format timestamps in log entries and in
442      * log file name suffix.
443      */

444     protected String localeName = Locale.getDefault().toString();
445
446
447     /**
448      * Locale used to format timestamps in log entries and in
449      * log file name suffix.
450      */

451     protected Locale locale = Locale.getDefault();
452
453     /**
454      * Array of AccessLogElement, they will be used to make log message.
455      */

456     protected AccessLogElement[] logElements = null;
457
458     /**
459      * Array of elements where the value needs to be cached at the start of the
460      * request.
461      */

462     protected CachedElement[] cachedElements = null;
463
464     /**
465      * Should this valve use request attributes for IP address, hostname,
466      * protocol and port used for the request.
467      * Default is <code>false</code>.
468      * @see #setRequestAttributesEnabled(boolean)
469      */

470     protected boolean requestAttributesEnabled = false;
471
472     /**
473      * Buffer pool used for log message generation. Pool used to reduce garbage
474      * generation.
475      */

476     private SynchronizedStack<CharArrayWriter> charArrayWriters =
477             new SynchronizedStack<>();
478
479     /**
480      * Log message buffers are usually recycled and re-used. To prevent
481      * excessive memory usage, if a buffer grows beyond this size it will be
482      * discarded. The default is 256 characters. This should be set to larger
483      * than the typical access log message size.
484      */

485     private int maxLogMessageBufferSize = 256;
486
487     /**
488      * Does the configured log pattern include a known TLS attribute?
489      */

490     private boolean tlsAttributeRequired = false;
491
492
493     // ------------------------------------------------------------- Properties
494
495     public int getMaxLogMessageBufferSize() {
496         return maxLogMessageBufferSize;
497     }
498
499
500     public void setMaxLogMessageBufferSize(int maxLogMessageBufferSize) {
501         this.maxLogMessageBufferSize = maxLogMessageBufferSize;
502     }
503
504
505     public boolean getIpv6Canonical() {
506         return ipv6Canonical;
507     }
508
509
510     public void setIpv6Canonical(boolean ipv6Canonical) {
511         this.ipv6Canonical = ipv6Canonical;
512     }
513
514
515     /**
516      * {@inheritDoc}
517      * Default is <code>false</code>.
518      */

519     @Override
520     public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
521         this.requestAttributesEnabled = requestAttributesEnabled;
522     }
523
524
525     /**
526      * {@inheritDoc}
527      */

528     @Override
529     public boolean getRequestAttributesEnabled() {
530         return requestAttributesEnabled;
531     }
532
533     /**
534      * @return the enabled flag.
535      */

536     public boolean getEnabled() {
537         return enabled;
538     }
539
540     /**
541      * @param enabled
542      *            The enabled to set.
543      */

544     public void setEnabled(boolean enabled) {
545         this.enabled = enabled;
546     }
547
548     /**
549      * @return the format pattern.
550      */

551     public String getPattern() {
552         return this.pattern;
553     }
554
555
556     /**
557      * Set the format pattern, first translating any recognized alias.
558      *
559      * @param pattern The new pattern
560      */

561     public void setPattern(String pattern) {
562         if (pattern == null) {
563             this.pattern = "";
564         } else if (pattern.equals(Constants.AccessLog.COMMON_ALIAS)) {
565             this.pattern = Constants.AccessLog.COMMON_PATTERN;
566         } else if (pattern.equals(Constants.AccessLog.COMBINED_ALIAS)) {
567             this.pattern = Constants.AccessLog.COMBINED_PATTERN;
568         } else {
569             this.pattern = pattern;
570         }
571         logElements = createLogElements();
572         cachedElements = createCachedElements(logElements);
573     }
574
575     /**
576      * Return whether the attribute name to look for when
577      * performing conditional logging. If null, every
578      * request is logged.
579      * @return the attribute name
580      */

581     public String getCondition() {
582         return condition;
583     }
584
585
586     /**
587      * Set the ServletRequest.attribute to look for to perform
588      * conditional logging. Set to null to log everything.
589      *
590      * @param condition Set to null to log everything
591      */

592     public void setCondition(String condition) {
593         this.condition = condition;
594     }
595
596
597     /**
598      * Return whether the attribute name to look for when
599      * performing conditional logging. If null, every
600      * request is logged.
601      * @return the attribute name
602      */

603     public String getConditionUnless() {
604         return getCondition();
605     }
606
607
608     /**
609      * Set the ServletRequest.attribute to look for to perform
610      * conditional logging. Set to null to log everything.
611      *
612      * @param condition Set to null to log everything
613      */

614     public void setConditionUnless(String condition) {
615         setCondition(condition);
616     }
617
618     /**
619      * Return whether the attribute name to look for when
620      * performing conditional logging. If null, every
621      * request is logged.
622      * @return the attribute name
623      */

624     public String getConditionIf() {
625         return conditionIf;
626     }
627
628
629     /**
630      * Set the ServletRequest.attribute to look for to perform
631      * conditional logging. Set to null to log everything.
632      *
633      * @param condition Set to null to log everything
634      */

635     public void setConditionIf(String condition) {
636         this.conditionIf = condition;
637     }
638
639     /**
640      * Return the locale used to format timestamps in log entries and in
641      * log file name suffix.
642      * @return the locale
643      */

644     public String getLocale() {
645         return localeName;
646     }
647
648
649     /**
650      * Set the locale used to format timestamps in log entries and in
651      * log file name suffix. Changing the locale is only supported
652      * as long as the AccessLogValve has not logged anything. Changing
653      * the locale later can lead to inconsistent formatting.
654      *
655      * @param localeName The locale to use.
656      */

657     public void setLocale(String localeName) {
658         this.localeName = localeName;
659         locale = findLocale(localeName, locale);
660     }
661
662     // --------------------------------------------------------- Public Methods
663
664     /**
665      * Log a message summarizing the specified request and response, according
666      * to the format specified by the <code>pattern</code> property.
667      *
668      * @param request Request being processed
669      * @param response Response being processed
670      *
671      * @exception IOException if an input/output error has occurred
672      * @exception ServletException if a servlet error has occurred
673      */

674     @Override
675     public void invoke(Request request, Response response) throws IOException,
676             ServletException {
677         if (tlsAttributeRequired) {
678             // The log pattern uses TLS attributes. Ensure these are populated
679             // before the request is processed because with NIO2 it is possible
680             // for the connection to be closed (and the TLS info lost) before
681             // the access log requests the TLS info. Requesting it now causes it
682             // to be cached in the request.
683             request.getAttribute(Globals.CERTIFICATES_ATTR);
684         }
685         for (CachedElement element : cachedElements) {
686                 element.cache(request);
687         }
688         getNext().invoke(request, response);
689     }
690
691
692     @Override
693     public void log(Request request, Response response, long time) {
694         if (!getState().isAvailable() || !getEnabled() || logElements == null
695                 || condition != null
696                 && null != request.getRequest().getAttribute(condition)
697                 || conditionIf != null
698                 && null == request.getRequest().getAttribute(conditionIf)) {
699             return;
700         }
701
702         /**
703          * XXX This is a bit silly, but we want to have start and stop time and
704          * duration consistent. It would be better to keep start and stop
705          * simply in the request and/or response object and remove time
706          * (duration) from the interface.
707          */

708         long start = request.getCoyoteRequest().getStartTime();
709         Date date = getDate(start + time);
710
711         CharArrayWriter result = charArrayWriters.pop();
712         if (result == null) {
713             result = new CharArrayWriter(128);
714         }
715
716         for (int i = 0; i < logElements.length; i++) {
717             logElements[i].addElement(result, date, request, response, time);
718         }
719
720         log(result);
721
722         if (result.size() <= maxLogMessageBufferSize) {
723             result.reset();
724             charArrayWriters.push(result);
725         }
726     }
727
728     // -------------------------------------------------------- Protected Methods
729
730     /**
731      * Log the specified message.
732      *
733      * @param message Message to be logged. This object will be recycled by
734      *  the calling method.
735      */

736     protected abstract void log(CharArrayWriter message);
737
738     // -------------------------------------------------------- Private Methods
739
740     /**
741      * This method returns a Date object that is accurate to within one second.
742      * If a thread calls this method to get a Date and it's been less than 1
743      * second since a new Date was created, this method simply gives out the
744      * same Date again so that the system doesn't spend time creating Date
745      * objects unnecessarily.
746      * @param systime The time
747      * @return the date object
748      */

749     private static Date getDate(long systime) {
750         Date date = localDate.get();
751         date.setTime(systime);
752         return date;
753     }
754
755
756     /**
757      * Find a locale by name.
758      * @param name The locale name
759      * @param fallback Fallback locale if the name is not found
760      * @return the locale object
761      */

762     protected static Locale findLocale(String name, Locale fallback) {
763         if (name == null || name.isEmpty()) {
764             return Locale.getDefault();
765         } else {
766             for (Locale l: Locale.getAvailableLocales()) {
767                 if (name.equals(l.toString())) {
768                     return l;
769                 }
770             }
771         }
772         log.error(sm.getString("accessLogValve.invalidLocale", name));
773         return fallback;
774     }
775
776
777     /**
778      * Start this component and implement the requirements
779      * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
780      *
781      * @exception LifecycleException if this component detects a fatal error
782      *  that prevents this component from being used
783      */

784     @Override
785     protected synchronized void startInternal() throws LifecycleException {
786
787         setState(LifecycleState.STARTING);
788     }
789
790
791     /**
792      * Stop this component and implement the requirements
793      * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
794      *
795      * @exception LifecycleException if this component detects a fatal error
796      *  that prevents this component from being used
797      */

798     @Override
799     protected synchronized void stopInternal() throws LifecycleException {
800
801         setState(LifecycleState.STOPPING);
802     }
803
804     /**
805      * AccessLogElement writes the partial message into the buffer.
806      */

807     protected interface AccessLogElement {
808         public void addElement(CharArrayWriter buf, Date date, Request request,
809                 Response response, long time);
810     }
811
812     /**
813      * Marks an AccessLogElement as needing to be have the value cached at the
814      * start of the request rather than just recorded at the end as the source
815      * data for the element may not be available at the end of the request. This
816      * typically occurs for remote network information, such as ports, IP
817      * addresses etc. when the connection is closed unexpectedly. These elements
818      * take advantage of these values being cached elsewhere on first request
819      * and do not cache the value in the element since the elements are
820      * state-less.
821      */

822     protected interface CachedElement {
823         public void cache(Request request);
824     }
825
826     /**
827      * write thread name - %I
828      */

829     protected static class ThreadNameElement implements AccessLogElement {
830         @Override
831         public void addElement(CharArrayWriter buf, Date date, Request request,
832                 Response response, long time) {
833             RequestInfo info = request.getCoyoteRequest().getRequestProcessor();
834             if(info != null) {
835                 buf.append(info.getWorkerThreadName());
836             } else {
837                 buf.append("-");
838             }
839         }
840     }
841
842     /**
843      * write local IP address - %A
844      */

845     protected static class LocalAddrElement implements AccessLogElement {
846
847         private final String localAddrValue;
848
849         public LocalAddrElement(boolean ipv6Canonical) {
850             String init;
851             try {
852                 init = InetAddress.getLocalHost().getHostAddress();
853             } catch (Throwable e) {
854                 ExceptionUtils.handleThrowable(e);
855                 init = "127.0.0.1";
856             }
857
858             if (ipv6Canonical) {
859                 localAddrValue = IPv6Utils.canonize(init);
860             } else {
861                 localAddrValue = init;
862             }
863         }
864
865         @Override
866         public void addElement(CharArrayWriter buf, Date date, Request request,
867                 Response response, long time) {
868             buf.append(localAddrValue);
869         }
870     }
871
872     /**
873      * write remote IP address - %a
874      */

875     protected class RemoteAddrElement implements AccessLogElement, CachedElement {
876         @Override
877         public void addElement(CharArrayWriter buf, Date date, Request request,
878                 Response response, long time) {
879             String value = null;
880             if (requestAttributesEnabled) {
881                 Object addr = request.getAttribute(REMOTE_ADDR_ATTRIBUTE);
882                 if (addr == null) {
883                     value = request.getRemoteAddr();
884                 } else {
885                     value = addr.toString();
886                 }
887             } else {
888                 value = request.getRemoteAddr();
889             }
890
891             if (ipv6Canonical) {
892                 value = IPv6Utils.canonize(value);
893             }
894             buf.append(value);
895         }
896
897         @Override
898         public void cache(Request request) {
899             if (!requestAttributesEnabled) {
900                 request.getRemoteAddr();
901             }
902         }
903     }
904
905     /**
906      * write remote host name - %h
907      */

908     protected class HostElement implements AccessLogElement, CachedElement {
909         @Override
910         public void addElement(CharArrayWriter buf, Date date, Request request,
911                 Response response, long time) {
912             String value = null;
913             if (requestAttributesEnabled) {
914                 Object host = request.getAttribute(REMOTE_HOST_ATTRIBUTE);
915                 if (host != null) {
916                     value = host.toString();
917                 }
918             }
919             if (value == null || value.length() == 0) {
920                 value = request.getRemoteHost();
921             }
922             if (value == null || value.length() == 0) {
923                 value = "-";
924             }
925
926             if (ipv6Canonical) {
927                 value = IPv6Utils.canonize(value);
928             }
929             buf.append(value);
930         }
931
932         @Override
933         public void cache(Request request) {
934             if (!requestAttributesEnabled) {
935                 request.getRemoteHost();
936             }
937         }
938     }
939
940     /**
941      * write remote logical username from identd (always returns '-') - %l
942      */

943     protected static class LogicalUserNameElement implements AccessLogElement {
944         @Override
945         public void addElement(CharArrayWriter buf, Date date, Request request,
946                 Response response, long time) {
947             buf.append('-');
948         }
949     }
950
951     /**
952      * write request protocol - %H
953      */

954     protected class ProtocolElement implements AccessLogElement {
955         @Override
956         public void addElement(CharArrayWriter buf, Date date, Request request,
957                 Response response, long time) {
958             if (requestAttributesEnabled) {
959                 Object proto = request.getAttribute(PROTOCOL_ATTRIBUTE);
960                 if (proto == null) {
961                     buf.append(request.getProtocol());
962                 } else {
963                     buf.append(proto.toString());
964                 }
965             } else {
966                 buf.append(request.getProtocol());
967             }
968         }
969     }
970
971     /**
972      * write remote user that was authenticated (if any), else '-' - %u
973      */

974     protected static class UserElement implements AccessLogElement {
975         @Override
976         public void addElement(CharArrayWriter buf, Date date, Request request,
977                 Response response, long time) {
978             if (request != null) {
979                 String value = request.getRemoteUser();
980                 if (value != null) {
981                     buf.append(value);
982                 } else {
983                     buf.append('-');
984                 }
985             } else {
986                 buf.append('-');
987             }
988         }
989     }
990
991     /**
992      * write date and time, in configurable format (default CLF) - %t or %{format}t
993      */

994     protected class DateAndTimeElement implements AccessLogElement {
995
996         /**
997          * Format prefix specifying request start time
998          */

999         private static final String requestStartPrefix = "begin";
1000
1001         /**
1002          * Format prefix specifying response end time
1003          */

1004         private static final String responseEndPrefix = "end";
1005
1006         /**
1007          * Separator between optional prefix and rest of format
1008          */

1009         private static final String prefixSeparator = ":";
1010
1011         /**
1012          * Special format for seconds since epoch
1013          */

1014         private static final String secFormat = "sec";
1015
1016         /**
1017          * Special format for milliseconds since epoch
1018          */

1019         private static final String msecFormat = "msec";
1020
1021         /**
1022          * Special format for millisecond part of timestamp
1023          */

1024         private static final String msecFractionFormat = "msec_frac";
1025
1026         /**
1027          * The patterns we use to replace "S" and "SSS" millisecond
1028          * formatting of SimpleDateFormat by our own handling
1029          */

1030         private static final String msecPattern = "{#}";
1031         private static final String trippleMsecPattern =
1032             msecPattern + msecPattern + msecPattern;
1033
1034         /* Our format description string, null if CLF */
1035         private final String format;
1036         /* Whether to use begin of request or end of response as the timestamp */
1037         private final boolean usesBegin;
1038         /* The format type */
1039         private final FormatType type;
1040         /* Whether we need to postprocess by adding milliseconds */
1041         private boolean usesMsecs = false;
1042
1043         protected DateAndTimeElement() {
1044             this(null);
1045         }
1046
1047         /**
1048          * Replace the millisecond formatting character 'S' by
1049          * some dummy characters in order to make the resulting
1050          * formatted time stamps cacheable. We replace the dummy
1051          * chars later with the actual milliseconds because that's
1052          * relatively cheap.
1053          */

1054         private String tidyFormat(String format) {
1055             boolean escape = false;
1056             StringBuilder result = new StringBuilder();
1057             int len = format.length();
1058             char x;
1059             for (int i = 0; i < len; i++) {
1060                 x = format.charAt(i);
1061                 if (escape || x != 'S') {
1062                     result.append(x);
1063                 } else {
1064                     result.append(msecPattern);
1065                     usesMsecs = true;
1066                 }
1067                 if (x == '\'') {
1068                     escape = !escape;
1069                 }
1070             }
1071             return result.toString();
1072         }
1073
1074         protected DateAndTimeElement(String header) {
1075             String format = header;
1076             boolean usesBegin = false;
1077             FormatType type = FormatType.CLF;
1078
1079             if (format != null) {
1080                 if (format.equals(requestStartPrefix)) {
1081                     usesBegin = true;
1082                     format = "";
1083                 } else if (format.startsWith(requestStartPrefix + prefixSeparator)) {
1084                     usesBegin = true;
1085                     format = format.substring(6);
1086                 } else if (format.equals(responseEndPrefix)) {
1087                     usesBegin = false;
1088                     format = "";
1089                 } else if (format.startsWith(responseEndPrefix + prefixSeparator)) {
1090                     usesBegin = false;
1091                     format = format.substring(4);
1092                 }
1093                 if (format.length() == 0) {
1094                     type = FormatType.CLF;
1095                 } else if (format.equals(secFormat)) {
1096                     type = FormatType.SEC;
1097                 } else if (format.equals(msecFormat)) {
1098                     type = FormatType.MSEC;
1099                 } else if (format.equals(msecFractionFormat)) {
1100                     type = FormatType.MSEC_FRAC;
1101                 } else {
1102                     type = FormatType.SDF;
1103                     format = tidyFormat(format);
1104                 }
1105             }
1106             this.format = format;
1107             this.usesBegin = usesBegin;
1108             this.type = type;
1109         }
1110
1111         @Override
1112         public void addElement(CharArrayWriter buf, Date date, Request request,
1113                 Response response, long time) {
1114             long timestamp = date.getTime();
1115             long frac;
1116             if (usesBegin) {
1117                 timestamp -= time;
1118             }
1119             /*  Implementation note: This is deliberately not implemented using
1120              *  switch. If a switch is used the compiler (at least the Oracle
1121              *  one) will use a synthetic class to implement the switch. The
1122              *  problem is that this class needs to be pre-loaded when using a
1123              *  SecurityManager and the name of that class will depend on any
1124              *  anonymous inner classes and any other synthetic classes. As such
1125              *  the name is not constant and keeping the pre-loading up to date
1126              *  as the name changes is error prone.
1127              */

1128             if (type == FormatType.CLF) {
1129                 buf.append(localDateCache.get().getFormat(timestamp));
1130             } else if (type == FormatType.SEC) {
1131                 buf.append(Long.toString(timestamp / 1000));
1132             } else if (type == FormatType.MSEC) {
1133                 buf.append(Long.toString(timestamp));
1134             } else if (type == FormatType.MSEC_FRAC) {
1135                 frac = timestamp % 1000;
1136                 if (frac < 100) {
1137                     if (frac < 10) {
1138                         buf.append('0');
1139                         buf.append('0');
1140                     } else {
1141                         buf.append('0');
1142                     }
1143                 }
1144                 buf.append(Long.toString(frac));
1145             } else {
1146                 // FormatType.SDF
1147                 String temp = localDateCache.get().getFormat(format, locale, timestamp);
1148                 if (usesMsecs) {
1149                     frac = timestamp % 1000;
1150                     StringBuilder trippleMsec = new StringBuilder(4);
1151                     if (frac < 100) {
1152                         if (frac < 10) {
1153                             trippleMsec.append('0');
1154                             trippleMsec.append('0');
1155                         } else {
1156                             trippleMsec.append('0');
1157                         }
1158                     }
1159                     trippleMsec.append(frac);
1160                     temp = temp.replace(trippleMsecPattern, trippleMsec);
1161                     temp = temp.replace(msecPattern, Long.toString(frac));
1162                 }
1163                 buf.append(temp);
1164             }
1165         }
1166     }
1167
1168     /**
1169      * write first line of the request (method and request URI) - %r
1170      */

1171     protected static class RequestElement implements AccessLogElement {
1172         @Override
1173         public void addElement(CharArrayWriter buf, Date date, Request request,
1174                 Response response, long time) {
1175             if (request != null) {
1176                 String method = request.getMethod();
1177                 if (method == null) {
1178                     // No method means no request line
1179                     buf.append('-');
1180                 } else {
1181                     buf.append(request.getMethod());
1182                     buf.append(' ');
1183                     buf.append(request.getRequestURI());
1184                     if (request.getQueryString() != null) {
1185                         buf.append('?');
1186                         buf.append(request.getQueryString());
1187                     }
1188                     buf.append(' ');
1189                     buf.append(request.getProtocol());
1190                 }
1191             } else {
1192                 buf.append('-');
1193             }
1194         }
1195     }
1196
1197     /**
1198      * write HTTP status code of the response - %s
1199      */

1200     protected static class HttpStatusCodeElement implements AccessLogElement {
1201         @Override
1202         public void addElement(CharArrayWriter buf, Date date, Request request,
1203                 Response response, long time) {
1204             if (response != null) {
1205                 // This approach is used to reduce GC from toString conversion
1206                 int status = response.getStatus();
1207                 if (100 <= status && status < 1000) {
1208                     buf.append((char) ('0' + (status / 100)))
1209                             .append((char) ('0' + ((status / 10) % 10)))
1210                             .append((char) ('0' + (status % 10)));
1211                 } else {
1212                    buf.append(Integer.toString(status));
1213                 }
1214             } else {
1215                 buf.append('-');
1216             }
1217         }
1218     }
1219
1220     /**
1221      * write local or remote port for request connection - %p and %{xxx}p
1222      */

1223     protected class PortElement implements AccessLogElement, CachedElement {
1224
1225         /**
1226          * Type of port to log
1227          */

1228         private static final String localPort = "local";
1229         private static final String remotePort = "remote";
1230
1231         private final PortType portType;
1232
1233         public PortElement() {
1234             portType = PortType.LOCAL;
1235         }
1236
1237         public PortElement(String type) {
1238             switch (type) {
1239             case remotePort:
1240                 portType = PortType.REMOTE;
1241                 break;
1242             case localPort:
1243                 portType = PortType.LOCAL;
1244                 break;
1245             default:
1246                 log.error(sm.getString("accessLogValve.invalidPortType", type));
1247                 portType = PortType.LOCAL;
1248                 break;
1249             }
1250         }
1251
1252         @Override
1253         public void addElement(CharArrayWriter buf, Date date, Request request,
1254                 Response response, long time) {
1255             if (requestAttributesEnabled && portType == PortType.LOCAL) {
1256                 Object port = request.getAttribute(SERVER_PORT_ATTRIBUTE);
1257                 if (port == null) {
1258                     buf.append(Integer.toString(request.getServerPort()));
1259                 } else {
1260                     buf.append(port.toString());
1261                 }
1262             } else {
1263                 if (portType == PortType.LOCAL) {
1264                     buf.append(Integer.toString(request.getServerPort()));
1265                 } else {
1266                     buf.append(Integer.toString(request.getRemotePort()));
1267                 }
1268             }
1269         }
1270
1271         @Override
1272         public void cache(Request request) {
1273             if (portType == PortType.REMOTE) {
1274                 request.getRemotePort();
1275             }
1276         }
1277     }
1278
1279     /**
1280      * write bytes sent, excluding HTTP headers - %b, %B
1281      */

1282     protected static class ByteSentElement implements AccessLogElement {
1283         private final boolean conversion;
1284
1285         /**
1286          * @param conversion <code>true</code> to write '-' instead of 0 - %b.
1287          */

1288         public ByteSentElement(boolean conversion) {
1289             this.conversion = conversion;
1290         }
1291
1292         @Override
1293         public void addElement(CharArrayWriter buf, Date date, Request request,
1294                 Response response, long time) {
1295             // Don't need to flush since trigger for log message is after the
1296             // response has been committed
1297             long length = response.getBytesWritten(false);
1298             if (length <= 0) {
1299                 // Protect against nulls and unexpected types as these values
1300                 // may be set by untrusted applications
1301                 Object start = request.getAttribute(
1302                         Globals.SENDFILE_FILE_START_ATTR);
1303                 if (start instanceof Long) {
1304                     Object end = request.getAttribute(
1305                             Globals.SENDFILE_FILE_END_ATTR);
1306                     if (end instanceof Long) {
1307                         length = ((Long) end).longValue() -
1308                                 ((Long) start).longValue();
1309                     }
1310                 }
1311             }
1312             if (length <= 0 && conversion) {
1313                 buf.append('-');
1314             } else {
1315                 buf.append(Long.toString(length));
1316             }
1317         }
1318     }
1319
1320     /**
1321      * write request method (GET, POST, etc.) - %m
1322      */

1323     protected static class MethodElement implements AccessLogElement {
1324         @Override
1325         public void addElement(CharArrayWriter buf, Date date, Request request,
1326                 Response response, long time) {
1327             if (request != null) {
1328                 buf.append(request.getMethod());
1329             }
1330         }
1331     }
1332
1333     /**
1334      * write time taken to process the request - %D, %T
1335      */

1336     protected static class ElapsedTimeElement implements AccessLogElement {
1337         private final boolean millis;
1338
1339         /**
1340          * @param millis <code>true</code>, write time in millis - %D,
1341          * if <code>false</code>, write time in seconds - %T
1342          */

1343         public ElapsedTimeElement(boolean millis) {
1344             this.millis = millis;
1345         }
1346
1347         @Override
1348         public void addElement(CharArrayWriter buf, Date date, Request request,
1349                 Response response, long time) {
1350             if (millis) {
1351                 buf.append(Long.toString(time));
1352             } else {
1353                 // second
1354                 buf.append(Long.toString(time / 1000));
1355                 buf.append('.');
1356                 int remains = (int) (time % 1000);
1357                 buf.append(Long.toString(remains / 100));
1358                 remains = remains % 100;
1359                 buf.append(Long.toString(remains / 10));
1360                 buf.append(Long.toString(remains % 10));
1361             }
1362         }
1363     }
1364
1365     /**
1366      * write time until first byte is written (commit time) in millis - %F
1367      */

1368     protected static class FirstByteTimeElement implements AccessLogElement {
1369         @Override
1370         public void addElement(CharArrayWriter buf, Date date, Request request, Response response, long time) {
1371             long commitTime = response.getCoyoteResponse().getCommitTime();
1372             if (commitTime == -1) {
1373                 buf.append('-');
1374             } else {
1375                 long delta = commitTime - request.getCoyoteRequest().getStartTime();
1376                 buf.append(Long.toString(delta));
1377             }
1378         }
1379     }
1380
1381     /**
1382      * write Query string (prepended with a '?' if it exists) - %q
1383      */

1384     protected static class QueryElement implements AccessLogElement {
1385         @Override
1386         public void addElement(CharArrayWriter buf, Date date, Request request,
1387                 Response response, long time) {
1388             String query = null;
1389             if (request != null) {
1390                 query = request.getQueryString();
1391             }
1392             if (query != null) {
1393                 buf.append('?');
1394                 buf.append(query);
1395             }
1396         }
1397     }
1398
1399     /**
1400      * write user session ID - %S
1401      */

1402     protected static class SessionIdElement implements AccessLogElement {
1403         @Override
1404         public void addElement(CharArrayWriter buf, Date date, Request request,
1405                 Response response, long time) {
1406             if (request == null) {
1407                 buf.append('-');
1408             } else {
1409                 Session session = request.getSessionInternal(false);
1410                 if (session == null) {
1411                     buf.append('-');
1412                 } else {
1413                     buf.append(session.getIdInternal());
1414                 }
1415             }
1416         }
1417     }
1418
1419     /**
1420      * write requested URL path - %U
1421      */

1422     protected static class RequestURIElement implements AccessLogElement {
1423         @Override
1424         public void addElement(CharArrayWriter buf, Date date, Request request,
1425                 Response response, long time) {
1426             if (request != null) {
1427                 buf.append(request.getRequestURI());
1428             } else {
1429                 buf.append('-');
1430             }
1431         }
1432     }
1433
1434     /**
1435      * write local server name - %v
1436      */

1437     protected class LocalServerNameElement implements AccessLogElement {
1438         @Override
1439         public void addElement(CharArrayWriter buf, Date date, Request request,
1440                 Response response, long time) {
1441             String value = null;
1442             if (requestAttributesEnabled) {
1443                 Object serverName = request.getAttribute(SERVER_NAME_ATTRIBUTE);
1444                 if (serverName != null) {
1445                     value = serverName.toString();
1446                 }
1447             }
1448             if (value == null || value.length() == 0) {
1449                 value = request.getServerName();
1450             }
1451             if (value == null || value.length() == 0) {
1452                 value = "-";
1453             }
1454
1455             if (ipv6Canonical) {
1456                 value = IPv6Utils.canonize(value);
1457             }
1458             buf.append(value);
1459         }
1460     }
1461
1462     /**
1463      * write any string
1464      */

1465     protected static class StringElement implements AccessLogElement {
1466         private final String str;
1467
1468         public StringElement(String str) {
1469             this.str = str;
1470         }
1471
1472         @Override
1473         public void addElement(CharArrayWriter buf, Date date, Request request,
1474                 Response response, long time) {
1475             buf.append(str);
1476         }
1477     }
1478
1479     /**
1480      * write incoming headers - %{xxx}i
1481      */

1482     protected static class HeaderElement implements AccessLogElement {
1483         private final String header;
1484
1485         public HeaderElement(String header) {
1486             this.header = header;
1487         }
1488
1489         @Override
1490         public void addElement(CharArrayWriter buf, Date date, Request request,
1491                 Response response, long time) {
1492             Enumeration<String> iter = request.getHeaders(header);
1493             if (iter.hasMoreElements()) {
1494                 buf.append(iter.nextElement());
1495                 while (iter.hasMoreElements()) {
1496                     buf.append(',').append(iter.nextElement());
1497                 }
1498                 return;
1499             }
1500             buf.append('-');
1501         }
1502     }
1503
1504     /**
1505      * write a specific cookie - %{xxx}c
1506      */

1507     protected static class CookieElement implements AccessLogElement {
1508         private final String header;
1509
1510         public CookieElement(String header) {
1511             this.header = header;
1512         }
1513
1514         @Override
1515         public void addElement(CharArrayWriter buf, Date date, Request request,
1516                 Response response, long time) {
1517             String value = "-";
1518             Cookie[] c = request.getCookies();
1519             if (c != null) {
1520                 for (int i = 0; i < c.length; i++) {
1521                     if (header.equals(c[i].getName())) {
1522                         value = c[i].getValue();
1523                         break;
1524                     }
1525                 }
1526             }
1527             buf.append(value);
1528         }
1529     }
1530
1531     /**
1532      * write a specific response header - %{xxx}o
1533      */

1534     protected static class ResponseHeaderElement implements AccessLogElement {
1535         private final String header;
1536
1537         public ResponseHeaderElement(String header) {
1538             this.header = header;
1539         }
1540
1541         @Override
1542         public void addElement(CharArrayWriter buf, Date date, Request request,
1543                 Response response, long time) {
1544             if (null != response) {
1545                 Iterator<String> iter = response.getHeaders(header).iterator();
1546                 if (iter.hasNext()) {
1547                     buf.append(iter.next());
1548                     while (iter.hasNext()) {
1549                         buf.append(',').append(iter.next());
1550                     }
1551                     return;
1552                 }
1553             }
1554             buf.append('-');
1555         }
1556     }
1557
1558     /**
1559      * write an attribute in the ServletRequest - %{xxx}r
1560      */

1561     protected static class RequestAttributeElement implements AccessLogElement {
1562         private final String header;
1563
1564         public RequestAttributeElement(String header) {
1565             this.header = header;
1566         }
1567
1568         @Override
1569         public void addElement(CharArrayWriter buf, Date date, Request request,
1570                 Response response, long time) {
1571             Object value = null;
1572             if (request != null) {
1573                 value = request.getAttribute(header);
1574             } else {
1575                 value = "??";
1576             }
1577             if (value != null) {
1578                 if (value instanceof String) {
1579                     buf.append((String) value);
1580                 } else {
1581                     buf.append(value.toString());
1582                 }
1583             } else {
1584                 buf.append('-');
1585             }
1586         }
1587     }
1588
1589     /**
1590      * write an attribute in the HttpSession - %{xxx}s
1591      */

1592     protected static class SessionAttributeElement implements AccessLogElement {
1593         private final String header;
1594
1595         public SessionAttributeElement(String header) {
1596             this.header = header;
1597         }
1598
1599         @Override
1600         public void addElement(CharArrayWriter buf, Date date, Request request,
1601                 Response response, long time) {
1602             Object value = null;
1603             if (null != request) {
1604                 HttpSession sess = request.getSession(false);
1605                 if (null != sess) {
1606                     value = sess.getAttribute(header);
1607                 }
1608             } else {
1609                 value = "??";
1610             }
1611             if (value != null) {
1612                 if (value instanceof String) {
1613                     buf.append((String) value);
1614                 } else {
1615                     buf.append(value.toString());
1616                 }
1617             } else {
1618                 buf.append('-');
1619             }
1620         }
1621     }
1622
1623     /**
1624      * Write connection status when response is completed - %X
1625      */

1626     protected static class ConnectionStatusElement implements AccessLogElement {
1627         @Override
1628         public void addElement(CharArrayWriter buf, Date date, Request request, Response response, long time) {
1629             if (response != null && request != null) {
1630                 boolean statusFound = false;
1631
1632                 // Check whether connection IO is in "not allowed" state
1633                 AtomicBoolean isIoAllowed = new AtomicBoolean(false);
1634                 request.getCoyoteRequest().action(ActionCode.IS_IO_ALLOWED, isIoAllowed);
1635                 if (!isIoAllowed.get()) {
1636                     buf.append('X');
1637                     statusFound = true;
1638                 } else {
1639                     // Check for connection aborted cond
1640                     if (response.isError()) {
1641                         Throwable ex = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
1642                         if (ex instanceof ClientAbortException) {
1643                             buf.append('X');
1644                             statusFound = true;
1645                         }
1646                     }
1647                 }
1648
1649                 // If status is not found yet, cont to check whether connection is keep-alive or close
1650                 if (!statusFound) {
1651                     String connStatus = response.getHeader(org.apache.coyote.http11.Constants.CONNECTION);
1652                     if (org.apache.coyote.http11.Constants.CLOSE.equalsIgnoreCase(connStatus)) {
1653                         buf.append('-');
1654                     } else {
1655                         buf.append('+');
1656                     }
1657                 }
1658             } else {
1659                 // Unknown connection status
1660                 buf.append('?');
1661             }
1662         }
1663     }
1664
1665     /**
1666      * Parse pattern string and create the array of AccessLogElement.
1667      * @return the log elements array
1668      */

1669     protected AccessLogElement[] createLogElements() {
1670         List<AccessLogElement> list = new ArrayList<>();
1671         boolean replace = false;
1672         StringBuilder buf = new StringBuilder();
1673         for (int i = 0; i < pattern.length(); i++) {
1674             char ch = pattern.charAt(i);
1675             if (replace) {
1676                 /*
1677                  * For code that processes {, the behavior will be ... if I do
1678                  * not encounter a closing } - then I ignore the {
1679                  */

1680                 if ('{' == ch) {
1681                     StringBuilder name = new StringBuilder();
1682                     int j = i + 1;
1683                     for (; j < pattern.length() && '}' != pattern.charAt(j); j++) {
1684                         name.append(pattern.charAt(j));
1685                     }
1686                     if (j + 1 < pattern.length()) {
1687                         /* the +1 was to account for } which we increment now */
1688                         j++;
1689                         list.add(createAccessLogElement(name.toString(),
1690                                 pattern.charAt(j)));
1691                         i = j; /* Since we walked more than one character */
1692                     } else {
1693                         // D'oh - end of string - pretend we never did this
1694                         // and do processing the "old way"
1695                         list.add(createAccessLogElement(ch));
1696                     }
1697                 } else {
1698                     list.add(createAccessLogElement(ch));
1699                 }
1700                 replace = false;
1701             } else if (ch == '%') {
1702                 replace = true;
1703                 list.add(new StringElement(buf.toString()));
1704                 buf = new StringBuilder();
1705             } else {
1706                 buf.append(ch);
1707             }
1708         }
1709         if (buf.length() > 0) {
1710             list.add(new StringElement(buf.toString()));
1711         }
1712         return list.toArray(new AccessLogElement[0]);
1713     }
1714
1715
1716     private CachedElement[] createCachedElements(AccessLogElement[] elements) {
1717         List<CachedElement> list = new ArrayList<>();
1718         for (AccessLogElement element : elements) {
1719             if (element instanceof CachedElement) {
1720                 list.add((CachedElement) element);
1721             }
1722         }
1723         return list.toArray(new CachedElement[0]);
1724     }
1725
1726
1727     /**
1728      * Create an AccessLogElement implementation which needs an element name.
1729      * @param name Header name
1730      * @param pattern char in the log pattern
1731      * @return the log element
1732      */

1733     protected AccessLogElement createAccessLogElement(String name, char pattern) {
1734         switch (pattern) {
1735         case 'i':
1736             return new HeaderElement(name);
1737         case 'c':
1738             return new CookieElement(name);
1739         case 'o':
1740             return new ResponseHeaderElement(name);
1741         case 'p':
1742             return new PortElement(name);
1743         case 'r':
1744             if (TLSUtil.isTLSRequestAttribute(name)) {
1745                 tlsAttributeRequired = true;
1746             }
1747             return new RequestAttributeElement(name);
1748         case 's':
1749             return new SessionAttributeElement(name);
1750         case 't':
1751             return new DateAndTimeElement(name);
1752         default:
1753             return new StringElement("???");
1754         }
1755     }
1756
1757     /**
1758      * Create an AccessLogElement implementation.
1759      * @param pattern char in the log pattern
1760      * @return the log element
1761      */

1762     protected AccessLogElement createAccessLogElement(char pattern) {
1763         switch (pattern) {
1764         case 'a':
1765             return new RemoteAddrElement();
1766         case 'A':
1767             return new LocalAddrElement(ipv6Canonical);
1768         case 'b':
1769             return new ByteSentElement(true);
1770         case 'B':
1771             return new ByteSentElement(false);
1772         case 'D':
1773             return new ElapsedTimeElement(true);
1774         case 'F':
1775             return new FirstByteTimeElement();
1776         case 'h':
1777             return new HostElement();
1778         case 'H':
1779             return new ProtocolElement();
1780         case 'l':
1781             return new LogicalUserNameElement();
1782         case 'm':
1783             return new MethodElement();
1784         case 'p':
1785             return new PortElement();
1786         case 'q':
1787             return new QueryElement();
1788         case 'r':
1789             return new RequestElement();
1790         case 's':
1791             return new HttpStatusCodeElement();
1792         case 'S':
1793             return new SessionIdElement();
1794         case 't':
1795             return new DateAndTimeElement();
1796         case 'T':
1797             return new ElapsedTimeElement(false);
1798         case 'u':
1799             return new UserElement();
1800         case 'U':
1801             return new RequestURIElement();
1802         case 'v':
1803             return new LocalServerNameElement();
1804         case 'I':
1805             return new ThreadNameElement();
1806         case 'X':
1807             return new ConnectionStatusElement();
1808         default:
1809             return new StringElement("???" + pattern + "???");
1810         }
1811     }
1812 }
1813