Instrumenting Servlets - SAP/cf-java-logging-support GitHub Wiki
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 Jakarta Servlet 6.0 Specification.
This new major version introduces several important changes:
- Single Servlet Module: The previous modules cf-java-logging-support-servlet and cf-java-logging-support-servlet-jakarta have been consolidated into a single module: cf-java-logging-support-servlet.
- Modern API Support: This new module exclusively supports Jakarta Servlet 6.0. Support for javax.servlet has been removed. This aligns with modern frameworks like Spring Boot 3.
- Dynamic Log Levels Extracted: The feature for dynamically changing log levels via JWT tokens has been moved to a separate module and is documented in its own article. This makes the core servlet instrumentation leaner and removes the dependency on java-jwt by default.
Request instrumentation is configured by adding the RequestLoggingFilter to your servlet configuration. This filter provides a comprehensive set of features and is the recommended approach for most applications.
You only need to take two steps:
- Add the
cf-java-logging-support-servletdependency - Register
com.sap.hcp.cf.logging.servlet.filter.RequestLoggingFilterin 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.
<dependency>
<groupId>com.sap.hcp.cf.logging</groupId>
<artifactId>cf-java-logging-support-servlet</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.
If you are using the sap-java-buildpack to deploy your application to SAP BTP Cloud Foundry runtime environment, you can declare this dependency with scope provided as it is contained in the buildpack.
The servlet filter is enabled by registering com.sap.hcp.cf.logging.servlet.filter.RequestLoggingFilter as filter in your servlet context.
It should be registered for the REQUEST dispatcher type to function properly.
The easiest way to enable that servlet filter is to declare it in your application's web.xml file:
<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 a Spring-based application. Add the following bean to your @Configuration file:
import com.sap.hcp.cf.logging.servlet.filter.RequestLoggingFilter;
import jakarta.servlet.DispatcherType;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@Configuration
public class LoggingFilterConfig {
@Bean
public FilterRegistrationBean<RequestLoggingFilter> loggingFilter() {
FilterRegistrationBean<RequestLoggingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new RequestLoggingFilter());
registrationBean.setName("request-logging");
registrationBean.addUrlPatterns("/*");
registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
// To log requests even if they are rejected by a security filter,
// set a high precedence.
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
}If you use Spring Security, setting the order to Ordered.HIGHEST_PRECEDENCE ensures that the RequestLoggingFilter runs before the security filter chain, allowing instrumentation for all incoming requests, including those that are later rejected.
Be aware that this can lead to a high volume of log entries, as it will log every attempt to access your application.
The RequestLoggingFilter bundles several features into one convenient filter. For use cases where you need more granular control, these features are also available as separate, individual filters.
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 from the VCAP_APPLICATION environment variable and adds them to the LogContext. |
| HTTP header propagation | com.sap.hcp.cf.logging.servlet.filter.AddHttpHeadersToLogContextFilter |
Extracts specified 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. It adds the correlation id to the LogContext and the response headers. |
| Dynamic Log Levels | com.sap.hcp.cf.logging.servlet.filter.DynamicLogLevelFilter |
Enables dynamic log level switching. |
| Request Logs | com.sap.hcp.cf.logging.servlet.filter.GenerateRequestLogFilter |
Creates and emits detailed 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 is also included in the GenerateRequestLogFilter. |
The order of that table corresponds to the order in which these features are applied within the RequestLoggingFilter. Most filters add fields to the LogContext, which are then included in the JSON log messages generated during request handling.
The primary difference between the two main filters is support for dynamic log levels:
-
RequestLoggingFilter: This is the default all-in-one filter. It includes theDynamicLogLevelFilterand will attempt to find aDynamicLogLevelProvideron the classpath via SPI at startup. -
StaticLevelRequestLoggingFilter: This filter provides the same functionality except for dynamic log levels. It does not include theDynamicLogLevelFilterand performs no SPI lookup. Use this filter if you want to explicitly disable the dynamic log level feature entirely.
This section describes each feature, along with its configuration options and extension points.
Applications running in Cloud Foundry have access to environment variables that describe the application's runtime context.
This filter reads the VCAP_APPLICATION environment variable and adds key information to the LogContext.
See VcapEnvReader for details on the extracted fields.
You can register this filter manually if you are not using RequestLoggingFilter:
<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 correlate different messages during log analysis. The AddHttpHeadersToLogContextFilter extracts the following HTTP headers by default and adds them as key-value pairs to the LogContext:
x-vcap-request-idX-CorrelationIDtenantidsap-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 provides constructors that allow you to extend or completely replace the list of propagated headers.
If you can register filter instances directly (e.g., using a Spring FilterRegistrationBean), you can use these constructors to pass your custom headers.
If you are using web.xml, you will need to create a custom subclass of AddHttpHeadersToLogContextFilter and call the appropriate constructor from your subclass's default constructor.
To provide custom headers, you must implement the HttpHeader interface:
public interface HttpHeader {
boolean isPropagated(); // Must 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 library's default headers are defined in the HttpHeaders class.
The W3C Trace Context defines two HTTP headers for tracing: traceparent and tracestate.
While traceparent is already support by default, you can add support for tracestate with the following implementation:
import com.sap.hcp.cf.logging.common.LogContext;
import com.sap.hcp.cf.logging.common.request.HttpHeader;
import java.util.Collections;
import java.util.List;
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 to create a configured filter instance, adding the new header to the default set provided by the library:
import com.sap.hcp.cf.logging.common.request.HttpHeaders;
import com.sap.hcp.cf.logging.servlet.filter.AddHttpHeadersToLogContextFilter;
import jakarta.servlet.Filter;
// In a Spring @Configuration class or similar
Filter extendedHeaderFilter = new AddHttpHeadersToLogContextFilter(HttpHeaders.propagated(), new W3cTraceState());The CorrelationIdFilter ensures every request has a unique correlation_id.
It attempts to read this ID from the X-CorrelationID or x-vcap-request-id headers.
If no ID is found, it generates a new one.
The ID is added to the LogContext and also injected as the X-CorrelationID response header.
This behavior is more specialized than the generic header propagation filter.
You can customize the CorrelationIdFilter by providing a custom HttpHeader to its constructor. This allows you to define which header should be used as the source for the correlation ID. This is similar to the customization process for propagating custom HTTP headers.
The DynamicLogLevelFilter enables changing log levels at runtime for specific requests. As of version 4.0.0, this filter is extensible via the Java Service Provider Interface (SPI).
The filter searches the classpath for implementations of the com.sap.hcp.cf.logging.servlet.filter.DynamicLogLevelProvider interface.
If a provider is found, the filter will use it to apply temporary log level changes during a request.
The specific implementation that uses JWT tokens has been extracted into a separate, optional module.
This change simplifies the default setup and removes the java-jwt dependency for applications that do not need this functionality.
Please refer to the dedicated Dynamic Log Levels article for instructions on how to add the JWT provider dependency and configure the feature.
The primary goal of this library is to generate structured, JSON-formatted request logs. These logs contain metrics similar to an Apache access log, such as response time, request size, and status code, but are generated within the application for greater accuracy. 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.
The GenerateRequestLogFilter is responsible for this.
You can find a detailed list of the fields in the request metrics documentation.
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.
For asynchronous request handling (as introduced in Servlet 3.0), the logging context (MDC) must be propagated across threads.
The GenerateRequestLogFilter automatically adds the MDC context map as a request attribute.
If you build a custom filter chain and need this functionality without generating full request logs, you can use the standalone LogContextToRequestAttributeFilter.
You can retrieve and set the context in a new thread like this:
MDC.setContextMap(httpRequest.getAttribute(MDC.class.getName()));Note: This functionality is already included in GenerateRequestLogFilter and, by extension, in RequestLoggingFilter.
You only need to register LogContextToRequestAttributeFilter separately if you are not using either of those filters.