This is an automated email from the ASF dual-hosted git repository.
rjung pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/10.1.x by this push:
new c95e5a3f92 Improve JsonAccessLogValve: support more patterns like for
headers and attributes.
c95e5a3f92 is described below
commit c95e5a3f922157a8ec67fb26df038e578a9bdbaf
Author: Rainer Jung <[email protected]>
AuthorDate: Thu Apr 27 10:39:25 2023 +0200
Improve JsonAccessLogValve: support more patterns like for headers and
attributes.
Those will be logged as sub objects.
---
.../apache/catalina/valves/JsonAccessLogValve.java | 115 +++++++++++++++++++--
webapps/docs/changelog.xml | 9 +-
webapps/docs/config/valve.xml | 32 ++++--
3 files changed, 137 insertions(+), 19 deletions(-)
diff --git a/java/org/apache/catalina/valves/JsonAccessLogValve.java
b/java/org/apache/catalina/valves/JsonAccessLogValve.java
index c7de7a47f0..f6162c8d70 100644
--- a/java/org/apache/catalina/valves/JsonAccessLogValve.java
+++ b/java/org/apache/catalina/valves/JsonAccessLogValve.java
@@ -57,6 +57,14 @@ import org.apache.tomcat.util.json.JSONFilter;
* <li>v: localServerName</li>
* <li>I: threadName</li>
* <li>X: connectionStatus</li>
+ * <li>%{xxx}a: remoteAddress-xxx</li>
+ * <li>%{xxx}p: port-xxx</li>
+ * <li>%{xxx}t: time-xxx</li>
+ * <li>%{xxx}c: cookies</li>
+ * <li>%{xxx}i: requestHeaders</li>
+ * <li>%{xxx}o: responseHeaders</li>
+ * <li>%{xxx}r: requestAttributes</li>
+ * <li>%{xxx}s: sessionAttributes</li>
* </ul>
* The attribute list is based on
*
https://github.com/fluent/fluentd/blob/master/lib/fluent/plugin/parser_apache2.rb#L72
@@ -92,6 +100,18 @@ public class JsonAccessLogValve extends AccessLogValve {
PATTERNS = Collections.unmodifiableMap(pattern2AttributeName);
}
+ private static final Map<Character, String> SUB_OBJECT_PATTERNS;
+ static {
+ // FIXME: finalize attribute names
+ Map<Character, String> pattern2AttributeName = new HashMap<>();
+ pattern2AttributeName.put(Character.valueOf('c'), "cookies");
+ pattern2AttributeName.put(Character.valueOf('i'), "requestHeaders");
+ pattern2AttributeName.put(Character.valueOf('o'), "responseHeaders");
+ pattern2AttributeName.put(Character.valueOf('r'), "requestAttributes");
+ pattern2AttributeName.put(Character.valueOf('s'), "sessionAttributes");
+ SUB_OBJECT_PATTERNS =
Collections.unmodifiableMap(pattern2AttributeName);
+ }
+
/**
* write any char
*/
@@ -108,8 +128,28 @@ public class JsonAccessLogValve extends AccessLogValve {
}
}
+ private boolean addSubkeyedItems(ListIterator<AccessLogElement> iterator,
List<JsonWrappedElement> elements, String patternAttribute) {
+ if (! elements.isEmpty()) {
+ iterator.add(new StringElement("\"" + patternAttribute + "\": {"));
+ for (JsonWrappedElement element: elements) {
+ iterator.add(element);
+ iterator.add(new CharElement(','));
+ }
+ iterator.previous();
+ iterator.remove();
+ iterator.add(new StringElement("},"));
+ return true;
+ }
+ return false;
+ }
+
@Override
protected AccessLogElement[] createLogElements() {
+ Map<Character, List<JsonWrappedElement>> subTypeLists = new
HashMap<>();
+ for (Character pattern: SUB_OBJECT_PATTERNS.keySet()) {
+ subTypeLists.put(pattern, new ArrayList<>());
+ }
+ boolean hasSub = false;
List<AccessLogElement> logElements = new
ArrayList<>(Arrays.asList(super.createLogElements()));
ListIterator<AccessLogElement> lit = logElements.listIterator();
lit.add(new CharElement('{'));
@@ -120,23 +160,58 @@ public class JsonAccessLogValve extends AccessLogValve {
lit.remove();
continue;
}
- lit.add(new CharElement(','));
+ // Remove items which should be written as
+ // Json objects and add them later in correct order
+ JsonWrappedElement wrappedLogElement =
(JsonWrappedElement)logElement;
+ AccessLogElement ale = wrappedLogElement.getDelegate();
+ if (ale instanceof HeaderElement) {
+
subTypeLists.get(Character.valueOf('i')).add(wrappedLogElement);
+ lit.remove();
+ } else if (ale instanceof ResponseHeaderElement) {
+
subTypeLists.get(Character.valueOf('o')).add(wrappedLogElement);
+ lit.remove();
+ } else if (ale instanceof RequestAttributeElement) {
+
subTypeLists.get(Character.valueOf('r')).add(wrappedLogElement);
+ lit.remove();
+ } else if (ale instanceof SessionAttributeElement) {
+
subTypeLists.get(Character.valueOf('s')).add(wrappedLogElement);
+ lit.remove();
+ } else if (ale instanceof CookieElement) {
+
subTypeLists.get(Character.valueOf('c')).add(wrappedLogElement);
+ lit.remove();
+ } else {
+ // Keep the simple items and add separator
+ lit.add(new CharElement(','));
+ }
+ }
+ // Add back the items that are output as Json objects
+ for (Character pattern: SUB_OBJECT_PATTERNS.keySet()) {
+ if (addSubkeyedItems(lit, subTypeLists.get(pattern),
SUB_OBJECT_PATTERNS.get(pattern))) {
+ hasSub = true;
+ }
}
- // remove last comma again
+ // remove last comma (or possibly "},")
lit.previous();
lit.remove();
- lit.add(new CharElement('}'));
+ // Last item was a sub object, close it
+ if (hasSub) {
+ lit.add(new StringElement("}}"));
+ } else {
+ lit.add(new CharElement('}'));
+ }
return logElements.toArray(new AccessLogElement[logElements.size()]);
}
+ @Override
+ protected AccessLogElement createAccessLogElement(String name, char
pattern) {
+ AccessLogElement ale = super.createAccessLogElement(name, pattern);
+ return new JsonWrappedElement(pattern, name, true, ale);
+ }
+
@Override
protected AccessLogElement createAccessLogElement(char pattern) {
AccessLogElement ale = super.createAccessLogElement(pattern);
- String attributeName = PATTERNS.get(Character.valueOf(pattern));
- if (attributeName == null) {
- attributeName = "other-" + new String(JSONFilter.escape(pattern));
- }
- return new JsonWrappedElement(attributeName, true, ale);
+ return new JsonWrappedElement(pattern, true, ale);
}
private static class JsonWrappedElement implements AccessLogElement,
CachedElement {
@@ -149,10 +224,30 @@ public class JsonAccessLogValve extends AccessLogValve {
return JSONFilter.escape(nonEscaped);
}
- JsonWrappedElement(String attributeName, boolean quoteValue,
AccessLogElement delegate) {
- this.attributeName = escapeJsonString(attributeName);
+ JsonWrappedElement(char pattern, String key, boolean quoteValue,
AccessLogElement delegate) {
this.quoteValue = quoteValue;
this.delegate = delegate;
+ String patternAttribute = PATTERNS.get(Character.valueOf(pattern));
+ if (patternAttribute == null) {
+ patternAttribute = "other-" + Character.toString(pattern);
+ }
+ if (key != null && ! "".equals(key)) {
+ if
(SUB_OBJECT_PATTERNS.containsKey(Character.valueOf(pattern))) {
+ this.attributeName = escapeJsonString(key);
+ } else {
+ this.attributeName = escapeJsonString(patternAttribute +
"-" + key);
+ }
+ } else {
+ this.attributeName = escapeJsonString(patternAttribute);
+ }
+ }
+
+ JsonWrappedElement(char pattern, boolean quoteValue, AccessLogElement
delegate) {
+ this(pattern, null, quoteValue, delegate);
+ }
+
+ public AccessLogElement getDelegate() {
+ return delegate;
}
@Override
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index fc354d5653..6aaabb42c6 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -118,11 +118,16 @@
</fix>
<update>
Change output of vertical tab in <code>AccessLogValve</code> from
- <code>\v</code> to <code>\u000b</code>. (rjung)
+ <code>\v</code> to <code>\u000b</code>. (rjung)
</update>
<update>
Improve performance of escaping in <code>AccessLogValve</code>
- roughly by a factor of two. (rjung)
+ roughly by a factor of two. (rjung)
+ </update>
+ <update>
+ Improve <code>JsonAccessLogValve</code>: support more patterns
+ like for headers and attributes. Those will be logged as sub objects.
+ (rjung)
</update>
</changelog>
</subsection>
diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml
index 85394d25b8..dcff5de3c4 100644
--- a/webapps/docs/config/valve.xml
+++ b/webapps/docs/config/valve.xml
@@ -543,18 +543,30 @@
<a href="#Access_Log_Valve">Access Log Valve</a>,
there are a few differences:
<ul>
- <li>requests are logged as JSON objects. Each "%" prefixed pattern
+ <li>requests are logged as JSON objects.</li>
+ <li>each supported "%X" single character pattern
identifier results in a key value pair in this object.
See below for the list of keys used for the respective pattern
identifiers.</li>
+ <li>each pattern identifiers using a subkey of the form
<code>%{xxx}X</code>
+ where "X" is one of "a", "p" or "t"
+ results in a key value pair of the form "key-xxx".
+ See below for the list of keys used for the respective pattern
+ identifiers.</li>
+ <li>each pattern identifiers using a subkey of the form
<code>%{xxx}X</code>
+ where "X" is one of "c", "i", "o", "r" or "s"
+ results in a sub object. See below for the key pointing at this
+ sub object. The keys in the sub object are the "xxx" subkeys in the
pattern.</li>
+ <li>each unsupported "%X" character pattern
+ identifier results in a key value pair using the key "other-X".</li>
<li>the values logged are the same as the ones logged by
the standard <a href="#Access_Log_Valve">Access Log Valve</a>
for the same pattern identifiers.</li>
- <li>pattern identifiers using a subkey of the form <code>%{subkey}</code>
- are not yet supported and get silently ignored.</li>
+ <li>any "xxx" subkeys get Json escaped.</li>
<li>any verbatim text between pattern identifiers gets silently
ignored.</li>
</ul>
- The JSON object keys used for the respective pattern identifiers are the
following:
+ The JSON object keys used for the pattern identifiers which
+ do not generate a sub object are the following:
<ul>
<li><b><code>%a</code></b>: remoteAddr</li>
<li><b><code>%A</code></b>: localAddr</li>
@@ -579,11 +591,17 @@
<li><b><code>%v</code></b>: localServerName</li>
<li><b><code>%X</code></b>: connectionStatus</li>
</ul>
+ The JSON object keys used for the pattern identifiers which
+ generate a sub object are the following:
+ <ul>
+ <li><b><code>%c</code></b>: cookies</li>
+ <li><b><code>%i</code></b>: requestHeaders</li>
+ <li><b><code>%o</code></b>: responseHeaders</li>
+ <li><b><code>%r</code></b>: requestAttributes</li>
+ <li><b><code>%s</code></b>: sessionAttributes</li>
+ </ul>
</p>
- <p>Fields using unknown pattern identifiers will be logged with JSON
object key
- <code>other-X</code> where <code>X</code> is the unknown identifier.</p>
-
</subsection>
</subsection>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]