Skip to content

Commit

Permalink
KVP is added
Browse files Browse the repository at this point in the history
  • Loading branch information
nehaev committed Dec 17, 2024
1 parent 8d53140 commit 59a1379
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 0 deletions.
6 changes: 6 additions & 0 deletions docs/docus/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ This layout has the following settings:
|format.message.loggerName.targetLength|-1|The desired target length of logger name: `-1` to disable abbreviation, `0` to print class name only, >`0` to abbreviate to the target length|
|format.message.logLevel.enabled|true|Enable logLevel provider|
|format.message.logLevel.fieldName|level|A JSON field name to use for logLevel|
|format.message.kvp.enabled|true|Enable keyValuePair provider|
|format.message.kvp.prefix|kvp_|A prefix added to each JSON field name written by this provider|
|format.message.kvp.noPrefix|false|Whether to omit prefix for this provider|
|format.message.kvp.fieldSerializer||An implementation of field JSON serializer. By default, `writeObjectField()` is used|
|format.message.kvp.include||A set of keys to include in JSON payload. If not specified, all keys are included|
|format.message.kvp.exclude||A set of keys to exclude from JSON payload. The exclude list has precedence over the include list. If not specified, all keys are included|
|format.message.mdc.enabled|true|Enable MDC provider|
|format.message.mdc.prefix|mdc_|A prefix added to each JSON field name written by this provider|
|format.message.mdc.noPrefix|false|Whether to omit prefix for this provider|
Expand Down
114 changes: 114 additions & 0 deletions docs/docus/docs/jsonlayout.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,117 @@ You can add as many custom providers as you want:
<customProvider class="io.my.ConstantProvider3" />
</message>
```

For more insights on how `JsonEventWriter` works, please check the next section.

## Serializing an arbitrary object

Our recommendation is to use plain structure of the log message (i.e., no nested objects), and only some basic types for fields (e.g.: string, number, boolean).
Following this recommendation will give you both maximum performance and simplicity.

However, Logback allows you to attach arbitrary objects to you log record as key-value pairs:

```java
log.atInfo().setMessage("Test KVP")
.addKeyValue("flow", "ingest")
.addKeyValue("msgId", 123456)
.addKeyValue("bool", true)
.addKeyValue("list", List.of(0, 1, 2))
.addKeyValue("date", ZonedDateTime.now())
.addKeyValue("obj", new TestJsonKvData(1001L, "admin", UUID.randomUUID()))
.log();
```

Loki4j uses fast and compact JSON serialization algorithm that is [3.5x](https://github.com/loki4j/loki-logback-appender/pull/210#issue-2113540494) faster than logstash-logback-encoder backed by jackson.
But that speed comes with the cost: no runtime reflection is used, no complex type conversion is implemented.
Which means we don't provide a built-in mechanism to serialize arbitrary object into JSON right away.
Instead, we offer you several options, so you can choose the one that better fits your use case.

Loki4j has `KeyValuePairsJsonProvider`, that by default uses `writeObjectField()` for all key-value pairs.

#### Default approach: `writeObjectField()`

Method `writeObjectField()` in class `JsonEventWriter` has the following logic:

- if value is `String`, it's rendered as JSON string: `"flow":"ingest"`, similar to `writeStringField()`;
- if value is `Integer` or `Long`, it's rendered as JSON number: `"msgId":123456`, similar to `writeNumericField()`;
- if value is `Boolean`, it's rendered as JSON boolean: `"bool":true`;
- if value is `Iterable`, it's rendered as JSON array, `writeObjectField()` to render each element: `"list":[0,1,2]`, similar to `writeArrayField()`;
- if value is `RawJsonString`, its value is rendered as raw JSON without any escaping (see the section below), similar to `writeRawJsonField()`;
- for any other type of value, the result of `toString()` is rendered as JSON string: `"obj":"TestJsonKvData@1de5f259"`.

#### Direct access to `JsonEventWriter`

If you write a custom provider, `JsonEventWriter` is exposed to you directly.
You can also configure `KeyValuePairsJsonProvider` to intercept writer calls as well by setting `fieldSerializer`:

```xml
<message class="com.github.loki4j.logback.JsonLayout">
...
<kvp>
<fieldSerializer class="io.my.TestFieldSerializer" />
</kvp>
</message>
```

Your custom serializer must implement interface `JsonFieldSerializer<Object>`:

```java
import com.github.loki4j.logback.json.JsonEventWriter;
import com.github.loki4j.logback.json.JsonFieldSerializer;

public class TestFieldSerializer implements JsonFieldSerializer<Object> {
@Override
public void writeField(JsonEventWriter writer, String fieldName, Object fieldValue) {
if (fieldValue instanceof TestJsonKvData) {
writer.writeCustomField(fieldName, w -> {
var td = (TestJsonKvData)fieldValue;
w.writeBeginObject();
w.writeObjectField("userId", td.userId);
w.writeFieldSeparator();
w.writeObjectField("userName", td.userName);
w.writeFieldSeparator();
w.writeObjectField("sessionId", td.sessionId);
w.writeEndObject();
}
);
} else {
writer.writeObjectField(fieldName, fieldValue);
}
}
}
```

Instead of `"obj":"TestJsonKvData@1de5f259"`, now you will see `"obj":{"userId":1001,"userName":"admin","sessionId":"92a26b23-9f10-47d8-bb0a-df2b9b15c374"}`.

#### `RawJsonString`

If for some reason you can not statically define serialization algorithm for your objects, you can use reflection-based frameworks and put the resulting string into `RawJsonString`.
Writer will render it as is, without any escaping.

```java
import com.github.loki4j.logback.json.RawJsonString;

public class TestFieldSerializer implements JsonFieldSerializer<Object> {
@Override
public void writeField(JsonEventWriter writer, String fieldName, Object fieldValue) {
writer.writeObjectField(fieldName, new RawJsonString(MyJson.serialize(fieldValue)));
// or
writer.writeRawJsonField(fieldName, MyJson.serialize(fieldValue));
}
}
```

It is your responsibility to make sure that you always put valid JSON into `RawJsonString`.

#### Custom message layout

You can use any implementation of `Layout<ILoggingEvent>` as a message layout, including third-party JSON layouts:

```xml
<message class="net.logstash.logback.layout.LoggingEventCompositeJsonLayout">
<!-- your logstash configuration goes here -->
</message>
```

Please note that Loki4j provides *no support* for any third-party components.

0 comments on commit 59a1379

Please sign in to comment.