Add jackson layout, rename stackdriver, include no logback.xml.
This commit is contained in:
parent
45c5a0a4c5
commit
5680a281ea
17 changed files with 246 additions and 65 deletions
38
README.md
38
README.md
|
@ -2,10 +2,35 @@
|
|||
|
||||
## Features
|
||||
|
||||
### Default logback.xml
|
||||
|
||||
If no `logback.xml` by user is provided a default [logback.xml](src/main/resources/io/kokuwa/logback/logback-default.xml) is loaded. Otherwise use custom [logback.xml](src/main/resources/io/kokuwa/logback/logback-example.xml):
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="true" scan="false">
|
||||
<include resource="io/kokuwa/logback/base.xml" />
|
||||
<logger name="io.micronaut.logging.PropertiesLoggingLevelsConfigurer" levels="WARN" />
|
||||
<root level="INFO">
|
||||
<appender-ref ref="${LOGBACK_APPENDER:-CONSOLE}" />
|
||||
</root>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
### Available Appender
|
||||
|
||||
* console format
|
||||
* Stackdriver format (with support for error reporting)
|
||||
* console with jansi for developers
|
||||
* gcp logging format (with support for error reporting)
|
||||
* json
|
||||
|
||||
### AutoSelect appender logback.xml
|
||||
|
||||
1. if `LOGBACK_APPENDER` is set this appender will be used
|
||||
2. if GCP is detected gcp appender will be used
|
||||
3. if Kubernetes is detected json appender will be used
|
||||
4. console appender else
|
||||
|
||||
*IMPORTENT*: only works without custom `logback.xml`
|
||||
|
||||
### Set log level based on MDC values
|
||||
|
||||
|
@ -72,14 +97,14 @@ logger:
|
|||
Configuration for server filter (prefixed with *logger.request.filter*):
|
||||
|
||||
* *enabled*: enable HTTP server filter (`true` is default)
|
||||
* *order*: order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/master/core/src/main/java/io/micronaut/core/order/Ordered.java) (highest is default)
|
||||
* *order*: order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/v2.5.13/core/src/main/java/io/micronaut/core/order/Ordered.java) (highest is default)
|
||||
* *path*: filter path (`/**` is default)
|
||||
* *header*: name of HTTP header (`x-log-level` is default)
|
||||
|
||||
Configuration for client filter for propagation (prefixed with *logger.request.propagation*):
|
||||
|
||||
* *enabled*: enable HTTP client filter (`true` is default)
|
||||
* *order*: order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/master/core/src/main/java/io/micronaut/core/order/Ordered.java) (tracing is default)
|
||||
* *order*: order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/v2.5.13/core/src/main/java/io/micronaut/core/order/Ordered.java) (tracing is default)
|
||||
* *path*: filter path (`/**` is default)
|
||||
* *header*: name of HTTP header (server header is default)
|
||||
|
||||
|
@ -105,7 +130,7 @@ logger:
|
|||
Configuration:
|
||||
|
||||
* *enabled*: enable HTTP principal filter (`true` is default)
|
||||
* *order*: order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/master/core/src/main/java/io/micronaut/core/order/Ordered.java) ([ServerFilterPhase.SECURITY.after()](https://github.com/micronaut-projects/micronaut-core/blob/v2.0.1/http/src/main/java/io/micronaut/http/filter/ServerFilterPhase.java#L54) is default)
|
||||
* *order*: order for [Ordered](https://github.com/micronaut-projects/micronaut-core/blob/v2.5.13/core/src/main/java/io/micronaut/core/order/Ordered.java) ([ServerFilterPhase.SECURITY.after()](https://github.com/micronaut-projects/micronaut-core/blob/v2.5.13/http/src/main/java/io/micronaut/http/filter/ServerFilterPhase.java#L54) is default)
|
||||
* *path*: filter path (`/**` is default)
|
||||
* *key*: name of MDC header (`principal` is default)
|
||||
|
||||
|
@ -148,6 +173,5 @@ mvn release:prepare release:perform release:clean -B -DreleaseProfiles=oss-relea
|
|||
## Open Topics
|
||||
|
||||
* configure mdc on refresh event
|
||||
* add stackdriver per configuration
|
||||
* add fluent per configuration
|
||||
* read **serviceName** and **serviceVersion** from yaml
|
||||
* support auto select appender with custom `logback.xml`
|
||||
|
|
6
pom.xml
6
pom.xml
|
@ -130,12 +130,6 @@
|
|||
</dependencies>
|
||||
|
||||
<build>
|
||||
<testResources>
|
||||
<testResource>
|
||||
<directory>src/test/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</testResource>
|
||||
</testResources>
|
||||
<plugins>
|
||||
|
||||
<!-- add compiler processors -->
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package io.kokuwa.micronaut.logging.configurator;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.Configurator;
|
||||
import ch.qos.logback.core.joran.spi.JoranException;
|
||||
import ch.qos.logback.core.spi.ContextAwareBase;
|
||||
|
||||
/**
|
||||
* Use logback-default.xml if no configuration is provided by user.
|
||||
*
|
||||
* @author Stephan Schnabel
|
||||
*/
|
||||
public class DefaultConfigurator extends ContextAwareBase implements Configurator {
|
||||
|
||||
@Override
|
||||
public void configure(LoggerContext loggerContext) {
|
||||
|
||||
var base = DefaultConfigurator.class.getResource("/io/kokuwa/logback/logback-default.xml");
|
||||
if (base == null) {
|
||||
addError("Failed to find logback.xml from io.kokuwa:micronaut-logging");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
addInfo("Use logback.xml from io.kokuwa:micronaut-logging");
|
||||
var configurator = new MicronautJoranConfigurator();
|
||||
configurator.setContext(loggerContext);
|
||||
configurator.doConfigure(base);
|
||||
} catch (JoranException e) {
|
||||
addError("Failed to load logback.xml from io.kokuwa:micronaut-logging", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package io.kokuwa.micronaut.logging.configurator;
|
||||
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||
import ch.qos.logback.core.joran.spi.ElementSelector;
|
||||
import ch.qos.logback.core.joran.spi.RuleStore;
|
||||
|
||||
/**
|
||||
* Add custom actions.
|
||||
*
|
||||
* @author Stephan Schnabel
|
||||
*/
|
||||
public class MicronautJoranConfigurator extends JoranConfigurator {
|
||||
|
||||
@Override
|
||||
public void addInstanceRules(RuleStore rs) {
|
||||
super.addInstanceRules(rs);
|
||||
rs.addRule(new ElementSelector("configuration/root/autoAppender"), new RootAutoSelectAppenderAction());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package io.kokuwa.micronaut.logging.configurator;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
import ch.qos.logback.core.joran.action.Action;
|
||||
import ch.qos.logback.core.joran.action.ActionConst;
|
||||
import ch.qos.logback.core.joran.spi.InterpretationContext;
|
||||
import io.micronaut.core.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Auto select appender by environment.
|
||||
*
|
||||
* @author Stephan Schnabel
|
||||
*/
|
||||
public class RootAutoSelectAppenderAction extends Action {
|
||||
|
||||
private static final boolean IS_KUBERNETES = StringUtils.isNotEmpty(System.getenv("KUBERNETES_SERVICE_HOST"));
|
||||
private static final boolean IS_GCP = StringUtils.isNotEmpty(System.getenv("GOOGLE_CLOUD_PROJECT"));
|
||||
|
||||
private static final String APPENDER_CONSOLE = "CONSOLE";
|
||||
private static final String APPENDER_JSON = "JSON";
|
||||
private static final String APPENDER_GCP = "GCP";
|
||||
private static final String LOGBACK_APPENDER = "LOGBACK_APPENDER";
|
||||
|
||||
@Override
|
||||
public void begin(InterpretationContext ic, String name, Attributes attributes) {
|
||||
|
||||
var rootLogger = LoggerContext.class.cast(context).getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
|
||||
var rootLoggerAppenders = rootLogger.iteratorForAppenders();
|
||||
if (rootLoggerAppenders.hasNext()) {
|
||||
addWarn("Skip because appender already found: " + rootLoggerAppenders.next().getName());
|
||||
return;
|
||||
}
|
||||
|
||||
var envAppender = System.getenv(LOGBACK_APPENDER);
|
||||
if (envAppender != null && setAppender(ic, rootLogger, envAppender)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IS_KUBERNETES && setAppender(ic, rootLogger, APPENDER_JSON)) {
|
||||
return;
|
||||
}
|
||||
if (IS_GCP && setAppender(ic, rootLogger, APPENDER_GCP)) {
|
||||
return;
|
||||
}
|
||||
setAppender(ic, rootLogger, APPENDER_CONSOLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end(InterpretationContext ic, String name) {}
|
||||
|
||||
private boolean setAppender(InterpretationContext ic, Logger rootLogger, String appenderName) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
var appenderBag = (Map<String, Appender<ILoggingEvent>>) ic.getObjectMap().get(ActionConst.APPENDER_BAG);
|
||||
var appender = appenderBag.get(appenderName);
|
||||
if (appender == null) {
|
||||
addError("Could not find an appender named [" + appenderName
|
||||
+ "]. Did you define it below instead of above in the configuration file?");
|
||||
return false;
|
||||
}
|
||||
|
||||
addInfo("Use appender: " + appenderName);
|
||||
rootLogger.addAppender(appender);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.kokuwa.micronaut.logging;
|
||||
package io.kokuwa.micronaut.logging.layout;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
|
@ -6,43 +6,33 @@ import java.util.LinkedHashMap;
|
|||
import java.util.Map;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.contrib.jackson.JacksonJsonFormatter;
|
||||
import ch.qos.logback.contrib.json.classic.JsonLayout;
|
||||
import io.micronaut.core.util.StringUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Stackdriver layout.
|
||||
* GCP logging layout.
|
||||
*
|
||||
* @author Stephan Schnabel
|
||||
* @see "https://cloud.google.com/logging/docs/agent/configuration#process-payload"
|
||||
* @see "https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext"
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class StackdriverJsonLayout extends JsonLayout {
|
||||
public class GcpJsonLayout extends JsonLayout {
|
||||
|
||||
private static final String TIMESTAMP_ATTR_NAME = "time";
|
||||
private static final String UNDEFINED = "_IS_UNDEFINED";
|
||||
private static final String TIME_ATTR_NAME = "time";
|
||||
private static final String SEVERITY_ATTR_NAME = "severity";
|
||||
|
||||
private Map<String, String> serviceContext;
|
||||
@Setter
|
||||
private String serviceName;
|
||||
@Setter
|
||||
private String serviceVersion;
|
||||
private boolean includeExceptionInMessage;
|
||||
|
||||
public StackdriverJsonLayout() {
|
||||
appendLineSeparator = true;
|
||||
includeContextName = false;
|
||||
includeMessage = true;
|
||||
includeExceptionInMessage = true;
|
||||
setJsonFormatter(new JacksonJsonFormatter());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> toJsonMap(ILoggingEvent event) {
|
||||
var map = new LinkedHashMap<String, Object>();
|
||||
add(TIMESTAMP_ATTR_NAME, includeTimestamp, Instant.ofEpochMilli(event.getTimeStamp()).toString(), map);
|
||||
add(TIME_ATTR_NAME, includeTimestamp, Instant.ofEpochMilli(event.getTimeStamp()).toString(), map);
|
||||
add(SEVERITY_ATTR_NAME, includeLevel, String.valueOf(event.getLevel()), map);
|
||||
add(THREAD_ATTR_NAME, includeThreadName, event.getThreadName(), map);
|
||||
add(CONTEXT_ATTR_NAME, includeContextName, event.getLoggerContextVO().getName(), map);
|
||||
|
@ -50,7 +40,7 @@ public class StackdriverJsonLayout extends JsonLayout {
|
|||
addMap(MDC_ATTR_NAME, includeMDC, event.getMDCPropertyMap(), map);
|
||||
add(FORMATTED_MESSAGE_ATTR_NAME, includeFormattedMessage, event.getFormattedMessage(), map);
|
||||
add(MESSAGE_ATTR_NAME, includeMessage, event.getMessage(), map);
|
||||
addThrowableInfo(JsonLayout.EXCEPTION_ATTR_NAME, includeException, event, map);
|
||||
addThrowableInfo(EXCEPTION_ATTR_NAME, includeException, event, map);
|
||||
addServiceContext(map);
|
||||
return map;
|
||||
}
|
||||
|
@ -58,10 +48,10 @@ public class StackdriverJsonLayout extends JsonLayout {
|
|||
private void addServiceContext(Map<String, Object> map) {
|
||||
if (serviceContext == null) {
|
||||
serviceContext = new HashMap<>(2);
|
||||
if (StringUtils.isNotEmpty(serviceName)) {
|
||||
if (StringUtils.isNotEmpty(serviceName) && !serviceName.endsWith(UNDEFINED)) {
|
||||
serviceContext.put("service", serviceName);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(serviceVersion)) {
|
||||
if (StringUtils.isNotEmpty(serviceVersion) && !serviceVersion.endsWith(UNDEFINED)) {
|
||||
serviceContext.put("version", serviceVersion);
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
io.kokuwa.micronaut.logging.configurator.DefaultConfigurator
|
12
src/main/resources/io/kokuwa/logback/appender-console.xml
Normal file
12
src/main/resources/io/kokuwa/logback/appender-console.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<withJansi>${CONSOLE_LOG_JANSI:-true}</withJansi>
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN:-%cyan(%d{HH:mm:ss.SSS}) %gray(%-6.6thread) %highlight(%-5level) %magenta(%32logger{32}) %mdc %msg%n}</pattern>
|
||||
<charset>${CONSOLE_LOG_CHARSET:-default}</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
</included>
|
17
src/main/resources/io/kokuwa/logback/appender-gcp.xml
Normal file
17
src/main/resources/io/kokuwa/logback/appender-gcp.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
|
||||
<appender name="GCP" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
|
||||
<layout class="io.kokuwa.micronaut.logging.layout.GcpJsonLayout">
|
||||
<serviceName>${serviceName}</serviceName>
|
||||
<serviceVersion>${serviceVersion}</serviceVersion>
|
||||
<jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter" />
|
||||
<appendLineSeparator>true</appendLineSeparator>
|
||||
<includeMessage>true</includeMessage>
|
||||
<includeContextName>false</includeContextName>
|
||||
</layout>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
</included>
|
15
src/main/resources/io/kokuwa/logback/appender-json.xml
Normal file
15
src/main/resources/io/kokuwa/logback/appender-json.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
|
||||
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
|
||||
<layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
|
||||
<jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter" />
|
||||
<appendLineSeparator>true</appendLineSeparator>
|
||||
<includeMessage>true</includeMessage>
|
||||
<includeContextName>false</includeContextName>
|
||||
</layout>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
</included>
|
8
src/main/resources/io/kokuwa/logback/base.xml
Normal file
8
src/main/resources/io/kokuwa/logback/base.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<included>
|
||||
|
||||
<include resource="io/kokuwa/logback/appender-console.xml" />
|
||||
<include resource="io/kokuwa/logback/appender-json.xml" />
|
||||
<include resource="io/kokuwa/logback/appender-gcp.xml" />
|
||||
|
||||
</included>
|
12
src/main/resources/io/kokuwa/logback/logback-default.xml
Normal file
12
src/main/resources/io/kokuwa/logback/logback-default.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="false" scan="false">
|
||||
|
||||
<include resource="io/kokuwa/logback/base.xml" />
|
||||
|
||||
<logger name="io.micronaut.logging.PropertiesLoggingLevelsConfigurer" levels="WARN" />
|
||||
|
||||
<root level="INFO">
|
||||
<autoAppender />
|
||||
</root>
|
||||
|
||||
</configuration>
|
12
src/main/resources/io/kokuwa/logback/logback-example.xml
Normal file
12
src/main/resources/io/kokuwa/logback/logback-example.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="true" scan="false">
|
||||
|
||||
<include resource="io/kokuwa/logback/base.xml" />
|
||||
|
||||
<logger name="io.micronaut.logging.PropertiesLoggingLevelsConfigurer" levels="WARN" />
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="${LOGBACK_APPENDER:-CONSOLE}" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
|
@ -1,25 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<property resource="META-INF/build-info.properties" />
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<withJansi>true</withJansi>
|
||||
<encoder>
|
||||
<pattern>%cyan(%d{HH:mm:ss.SSS}) %gray(%-6.6thread) %highlight(%-5level) %magenta(%32logger{32}) %mdc %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="STACKDRIVER" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
|
||||
<layout class="io.kokuwa.micronaut.logging.StackdriverJsonLayout">
|
||||
<serviceName>${serviceName}</serviceName>
|
||||
<serviceVersion>${serviceVersion}</serviceVersion>
|
||||
</layout>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="${LOGBACK_APPENDER:-CONSOLE}" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
|
@ -39,6 +39,8 @@ public class TestController {
|
|||
level = Level.ERROR;
|
||||
}
|
||||
|
||||
log.info("Test log for MDC inclusion, expected: {}", principal);
|
||||
|
||||
return new TestResponse(level.toString(), principal);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
serviceName: ${project.artifactId}
|
||||
serviceVersion: ${project.version}
|
|
@ -10,7 +10,3 @@ micronaut:
|
|||
http:
|
||||
client:
|
||||
logger-name: io.kokuwa.Test
|
||||
|
||||
logger:
|
||||
levels:
|
||||
io.kokuwa.Test: TRACE
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue