-
Notifications
You must be signed in to change notification settings - Fork 50
Dynamic Log Levels
Dynamic Log Levels enable you to change the log level for specific incoming HTTP requests during runtime, without restarting your application. This is a powerful feature for debugging live systems, allowing you to get detailed DEBUG or TRACE logs for a single problematic request or user, without being overwhelmed by a flood of logs from all other traffic.
In version 4.0.0, this feature has been refactored around a new, extensible architecture.
The servlet instrumentation (cf-java-logging-support-servlet) now defines a DynamicLogLevelProvider interface.
The library uses Java's Service Provider Interface (SPI) to discover and load implementations of this interface at runtime.
This allows you to define your own application-specific logic for when and how to change the log level.
The most common way to use this feature is by providing a dynamic log level via a JWT in an HTTP header.
This approach, which was the default in previous versions, has been extracted into a separate, optional module: cf-java-logging-support-servlet-dynlog-jwt.
By adding this dependency, you restore the familiar JWT-based functionality and compatibility with the configuration options (environment variables and logback/log4j2 config files) from older versions.
This approach has several benefits:
- Fine-grained Control: The log level is scoped to a single request.
- Security: The JWT is signed with an RSA key and includes an expiration date to prevent misuse.
- Traceability Across Services: The JWT can be forwarded in outgoing requests. If upstream services also use this library, you can enable detailed logging across a distributed transaction.
To enable dynamic log levels via JWTs, you need to configure three parts: the dependency, the request instrumentation, and the logging backend.
First, add the cf-java-logging-support-servlet-dynlog-jwt module to your project's dependencies.
Maven:
<dependency>
<groupId>com.sap.hcp.cf.logging</groupId>
<artifactId>cf-java-logging-support-servlet-dynlog-jwt</artifactId>
<version>4.x.x</version>
</dependency>The RequestLoggingFilter (or the more specific DynamicLogLevelFilter) processes the incoming request headers.
When the -dynlog-jwt module is on the classpath, it automatically enables the JWT processing logic.
You need to configure one of these filters as described in Instrumenting Servlets.
The JWT processing is controlled by two environment variables:
-
DYN_LOG_HEADER: Specifies the HTTP header name for the JWT. If not set, it defaults toSAP-LOG-LEVEL. -
DYN_LOG_LEVEL_KEY: The public RSA key (in PEM format) used to verify the JWT's signature. This is mandatory for the feature to work unless you provide your ownJwtAlgorithProvidervia SPI.
The log level information extracted from the JWT is passed to the logging backend via the MDC (Mapped Diagnostic Context). You must configure a filter in your logging configuration to apply this information.
Logback Specific Configuration
In your logback.xml file, add the following turboFilter.
This filter supports restricting the dynamic log level to specific packages defined in the JWT.
<turboFilter class="com.sap.hcp.cf.logback.filter.DynamicLevelPrefixLoggerTurboFilter" />See the logback.xml of the sample app for a full example.
Note: The legacy
com.sap.hcp.cf.logback.filter.CustomLoggingTurboFilteris still available for backward compatibility but is less flexible and not recommended for new projects.
Log4j2 Specific Configuration
In your log4j2.xml file, add the following filter to the <Filters> section of your configuration.
This filter also supports package-based filtering.
<DynamicLevelPrefixLoggerFilter />Note: The legacy
DynamicThresholdFilterapproach is still functional but less powerful. The new filter is recommended.
See the log4j2.xml of the sample app for a full example.
To trigger a dynamic log level, a client must send an HTTP request with a valid, signed JWT in the configured header.
A valid JWT must be signed with the RS256 algorithm and contain a specific payload structure.
Header:
{
"alg": "RS256",
"typ": "JWT"
}Payload:
{
"level": "TRACE",
"packages": "com.mypackage.service,com.mypackage.utils",
"iat": 1672531200,
"exp": 1672534800
}-
level: The desired log level (ERROR,WARN,INFO,DEBUG, orTRACE). -
packages: (Optional) A comma-separated list of package name prefixes. If provided, the dynamic log level will only apply to log events originating from these packages. -
iat: "Issued At" timestamp (Unix epoch seconds). -
exp: "Expiration" timestamp (Unix epoch seconds). The token will be rejected after this time.
The old TokenCreator utility class has been removed.
The new Spring Boot sample application provides a convenient REST endpoint to generate tokens for testing.
Once the sample app is running, you can generate a token by making a GET request to /token/{logLevel}.
Example: Generate a DEBUG token valid for one hour for the com.sap.hcp.cf package.
# Request a token from the sample app
$ curl -u user:secret "http://localhost:8080/token/DEBUG?p=com.sap.hcp.cf&exp=$(($(date +%s) + 3600))"
# The app will return a JWT, for example:
# eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2Nz...To use this token with your own application you can get the public key from endpoint /publickey.
$ DYN_LOG_LEVEL_KEY=$(curl -u user:secret "http://localhost:8080/publickey")When your application is configured with this public key you can use the token generate above with your application.
With version 4.0.0, the extension mechanism has been completely redesigned to use a Service Provider Interface (SPI).
This provides a cleaner, more decoupled way to customize behavior.
The old extension points from versions prior to 4.0.0 (DynamicLogLevelProcessor, DynamicLogLevelConfiguration) have been removed.
The central extension point is the com.sap.hcp.cf.logging.servlet.dynlog.api.DynamicLogLevelProvider interface, located in the cf-java-logging-support-servlet module.
This is the most flexible way to integrate dynamic logging, allowing you to fetch the log level from any source, such as a different header, a database, or a feature flag service.
The interface is a java.util.function.Function<HttpServletRequest, DynamicLogLevelConfiguration>, meaning its purpose is to inspect the incoming HttpServletRequest and return a DynamicLogLevelConfiguration.
This configuration is a simple record containing the level and packages to be applied.
To create your own provider:
-
Create an implementation: Implement the
DynamicLogLevelProviderinterface. Your apply method should contain the custom logic. If no dynamic log level should be applied, returnDynamicLogLevelConfiguration.EMPTY.
package com.mycompany.logging;
import com.sap.hcp.cf.logging.servlet.dynlog.DynamicLogLevelConfiguration;
import com.sap.hcp.cf.logging.servlet.dynlog.DynamicLogLevelProvider;
import jakarta.servlet.http.HttpServletRequest;
public class MyCustomHeaderProvider implements DynamicLogLevelProvider {
@Override
public DynamicLogLevelConfiguration apply(HttpServletRequest request) {
// Your custom logic, e.g., read a special header
String level = request.getHeader("X-Debug-Level");
String packages = request.getHeader("X-Debug-Packages");
// Only return a configuration if a level is provided
if (level != null && !level.isBlank()) {
return new DynamicLogLevelConfiguration(level, packages);
}
// Return null if no dynamic logging should be applied
return DynamicLogLevelConfiguration.EMPTY;
}
}-
Register it via SPI: Create a file named
com.sap.hcp.cf.logging.servlet.dynlog.api.DynamicLogLevelProviderin thesrc/main/resources/META-INF/services/directory of your project. The content of this file should be the fully qualified name of your implementation class:
com.mycompany.logging.MyCustomHeaderProviderThe DynamicLogLevelFilter will automatically discover and use your provider.
If multiple providers are found, it will use the first one that returns a valid configuration (i.e., not DynamicLogLevelConfiguration.EMPTY) .
The JWT provider from the -dynlog-jwt module has a lower priority, so your custom provider will be preferred.
If you are using the cf-java-logging-support-servlet-dynlog-jwt module but need to load the public key from a source other than the DYN_LOG_LEVEL_KEY environment variable (e.g., a vault or a remote key service), you can implement the JwtAlgorithmProvider interface.
This interface is also loaded via SPI and is defined in the cf-java-logging-support-servlet-dynlog-jwt module.
-
Create an implementation: Your implementation must supply a
com.auth0.jwt.algorithms.Algorithminstance, usually via a Supplier for lazy initialization.
package com.mycompany.logging.jwt;
import com.auth0.jwt.algorithms.Algorithm;
import com.sap.hcp.cf.logging.servlet.dynlog.jwt.JwtAlgorithmProvider;
import java.security.interfaces.RSAPublicKey;
import java.util.function.Supplier;
public class VaultJwtAlgorithmProvider implements JwtAlgorithmProvider {
private volatile Algorithm algorithm;
@Override
public Supplier<Algorithm> get() {
// The supplier enables lazy-loading of the key.
// We return a supplier that initializes the algorithm on first use.
return () -> {
if (algorithm == null) {
synchronized (this) {
if (algorithm == null) {
RSAPublicKey publicKey = // ... your logic to fetch key from a vault ...
algorithm = Algorithm.RSA256(publicKey, null);
}
}
}
return algorithm;
};
}
}-
Register it via SPI: Create the file
src.main/resources/META-INF/services/com.sap.hcp.cf.logging.servlet.dynlog.jwt.JwtAlgorithmProviderwith the content:
com.mycompany.logging.jwt.VaultJwtAlgorithmProviderThis example shows how to use the JWT-based dynamic logging feature from start to finish. We assume you have an application configured as described in the App Configuration section above.
First, run the Spring Boot sample application. Then, fetch its public key, which you will use to configure your own application.
# Get the public key and store it in an environment variable
export DYN_LOG_LEVEL_KEY=$(curl -u user:secret http://localhost:8080/publickey)
echo "Public key is set."Start your own application with the DYN_LOG_LEVEL_KEY environment variable set from the previous step.
Use the running Spring Boot sample app to generate a TRACE level token valid for 10 minutes.
# Generate a token and store it in a shell variable
TOKEN=$(curl -s -u user:secret "http://localhost:8080/token/TRACE?exp=$(($(date +%s) + 600))")
echo "Generated Token: $TOKEN"Call an endpoint on your application, passing the generated JWT in the SAP-LOG-LEVEL header (or your custom header name).
curl http://<your-app-host>/<your-endpoint> \
-H "SAP-LOG-LEVEL: $TOKEN"In your application's logs, you will now see TRACE and DEBUG level messages for this specific request, which would normally be suppressed by a higher static log level like INFO.
Log output with dynamic level (example):
[REQUEST]... TRACE ... This is a trace message
[REQUEST]... DEBUG ... This is a debug message
[REQUEST]... INFO ... This is an info message
If you make the same request without the SAP-LOG-LEVEL header, the logs will revert to the statically configured level.
Log output without dynamic level (example):
[REQUEST]... INFO ... This is an info message
Of course, you can also use the token for requests to the sample app and observe its logs.