org.glassfish.grizzly.http.server.accesslog.ApacheLogFormat Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.grizzly.http.server.accesslog;
import static java.util.logging.Level.WARNING;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import java.util.logging.Logger;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.http.Cookie;
import org.glassfish.grizzly.http.Method;
import org.glassfish.grizzly.http.Protocol;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.http.server.Response;
import org.glassfish.grizzly.http.util.MimeHeaders;
/**
* An {@link AccessLogFormat} using a standard vaguely similar and heavily
* influenced by Apache's
* own custom access log formats.
*
* As with Apache, the format string specified at
* {@linkplain #ApacheLogFormat(String) construction} should be composed of
* these tokens:
*
*
* %%
* The literal percent sign (can also be escaped with back-slash, like "\%
"
* %a
* Remote IP-address
* %A
* Local IP-address
* %b
* Size of response in bytes, excluding HTTP headers, using "-
" (a dash character) rather than a "0
" (zero) when no bytes are sent
* %B
* Size of response in bytes, excluding HTTP headers
* %{Foobar}C
* The contents of cookie "Foobar
" in the request sent to the server
* %D
* The time taken to serve the request, in microseconds
* %h
* Remote host name
* %{local|remote}h
* Host name, either "local
" or "remote
"
* %H
* The request protocol
* %{Foobar}i
* The contents of the "Foobar: ...
" header in the request
* %m
* The request method
* %{Foobar}o
* The contents of the "Foobar: ...
" header in the response
* %p
* Local port number
* %{local|remote}p
* The port number, either "local
" or "remote
"
* %q
* The query string, prepended with a "?
" (question mark) if a query string exists, otherwise an empty string
* %r
* First line of request, an alias to "%m %U%q %H
"
* %s
* Status code
* %t
* The time the request was received, in standard English format (like "[09/Feb/2014:12:00:34 +0900]
")
* %{[format][@timezone]}t
* The time the request was received. Both format and timezone are optional
* - When "
format
" is left unspecified, the default %t
format [yyyy/MMM/dd:HH:mm:ss Z]
is used
* - When "
format
" is specified, the given pattern must be a valid {@link SimpleDateFormat} pattern.
* - When "
@timezone
" is left unspecified, the {@linkplain TimeZone#getDefault() default time zone} is used.
* - When "
@timezone
" is specified, the time zone will be looked up by {@linkplain TimeZone#getTimeZone(String) time zone identifier}
* (note that this will default to GMT if the specified identifier was not recognized).
*
* When the "@
" character needs to be used in the format, it must be escaped as "@@"
* %T
* The time taken to serve the request, in seconds
* %{...}T
* The time taken to serve the request. The parameter can be a time unit like:
* - "
n
", "nano[s]
", "nanosec[s]
", "nanosecond[s]
"
* - "
micro[s]
", "microsec[s]
", "microsecond[s]
"
* - "
m
", "milli[s]
", "millisec[s]
", "millisecond[s]
"
* - "
s
", "sec[s]
", "second[s]
"
* %u
* The remote user name
* %U
* The URL path requested, not including any query string
* %v
* The name of the server which served the request
*
*
* @author Pier Fumagalli
* @author USRZ.com
*/
public class ApacheLogFormat implements AccessLogFormat {
/* The UTC time zone */
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
/** A {@link String} representing our version of Apache's common format. */
public static final String COMMON_FORMAT = "%h - %u %t \"%r\" %s %b";
/** A {@link String} representing our version of Apache's combined format. */
public static final String COMBINED_FORMAT = "%h - %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-agent}i\"";
/** A {@link String} representing our version of Apache's common with virtual-hosts format. */
public static final String VHOST_COMMON_FORMAT = "%v %h - %u %t \"%r\" %s %b";
/** A {@link String} representing our version of Apache's combined with virtual-hosts format. */
public static final String VHOST_COMBINED_FORMAT = "%v %h - %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-agent}i\"";
/** A {@link String} representing our version of Apache's referer format. */
public static final String REFERER_FORMAT = "%{Referer}i -> %U";
/** A {@link String} representing our version of Apache's user-agent format. */
public static final String AGENT_FORMAT = "%{User-agent}i";
/** A {@linkplain ApacheLogFormat format} compatible with Apache's common format. */
public static final ApacheLogFormat COMMON = new ApacheLogFormat(COMMON_FORMAT);
/** A {@linkplain ApacheLogFormat format} compatible with Apache's combined format. */
public static final ApacheLogFormat COMBINED = new ApacheLogFormat(COMBINED_FORMAT);
/** A {@linkplain ApacheLogFormat format} compatible with Apache's common with virtual-hosts format. */
public static final ApacheLogFormat VHOST_COMMON = new ApacheLogFormat(VHOST_COMMON_FORMAT);
/** A {@linkplain ApacheLogFormat format} compatible with Apache's combined with virtual-hosts format. */
public static final ApacheLogFormat VHOST_COMBINED = new ApacheLogFormat(VHOST_COMBINED_FORMAT);
/** A {@linkplain ApacheLogFormat format} compatible with Apache's referer format. */
public static final ApacheLogFormat REFERER = new ApacheLogFormat(REFERER_FORMAT);
/** A {@linkplain ApacheLogFormat format} compatible with Apache's user-agent format. */
public static final ApacheLogFormat AGENT = new ApacheLogFormat(AGENT_FORMAT);
/** A {@linkplain ApacheLogFormat format} compatible with Apache's common format set to use the UTC {@linkplain TimeZone time zone}. */
public static final ApacheLogFormat COMMON_UTC = new ApacheLogFormat(UTC, COMMON_FORMAT);
/** A {@linkplain ApacheLogFormat format} compatible with Apache's combined format set to use the UTC {@linkplain TimeZone time zone}. */
public static final ApacheLogFormat COMBINED_UTC = new ApacheLogFormat(UTC, COMBINED_FORMAT);
/** A {@linkplain ApacheLogFormat format} compatible with Apache's common with virtual-hosts format set to use the UTC {@linkplain TimeZone time zone}. */
public static final ApacheLogFormat VHOST_COMMON_UTC = new ApacheLogFormat(UTC, VHOST_COMMON_FORMAT);
/** A {@linkplain ApacheLogFormat format} compatible with Apache's combined with virtual-hosts format set to use the UTC {@linkplain TimeZone time zone}. */
public static final ApacheLogFormat VHOST_COMBINED_UTC = new ApacheLogFormat(UTC, VHOST_COMBINED_FORMAT);
/** A {@linkplain ApacheLogFormat format} compatible with Apache's referer format set to use the UTC {@linkplain TimeZone time zone}. */
public static final ApacheLogFormat REFERER_UTC = new ApacheLogFormat(UTC, REFERER_FORMAT);
/** A {@linkplain ApacheLogFormat format} compatible with Apache's user-agent format set to use the UTC {@linkplain TimeZone time zone}. */
public static final ApacheLogFormat AGENT_UTC = new ApacheLogFormat(UTC, AGENT_FORMAT);
/* Log log log, never enough */
private static final Logger LOGGER = Grizzly.logger(HttpServer.class);
/* Our list of fields for formatting */
private final List fields;
/* Our timezone */
private final TimeZone timeZone;
/**
* Create a new {@link ApacheLogFormat} instance by parsing the format
* from the specified {@link String}.
*/
public ApacheLogFormat(String format) {
this(TimeZone.getDefault(), format);
}
/**
* Create a new {@link ApacheLogFormat} instance by parsing the format
* from the specified {@link String}.
*/
public ApacheLogFormat(TimeZone timeZone, String format) {
if (timeZone == null) throw new NullPointerException("Null time zone");
fields = new ArrayList();
this.timeZone = timeZone;
parse(format);
}
@Override
public String format(Response response, Date timeStamp, long responseNanos) {
final StringBuilder builder = new StringBuilder();
final Request request = response.getRequest();
for (Field field: fields) try {
field.format(builder, request, response, timeStamp, responseNanos);
} catch (Exception exception) {
LOGGER.log(WARNING, "Exception formatting access log entry", exception);
builder.append('-');
}
return builder.toString();
}
String unsafeFormat(Response response, Date timeStamp, long responseNanos) {
final StringBuilder builder = new StringBuilder();
final Request request = response.getRequest();
for (Field field: fields) {
field.format(builder, request, response, timeStamp, responseNanos);
}
return builder.toString();
}
/**
* Return the normalized format associated with this instance.
*/
public String getFormat() {
final StringBuilder builder = new StringBuilder();
for (Field field: fields) builder.append(field.toString());
return builder.toString();
}
/* ====================================================================== */
/* PARSING OF FORMAT STRINGS */
/* ====================================================================== */
private void parse(String format) {
for (int x = 0; x < format.length(); x ++) {
switch (format.charAt(x)) {
case '\\': x = parseEscape(format, x); break;
case '%': x = parseFormat(format, null, x); break;
default: addLiteral(format.charAt(x));
}
}
}
private int parseFormat(String format, String parameter, int position) {
if (++position < format.length()) {
final char field = format.charAt(position);
/* Initial check to see if parameter is supposed to be there */
if (parameter != null) switch (field) {
case 'C': break; // parameter is cookie name
case 'h': break; // parameter for host ("local" or "remote")
case 'i': break; // parameter is request header name
case 'o': break; // parameter is response header name
case 'p': break; // parameter for port ("local" or "remote")
case 't': break; // parameter is simple date format's format
case 'T': break; // parameter is scale ("nano", "micro", "milli" or number)
default: throw new IllegalArgumentException("Unsupported parameter \"" + parameter + "\" for field '" + field + "' in [" + format + "] at character " + position);
}
switch (field) {
case '{': return parseParameter(format, position);
case '%': addLiteral('%'); break; // The percent sign
case 'a': fields.add(new RemoteAddressField()); break; // Remote IP-address
case 'A': fields.add(new LocalAddressField()); break; // Local IP-address
case 'b': fields.add(new ResponseSizeField(false)); break; // Size of response in bytes in CLF format
case 'B': fields.add(new ResponseSizeField(true)); break; // Size of response in bytes with zeroes
/* */ case 'C': fields.add(new RequestCookieField(parameter)); break; // The contents of a cookie
case 'D': fields.add(new ResponseTimeField("micro", format, position)); break; // The time taken to serve the request, in microseconds.
/* */ case 'h': // Remote (or local if parameterized) host
fields.add(parseLocal(parameter, false, field, format, position) ?
new LocalHostField() :
new RemoteHostField());
break;
case 'H': fields.add(new RequestProtocolField()); break; // The request protocol
/* */ case 'i': fields.add(new RequestHeaderField(parameter)); break; // A request header
case 'm': fields.add(new RequestMethodField()); break; // The request method
/* */ case 'o': fields.add(new ResponseHeaderField(parameter)); break; // A response header
/* */ case 'p': // Local (or remote if parameterized) port
fields.add(parseLocal(parameter, true, field, format, position) ?
new LocalPortField() :
new RemotePortField());
break;
case 'q': fields.add(new RequestQueryField()); break; // The query string (prepended with a ?)
case 'r': // First line of request, alias to "%m %U%q %H"
fields.add(new RequestMethodField());
addLiteral(' ');
fields.add(new RequestURIField());
fields.add(new RequestQueryField());
addLiteral(' ');
fields.add(new RequestProtocolField());
break;
case 's': fields.add(new ResponseStatusField()); break; // Status.
/* */ case 't': fields.add(new RequestTimeField(parameter, timeZone)); break; // The time, in the form given by format, which should be in strftime(3) format. (potentially localized)
/* */ case 'T': fields.add(new ResponseTimeField(parameter, format, position)); break; // The time taken to serve the request, in the scale specified.
case 'u': fields.add(new RequestUserField()); break; // The URL path requested, not including any query string.
case 'U': fields.add(new RequestURIField()); break; // The URL path requested, not including any query string.
case 'v': fields.add(new ServerNameField()); break; // The canonical ServerName of the server serving the request.
default: throw new IllegalArgumentException("Unsupported field '" + field + "' in [" + format + "] at character " + position);
}
return position;
}
throw new IllegalArgumentException("Unterminated field declaration in [" + format + "] at character " + position);
}
private boolean parseLocal(String parameter, boolean defaultValue, char field, String format, int position) {
if (parameter == null) return defaultValue;
final String p = parameter.trim().toLowerCase();
if (p.equals("local")) {
return true;
} else if (p.equals("remote")) {
return false;
} else {
throw new IllegalArgumentException("Unsupported parameter \"" + parameter + "\" for field '" + field + "' in [" + format + "] at character " + position);
}
}
private int parseParameter(String format, int position) {
if (++position < format.length()) {
final int end = format.indexOf('}', position);
if (end == position) {
return parseFormat(format, null, end);
} else if (end > position) {
return parseFormat(format, format.substring(position, end), end);
}
}
throw new IllegalArgumentException("Unterminated format parameter in [" + format + "] at character " + position);
}
private int parseEscape(String format, int position) {
if (++position < format.length()) {
final char escaped = format.charAt(position);
switch (escaped) {
case 't': addLiteral('\t'); break;
case 'b': addLiteral('\b'); break;
case 'n': addLiteral('\n'); break;
case 'r': addLiteral('\r'); break;
case 'f': addLiteral('\f'); break;
default: addLiteral(escaped);
}
return position;
}
throw new IllegalArgumentException("Unterminated escape sequence in [" + format + "] at character " + position);
}
/* ====================================================================== */
private void addLiteral(char c) {
/* See if we can add to the previuos literal field */
if (!fields.isEmpty()) {
final Field last = fields.get(fields.size() - 1);
if (last instanceof LiteralField) {
((LiteralField) last).append(c);
return;
}
}
/* List empty or last field was not a literal, add a new one */
fields.add(new LiteralField(c));
}
/* ====================================================================== */
/* FIELD DECLARATIONS */
/* ====================================================================== */
private static abstract class Field {
abstract StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos);
@Override
public abstract String toString();
}
private static abstract class AbstractField extends Field {
private final char format;
private final String parameter;
protected AbstractField(char format) {
this(format, null);
}
protected AbstractField(char format, String parameter) {
this.format = format;
this.parameter = parameter;
}
@Override
public final String toString() {
final StringBuilder builder = new StringBuilder().append('%');
if (parameter != null) builder.append('{').append(parameter).append('}');
return builder.append(format).toString();
}
}
/* ====================================================================== */
private abstract static class HeaderField extends AbstractField {
final String name;
HeaderField(char format, String name) {
super(format, name.trim().toLowerCase());
this.name = name.trim().toLowerCase();
}
StringBuilder format(StringBuilder builder, MimeHeaders headers) {
final Iterator iterator = headers.values(name).iterator();
if (iterator.hasNext()) builder.append(iterator.next());
while (iterator.hasNext()) builder.append("; ").append(iterator.next());
return builder;
}
}
/* ====================================================================== */
private static class LiteralField extends Field {
final StringBuilder contents;
LiteralField(char character) {
contents = new StringBuilder().append(character);
}
void append(char character) {
contents.append(character);
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
return builder.append(contents);
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
for (int x = 0; x < contents.length(); x ++) {
final char character = contents.charAt(x);
/* Escape \t \b \n \r \f and %% */
switch(character) {
case 't': case 'b': case 'n': case 'r': case 'f':
builder.append('\\'); break;
case '%':
builder.append('%'); break;
}
builder.append(character);
}
return builder.toString();
}
}
/* ====================================================================== */
private static class ServerNameField extends AbstractField {
ServerNameField() {
super('v');
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final String name = request.getServerName();
return builder.append(name == null ? "-" : name);
}
}
/* ====================================================================== */
private static class LocalHostField extends AbstractField {
LocalHostField() {
super('h', "local");
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final String host = request.getLocalName();
return builder.append(host == null ? "-" : host);
}
}
/* ====================================================================== */
private static class LocalAddressField extends AbstractField {
LocalAddressField() {
super('A');
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final String address = request.getLocalAddr();
return builder.append(address == null ? "-" : address);
}
}
/* ====================================================================== */
private static class LocalPortField extends AbstractField {
LocalPortField() {
super('p');
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final int port = request.getLocalPort();
return builder.append(port < 1 ? "-" : port);
}
}
/* ====================================================================== */
private static class RemoteHostField extends AbstractField {
RemoteHostField() {
super('h');
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final String host = request.getRemoteHost();
return builder.append(host == null ? "-" : host);
}
}
/* ====================================================================== */
private static class RemoteAddressField extends AbstractField {
RemoteAddressField() {
super('a');
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final String address = request.getRemoteAddr();
return builder.append(address == null ? "-" : address);
}
}
/* ====================================================================== */
private static class RemotePortField extends AbstractField {
RemotePortField() {
super('p', "remote");
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final int port = request.getRemotePort();
return builder.append(port < 1 ? "-" : port);
}
}
/* ====================================================================== */
private static class RequestTimeField extends Field {
private static final String DEFAULT_PATTERN = "[yyyy/MMM/dd:HH:mm:ss Z]";
private final SimpleDateFormatThreadLocal simpleDateFormat;
private final TimeZone timeZone;
private final String pattern;
private final String format;
RequestTimeField(String format, TimeZone zone) {
this.format = format;
if (format == null) {
pattern = DEFAULT_PATTERN;
timeZone = zone;
} else {
/* Check for timezone separation */
final int pos = format.lastIndexOf('@');
if ((pos < 0) || ((pos > 0) && (format.charAt(pos - 1) == '@'))) {
/* There is no '@' or the last '@' is actually an '@@' */
pattern = format.replace("@@", "@");
timeZone = zone;
} else if (pos == 0) {
/* We have *ONLY* a time zone specified */
pattern = DEFAULT_PATTERN;
timeZone = TimeZone.getTimeZone(format.substring(1));
} else {
/* We have both format and time zone */
pattern = format.substring(0, pos).replace("@@", "@");
timeZone = TimeZone.getTimeZone(format.substring(pos + 1));
}
}
/* Get our simple date format */
simpleDateFormat = new SimpleDateFormatThreadLocal(pattern);
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
if (timeStamp == null) return builder.append('-');
final SimpleDateFormat format = simpleDateFormat.get();
format.setTimeZone(timeZone);
return builder.append(format.format(timeStamp));
}
@Override
public String toString() {
return format == null ? "%t" : "%{" + format + "}t";
}
}
/* ====================================================================== */
private static class RequestMethodField extends AbstractField {
RequestMethodField() {
super('m');
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final Method method = request.getMethod();
return builder.append(method == null ? "-" : method.toString());
}
}
/* ====================================================================== */
private static class RequestUserField extends AbstractField {
RequestUserField() {
super('u');
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final String user = request.getRemoteUser();
return builder.append(user == null ? "-" : user);
}
}
/* ====================================================================== */
private static class RequestURIField extends AbstractField {
RequestURIField() {
super('U');
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final String uri = request.getRequestURI();
return builder.append(uri == null ? "-" : uri);
}
}
/* ====================================================================== */
private static class RequestQueryField extends AbstractField {
RequestQueryField() {
super('q');
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final String query = request.getQueryString();
if (query != null) builder.append('?').append(query);
return builder;
}
}
/* ====================================================================== */
private static class RequestProtocolField extends AbstractField {
RequestProtocolField() {
super('H');
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final Protocol protocol = request.getProtocol();
if (protocol == null) return builder.append("-");
switch (protocol) {
case HTTP_0_9: return builder.append("HTTP/0.9");
case HTTP_1_0: return builder.append("HTTP/1.0");
case HTTP_1_1: return builder.append("HTTP/1.1");
default: return builder.append("-");
}
}
}
/* ====================================================================== */
private static class RequestHeaderField extends HeaderField {
RequestHeaderField(String name) {
super('i', name);
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
return this.format(builder, request.getRequest().getHeaders());
}
}
/* ====================================================================== */
private static class RequestCookieField extends AbstractField {
final String name;
RequestCookieField(String name) {
super('C', name.trim().toLowerCase());
this.name = name.trim().toLowerCase();
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final Cookie[] cookies = request.getCookies();
if (cookies != null) for (Cookie cookie: cookies) {
if (name.equals(cookie.getName().toLowerCase())) {
return builder.append(cookie.getValue());
}
}
return builder;
}
}
/* ====================================================================== */
private static class ResponseStatusField extends AbstractField {
ResponseStatusField() {
super('s');
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final int status = response.getStatus();
if (status < 10) builder.append('0');
if (status < 100) builder.append('0');
return builder.append(status);
}
}
/* ====================================================================== */
private static class ResponseSizeField extends AbstractField {
final String zero;
ResponseSizeField(boolean zero) {
super(zero ? 'B' : 'b');
this.zero = zero ? "0" : "-";
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
final long size = response.getContentLengthLong();
return builder.append(size < 1 ? zero : Long.toString(size));
}
}
/* ====================================================================== */
private static class ResponseTimeField extends Field {
private final long scale;
ResponseTimeField(String unit, String format, int position) {
/* Figure out the scale */
if (unit == null) {
scale = 1000000000;
return;
}
String s = unit.trim().toLowerCase();
if (s.equals("n") || s.equals("nano") || s.equals(
"nanos") || s.equals("nanosec") || s.equals(
"nanosecs") || s.equals("nanosecond") || s.equals(
"nanoseconds")) {
scale = 1;
} else if (s.equals("micro") || s.equals("micros") || s.equals(
"microsec") || s.equals("microsecs") || s.equals(
"microsecond") || s.equals("microseconds")) {
scale = 1000;
} else if (s.equals("m") || s.equals("milli") || s.equals(
"millis") || s.equals("millisec") || s.equals(
"millisecs") || s.equals("millisecond") || s.equals(
"milliseconds")) {
scale = 1000000;
} else if (s.equals("s") || s.equals("sec") || s.equals(
"secs") || s.equals("second") || s.equals("seconds")) {
scale = 1000000000;
} else {
throw new IllegalArgumentException(
"Unsupported time unit \"" + unit + "\" for field 'T' in [" + format + "] at character " + position);
}
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
if (responseNanos < 0) return builder.append('-');
return builder.append(responseNanos / scale);
}
@Override
public String toString() {
final StringBuilder string = new StringBuilder().append('%');
if (scale == 1) string.append("{n}T");
else if (scale == 1000) string.append('D');
else if (scale == 1000000) string.append("{m}T");
else if (scale == 1000000000) string.append('T');
else string.append('{').append(scale).append("}T");
return string.toString();
}
}
/* ====================================================================== */
private static class ResponseHeaderField extends HeaderField {
ResponseHeaderField(String name) {
super('o', name);
}
@Override
StringBuilder format(StringBuilder builder, Request request, Response response, Date timeStamp, long responseNanos) {
return this.format(builder, response.getResponse().getHeaders());
}
}
}