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(null, null);
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