-
Notifications
You must be signed in to change notification settings - Fork 48
Instrumenting Servlets
Servlet instrumentation enables application developers to enhance log messages with metadata extracted from HTTP headers and generates request logs. It is implemented as a standard servlet filter as defined in the Java Servlet Specification, Version 3.0 . There are several distinct feature, that can be configured separately.
We provide two versions of the servlet instrumentation to address the packaging switch from javax.servlet
to jakarta.servlet
:
- cf-java-logging-support-servlet using
javax.servlet
in version 3.1 - cf-java-logging-support-servlet-jakarta usin
jakarta.servlet
in version 5.0
Both artefacts contain the same classes and provide the same features. Depending on your project setup, you can choose the matching dependency version. For example, when building Spring Boot 3.0 apps, choose the Jakarta version.
Request instrumentation is configured by adding the RequestLoggingFilter to your servlet configuration. This filter contains the full feature set and is a good general approach for most applications. Custom configurations are described in section Customizing Request Filtering. You only need to take two steps:
- add the
cf-java-logging-support-servlet(-jakarta)
dependency - register
com.sap.hcp.cf.logging.servlet.filter.RequestLoggingFilter
in your servlet context
Add either of the following Maven dependencies to your pom.xml
.
It contains all required classes to provide request instrumentation for your application.
For javax.servlet
support (Java 8 and before):
<!-- We're using the Servlet Filter instrumentation -->
<dependency>
<groupId>com.sap.hcp.cf.logging</groupId>
<artifactId>cf-java-logging-support-servlet</artifactId>
<version>${cf-logging-version}</version>
</dependency>
For jakarta.servlet
support (Java 11 and beyond):
<!-- We're using the Servlet Filter instrumentation -->
<dependency>
<groupId>com.sap.hcp.cf.logging</groupId>
<artifactId>cf-java-logging-support-servlet-jakarta</artifactId>
<version>${cf-logging-version}</version>
</dependency>
Either configure a property cf-logging-version
with the current library version or just put it in the <version>
tag.
The servlet filter is enabled by registering com.sap.hcp.cf.logging.servlet.filter.RequestLoggingFilter
as filter in a servlet context. It should be registered for the dispatch type request to function properly. See the examples below for more details.
The easiest way to enable that servlet filter is to declare it in your application's web.xml
file. To do so, add the following lines to that file, as we've done in the sample application:
<filter>
<filter-name>request-logging</filter-name>
<filter-class>com.sap.hcp.cf.logging.servlet.filter.RequestLoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>request-logging</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
You can use FilterRegistrationBean to enable the servlet filter in Spring Boot. Add the following bean to your @Configuration file:
@Bean
public FilterRegistrationBean loggingFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new RequestLoggingFilter());
filterRegistrationBean.setName("request-logging");
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST);
return filterRegistrationBean;
}
If you use Spring Security, the above registration will register the RequestLoggingFilter after the Spring Security filter.
So request instrumentation will only happen after a request is accepted.
If you want to have request intrumentation for all requests, add the following line to raise the order in the FilterRegistrationBean
:
filterRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
The RequestLoggingFilter bundles different functionalities in one easy to use filter. There are use-cases, where some of the features may not be required but some are. For those scenarios, all features are provided by separate filters, that can be configured to match the requirements of the developer.
The following table gives an overview of the available feature and the respective filters.
Feature | Filter | Description |
---|---|---|
VCAP metadata | com.sap.hcp.cf.logging.servlet.filter.AddVcapEnvironmentToLogContextFilter |
Extracts metadata about the application from the VCAP environment variable and adds them to the LogContext. |
HTTP header propagation | com.sap.hcp.cf.logging.servlet.filter.AddHttpHeadersToLogContextFilter |
Extracts HTTP headers and adds them to the LogContext. |
Correlation Id | com.sap.hcp.cf.logging.servlet.filter.CorrelationIdFilter |
Extracts a correlation id from an HTTP header or generates one, if none is found. Adds the correlation id to the LogContext. |
Dynamic Log Levels | com.sap.hcp.cf.logging.servlet.filter.DynamicLogLevelFilter |
Enables dynamic log level switching with JWT tokens. |
Request Logs | com.sap.hcp.cf.logging.servlet.filter.GenerateRequestLogFilter |
Creates the request logs for incoming requests. |
LogContext to Request Attribute | com.sap.hcp.cf.logging.servlet.filter.LogContextToRequestAttributeFilter |
Adds the current LogContext as an request attribute for asynchronous request handling. This feature is also part of generating reqeust logs. |
The order of that table corresponds to the ordering these features are applied in the RequestLoggingFilter.
Most filters add fields to the LogContext, which leads to emitting these fields as part of the JSON log messages generated during request handling.
By customizing the filter registration, you can choose what filters to apply on which parts of your application explicitly.
To ease extensions, the static method RequestLoggingFilter.getDefaultFilters()
returns a freshly generated array of filter instances in the order used by the RequestLoggingFilter.
This section describes the provided features. It also explains possible configuration options and extension endpoints.
Applications running in CloudFoundry can access environment variables, that are configured in the manifest or injected by the runtime, e.g. service bindings.
To ease identification of the log message source the following information is read from the VCAP_APPLICATION
environment variable:
- application id
- application name
- instance index
- space id
- space name
- organization id
- organization name
For details see VcapEnvReader.
The extracted information can usually also be obtained from CF Loggregator shipment channels. When your logging system has access to those, you can omit this feature.
VCAP metadata is provided by com.sap.hcp.cf.logging.servlet.filter.AddVcapEnvironmentToLogContextFilter
.
The filter does not provide custom configuration or extensibility.
Basic web.xml
configuration is as follows:
<filter>
<filter-name>log-vcap-metadata</filter-name>
<filter-class>com.sap.hcp.cf.logging.servlet.filter.AddVcapEnvironmentToLogContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>log-vcap-metadata</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
HTTP headers can serve as a powerful tool to group log messages, e.g. request or tenant ids. This helps in log analysis to correlate different messages. The AddHttpHeadersToLogContextFilter by default extracts the following HTTP headers as key-value pairs and adds them to the LogContext:
x-vcap-request-id
X-CorrelationID
tenantid
sap-passport
Note: The correlation and request id are also processed by the CorrelationIdFilter. See this section for details on the differences between the two filters.
To use the default HTTP headers as described above, simply register the AddHttpHeadersToLogContextFilter without parameters:
<filter>
<filter-name>log-http-headers</filter-name>
<filter-class>com.sap.hcp.cf.logging.servlet.filter.AddHttpHeadersToLogContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>log-http-headers</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
The AddHttpHeadersToLogContextFilter comes with three constructors:
public AddHttpHeadersToLogContextFilter() {
this(HttpHeaders.propagated());
}
public AddHttpHeadersToLogContextFilter(HttpHeader... headers) {
this(Collections.emptyList(), headers);
}
public AddHttpHeadersToLogContextFilter(List<? extends HttpHeader> list, HttpHeader... custom) {
Stream<HttpHeader> allHeaders = Stream.concat(list.stream(), Arrays.stream(custom));
this.headers = unmodifiableList(allHeaders.filter(HttpHeader::isPropagated).collect(toList()));
this.fields = unmodifiableList(headers.stream().map(HttpHeader::getField).filter(Objects::nonNull).collect(
toList()));
}
The first constructor is used in the default configuration.
The second and third constructor allow to exchange and extended the list of forwarded headers.
If you can register objects as filter, e.g. using Spring Boot, just use these constructors with the headers you need.
If you can only register classes, e.g. using web.xml
, create a custom subclass of AddHttpHeadersToLogContextFilter and redirect the default constructor to one of the other two constructors.
To provide custom headers, you have to implement HttpHeader:
public interface HttpHeader {
boolean isPropagated(); // needs to be true for this use-case
String getName(); // the name of the HTTP header
String getField(); // the name of the JSON log field
List<HttpHeader> getAliases(); // other headers to use, if this one is missing
String getFieldValue(); // get current or default value from LogContext
}
The default headers are defined in HttpHeaders.
The W3C Trace Context defines two HTTP headers for tracing requests: traceparent
and tracestate
.
traceparent
is already support by default.
You can add support for tracestate
with the following implementation:
public class W3cTraceState implements HttpHeader {
@Override
public boolean isPropagated() {
return true;
}
@Override
public String getName() {
return "tracestate";
}
@Override
public String getField() {
return "w3c_tracestate";
}
@Override
public List<HttpHeader> getAliases() {
return Collections.emptyList();
}
@Override
public String getFieldValue() {
return LogContext.get(getField());
}
}
This implementation can now be used in the AddHttpHeadersToLogContextFilter:
Filter extendedHeaderFilter = new AddHttpHeadersToLogContextFilter(HttpHeaders.propagated(), new W3cTraceState());
This adds the new header to the default headers provided by the library.
Correlation ids are a mechanism to correlate multiple log messages to one request. They are taken from an HTTP header and should be forwarded to all outgoing requests made to serve the incoming request. This allows tracing the request execution amongst different applications. The CorrelationIdFilter implements this simple tracing approach.
By default it uses the header X-CorrelationID
with a fall-back to x-vcap-request-id
, if X-CorrelationID
cannot be found.
If neither header is present the filter checks the LogContext for the field correlation_id
.
If there is still no value to be found a random correlation-id is generated.
In all cases, the correlation id is added as response header X-CorrelationID
, if possible.
The correlation id is added as field correlation_id
to the LogContext.
The main additional features compared to the AddHttpHeadersToLogContextFilter are:
- generation of a random correlation id, if none found
- addition of response header
X-CorrelationID
Both filters produce idempotent results and can be combined in any order.
To use the default HTTP headers as described above, simply register the [CorrelationIdFilter:
<filter>
<filter-name>log-correlation-id</filter-name>
<filter-class>com.sap.hcp.cf.logging.servlet.filter.CorrelationIdFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>log-correlation-id</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
The CorrelationIdFilter has a constructor, that takes an HttpHeader to be used as correlation-id as an argument.
If you can register objects as filter, e.g. using Spring Boot, just use this constructor with the headers you need.
If you can only register classes, e.g. using web.xml
, create a custom subclass of CorrelationIdFilter, that calls this constructor with the header you want.
This is very similar to propagating custom http headers with the AddHttpHeadersToLogContextFilter.
Dynamic Log Levels refer to the possibility to switch the configured log levels at runtime. There are different approaches to achieve that goal either through configuration servers or exposing a management endpoint, e.g. in Spring Boot Actuator. This library takes a different approach, that is described in detail in the Dynamic Log Levels documentation. In summary, there is the DynamicLogLevelFilter, that scans HTTP requests for a specific header containing a JWT. After validation the token is inspected for claims changing the logging configuration. The changed configuration is applied by a filter class depending on the used logging backend (log4j2 or logback). It is possible to customize the JWT processing and configuration such as the HTTP header to be used. See DynamicLogLevelFilter for details.
To use the default Dynamic Log Level implementation, simply register the [DynamicLogLevelFilter:
<filter>
<filter-name>dynamic-log-levels</filter-name>
<filter-class>com.sap.hcp.cf.logging.servlet.filter.DynamicLogLevelFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>dynamic-log-levels</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Since the JWT processing requires the additional dependency java-jwt, there is a simplified version of the RequestLoggingFilter, that is stripped of the dynamic log level feature. To use this simplified version, register the [StaticLevelRequestLoggingFilter:
<filter>
<filter-name>static-request-logging</filter-name>
<filter-class>com.sap.hcp.cf.logging.servlet.filter.StaticLevelRequestLoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>static-request-logging</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
You can exclude the java-jwt dependency when using the StaticLevelRequestLoggingFilter:
<dependency>
<groupId>com.sap.hcp.cf.logging</groupId>
<artifactId>cf-java-logging-support-servlet</artifactId>
<exclusions>
<exclusion>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
</exclusion>
</exclusions>
</dependency>
The main goal of the request instrumentation is to provide request logs. This means, for every incoming request a log message containing information about request and response is emitted. These messages contain metrics similar to Apache access logs. The main benefits of creating these logs in the library are:
- metrics in particular response times are determined for the application and do not include network hops due to routing
- request logs can be customized by the other features, such as correlation ids and custom headers
- the logs are generated regardless of the application deployment, e.g. they do not rely on the CF Gorouter
Request logs are emitted in JSON format. See the exported fields for request metrics for details.
To generate request logs, register the GenerateRequestLogFilter:
<filter>
<filter-name>generate-request-logs</filter-name>
<filter-class>com.sap.hcp.cf.logging.servlet.filter.GenerateRequestLogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>generate-request-logs</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Note: All configuration describe in the following sections also apply to the general RequestLoggingFilter and StaticLevelRequestLoggingFilter.
There are several ways to customize the generated request logs. They are configured by different mechanisms. The following table shows the properties and the kind of configuration.
Property | Configuration Parameter |
---|---|
generate Logs | logging configuration for RequestLogger |
wrap request | filter init parameter "wrapRequest" |
wrap response | filter init parameter "wrapResponse" |
include sensitive data | environment variable "LOG_SENSITIVE_CONNECTION_DATA" |
include remote user | environment variable "LOG_REMOTE_USER" |
include referrer | environment variable "LOG_REFERER" |
include x-ssl-headers | environment variable "LOG_SSL_HEADERS" |
Request logs are generated by the RequestLogger with an SL4J marker REQUEST_MARKER at INFO level. You can disable the generation of request logs by disabling these logs in you logging configuration.
Using Log4j2 add the following line:
<Logger name="com.sap.hcp.cf.logging.servlet.filter.RequestLogger" level="WARN" />
Using Logback add the following line:
<logger name="com.sap.hcp.cf.logging.servlet.filter.RequestLogger" level="WARN" />
This configuration is helpful, when combined with dynamic log levels. It can be used to dynamically control the generation of request logs by a JWT header. If the request logs are disabled no instrumentation of requests or responses are done.
The GenerateRequestLogFilter wraps HTTP requests and responses to determine there sizes.
This wrapping can be disabled by the filter parameters wrapRequest
and wrapResponse
respectively.
<filter>
<filter-name>generate-request-logs</filter-name>
<filter-class>com.sap.hcp.cf.logging.servlet.filter.GenerateRequestLogFilter</filter-class>
<init-param>
<param-name>wrapRequest</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>wrapResponse</param-name>
<param-value>false</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>generate-request-logs</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Note: In order to support asynchronous request handling, HTTP request will always be wrapped by LoggingContextRequestWrapper. This can only be disabled by disabling request logs.
By default the GenerateRequestLogFilter will not log personalizable information on the incoming requests.
This is controlled by three environment variables: LOG_SENSITIVE_CONNECTION_DATA
, LOG_REMOTE_USER
and LOG_REFERER
.
You need to provide the value true
for each of those variables to include the respective information in the request logs.
The following table shows what data can be enabled by which variable.
Environment Variable | Provided Request Data |
---|---|
LOG_SENSITIVE_CONNECTION_DATA |
remote address remote host from request remote port HTTP header x-forwarded-for
|
LOG_REMOTE_USER |
remote user |
LOG_REFERER |
HTTP header referer
|
Note: Due to Load-Balancing in CF, the remote data may refer to the Gorouter and not the user client.
HA-Proxy can be configured to terminate ssl connections and forward the verification as http headers to the application. See the HA-Proxy Documentation for details. This results in eight possible headers attached to the http request:
- x-ssl
- x-ssl-client-verify
- x-ssl-client-subject-dn
- x-ssl-client-subject-cn
- x-ssl-client-issuer-dn
- x-ssl-client-notbefore
- x-ssl-client-notafter
- x-ssl-client-session-id
These headers might be logged as fields within the request logs with hyphens replaced by underscores.
In any case, these fields are only emitted, when explicitly enabled.
This is achieved by providing the environment variable LOG_SSL_HEADERS
with a value of true
.
The Java Servlet API 3.0 introduced asynchronous request handling. The main distinction of asynchronous request handling is, that requests are no longer handled by the same Java thread in which was assigned on request dispatch. Logging usually leverages a context (MDC) bound to the current thread to manage metadata. #cf-java-logging-support uses the MDC for request tracking such as request id and further CF metadata. When changing threads this context may be lost. Our current implementation ensures that the context is properly migrated within the threads of the servlet container.
To enable custom thread models or asynchronous execution the GenerateRequestLogFilter will add the MDC context map as a request attribute with the key org.slf4j.MDC
.
If necessary you can set the MDC in any custom thread by calling
MDC.setContextMap(httpRequest.getAttribute(MDC.class.getName()));
Note: This is only necessary when you create own threads, e.g. using custom Executors. One such case would be the @Async
support in Spring.
If you do not want to use the GenerateRequestLogFilter but still want to have the LogContext added as request attribute, you can configure the LogContextToRequestAttributeFilter:
<filter>
<filter-name>mdc-request-attribute</filter-name>
<filter-class>com.sap.hcp.cf.logging.servlet.filter.LogContextToRequestAttributeFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>mdc-request-attribute</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Note: This functionality is contained in the GenerateRequestLogFilter and hence in both in the RequestLoggingFilter and the StaticLevelRequestLoggingFilter. You will only need the LogContextToRequestAttributeFilter if you use neither of those three filters.