Skip to content

Dynamic Log Levels

Karsten Schnitter edited this page Dec 14, 2025 · 12 revisions

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.

Using JWTs for Dynamic Log Levels

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.

App Configuration

To enable dynamic log levels via JWTs, you need to configure three parts: the dependency, the request instrumentation, and the logging backend.

1. Add the Dependency

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>

2. Configure Request Instrumentation

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 to SAP-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 own JwtAlgorithProvider via SPI.

3. Configure the Log Event Filter

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.CustomLoggingTurboFilter is 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 DynamicThresholdFilter approach is still functional but less powerful. The new filter is recommended.

See the log4j2.xml of the sample app for a full example.

Usage

To trigger a dynamic log level, a client must send an HTTP request with a valid, signed JWT in the configured header.

JWT Structure

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, or TRACE).
  • 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.

Generating Tokens with the Sample App

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.

Extending Dynamic Log Levels

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.

Providing a Custom Log Level Source (DynamicLogLevelProvider)

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:

  1. Create an implementation: Implement the DynamicLogLevelProvider interface. Your apply method should contain the custom logic. If no dynamic log level should be applied, return DynamicLogLevelConfiguration.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;
    }
}
  1. Register it via SPI: Create a file named com.sap.hcp.cf.logging.servlet.dynlog.api.DynamicLogLevelProvider in the src/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.MyCustomHeaderProvider

The 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.

Customizing the JWT Verification Key (JwtAlgorithmProvider)

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.

  1. Create an implementation: Your implementation must supply a com.auth0.jwt.algorithms.Algorithm instance, 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;
        };
    }
}
  1. Register it via SPI: Create the file src.main/resources/META-INF/services/com.sap.hcp.cf.logging.servlet.dynlog.jwt.JwtAlgorithmProvider with the content:
com.mycompany.logging.jwt.VaultJwtAlgorithmProvider

End-to-End Example with the Spring Boot Sample App

This 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.

Step 1: Get the Public Key from the Sample App

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."

Step 2: Configure and Run Your Application

Start your own application with the DYN_LOG_LEVEL_KEY environment variable set from the previous step.

Step 3: Generate a Token

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"

Step 4: Make a Request with the Dynamic Log Level Header

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"

Step 5: Observe the Logs

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.