Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 108 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,123 @@
[![Discord](https://img.shields.io/discord/1074074312421683250?color=%237289da&label=discord)](https://discord.gg/Qcqf9R27BR)

# avaje-logback-encoder
logback encoder that uses avaje-jsonb to log events as json
Logback encoder that log events as json (similar to Logstash).

## Usage
Example:
```json
{"timestamp":"2025-01-10T14:47:42.313+13:00","level":"INFO","logger":"org.example.Foo","message":"Hi","thread":"main"}
```

Example with component and environment:
```json
{"component":"my-component","env":"DEV","timestamp":"2025-01-10T14:47:42.313+13:00","level":"INFO","logger":"org.example.Foo","message":"Hi","thread":"main"}
```

## Fields
#### Standard Fields
- `timestamp`
- `level`
- `logger`
- `message`
- `thread`
- `stacktrace`

#### MDC Fields
MDC key/values are included in logged events.

#### Custom Fields
Extra Custom fields can be declared in JSON form, these are added to all logged events.

#### Extra recommended Fields
- `component` - Use to define the "component" (approximately application or a specific component of an application)
- `env` - Use to define the "environment" such as dev, test, prod etc

These default by reading System Environment variables `COMPONENT` and `ENVIRONMENT` respectively and
can also be explicitly set via configuration.



# How to use

#### 1 - Add dependency

For Java 11+ and Logback 1.2.x+ use version `1.0` of the dependency:
```xml
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-logback-encoder</artifactId>
<version>1.0</version>
</dependency>
```

Add the encoder to your appender
For Java 8 and Logback 1.1.x use version `1.0-java8` of the dependency.
```xml
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-logback-encoder</artifactId>
<version>1.0-java8</version>
</dependency>
```

#### 2 - Use the Encoder in logback.xml

In `logback.xml` specify JsonEncoder as the encoder like:
```xml
<appender name="app" class="your.appender.class">
<encoder class="io.avaje.logback.encoder.JsonEncoder"/>
</appender>
```

Optionally, configure with `component` and `environment` like:
```xml
<appender name="app" class="your.appender.class">
<encoder class="io.avaje.logback.encoder.JsonEncoder">
<-- configuration -->
</encoder>
<encoder class="io.avaje.logback.encoder.JsonEncoder">
<component>my-component</component> <!-- OPTIONAL -->
<enviroment>dev</enviroment> <!-- OPTIONAL -->
</encoder>
</appender>
```

### JPMS Use
Optionally specify custom fields that will appear in every LoggingEvent like:
```xml
<encoder class="io.avaje.logback.encoder.JsonEncoder">
<customFields>{"appname":"myWebservice","roles":["orders","auth"]}</customFields>
</encoder>
```

#### AWS Lambda / StdOutAppender

For AWS Lambda log events are written to `System.out` and so this also provides
an appender that defaults to using JsonEncoder to write log events to `System.out`.
We can specify to use that appender like:

```xml
<!-- Defaults to using the JsonEncoder -->
<appender class="io.avaje.logback.encoder.StdOutAppender"/>
```

Or with configuration options for component and environment like:

```xml
<appender class="io.avaje.logback.encoder.StdOutAppender">
<component>my-foo</component>
</appender>
```

Or with an encoder (potentially a different encoder):

```xml
<appender class="io.avaje.logback.encoder.StdOutAppender">
<encoder class="io.avaje.logback.encoder.JsonEncoder">
<component>my-foo</component>
<environment>prod</environment>
<customFields>{"appname":"myWebservice","buildinfo":42, "roles":["customerorder","auth"],"f":true}</customFields>
</encoder>
</appender>
```


## Java modules
To ensure `jlink` correctly determines the runtime modules required, add the following to your `module-info.java`:

```java
Expand All @@ -28,18 +129,7 @@ module my.module {
}
```

## Global Custom Fields

Add custom fields that will appear in every LoggingEvent like this :

```xml

<encoder class="io.avaje.logback.encoder.JsonEncoder">
<customFields>{"appname":"myWebservice","roles":["customerorder","auth"],"buildinfo":{"version":"Version
0.1.0-SNAPSHOT","lastcommit":"75473700d5befa953c45f630c6d9105413c16fe1"}}
</customFields>
</encoder>
```

## Customizing Timestamp

Expand All @@ -50,7 +140,6 @@ By default, timestamps are written as string values in the format specified by
You can change the pattern like this:

```xml

<encoder class="io.avaje.logback.encoder.JsonEncoder">
<timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSS</timestampPattern>
</encoder>
Expand All @@ -64,7 +153,6 @@ The value of the `timestampPattern` can be any of the following:
The formatter uses the default TimeZone of the host Java platform by default. You can change it like this:

```xml

<encoder class="io.avaje.logback.encoder.JsonEncoder">
<timeZone>UTC</timeZone>
</encoder>
Expand Down
42 changes: 0 additions & 42 deletions src/main/java/io/avaje/logback/encoder/AwsLambdaAppender.java

This file was deleted.

65 changes: 65 additions & 0 deletions src/main/java/io/avaje/logback/encoder/StdOutAppender.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.avaje.logback.encoder;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.encoder.Encoder;

import java.io.IOException;

/**
* Appender that writes to STDOUT that defaults to using JsonEncoder.
*/
public final class StdOutAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

private Encoder<ILoggingEvent> encoder;

public StdOutAppender() {
this.encoder = new JsonEncoder();
}

@Override
protected void append(ILoggingEvent event) {
try {
System.out.write(encoder.encode(event));
} catch (IOException e) {
// NOTE: When actually running on AWS Lambda, an IOException would never happen
e.printStackTrace();
}
}

@Override
public void start() {
encoder.start();
super.start();
}

/**
* Change the encoder from the default JsonEncoder.
*/
public void setEncoder(Encoder<ILoggingEvent> encoder) {
this.encoder = encoder;
}

/**
* Set the component on an underlying JsonEncoder otherwise throw IllegalStateException.
*/
public void setComponent(String component) {
if (encoder instanceof JsonEncoder) {
((JsonEncoder) encoder).setComponent(component);
} else {
throw new IllegalStateException("Can only set component when using JsonEncoder");
}
}

/**
* Set the environment on an underlying JsonEncoder otherwise throw IllegalStateException.
*/
public void setEnvironment(String environment) {
if (encoder instanceof JsonEncoder) {
((JsonEncoder) encoder).setEnvironment(environment);
} else {
throw new IllegalStateException("Can only set environment when using JsonEncoder");
}
}

}
4 changes: 3 additions & 1 deletion src/test/java/io/avaje/logback/encoder/JsonEncoderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ void throwable_usingConverter() {

@Test
void awsAppender() {
AwsLambdaAppender appender = new AwsLambdaAppender();
StdOutAppender appender = new StdOutAppender();
appender.setComponent("my-other");
appender.setEnvironment("localdev");
appender.start();

appender.append(createLogEvent());
Expand Down
Loading