From 9701888c8f91579d400d83246902e5b12d238366 Mon Sep 17 00:00:00 2001 From: Fabian Hahn Date: Sat, 2 May 2026 14:11:54 +0200 Subject: [PATCH 1/2] AccessLogValve log-format pattern: - Added %% -> % - Format modifier %{xxx}D as in %{xxx}T Down-version patch of PR#1002 for <= 11.0.x --- .../valves/AbstractAccessLogValve.java | 35 +++++++++++-------- .../valves/ExtendedAccessLogValve.java | 4 ++- .../catalina/valves/TestAccessLogValve.java | 1 + webapps/docs/changelog.xml | 5 ++- webapps/docs/config/valve.xml | 1 + 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/java/org/apache/catalina/valves/AbstractAccessLogValve.java b/java/org/apache/catalina/valves/AbstractAccessLogValve.java index 44a70d180a75..8c1231d1936f 100644 --- a/java/org/apache/catalina/valves/AbstractAccessLogValve.java +++ b/java/org/apache/catalina/valves/AbstractAccessLogValve.java @@ -1624,17 +1624,6 @@ public ElapsedTimeElement(Style style) { this.style = style; } - /** - * Creates a new ElapsedTimeElement that will log the time in the specified style. - * - * @param micros true, write time in microseconds - %D - * @param millis true, write time in milliseconds, if both arguments are false, write - * time in seconds - %T - */ - public ElapsedTimeElement(boolean micros, boolean millis) { - this(micros ? Style.MICROSECONDS : millis ? Style.MILLISECONDS : Style.SECONDS); - } - @Override public void addElement(CharArrayWriter buf, Request request, Response response, long time) { style.append(buf, time); @@ -2166,6 +2155,20 @@ protected AccessLogElement createAccessLogElement(String name, char pattern) { return new RemoteAddrElement(name); case 'c': return new CookieElement(name); + case 'D': + if ("ns".equals(name)) { + return new ElapsedTimeElement(ElapsedTimeElement.Style.NANOSECONDS); + } else if ("us".equals(name)) { + return new ElapsedTimeElement(ElapsedTimeElement.Style.MICROSECONDS); + } else if ("ms".equals(name)) { + return new ElapsedTimeElement(ElapsedTimeElement.Style.MILLISECONDS); + } else if ("s".equals(name)) { + return new ElapsedTimeElement(ElapsedTimeElement.Style.SECONDS); + } else if ("fracsec".equals(name)) { + return new ElapsedTimeElement(ElapsedTimeElement.Style.SECONDS_FRACTIONAL); + } else { + return new StringElement("???" + name + "???"); + } case 'i': return new HeaderElement(name); case 'L': @@ -2191,10 +2194,12 @@ protected AccessLogElement createAccessLogElement(String name, char pattern) { return new ElapsedTimeElement(ElapsedTimeElement.Style.MICROSECONDS); } else if ("ms".equals(name)) { return new ElapsedTimeElement(ElapsedTimeElement.Style.MILLISECONDS); + } else if ("s".equals(name)) { + return new ElapsedTimeElement(ElapsedTimeElement.Style.SECONDS); } else if ("fracsec".equals(name)) { return new ElapsedTimeElement(ElapsedTimeElement.Style.SECONDS_FRACTIONAL); } else { - return new ElapsedTimeElement(false, false); + return new StringElement("???" + name + "???"); } default: return new StringElement("???"); @@ -2210,6 +2215,8 @@ protected AccessLogElement createAccessLogElement(String name, char pattern) { */ protected AccessLogElement createAccessLogElement(char pattern) { switch (pattern) { + case '%': + return new StringElement("%"); case 'a': return new RemoteAddrElement(); case 'A': @@ -2219,7 +2226,7 @@ protected AccessLogElement createAccessLogElement(char pattern) { case 'B': return new ByteSentElement(false); case 'D': - return new ElapsedTimeElement(true, false); + return new ElapsedTimeElement(ElapsedTimeElement.Style.MICROSECONDS); case 'F': return new FirstByteTimeElement(); case 'h': @@ -2243,7 +2250,7 @@ protected AccessLogElement createAccessLogElement(char pattern) { case 't': return new DateAndTimeElement(); case 'T': - return new ElapsedTimeElement(false, false); + return new ElapsedTimeElement(ElapsedTimeElement.Style.SECONDS); case 'u': return new UserElement(); case 'U': diff --git a/java/org/apache/catalina/valves/ExtendedAccessLogValve.java b/java/org/apache/catalina/valves/ExtendedAccessLogValve.java index 2bdb9f1ac7fc..ef272c1d0ad4 100644 --- a/java/org/apache/catalina/valves/ExtendedAccessLogValve.java +++ b/java/org/apache/catalina/valves/ExtendedAccessLogValve.java @@ -518,10 +518,12 @@ protected AccessLogElement getLogElement(String token, PatternTokenizer tokenize return new ElapsedTimeElement(ElapsedTimeElement.Style.MICROSECONDS); } else if ("ms".equals(nextToken)) { return new ElapsedTimeElement(ElapsedTimeElement.Style.MILLISECONDS); + } else if ("s".equals(nextToken)) { + return new ElapsedTimeElement(ElapsedTimeElement.Style.SECONDS); } else if ("fracsec".equals(nextToken)) { return new ElapsedTimeElement(ElapsedTimeElement.Style.SECONDS_FRACTIONAL); } else { - return new ElapsedTimeElement(ElapsedTimeElement.Style.SECONDS); + return new StringElement("???" + nextToken + "???"); } } else { return new ElapsedTimeElement(ElapsedTimeElement.Style.SECONDS); diff --git a/test/org/apache/catalina/valves/TestAccessLogValve.java b/test/org/apache/catalina/valves/TestAccessLogValve.java index 49784e642118..08e5630252f0 100644 --- a/test/org/apache/catalina/valves/TestAccessLogValve.java +++ b/test/org/apache/catalina/valves/TestAccessLogValve.java @@ -90,6 +90,7 @@ public class TestAccessLogValve extends TomcatBaseTest { public static Collection parameters() { List parameterSets = new ArrayList<>(); + parameterSets.add(new Object[] {"pct-pct", TEXT_TYPE, "/", "%%", "%"}); parameterSets.add(new Object[] {"pct-a", TEXT_TYPE, "/", "%a", LOCAL_IP_PATTERN}); parameterSets.add(new Object[] {"pct-a", JSON_TYPE, "/", "%a", "\\{\"remoteAddr\":\"" + LOCAL_IP_PATTERN + "\"\\}"}); parameterSets.add(new Object[] {"pct-A", TEXT_TYPE, "/", "%A", IP_PATTERN}); diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index ded67f2e7323..5fc97d3067ef 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -169,6 +169,10 @@ false, meaning user names are treated in a case insensitive manner. (markt) + + Add %% in AccessLogValve pattern. Align %{xxx}D modifiers with %{xxx}T. + Pull request 1003 provided by effhaa. + @@ -5649,4 +5653,3 @@ - diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml index ec00e39570e4..39bbbd67d459 100644 --- a/webapps/docs/config/valve.xml +++ b/webapps/docs/config/valve.xml @@ -282,6 +282,7 @@ the current request and response. The following pattern codes are supported:

    +
  • %% - Literal '%' character
  • %a - Remote IP address. See also %{xxx}a below.
  • %A - Local IP address
  • From c6f12070c9e68bb4884799d8b5bf7a4a13f1cafe Mon Sep 17 00:00:00 2001 From: Fabian Hahn Date: Sat, 2 May 2026 14:59:26 +0200 Subject: [PATCH 2/2] Typo --- webapps/docs/config/valve.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml index 39bbbd67d459..6ad322e86ca9 100644 --- a/webapps/docs/config/valve.xml +++ b/webapps/docs/config/valve.xml @@ -288,7 +288,7 @@
  • %A - Local IP address
  • %b - Bytes sent, excluding HTTP headers, or '-' if zero
  • %B - Bytes sent, excluding HTTP headers
  • -
  • %D - Time taken to process the request in microseconds
  • +
  • %D - Time taken to process the request, in microseconds
  • %F - Time taken to commit the response, in milliseconds
  • %h - Remote host name (or IP address if enableLookups for the connector is false)