org.apache.catalina.valves.JsonAccessLogValve Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.valves;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.tomcat.util.json.JSONFilter;
/**
* Access log valve derivative that rewrites entries as JSON.
* Important note: the attribute names are not final
* Patterns are mapped to attributes as followed:
*
* - a: remoteAddr
* - A: localAddr
* - b: size (byteSent: size)
* - B: byteSentNC
* - D: elapsedTime
* - F: firstByteTime
* - h: host
* - H: protocol
* - l: logicalUserName
* - m: method
* - p: port
* - q: query
* - r: request
* - s: statusCode
* - S: sessionId
* - t: time (dateTime: time)
* - T: elapsedTimeS
* - u: user
* - U: path (requestURI: path)
* - v: localServerName
* - I: threadName
* - X: connectionStatus
*
* The attribute list is based on
* https://github.com/fluent/fluentd/blob/master/lib/fluent/plugin/parser_apache2.rb#L72
*/
public class JsonAccessLogValve extends AccessLogValve {
private static final Map PATTERNS;
static {
// FIXME: finalize attribute names
Map pattern2AttributeName = new HashMap<>();
pattern2AttributeName.put(Character.valueOf('a'), "remoteAddr");
pattern2AttributeName.put(Character.valueOf('A'), "localAddr");
pattern2AttributeName.put(Character.valueOf('b'), "size"); /* byteSent -> size */
pattern2AttributeName.put(Character.valueOf('B'), "byteSentNC");
pattern2AttributeName.put(Character.valueOf('D'), "elapsedTime");
pattern2AttributeName.put(Character.valueOf('F'), "firstByteTime");
pattern2AttributeName.put(Character.valueOf('h'), "host");
pattern2AttributeName.put(Character.valueOf('H'), "protocol");
pattern2AttributeName.put(Character.valueOf('l'), "logicalUserName");
pattern2AttributeName.put(Character.valueOf('m'), "method");
pattern2AttributeName.put(Character.valueOf('p'), "port");
pattern2AttributeName.put(Character.valueOf('q'), "query");
pattern2AttributeName.put(Character.valueOf('r'), "request");
pattern2AttributeName.put(Character.valueOf('s'), "statusCode");
pattern2AttributeName.put(Character.valueOf('S'), "sessionId");
pattern2AttributeName.put(Character.valueOf('t'), "time"); /* dateTime -> time */
pattern2AttributeName.put(Character.valueOf('T'), "elapsedTimeS");
pattern2AttributeName.put(Character.valueOf('u'), "user");
pattern2AttributeName.put(Character.valueOf('U'), "path"); /* requestURI -> path */
pattern2AttributeName.put(Character.valueOf('v'), "localServerName");
pattern2AttributeName.put(Character.valueOf('I'), "threadName");
pattern2AttributeName.put(Character.valueOf('X'), "connectionStatus");
PATTERNS = Collections.unmodifiableMap(pattern2AttributeName);
}
@Override
protected AccessLogElement[] createLogElements() {
List logElements = new ArrayList<>(Arrays.asList(super.createLogElements()));
ListIterator lit = logElements.listIterator();
lit.add((buf, date, req, resp, time) -> buf.write('{'));
while (lit.hasNext()) {
AccessLogElement logElement = lit.next();
// remove all other elements, like StringElements
if (!(logElement instanceof JsonWrappedElement)) {
lit.remove();
continue;
}
lit.add((buf, date, req, resp, time) -> buf.write(','));
}
// remove last comma again
lit.previous();
lit.remove();
lit.add((buf, date, req, resp, time) -> buf.write('}'));
return logElements.toArray(new AccessLogElement[logElements.size()]);
}
@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);
}
/**
* JSON string escaping writer
*/
private static class JsonCharArrayWriter extends CharArrayWriter {
JsonCharArrayWriter(int i) {
super(i);
}
@Override
public void write(int c) {
try {
super.write(JSONFilter.escape((char) c));
} catch (IOException e) {
// ignore
}
}
@Override
public void write(char[] c, int off, int len) {
try {
super.write(JSONFilter.escape(new String(c, off, len)));
} catch (IOException e) {
// ignore
}
}
@Override
public void write(String str, int off, int len) {
CharSequence escaped = JSONFilter.escape(str, off, len);
super.write(escaped.toString(), 0, escaped.length());
}
}
private static class JsonWrappedElement implements AccessLogElement, CachedElement {
private CharSequence attributeName;
private boolean quoteValue;
private AccessLogElement delegate;
private CharSequence escapeJsonString(CharSequence nonEscaped) {
return JSONFilter.escape(nonEscaped);
}
JsonWrappedElement(String attributeName, boolean quoteValue, AccessLogElement delegate) {
this.attributeName = escapeJsonString(attributeName);
this.quoteValue = quoteValue;
this.delegate = delegate;
}
@Override
public void addElement(CharArrayWriter buf, Date date, Request request, Response response, long time) {
buf.append('"').append(attributeName).append('"').append(':');
if (quoteValue) {
buf.append('"');
}
CharArrayWriter valueWriter = new JsonCharArrayWriter(8);
try {
delegate.addElement(valueWriter, date, request, response, time);
valueWriter.writeTo(buf);
} catch (IOException e) {
// ignore
}
if (quoteValue) {
buf.append('"');
}
}
@Override
public void cache(Request request) {
if (delegate instanceof CachedElement) {
((CachedElement) delegate).cache(request);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy