Add request filter.
This commit is contained in:
parent
059ff67d2b
commit
addc97f5cd
7 changed files with 248 additions and 0 deletions
|
@ -64,6 +64,15 @@ logger:
|
||||||
user: {}
|
user: {}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Set log level based on HTTP request header
|
||||||
|
|
||||||
|
Confguration:
|
||||||
|
* *enabled*: enable HTTP request 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)
|
||||||
|
* *pattern*: filter pattern (`/**` is default)
|
||||||
|
* *header*: name of HTTP header (`x-log-level` is default)
|
||||||
|
|
||||||
## Build & Release
|
## Build & Release
|
||||||
|
|
||||||
### Dependency updates
|
### Dependency updates
|
||||||
|
|
10
pom.xml
10
pom.xml
|
@ -90,6 +90,16 @@
|
||||||
<artifactId>micronaut-test-junit5</artifactId>
|
<artifactId>micronaut-test-junit5</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micronaut</groupId>
|
||||||
|
<artifactId>micronaut-http-client</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micronaut</groupId>
|
||||||
|
<artifactId>micronaut-http-server-netty</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- logging -->
|
<!-- logging -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package io.kokuwa.micronaut.logging.request;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.annotation.PreDestroy;
|
||||||
|
|
||||||
|
import org.reactivestreams.Publisher;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import ch.qos.logback.classic.turbo.TurboFilter;
|
||||||
|
import io.kokuwa.micronaut.logging.LogbackUtil;
|
||||||
|
import io.micronaut.context.annotation.Requires;
|
||||||
|
import io.micronaut.context.annotation.Value;
|
||||||
|
import io.micronaut.core.order.Ordered;
|
||||||
|
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.runtime.server.EmbeddedServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http request logging filter.
|
||||||
|
*
|
||||||
|
* @author Stephan Schnabel
|
||||||
|
*/
|
||||||
|
@Requires(beans = EmbeddedServer.class)
|
||||||
|
@Requires(property = RequestLoggingHttpFilter.ENABLED, notEquals = "false")
|
||||||
|
@Filter("${" + RequestLoggingHttpFilter.PREFIX + ".pattern:" + RequestLoggingHttpFilter.DEFAULT_PATTERN + ":/**}")
|
||||||
|
public class RequestLoggingHttpFilter implements HttpServerFilter {
|
||||||
|
|
||||||
|
public static final String PREFIX = "logger.request";
|
||||||
|
public static final String ENABLED = PREFIX + ".enabled";
|
||||||
|
public static final String MDC_FILTER_NAME = PREFIX + ".filter";
|
||||||
|
public static final String MDC_KEY = "level";
|
||||||
|
|
||||||
|
public static final String DEFAULT_HEADER = "x-log-level";
|
||||||
|
public static final String DEFAULT_PATTERN = "/**";
|
||||||
|
|
||||||
|
private final LogbackUtil logback;
|
||||||
|
private final String header;
|
||||||
|
private final int order;
|
||||||
|
|
||||||
|
public RequestLoggingHttpFilter(
|
||||||
|
LogbackUtil logback,
|
||||||
|
@Value("${" + PREFIX + ".header:" + DEFAULT_HEADER + "}") String header,
|
||||||
|
@Value("${" + PREFIX + ".order:" + Ordered.HIGHEST_PRECEDENCE + "}") int order) {
|
||||||
|
this.logback = logback;
|
||||||
|
this.header = header;
|
||||||
|
this.order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
void startTurbofilter() {
|
||||||
|
logback.getTurboFilter(RequestLoggingTurboFilter.class, MDC_FILTER_NAME, RequestLoggingTurboFilter::new).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
void stopTurbofilter() {
|
||||||
|
logback.getTurboFilter(RequestLoggingTurboFilter.class, MDC_FILTER_NAME).ifPresent(TurboFilter::stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
|
||||||
|
var level = request.getHeaders().getFirst(header).map(Level::valueOf);
|
||||||
|
if (level.isPresent()) {
|
||||||
|
MDC.put(MDC_KEY, level.get().toString());
|
||||||
|
}
|
||||||
|
return chain.proceed(request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package io.kokuwa.micronaut.logging.request;
|
||||||
|
|
||||||
|
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.spi.FilterReply;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter for log levels based on MDC.
|
||||||
|
*
|
||||||
|
* @author Stephan Schnabel
|
||||||
|
*/
|
||||||
|
public class RequestLoggingTurboFilter extends TurboFilter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
|
||||||
|
|
||||||
|
if (!isStarted()) {
|
||||||
|
return FilterReply.NEUTRAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = MDC.get(RequestLoggingHttpFilter.MDC_KEY);
|
||||||
|
if (value == null) {
|
||||||
|
return FilterReply.NEUTRAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return level.isGreaterOrEqual(Level.valueOf(value)) ? FilterReply.ACCEPT : FilterReply.NEUTRAL;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package io.kokuwa.micronaut.logging.request;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import io.micronaut.http.annotation.Controller;
|
||||||
|
import io.micronaut.http.annotation.Get;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contoller for testing {@link RequestLoggingHttpFilter}.
|
||||||
|
*
|
||||||
|
* @author Stephan Schnabel
|
||||||
|
*/
|
||||||
|
@Controller
|
||||||
|
@Slf4j
|
||||||
|
public class RequestLoggingController {
|
||||||
|
|
||||||
|
@Get
|
||||||
|
String run() {
|
||||||
|
|
||||||
|
var level = Level.OFF;
|
||||||
|
if (log.isTraceEnabled()) {
|
||||||
|
level = Level.TRACE;
|
||||||
|
} else if (log.isDebugEnabled()) {
|
||||||
|
level = Level.DEBUG;
|
||||||
|
} else if (log.isInfoEnabled()) {
|
||||||
|
level = Level.INFO;
|
||||||
|
} else if (log.isWarnEnabled()) {
|
||||||
|
level = Level.WARN;
|
||||||
|
} else if (log.isErrorEnabled()) {
|
||||||
|
level = Level.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return level.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package io.kokuwa.micronaut.logging.request;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import ch.qos.logback.classic.Level;
|
||||||
|
import io.kokuwa.micronaut.logging.AbstractTest;
|
||||||
|
import io.micronaut.http.HttpRequest;
|
||||||
|
import io.micronaut.http.HttpStatus;
|
||||||
|
import io.micronaut.http.client.HttpClient;
|
||||||
|
import io.micronaut.http.client.annotation.Client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link RequestLoggingHttpFilter}.
|
||||||
|
*
|
||||||
|
* @author Stephan Schnabel
|
||||||
|
*/
|
||||||
|
@DisplayName("request")
|
||||||
|
public class RequestLoggingTest extends AbstractTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Client("/")
|
||||||
|
HttpClient client;
|
||||||
|
|
||||||
|
@DisplayName("header missing")
|
||||||
|
@Test
|
||||||
|
void headerMissing() {
|
||||||
|
assertLevel(Level.INFO, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("header invalid, use DEBUG as default from logback")
|
||||||
|
@Test
|
||||||
|
void headerInvalid() {
|
||||||
|
assertLevel(Level.DEBUG, "TRCE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("level trace (below default)")
|
||||||
|
@Test
|
||||||
|
void headerLevelTrace() {
|
||||||
|
assertLevel(Level.TRACE, "TRACE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("level debug (below default)")
|
||||||
|
@Test
|
||||||
|
void headerLevelDebug() {
|
||||||
|
assertLevel(Level.DEBUG, "DEBUG");
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("level info (is default)")
|
||||||
|
@Test
|
||||||
|
void headerLevelInfo() {
|
||||||
|
assertLevel(Level.INFO, "INFO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("level warn (above default)")
|
||||||
|
@Test
|
||||||
|
void headerLevelWarn() {
|
||||||
|
assertLevel(Level.INFO, "WARN");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertLevel(Level expected, String header) {
|
||||||
|
|
||||||
|
var request = HttpRequest.GET("/");
|
||||||
|
if (header != null) {
|
||||||
|
request.getHeaders().add(RequestLoggingHttpFilter.DEFAULT_HEADER, header);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = client.toBlocking().exchange(request, String.class);
|
||||||
|
assertEquals(HttpStatus.OK, response.getStatus(), "status");
|
||||||
|
assertTrue(response.getBody().isPresent(), "body");
|
||||||
|
assertEquals(expected, Level.valueOf(response.body()), "level");
|
||||||
|
}
|
||||||
|
}
|
8
src/test/resources/application-test.yaml
Normal file
8
src/test/resources/application-test.yaml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
micronaut:
|
||||||
|
http:
|
||||||
|
client:
|
||||||
|
logger-name: io.kokuwa.Test
|
||||||
|
|
||||||
|
logger:
|
||||||
|
levels:
|
||||||
|
io.kokuwa: INFO
|
Loading…
Add table
Add a link
Reference in a new issue