How to log request and response bodies in Spring WebFlux

前端 未结 9 1057
一向
一向 2020-11-27 16:41

I want to have centralised logging for requests and responses in my REST API on Spring WebFlux with Kotlin. So far I\'ve tried this approaches

@Bean
fun apiR         


        
相关标签:
9条回答
  • 2020-11-27 17:15

    If your using controller instead of handler best way is aop with annotating you controller class with @Log annotation.And FYI this takes plain json object as request not mono.

    @Target(AnnotationTarget.FUNCTION)
    @Retention(AnnotationRetention.RUNTIME)
    annotation class Log
    
    @Aspect
    @Component
    class LogAspect {
        companion object {
            val log = KLogging().logger
        }
    
        @Around("@annotation(Log)")
        @Throws(Throwable::class)
        fun logAround(joinPoint: ProceedingJoinPoint): Any? {
            val start = System.currentTimeMillis()
            val result = joinPoint.proceed()
            return if (result is Mono<*>) result.doOnSuccess(getConsumer(joinPoint, start)) else result
        }
    
        fun getConsumer(joinPoint: ProceedingJoinPoint, start: Long): Consumer<Any>? {
            return Consumer {
                var response = ""
                if (Objects.nonNull(it)) response = it.toString()
                log.info(
                    "Enter: {}.{}() with argument[s] = {}",
                    joinPoint.signature.declaringTypeName, joinPoint.signature.name,
                    joinPoint.args
                )
                log.info(
                    "Exit: {}.{}() had arguments = {}, with result = {}, Execution time = {} ms",
                    joinPoint.signature.declaringTypeName, joinPoint.signature.name,
                    joinPoint.args[0],
                    response, System.currentTimeMillis() - start
                )
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-27 17:25

    I am pretty new to Spring WebFlux, and I don't know how to do it in Kotlin, but should be the same as in Java using WebFilter:

    public class PayloadLoggingWebFilter implements WebFilter {
    
        public static final ByteArrayOutputStream EMPTY_BYTE_ARRAY_OUTPUT_STREAM = new ByteArrayOutputStream(0);
    
        private final Logger logger;
        private final boolean encodeBytes;
    
        public PayloadLoggingWebFilter(Logger logger) {
            this(logger, false);
        }
    
        public PayloadLoggingWebFilter(Logger logger, boolean encodeBytes) {
            this.logger = logger;
            this.encodeBytes = encodeBytes;
        }
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
            if (logger.isInfoEnabled()) {
                return chain.filter(decorate(exchange));
            } else {
                return chain.filter(exchange);
            }
        }
    
        private ServerWebExchange decorate(ServerWebExchange exchange) {
            final ServerHttpRequest decorated = new ServerHttpRequestDecorator(exchange.getRequest()) {
    
                @Override
                public Flux<DataBuffer> getBody() {
    
                    if (logger.isDebugEnabled()) {
                        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        return super.getBody().map(dataBuffer -> {
                            try {
                                Channels.newChannel(baos).write(dataBuffer.asByteBuffer().asReadOnlyBuffer());
                            } catch (IOException e) {
                                logger.error("Unable to log input request due to an error", e);
                            }
                            return dataBuffer;
                        }).doOnComplete(() -> flushLog(baos));
    
                    } else {
                        return super.getBody().doOnComplete(() -> flushLog(EMPTY_BYTE_ARRAY_OUTPUT_STREAM));
                    }
                }
    
            };
    
            return new ServerWebExchangeDecorator(exchange) {
    
                @Override
                public ServerHttpRequest getRequest() {
                    return decorated;
                }
    
                private void flushLog(ByteArrayOutputStream baos) {
                    ServerHttpRequest request = super.getRequest();
                    if (logger.isInfoEnabled()) {
                        StringBuffer data = new StringBuffer();
                        data.append('[').append(request.getMethodValue())
                            .append("] '").append(String.valueOf(request.getURI()))
                            .append("' from ")
                                .append(
                                    Optional.ofNullable(request.getRemoteAddress())
                                                .map(addr -> addr.getHostString())
                                            .orElse("null")
                                );
                        if (logger.isDebugEnabled()) {
                            data.append(" with payload [\n");
                            if (encodeBytes) {
                                data.append(new HexBinaryAdapter().marshal(baos.toByteArray()));
                            } else {
                                data.append(baos.toString());
                            }
                            data.append("\n]");
                            logger.debug(data.toString());
                        } else {
                            logger.info(data.toString());
                        }
    
                    }
                }
            };
        }
    
    }
    

    Here some tests on this: github

    I think this is what Brian Clozel (@brian-clozel) meant.

    0 讨论(0)
  • 2020-11-27 17:25

    Assuming we are dealing with a simple JSON or XML response, if debug level for corresponding loggers is not sufficient for some reason, one can use string representation before transforming it to object:

    Mono<Response> mono = WebClient.create()
                                   .post()
                                   .body(Mono.just(request), Request.class)
                                   .retrieve()
                                   .bodyToMono(String.class)
                                   .doOnNext(this::sideEffectWithResponseAsString)
                                   .map(this::transformToResponse);
    

    the following are the side-effect and transformation methods:

    private void sideEffectWithResponseAsString(String response) { ... }
    private Response transformToResponse(String response) { /*use Jackson or JAXB*/ }    
    
    0 讨论(0)
提交回复
热议问题