
com.kdgregory.logback.aws.JsonAccessLayout Maven / Gradle / Ivy
Show all versions of logback-aws-appenders Show documentation
// Copyright (c) Keith D Gregory
//
// Licensed 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 com.kdgregory.logback.aws;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import ch.qos.logback.access.spi.IAccessEvent;
import com.kdgregory.logback.aws.internal.AbstractJsonLayout;
/**
* Formats an IAccessEvent
as a JSON string, with additional parameters.
*
* The JSON object always contains the following properties. Most come from the event,
* but some are extracted from the request itself (which is provided by the event).
*
* -
timestamp
: the date/time that the message was logged, in ISO-8601 UTC format.
* -
hostname
: the name of the host running the app-server, extracted from
* RuntimeMxBean
(may not be available on all platforms).
* -
processId
: the process ID of the app-server.
* -
thread
: the name of the thread that processed the request.
* -
elapsedTime
: the elapsed time for request processing, in milliseconds.
* -
protocol
: the name and version of the request protocol (eg, "HTTP/1.1").
* -
requestMethod
: the HTTP request method (eg, "GET").
* -
requestURI
: the requested resource, excluding host, port, or query string.
* -
statusCode
: the response status code.
* -
bytesSent
: the number of bytes (content-length) of the response.
* -
remoteIP
: the IP address of the originating server.
* -
forwardedFor
: the value of the X-Forwarded-For
header, if it exists.
* Used to identify source IP when running behind a load balancer.
*
*
* The following properties are potentially expensive to compute, may leak secrets, or significantly
* increase the size of the generated JSON, so will only appear if specifically enabled:
*
* -
instanceId
: the EC2 instance ID of the machine where the logger is running.
* WARNING: do not enable this elsewhere, as the operation to retrieve
* this value may take a long time (and return nothing).
* -
cookies
: a list of cookie objects, with all attributes (name, value, domain,
* max age, &c). These are subject to whitelisting and blacklisting
* via {@link #includeCookies} and {@link #excludeCookies}.
* -
parameters
: the request parameters, if any. These are subject to whitelisting and blacklisting
* via {@link #includeParameters} and {@link #excludeParameters}.
* -
queryString
: the query string, if any.
* -
remoteHost
: the remote hostname. This may involve a DNS lookup.
* -
requestHeaders
: the request headers. These are subject to whitelisting and blacklisting
* via {@link #includeHeaders} and {@link #excludeHeaders}.
* -
responseHeaders
: the response headers. These are subject to whitelisting and blacklisting
* via {@link #includeHeaders} and {@link #excludeHeaders}.
* -
server
: the destination server name. This is normally extracted from the
* Host
request header, but may involve a DNS lookup (see
* J2EE docs for more info).
* -
sessionId
: the session ID. This may not be available, which will trigger an
* ignored exception on retrieval.
* -
user
: the remote user, if one exists.
*
*
* Lastly, you can define a set of user tags, which are written as a child object with
* the key tags
. These are intended to provide program-level information,
* in the case where multiple programs send their logs to the same stream. They are set
* as a single comma-separate string, which may contain substitution values (example:
* appserver-name=Fribble,startedAt={startupTimestamp}
).
*
* WARNING: you should not rely on the order in which elements are output. Any apparent
* ordering is an implementation artifact and subject to change without notice.
*/
public class JsonAccessLayout
extends AbstractJsonLayout
{
//----------------------------------------------------------------------------
// Configuration
//----------------------------------------------------------------------------
private boolean enableServer;
private boolean enableRemoteHost;
private boolean enableRemoteUser;
private boolean enableSessionId;
private boolean enableRequestHeaders;
private boolean enableResponseHeaders;
private boolean enableCookies;
private boolean enableParameters;
private boolean enableQueryString;
private String includeHeaders;
private String excludeHeaders;
private boolean whitelistAllHeaders = false;
private Set headerWhitelist = Collections.emptySet();
private Set headerBlacklist = Collections.emptySet();
private String includeCookies;
private String excludeCookies;
private boolean whitelistAllCookies = false;
private Set cookieWhitelist = Collections.emptySet();
private Set cookieBlacklist = Collections.emptySet();
private String includeParameters;
private String excludeParameters;
private boolean whitelistAllParameters = false;
private Set parameterWhitelist = Collections.emptySet();
private Set parameterBlacklist = Collections.emptySet();
/**
* Optionally enables inserting the hostname of the destination server, taken
* from the HTTP request.
*/
public void setEnableServer(boolean value)
{
enableServer = value;
}
public boolean getEnableServer()
{
return enableServer;
}
/**
* Optionally enables looking up the hostname of the remote host. This
* is likely to be an expensive operation.
*/
public void setEnableRemoteHost(boolean value)
{
enableRemoteHost = value;
}
public boolean getEnableRemoteHost()
{
return enableRemoteHost;
}
/**
* Enables retrieving the remote user's name, if it's known.
*/
public void setEnableRemoteUser(boolean value)
{
enableRemoteUser = value;
}
public boolean getEnableRemoteUser()
{
return enableRemoteUser;
}
/**
* Optionally enables storing the session ID in the result.
*/
public void setEnableSessionId(boolean value)
{
enableSessionId = value;
}
public boolean getEnableSessionId()
{
return enableSessionId;
}
/**
* Enables storing request headers in the output. This must be used along with
* either includeHeaders
or excludeHeaders
to avoid
* accidentally leaking secrets.
*/
public void setEnableRequestHeaders(boolean value)
{
enableRequestHeaders = value;
}
public boolean getEnableRequestHeaders()
{
return enableRequestHeaders;
}
/**
* Enables storing request headers in the output. This must be used along with
* either includeHeaders
and excludeHeaders
to avoid
* accidentally leaking secrets.
*/
public void setEnableResponseHeaders(boolean value)
{
enableResponseHeaders = value;
}
public boolean getEnableResponseHeaders()
{
return enableResponseHeaders;
}
/**
* Sets the list of headers that will be included in the output. This is a comma-separated
* list of names or the special value "*". If given a list of names, only those names will
* be included; "*" includes all headers. Note that excludeHeaders
still
* applies; even if a header is explicitly whitelisted it may still be omitted.
*/
public void setIncludeHeaders(String value)
{
includeHeaders = value;
if ("*".equals(value))
{
whitelistAllHeaders = true;
}
else
{
headerWhitelist = new HashSet();
for (String headerName : value.split(","))
{
headerWhitelist.add(headerName.toLowerCase());
}
}
}
public String getIncludeHeaders()
{
return includeHeaders;
}
/**
* Sets the list of headers that will be explicitly excluded from the output. This is
* a comma-separated list of names, and takes precedence over includeHeaders
.
*/
public void setExcludeHeaders(String value)
{
excludeHeaders = value;
headerBlacklist = new HashSet();
for (String headerName : value.split(","))
{
headerBlacklist.add(headerName.toLowerCase());
}
}
public String getExcludeHeaders()
{
return excludeHeaders;
}
/**
* Enables storing request parameters in the output. This must be used along with
* either includeParameters
and excludeParameters
to
* avoid accidentally leaking secrets.
*/
public void setEnableParameters(boolean value)
{
enableParameters = value;
}
public boolean getEnableParameters()
{
return enableParameters;
}
/**
* Sets the list of parameters that will be included in the output. This is a comma-separated
* list of names, or the special value "*". If given a list of names, only those names will
* be included; "*" includes all parameters. Note that excludeParameters
still
* applies; even if a parameter is explicitly whitelisted it may still be omitted.
*/
public void setIncludeParameters(String value)
{
includeParameters = value;
if ("*".equals(value))
{
whitelistAllParameters = true;
}
else
{
parameterWhitelist = new HashSet();
for (String paramName : value.split(","))
{
parameterWhitelist.add(paramName.toLowerCase());
}
}
}
public String getIncludeParameters()
{
return includeParameters;
}
/**
* Sets the list of parameters that will be explicitly excluded from the output. This is
* a comma-separated list of names, and takes precedence over includeParameters
.
*/
public void setExcludeParameters(String value)
{
excludeParameters = value;
parameterBlacklist = new HashSet(Arrays.asList(value.split(",")));
}
public String getExcludeParameters()
{
return excludeParameters;
}
/**
* Enables storing request cookies in the output. You must also configure,
* includeCookies
and (optionally) excludeCookies
* to avoid accidentally leaking secrets.
*/
public void setEnableCookies(boolean value)
{
enableCookies = value;
}
public boolean getEnableCookies()
{
return enableCookies;
}
/**
* Sets the list of cookies that will be included in the output. This is a comma-separated
* list of names, or the special value "*". If given a list of names, only those names will
* be included; "*" includes all cookies. Note that excludeCookies
takes
* precedence; even if a cookie is explicitly whitelisted it may still be omitted.
*/
public void setIncludeCookies(String value)
{
includeCookies = value;
if ("*".equals(value))
{
whitelistAllCookies = true;
}
else
{
cookieWhitelist = new HashSet();
for (String cookieName : value.split(","))
{
cookieWhitelist.add(cookieName.toLowerCase());
}
}
}
public String getIncludeCookies()
{
return includeCookies;
}
/**
* Sets the list of cookies that will be explicitly excluded from the output. This is a
* comma-separated list of names, and takes precedence over includeCookies
.
*/
public void setExcludeCookies(String value)
{
excludeCookies = value;
cookieBlacklist = new HashSet(Arrays.asList(value.split(",")));
}
public String getExcludeCookies()
{
return excludeCookies;
}
/**
* Enables storing the unparsed request query string in the output. This is
* not as useful as storing the request parameters, and may leak secrets.
*/
public void setEnableQueryString(boolean value)
{
enableQueryString = value;
}
public boolean getEnableQueryString()
{
return enableQueryString;
}
//----------------------------------------------------------------------------
// Layout Overrides
//----------------------------------------------------------------------------
@Override
public String doLayout(IAccessEvent event)
{
Map map = new TreeMap();
map.put("timestamp", new Date(event.getTimeStamp()));
map.put("thread", event.getThreadName());
map.put("elapsedTime", event.getElapsedTime());
map.put("protocol", event.getProtocol());
map.put("requestMethod", event.getMethod());
map.put("requestURI", event.getRequestURI());
map.put("statusCode", event.getStatusCode());
map.put("bytesSent", event.getContentLength());
map.put("remoteIP", event.getRemoteAddr());
if (enableQueryString)
map.put("queryString", event.getQueryString());
if (enableRemoteHost)
map.put("remoteHost", event.getRemoteHost());
if (enableServer)
map.put("server", event.getServerName());
if (enableRemoteUser)
map.put("user", event.getRemoteUser());
if (enableSessionId)
{
try
{
map.put("sessionId", event.getSessionID());
}
catch (Exception ex) // Tomcat is IllegalStateException; other servers might differ
{
map.put("sessionId", "");
}
}
if (enableRequestHeaders)
map.put("requestHeaders", applyFilters(event.getRequestHeaderMap(), whitelistAllHeaders, headerWhitelist, headerBlacklist));
if (enableResponseHeaders)
map.put("responseHeaders", applyFilters(event.getResponseHeaderMap(), whitelistAllHeaders, headerWhitelist, headerBlacklist));
if (enableCookies)
map.put("cookies", getCookies(event.getRequest()));
if (enableParameters)
map.put("parameters", applyFilters(event.getRequestParameterMap(), whitelistAllParameters, parameterWhitelist, parameterBlacklist));
String forwardedFor = event.getRequestHeader("x-forwarded-for");
if (forwardedFor != null)
map.put("forwardedFor", forwardedFor);
return addCommonAttributesAndConvert(map);
}
//----------------------------------------------------------------------------
// Internals
//----------------------------------------------------------------------------
private List