Add config options to add authentication attributes as mdc.
This commit is contained in:
parent
a350698f52
commit
7ad1ee0add
32 changed files with 964 additions and 541 deletions
|
@ -1,5 +1,6 @@
|
|||
package io.kokuwa.micronaut.logging.configurator;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.Configurator;
|
||||
import ch.qos.logback.core.joran.spi.JoranException;
|
||||
|
@ -29,5 +30,7 @@ public class DefaultConfigurator extends ContextAwareBase implements Configurato
|
|||
} catch (JoranException e) {
|
||||
addError("Failed to load logback.xml from io.kokuwa:micronaut-logging", e);
|
||||
}
|
||||
|
||||
loggerContext.getLogger("io.micronaut.logging.PropertiesLoggingLevelsConfigurer").setLevel(Level.WARN);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package io.kokuwa.micronaut.logging.http;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import io.micronaut.core.async.publisher.Publishers;
|
||||
import io.micronaut.http.HttpRequest;
|
||||
import io.micronaut.http.MutableHttpResponse;
|
||||
import io.micronaut.http.filter.HttpServerFilter;
|
||||
import io.micronaut.http.filter.ServerFilterChain;
|
||||
|
||||
/**
|
||||
* Base for all MDC related http filters.
|
||||
*
|
||||
* @author Stephan Schnabel
|
||||
*/
|
||||
public abstract class AbstractMdcFilter implements HttpServerFilter {
|
||||
|
||||
private final int order;
|
||||
|
||||
public AbstractMdcFilter(Integer order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
protected Publisher<MutableHttpResponse<?>> doFilter(
|
||||
HttpRequest<?> request,
|
||||
ServerFilterChain chain,
|
||||
Map<String, String> mdc) {
|
||||
|
||||
if (mdc.isEmpty()) {
|
||||
return chain.proceed(request);
|
||||
}
|
||||
|
||||
mdc.forEach(MDC::put);
|
||||
return Publishers.map(chain.proceed(request), response -> {
|
||||
mdc.keySet().forEach(MDC::remove);
|
||||
return response;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.kokuwa.micronaut.logging.request;
|
||||
package io.kokuwa.micronaut.logging.http.level;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
@ -13,30 +13,29 @@ import io.micronaut.http.annotation.Filter;
|
|||
import io.micronaut.http.context.ServerRequestContext;
|
||||
import io.micronaut.http.filter.ClientFilterChain;
|
||||
import io.micronaut.http.filter.HttpClientFilter;
|
||||
import io.micronaut.http.filter.ServerFilterPhase;
|
||||
|
||||
/**
|
||||
* Http request logging filter.
|
||||
* Propagates log-level from server request to client.
|
||||
*
|
||||
* @author Stephan Schnabel
|
||||
*/
|
||||
@Requires(beans = HeaderLoggingServerHttpFilter.class)
|
||||
@Requires(property = HeaderLoggingClientHttpFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
|
||||
@Filter("${" + HeaderLoggingClientHttpFilter.PREFIX + ".path:/**}")
|
||||
public class HeaderLoggingClientHttpFilter implements HttpClientFilter {
|
||||
@Requires(beans = LogLevelServerFilter.class)
|
||||
@Requires(property = LogLevelClientFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
|
||||
@Filter("${" + LogLevelClientFilter.PREFIX + ".path:/**}")
|
||||
public class LogLevelClientFilter implements HttpClientFilter {
|
||||
|
||||
public static final String PREFIX = "logger.request.propagation";
|
||||
public static final int DEFAULT_ORDER = ServerFilterPhase.TRACING.order();
|
||||
public static final String PREFIX = "logger.http.level.propagation";
|
||||
public static final int DEFAULT_ORDER = HIGHEST_PRECEDENCE;
|
||||
|
||||
private final String serverHeader;
|
||||
private final String propagationHeader;
|
||||
private final int order;
|
||||
|
||||
public HeaderLoggingClientHttpFilter(
|
||||
@Value("${" + HeaderLoggingServerHttpFilter.PREFIX + ".header}") Optional<String> serverHeader,
|
||||
public LogLevelClientFilter(
|
||||
@Value("${" + LogLevelServerFilter.PREFIX + ".header}") Optional<String> serverHeader,
|
||||
@Value("${" + PREFIX + ".header}") Optional<String> propagationHeader,
|
||||
@Value("${" + PREFIX + ".order}") Optional<Integer> order) {
|
||||
this.serverHeader = serverHeader.orElse(HeaderLoggingServerHttpFilter.DEFAULT_HEADER);
|
||||
this.serverHeader = serverHeader.orElse(LogLevelServerFilter.DEFAULT_HEADER);
|
||||
this.propagationHeader = propagationHeader.orElse(this.serverHeader);
|
||||
this.order = order.orElse(DEFAULT_ORDER);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.kokuwa.micronaut.logging.request;
|
||||
package io.kokuwa.micronaut.logging.http.level;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
@ -10,6 +10,7 @@ import org.slf4j.MDC;
|
|||
|
||||
import ch.qos.logback.classic.turbo.TurboFilter;
|
||||
import io.kokuwa.micronaut.logging.LogbackUtil;
|
||||
import io.kokuwa.micronaut.logging.http.AbstractMdcFilter;
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import io.micronaut.context.annotation.Value;
|
||||
import io.micronaut.core.async.publisher.Publishers;
|
||||
|
@ -17,54 +18,46 @@ import io.micronaut.core.util.StringUtils;
|
|||
import io.micronaut.http.HttpRequest;
|
||||
import io.micronaut.http.MutableHttpResponse;
|
||||
import io.micronaut.http.annotation.Filter;
|
||||
import io.micronaut.http.filter.HttpServerFilter;
|
||||
import io.micronaut.http.filter.ServerFilterChain;
|
||||
import io.micronaut.http.filter.ServerFilterPhase;
|
||||
import io.micronaut.runtime.server.EmbeddedServer;
|
||||
import io.micronaut.runtime.context.scope.Refreshable;
|
||||
|
||||
/**
|
||||
* Http request logging filter.
|
||||
*
|
||||
* @author Stephan Schnabel
|
||||
*/
|
||||
@Requires(beans = EmbeddedServer.class)
|
||||
@Requires(property = HeaderLoggingServerHttpFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
|
||||
@Filter("${" + HeaderLoggingServerHttpFilter.PREFIX + ".path:/**}")
|
||||
public class HeaderLoggingServerHttpFilter implements HttpServerFilter {
|
||||
|
||||
public static final String PREFIX = "logger.request.filter";
|
||||
public static final String MDC_FILTER = PREFIX;
|
||||
public static final String MDC_KEY = "level";
|
||||
@Refreshable
|
||||
@Requires(property = LogLevelServerFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
|
||||
@Filter("${" + LogLevelServerFilter.PREFIX + ".path:/**}")
|
||||
public class LogLevelServerFilter extends AbstractMdcFilter {
|
||||
|
||||
public static final String PREFIX = "logger.http.level";
|
||||
public static final String DEFAULT_HEADER = "x-log-level";
|
||||
public static final int DEFAULT_ORDER = ServerFilterPhase.FIRST.before();
|
||||
public static final String MDC_KEY = "level";
|
||||
public static final String MDC_FILTER = PREFIX;
|
||||
|
||||
private final LogbackUtil logback;
|
||||
private final String header;
|
||||
private final int order;
|
||||
|
||||
public HeaderLoggingServerHttpFilter(
|
||||
public LogLevelServerFilter(
|
||||
LogbackUtil logback,
|
||||
@Value("${" + PREFIX + ".header}") Optional<String> header,
|
||||
@Value("${" + PREFIX + ".order}") Optional<Integer> order) {
|
||||
super(order.orElse(DEFAULT_ORDER));
|
||||
this.logback = logback;
|
||||
this.header = header.orElse(DEFAULT_HEADER);
|
||||
this.order = order.orElse(DEFAULT_ORDER);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void startTurbofilter() {
|
||||
logback.getTurboFilter(HeaderLoggingTurboFilter.class, MDC_FILTER, HeaderLoggingTurboFilter::new).start();
|
||||
logback.getTurboFilter(LogLevelTurboFilter.class, MDC_FILTER, LogLevelTurboFilter::new).start();
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
void stopTurbofilter() {
|
||||
logback.getTurboFilter(HeaderLoggingTurboFilter.class, MDC_FILTER).ifPresent(TurboFilter::stop);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return order;
|
||||
logback.getTurboFilter(LogLevelTurboFilter.class, MDC_FILTER).ifPresent(TurboFilter::stop);
|
||||
}
|
||||
|
||||
@Override
|
|
@ -1,4 +1,4 @@
|
|||
package io.kokuwa.micronaut.logging.request;
|
||||
package io.kokuwa.micronaut.logging.http.level;
|
||||
|
||||
import org.slf4j.MDC;
|
||||
import org.slf4j.Marker;
|
||||
|
@ -13,7 +13,7 @@ import ch.qos.logback.core.spi.FilterReply;
|
|||
*
|
||||
* @author Stephan Schnabel
|
||||
*/
|
||||
public class HeaderLoggingTurboFilter extends TurboFilter {
|
||||
public class LogLevelTurboFilter extends TurboFilter {
|
||||
|
||||
@Override
|
||||
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
|
||||
|
@ -22,7 +22,7 @@ public class HeaderLoggingTurboFilter extends TurboFilter {
|
|||
return FilterReply.NEUTRAL;
|
||||
}
|
||||
|
||||
var value = MDC.get(HeaderLoggingServerHttpFilter.MDC_KEY);
|
||||
var value = MDC.get(LogLevelServerFilter.MDC_KEY);
|
||||
if (value == null) {
|
||||
return FilterReply.NEUTRAL;
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package io.kokuwa.micronaut.logging.http.mdc;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import io.kokuwa.micronaut.logging.http.AbstractMdcFilter;
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import io.micronaut.context.annotation.Value;
|
||||
import io.micronaut.core.util.StringUtils;
|
||||
import io.micronaut.http.HttpRequest;
|
||||
import io.micronaut.http.MutableHttpResponse;
|
||||
import io.micronaut.http.annotation.Filter;
|
||||
import io.micronaut.http.filter.ServerFilterChain;
|
||||
import io.micronaut.http.filter.ServerFilterPhase;
|
||||
import io.micronaut.runtime.context.scope.Refreshable;
|
||||
import io.micronaut.security.authentication.Authentication;
|
||||
|
||||
/**
|
||||
* Filter to add claims from authentication to MDC.
|
||||
*
|
||||
* @author Stephan Schnabel
|
||||
*/
|
||||
@Refreshable
|
||||
@Requires(classes = Authentication.class)
|
||||
@Requires(property = AuthenticationMdcFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
|
||||
@Filter("${" + AuthenticationMdcFilter.PREFIX + ".path:/**}")
|
||||
public class AuthenticationMdcFilter extends AbstractMdcFilter {
|
||||
|
||||
public static final String PREFIX = "logger.http.authentication";
|
||||
public static final String DEFAULT_NAME = "principal";
|
||||
public static final int DEFAULT_ORDER = ServerFilterPhase.SECURITY.after();
|
||||
|
||||
private final String name;
|
||||
private final List<String> attributes;
|
||||
private final String prefix;
|
||||
|
||||
public AuthenticationMdcFilter(
|
||||
@Value("${" + PREFIX + ".name:principal}") Optional<String> name,
|
||||
@Value("${" + PREFIX + ".attributes:[]}") List<String> attributes,
|
||||
@Value("${" + PREFIX + ".prefix}") Optional<String> prefix,
|
||||
@Value("${" + PREFIX + ".order}") Optional<Integer> order) {
|
||||
super(order.orElse(DEFAULT_ORDER));
|
||||
this.name = name.orElse(DEFAULT_NAME);
|
||||
this.prefix = prefix.orElse(null);
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
|
||||
|
||||
// get authentication
|
||||
|
||||
var optional = request.getUserPrincipal(Authentication.class);
|
||||
if (optional.isEmpty()) {
|
||||
return chain.proceed(request);
|
||||
}
|
||||
var authentication = optional.get();
|
||||
var authenticationAttributes = authentication.getAttributes();
|
||||
|
||||
// add mdc
|
||||
|
||||
var mdc = new HashMap<String, String>();
|
||||
MDC.put(prefix == null ? name : prefix + name, authentication.getName());
|
||||
for (var header : attributes) {
|
||||
var value = authenticationAttributes.get(header);
|
||||
if (value != null) {
|
||||
mdc.put(prefix == null ? header : prefix + header, String.valueOf(value));
|
||||
}
|
||||
}
|
||||
|
||||
return doFilter(request, chain, mdc);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package io.kokuwa.micronaut.logging.http.mdc;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import io.kokuwa.micronaut.logging.http.AbstractMdcFilter;
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import io.micronaut.context.annotation.Value;
|
||||
import io.micronaut.core.util.StringUtils;
|
||||
import io.micronaut.http.HttpRequest;
|
||||
import io.micronaut.http.MutableHttpResponse;
|
||||
import io.micronaut.http.annotation.Filter;
|
||||
import io.micronaut.http.filter.ServerFilterChain;
|
||||
import io.micronaut.http.filter.ServerFilterPhase;
|
||||
import io.micronaut.runtime.context.scope.Refreshable;
|
||||
|
||||
/**
|
||||
* Filter to add http headers to MDC.
|
||||
*
|
||||
* @author Stephan Schnabel
|
||||
*/
|
||||
@Refreshable
|
||||
@Requires(property = HttpHeadersMdcFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
|
||||
@Requires(property = HttpHeadersMdcFilter.PREFIX + ".names")
|
||||
@Filter("${" + HttpHeadersMdcFilter.PREFIX + ".path:/**}")
|
||||
public class HttpHeadersMdcFilter extends AbstractMdcFilter {
|
||||
|
||||
public static final String PREFIX = "logger.http.headers";
|
||||
public static final int DEFAULT_ORDER = ServerFilterPhase.FIRST.before();
|
||||
|
||||
private final Set<String> headers;
|
||||
private final String prefix;
|
||||
|
||||
public HttpHeadersMdcFilter(
|
||||
@Value("${" + PREFIX + ".names}") List<String> headers,
|
||||
@Value("${" + PREFIX + ".prefix}") Optional<String> prefix,
|
||||
@Value("${" + PREFIX + ".order}") Optional<Integer> order) {
|
||||
super(order.orElse(DEFAULT_ORDER));
|
||||
this.prefix = prefix.orElse(null);
|
||||
this.headers = headers.stream().map(String::toLowerCase).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
|
||||
var mdc = new HashMap<String, String>();
|
||||
for (var header : headers) {
|
||||
request.getHeaders()
|
||||
.getFirst(header)
|
||||
.ifPresent(value -> mdc.put(prefix == null ? header : prefix + header, String.valueOf(value)));
|
||||
}
|
||||
return doFilter(request, chain, mdc);
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package io.kokuwa.micronaut.logging.request;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import io.micronaut.context.annotation.Requires;
|
||||
import io.micronaut.context.annotation.Value;
|
||||
import io.micronaut.core.async.publisher.Publishers;
|
||||
import io.micronaut.core.util.StringUtils;
|
||||
import io.micronaut.http.HttpRequest;
|
||||
import io.micronaut.http.MutableHttpResponse;
|
||||
import io.micronaut.http.annotation.Filter;
|
||||
import io.micronaut.http.filter.HttpServerFilter;
|
||||
import io.micronaut.http.filter.ServerFilterChain;
|
||||
import io.micronaut.http.filter.ServerFilterPhase;
|
||||
import io.micronaut.runtime.server.EmbeddedServer;
|
||||
|
||||
/**
|
||||
* Http request principal filter.
|
||||
*
|
||||
* @author Stephan Schnabel
|
||||
*/
|
||||
@Requires(beans = EmbeddedServer.class)
|
||||
@Requires(property = PrincipalHttpFilter.PREFIX + ".enabled", notEquals = StringUtils.FALSE)
|
||||
@Filter("${" + PrincipalHttpFilter.PREFIX + ".path:/**}")
|
||||
public class PrincipalHttpFilter implements HttpServerFilter {
|
||||
|
||||
public static final String PREFIX = "logger.request.principal";
|
||||
|
||||
public static final String DEFAULT_KEY = "principal";
|
||||
public static final int DEFAULT_ORDER = ServerFilterPhase.SECURITY.after();
|
||||
|
||||
private final String key;
|
||||
private final int order;
|
||||
|
||||
public PrincipalHttpFilter(
|
||||
@Value("${" + PREFIX + ".key:" + DEFAULT_KEY + "}") String key,
|
||||
@Value("${" + PREFIX + ".order}") Optional<Integer> order) {
|
||||
this.key = key;
|
||||
this.order = order.orElse(DEFAULT_ORDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
|
||||
var princial = request.getUserPrincipal();
|
||||
if (princial.isPresent()) {
|
||||
MDC.put(key, princial.get().getName());
|
||||
return Publishers.map(chain.proceed(request), response -> {
|
||||
MDC.remove(key);
|
||||
return response;
|
||||
});
|
||||
} else {
|
||||
return chain.proceed(request);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue