Skip to content

Structured logging support#10746

Open
lujop wants to merge 3 commits intodropwizard:release/5.0.xfrom
lujop:feature/structured-logging-json-layout
Open

Structured logging support#10746
lujop wants to merge 3 commits intodropwizard:release/5.0.xfrom
lujop:feature/structured-logging-json-layout

Conversation

@lujop
Copy link

@lujop lujop commented Nov 2, 2025

Add support for SLF4J 2.0 structured logging key-value pairs in EventJsonLayout. This allows developers to add structured data to individual log entries using the addKeyValue() API.

Problem:

Solves issue #9734
Support for SLF4J 2.0 structured logging in JSON Layout.

Solution:

Implement keyValuePairs handling into EventJsonLayout in the same way MDC values are handled.
By default all pairs are logged in nested format but they can be filtered and flattened using configuration parameters.

KEY_VALUE_PAIRS has been added to EventAttribute enum to be able to disable it.

Result:

Given

 logger.atInfo()
        .addKeyValue("userId", userId)
        .addKeyValue("action", "login")
        .addKeyValue("duration", duration)
        .log("User login completed");

by default is rendered into:

  {
    "timestamp": "2024-01-02T15:19:21.000+0000",
    "level": "INFO",
    "message": "User login completed",
    "keyValuePairs": {
      "userId": "12345",
      "action": "login",
      "duration": 150
    }
  }

Add support for SLF4J 2.0 structured logging key-value pairs in
EventJsonLayout. This allows developers to add structured data to
individual log entries using the addKeyValue() API.

Closes dropwizard#9734
@lujop lujop requested a review from a team as a code owner November 2, 2025 22:41
@github-actions github-actions bot added this to the 5.0.1 milestone Nov 2, 2025
@lujop lujop marked this pull request as draft November 5, 2025 20:27
@lujop
Copy link
Author

lujop commented Nov 5, 2025

I left it in draft because I forgot to ask my employer for permission, but I'm currently in the process of getting it.

@lujop lujop marked this pull request as ready for review January 18, 2026 17:22
@lujop
Copy link
Author

lujop commented Jan 18, 2026

Put it again on review. I got the approval, but I didn't do it at the moment and forgot for some time 🙈

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds support for SLF4J 2.0 structured logging key-value pairs in the JSON logging layout. The implementation follows the existing MDC pattern, allowing developers to add structured data to log entries using the addKeyValue() API. By default, key-value pairs are logged in nested format under the keyValuePairs field, but can be filtered and flattened using new configuration parameters.

Changes:

  • Added KEY_VALUE_PAIRS to the EventAttribute enum to allow enabling/disabling this feature
  • Implemented key-value pair handling in EventJsonLayout with filtering and flattening support
  • Added configuration properties includesKeyValuePairsKeys and flattenKeyValuePairs to EventJsonLayoutBaseFactory
  • Enhanced MapBuilder to handle Boolean values and generic Objects with type-aware serialization
  • Added comprehensive test coverage for all new functionality
  • Documented the structured logging feature and configuration options

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
dropwizard-json-logging/src/main/java/io/dropwizard/logging/json/EventAttribute.java Added KEY_VALUE_PAIRS enum value to support the new attribute
dropwizard-json-logging/src/main/java/io/dropwizard/logging/json/layout/EventJsonLayout.java Core implementation: added logic to process, filter, and flatten key-value pairs similar to MDC handling
dropwizard-json-logging/src/main/java/io/dropwizard/logging/json/EventJsonLayoutBaseFactory.java Added configuration properties for key-value pairs (includesKeyValuePairsKeys, flattenKeyValuePairs) and included KEY_VALUE_PAIRS in default attributes
dropwizard-json-logging/src/main/java/io/dropwizard/logging/json/layout/MapBuilder.java Added addBoolean and generic add methods to support proper type serialization of key-value pair values
dropwizard-json-logging/src/test/java/io/dropwizard/logging/json/layout/EventJsonLayoutTest.java Comprehensive test coverage for nested/flattened formats, filtering, null handling, and type conversion
dropwizard-json-logging/src/test/java/io/dropwizard/logging/json/LayoutIntegrationTests.java Integration tests verifying configuration deserialization and end-to-end logging with key-value pairs
dropwizard-json-logging/src/test/resources/yaml/json-log.yml Test configuration demonstrating key-value pairs configuration options
docs/source/manual/core.rst Added "Structured Logging with JSON" section with usage examples and output format examples
docs/source/manual/configuration.rst Updated JSON layout configuration documentation with new key-value pairs settings

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


@Test
void testLogsFlattenedKeyValuePairsFilteringNullValues() {
when(event.getKeyValuePairs()).thenReturn(List.of(new KeyValuePair("test", "value"),new KeyValuePair("testNull", null)));
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma between the two KeyValuePair arguments. Should be: new KeyValuePair("test", "value"), new KeyValuePair("testNull", null)

Copilot uses AI. Check for mistakes.
void testFilterKeyValueKeys() {
final Set<String> includesKeyValuePairsKeys = Set.of("test");
when(event.getKeyValuePairs()).thenReturn(List.of(new KeyValuePair("test", "value"), new KeyValuePair("test2", "value2")));
eventJsonLayout= new EventJsonLayout(jsonFormatter, timestampFormatter, throwableProxyConverter, DEFAULT_EVENT_ATTRIBUTES,
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space before the assignment operator. Should be: eventJsonLayout = instead of eventJsonLayout=

Suggested change
eventJsonLayout= new EventJsonLayout(jsonFormatter, timestampFormatter, throwableProxyConverter, DEFAULT_EVENT_ATTRIBUTES,
eventJsonLayout = new EventJsonLayout(jsonFormatter, timestampFormatter, throwableProxyConverter, DEFAULT_EVENT_ATTRIBUTES,

Copilot uses AI. Check for mistakes.
@Test
void testFilterAndFlattenKeyValueKeys() {
final Set<String> includesKeyValuePairsKeys = Set.of("test");
when(event.getKeyValuePairs()).thenReturn(java.util.List.of(new KeyValuePair("test", "value"), new KeyValuePair("test2", "value2")));
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use List.of consistently instead of the fully qualified java.util.List.of. All other test methods in this file use the shorter form List.of.

Suggested change
when(event.getKeyValuePairs()).thenReturn(java.util.List.of(new KeyValuePair("test", "value"), new KeyValuePair("test2", "value2")));
when(event.getKeyValuePairs()).thenReturn(List.of(new KeyValuePair("test", "value"), new KeyValuePair("test2", "value2")));

Copilot uses AI. Check for mistakes.
}

final boolean includeKeyValuePairs = isIncluded(EventAttribute.KEY_VALUE_PAIRS);
if(includeKeyValuePairs && event.getKeyValuePairs() != null) {
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after if keyword. Should be: if (includeKeyValuePairs instead of if(includeKeyValuePairs

Suggested change
if(includeKeyValuePairs && event.getKeyValuePairs() != null) {
if (includeKeyValuePairs && event.getKeyValuePairs() != null) {

Copilot uses AI. Check for mistakes.
import io.dropwizard.logging.common.DefaultLoggingFactory;
import io.dropwizard.request.logging.LogbackAccessRequestLogFactory;
import io.dropwizard.validation.BaseValidator;
import jdk.jfr.Event;
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import jdk.jfr.Event is unused and should be removed.

Suggested change
import jdk.jfr.Event;

Copilot uses AI. Check for mistakes.

@Test
void testLogsKeyValuePairsFilteringNullValues() {
when(event.getKeyValuePairs()).thenReturn(List.of(new KeyValuePair("test", "value"),new KeyValuePair("testNull", null)));
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma between the two KeyValuePair arguments. Should be: new KeyValuePair("test", "value"), new KeyValuePair("testNull", null)

Copilot uses AI. Check for mistakes.
* Adds the value to the provided map under the provided field name if it should be included.
* If the value is a String, Number, or Boolean it's added as it is, otherwise it's converted to string using `toString()`.
*/
public MapBuilder add(String fieldName, boolean include, @Nullable Object value) {
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method MapBuilder.add(..) could be confused with overloaded method add, since dispatch depends on static types.
Method MapBuilder.add(..) could be confused with overloaded method add, since dispatch depends on static types.
Method MapBuilder.add(..) could be confused with overloaded method add, since dispatch depends on static types.

Copilot uses AI. Check for mistakes.
* Adds the value to the provided map under the provided field name if it should be included.
* If the value is a String, Number, or Boolean it's added as it is, otherwise it's converted to string using `toString()`.
*/
public MapBuilder add(String fieldName, boolean include, @Nullable Object value) {

Check notice

Code scanning / CodeQL

Confusing overloading of methods Note

Method MapBuilder.add(..) could be confused with overloaded method
add
, since dispatch depends on static types.
Method MapBuilder.add(..) could be confused with overloaded method
add
, since dispatch depends on static types.
Method MapBuilder.add(..) could be confused with overloaded method
add
, since dispatch depends on static types.
@joschi joschi modified the milestones: 5.0.1, 5.0.2 Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants