Skip to content

Custom Fields

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

Custom fields are user-defined key-value pairs that you can add to your structured logs. They allow you to enrich your logs with business-specific data, such as an order_id, customer_id, or any other metric that helps you trace, debug, or analyze your application's behavior.

This article first covers how to add custom fields directly from your application code, then discusses advanced methods for programmatic field injection, and finally explains the different JSON output formats and their configuration.

Adding Custom Fields from Application Code

There are several ways to add custom fields to your logs directly from your Java code. These methods work regardless of the final JSON format.

Using the SLF4J Fluent Logging API (since v4.0.0)

With SLF4J 2.0+, the Fluent Logging API provides a modern, chainable way to add key-value pairs directly to a log statement. This is the recommended approach for adding context to a single log event. cf-java-logging-support version 4.0.0 and higher automatically recognizes these key-value pairs.

// Requires SLF4J 2.0+
LOG.atInfo()
   .addKeyValue("order_id", "A-12345")
   .addKeyValue("payment_method", "credit_card")
   .log("Payment processed for order.");

Using the Mapped Diagnostic Context (MDC)

The MDC is ideal for adding context that should be present in all log messages within a specific scope, such as a single web request.

import org.slf4j.MDC;

// Add a field to the context
MDC.put("order_id", "A-12345");
MDC.put("tenant_id", "T01");

LOG.info("Starting order processing..."); // This log will contain order_id and tenant_id
LOG.warn("Inventory is low.");           // This log will also contain the same fields

// It's a good practice to clear the MDC after the request is processed
MDC.clear();

Using the CustomField Log Parameter

For applications on older SLF4J versions, you can pass a CustomField object as a log parameter. This is useful for adding values relevant only to a single log statement.

import static com.sap.hcp.cf.logging.common.customfields.CustomField.customField;

// ...

LOG.info("Payment received for order {}", customField("order_id", "A-12345"));

If a key is duplicated, the precedence is: Fluent API / CustomField parameter > MDC fields.

Advanced: Adding Custom Fields via Suppliers

For more advanced use cases, such as programmatically adding complex or dynamically derived data to every log message, you can implement a ContextFieldSupplier.

There are three interfaces available:

  • com.sap.hcp.cf.logging.common.serialization.ContextFieldSupplier: A common interface that works for both Logback and Log4j2.
  • com.sap.hcp.cf.logback.converter.api.LogbackContextFieldSupplier: A Logback-specific interface.
  • com.sap.hcp.cf.log4j2.converter.api.Log4jContextFieldSupplier: A Log4j2-specific interface.

There are two ways to register your implementation:

A. Via Service Provider Interface (SPI) (since v4.0.0)

This is the easiest way to register a supplier.

  1. Create a class that implements one of the interfaces above.
  2. Create a file named after the fully qualified interface name in the META-INF/services/ directory of your project.
  3. Add the fully qualified name of your implementation class to this file. The library will automatically discover and load your supplier at runtime.

B. Via XML Configuration

You can also register your supplier explicitly in your logback.xml or log4j2.xml file.

Logback (logback.xml)

<appender name="STDOUT-JSON" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="com.sap.hcp.cf.logback.encoder.JsonEncoder">
        <!-- Register a common supplier -->
        <contextFieldSupplier>com.example.MyCommonSupplier</contextFieldSupplier>
        <!-- Register a Logback-specific supplier -->
        <logbackContextFieldSupplier>com.example.MyLogbackSupplier</logbackContextFieldSupplier>
    </encoder>
</appender>

Log4j2 (log4j2.xml)

<JsonPatternLayout>
    <!-- Register a common supplier -->
    <contextFieldSupplier class="com.example.MyCommonSupplier" />
    <!-- Register a Log4j2-specific supplier -->
    <log4jContextFieldSupplier class="com.example.MyLog4j2Supplier" />
</JsonPatternLayout>

Understanding Custom Field Formats & Configuration

The library supports two JSON formats for custom fields, automatically selecting the correct one based on your environment.

Simple (Top-Level) Format

This is the default behavior required by the modern SAP Cloud Logging service. Custom fields are added as top-level key-value pairs, which is intuitive and easy to query.

Example Log:

{
  "written_at": "2023-10-27T12:00:00.000Z",
  "level": "INFO",
  "msg": "Order processed successfully",
  "order_id": "A-12345" 
}

No special configuration is needed to enable this format.

Legacy Nested (#cf) Format — For SAP Application Logging

This format is required by the older SAP BTP Application Logging service. Custom fields are bundled into a special nested JSON object named #cf.

Example Log:

{
    "msg": "Order processed successfully",
    "#cf": {
        "string": [
            {"k": "order_id", "v": "A-12345", "i": 0}
        ]
    }
}

This format is activated automatically if the library detects a binding to the application-logs service. You can also force it by setting the environment variable LOG_GENERATE_APPLICATION_LOGGING_CUSTOM_FIELDS to true.

Configuration for the Legacy Nested (#cf) Format

This configuration is only required if you are targeting the SAP BTP Application Logging service and need to generate the nested #cf format.

You must declare the keys of your custom fields in the logging configuration.

Logback Example (logback.xml)

<encoder class="com.sap.hcp.cf.logback.encoder.JsonEncoder">
    <!-- Fields to be moved into the #cf object -->
    <customFields>order_id,customer_id,payment_method</customFields>
    <!-- Fields to be copied to #cf but also kept at the top level -->
    <retainedFields>tenant_id</retainedFields>
</encoder>

Log4j2 Example (log4j2.xml)

<JsonPatternLayout>
    <!-- This field will be moved into the #cf object -->
    <customField mdcKeyName="order_id" />
    <!-- This field will be in #cf AND at the top level -->
    <customField mdcKeyName="tenant_id" retainOriginal="true" />
</JsonPatternLayout>