Initial commit

This commit is contained in:
Stephan Schnabel 2020-08-05 21:38:05 +02:00
commit 976b2d27a5
Signed by: stephan.schnabel
GPG key ID: F74FE2422AA07290
7 changed files with 640 additions and 0 deletions

View file

@ -0,0 +1,74 @@
package io.kokuwa.micronaut.logging;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.slf4j.MDC;
import org.slf4j.Marker;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.turbo.TurboFilter;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.spi.FilterReply;
/**
* Filter for log levels based on mdc.
*
* @author Stephan Schnabel
*/
public class MDCTurboFilter extends TurboFilter {
private final String key;
private final Map<String, Boolean> cache = new HashMap<>();
private final Set<String> loggers = new HashSet<>();
private final Set<String> values = new HashSet<>();
private Level level;
public MDCTurboFilter(String name, String key, Context context) {
this.key = key;
this.level = Level.TRACE;
this.setName(name);
this.setContext(context);
}
public MDCTurboFilter setLoggers(Set<String> loggers) {
this.cache.clear();
this.loggers.clear();
this.loggers.addAll(loggers);
return this;
}
public MDCTurboFilter setValues(Set<String> values) {
this.values.clear();
this.values.addAll(values);
return this;
}
public MDCTurboFilter setLevel(Level level) {
this.level = level;
return this;
}
@Override
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
if (logger == null || !isStarted() || values.isEmpty() || loggers.isEmpty()) {
return FilterReply.NEUTRAL;
}
var value = MDC.get(key);
if (value == null || !values.contains(value)) {
return FilterReply.NEUTRAL;
}
var isLoggerIncluded = !cache.computeIfAbsent(logger.getName(), k -> loggers.stream().anyMatch(k::startsWith));
if (isLoggerIncluded) {
return FilterReply.NEUTRAL;
}
return level.isGreaterOrEqual(this.level) ? FilterReply.ACCEPT : FilterReply.NEUTRAL;
}
}

View file

@ -0,0 +1,86 @@
package io.kokuwa.micronaut.logging;
import java.util.Objects;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import io.micronaut.context.annotation.BootstrapContextCompatible;
import io.micronaut.context.annotation.Context;
import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.type.Argument;
import io.micronaut.runtime.context.scope.refresh.RefreshEvent;
/**
* Configure mdc filter.
*
* @author Stephan Schnabel
*/
@BootstrapContextCompatible
@Context
@Requires(classes = LoggerContext.class)
@Requires(property = MDCTurboFilterConfigurer.LOGGER_MDCS_PROPERTY_PREFIX)
@Internal
public class MDCTurboFilterConfigurer implements ApplicationEventListener<RefreshEvent> {
public static final String LOGGER_MDCS_PROPERTY_PREFIX = "logger.mdc";
private static final Logger log = LoggerFactory.getLogger(MDCTurboFilterConfigurer.class);
private final LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
private final Environment environment;
public MDCTurboFilterConfigurer(Environment environment) {
this.environment = environment;
configure();
}
@Override
public void onApplicationEvent(RefreshEvent event) {
if (event.getSource().keySet().stream().anyMatch(key -> key.startsWith(LOGGER_MDCS_PROPERTY_PREFIX))) {
configure();
}
}
private void configure() {
for (var name : environment.getPropertyEntries(LOGGER_MDCS_PROPERTY_PREFIX)) {
var prefix = LOGGER_MDCS_PROPERTY_PREFIX + "." + name + ".";
var key = environment.getProperty(prefix + "key", String.class, name);
var loggers = environment.getProperty(prefix + "loggers", Argument.setOf(String.class)).orElseGet(Set::of);
var values = environment.getProperty(prefix + "values", Argument.setOf(String.class)).orElseGet(Set::of);
var level = Level.valueOf(environment.getProperty(prefix + "level", String.class, Level.TRACE.toString()));
getFilter(name, key).setLoggers(loggers).setValues(values).setLevel(level);
log.info("Configured MDC filter {} for key {} with level {}.", name, key, level);
}
}
private MDCTurboFilter getFilter(String name, String key) {
// get filter
var filterName = LOGGER_MDCS_PROPERTY_PREFIX + "." + key;
var filterOptional = context.getTurboFilterList().stream()
.filter(f -> Objects.equals(filterName, f.getName()))
.filter(MDCTurboFilter.class::isInstance)
.map(MDCTurboFilter.class::cast)
.findAny();
if (filterOptional.isPresent()) {
return filterOptional.get();
}
// add filter
var filter = new MDCTurboFilter(name, key, context);
filter.start();
context.addTurboFilter(filter);
return filter;
}
}

View file

@ -0,0 +1,72 @@
package io.kokuwa.micronaut.logging;
import java.time.Instant;
import java.util.HashMap;
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.
*
* @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 {
private static final String TIMESTAMP_ATTR_NAME = "time";
private static final String SEVERITY_ATTR_NAME = "severity";
private Map<String, String> serviceContext;
private String serviceName;
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(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);
add(LOGGER_ATTR_NAME, includeLoggerName, event.getLoggerName(), map);
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);
addServiceContext(map);
return map;
}
private void addServiceContext(Map<String, Object> map) {
if (serviceContext == null) {
serviceContext = new HashMap<>(2);
if (StringUtils.isNotEmpty(serviceName)) {
serviceContext.put("service", serviceName);
}
if (StringUtils.isNotEmpty(serviceVersion)) {
serviceContext.put("version", serviceVersion);
}
}
if (!serviceContext.isEmpty()) {
map.put("serviceContext", serviceContext);
}
}
}