7

I am looking to develop a gateway server based on spring-cloud-gateway:2.0.2-RELEASE and would like to utilize sleuth for logging purposes. I have sleuth running since when I write to the log I see the Sleuth details (span Id, etc), but I an hoping to see the body of messages being logged automatically. Is there something I need to do to get Sleuth to log request/response out of the box with Spring-Cloud-Gateway?

Here is the request headers that arrive at my downstream service

    headers:
       { 'x-request-foo': '2a9c5e36-2c0f-4ad3-926c-cb20d4428462',
         forwarded: 'proto=http;host=localhost;for="0:0:0:0:0:0:0:1:51720"',
         'x-forwarded-for': '0:0:0:0:0:0:0:1',
         'x-forwarded-proto': 'http',
         'x-forwarded-port': '80',
         'x-forwarded-host': 'localhost',
         'x-b3-traceid': '5bd33eb8050c7a32dfce6adfe68b06ca',
         'x-b3-spanid': 'ba202a6d6f3e2893',
         'x-b3-parentspanid': 'dfce6adfe68b06ca',
         'x-b3-sampled': '0',
         host: 'localhost:8080' },

Gradle file in the gateway service..

    buildscript {
        ext {
            kotlinVersion = '1.2.61'
            springBootVersion = '2.0.6.RELEASE'
            springCloudVersion = 'Finchley.RELEASE'
        }
    }
    dependencyManagement {
        imports {
            mavenBom "org.springframework.cloud:spring-cloud-sleuth:2.0.2.RELEASE"
            mavenBom 'org.springframework.cloud:spring-cloud-gateway:2.0.2.RELEASE'
            mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
        }
    }
    dependencies {
        implementation('org.springframework.cloud:spring-cloud-starter-sleuth')
        implementation('org.springframework.cloud:spring-cloud-starter-gateway')
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
        implementation("org.jetbrains.kotlin:kotlin-reflect")
        testImplementation('org.springframework.boot:spring-boot-starter-test')
    }

and finally the application.yml file for the gateway service...

    server:
      servlet:
        contextPath: /
      port: 80
    spring:
      application:
        name: api.gateway.ben.com
      sleuth:
        trace-id128: true
        sampler:
          probability: 1.0
      cloud:
        gateway:
          routes:
          - id: admin-ui-2
            predicates:
            - Path=/admin-ui-2/echo/*
            filters:
            - SetPath=/fred
            - AddRequestHeader=X-Request-Foo, 2a9c5e36-2c0f-4ad3-926c-cb20d4428462
            - AddResponseHeader=X-Response-Foo, Bar
            uri: http://localhost:8080
    logging:
      pattern:
        level: "[%X{X-B3-TraceId}/%X{X-B3-SpanId}] %-5p [%t] %C{2} - %m%n"
      level:
        org.springframework.web: DEBUG

3
  • what it means level:enter code here?. I tried your log settings I am able to view the logs. Commented Oct 28, 2018 at 5:29
  • Sorry, it is a typo. It has been removed. Commented Oct 30, 2018 at 12:13
  • Ben, I tried and it is good for me. Share the code to the link and let me know your expectations? just to enable root level logging and see whether you are able to see it Commented Oct 30, 2018 at 12:57

2 Answers 2

4

Spring Cloud Gateway already can log the request and response, you just need to change your log level to TRACE.

logging:
  level:
    org.springframework: TRACE

or to be more precise to just log req/resp:

logging:
  level:
    org.springframework.core.codec.StringDecoder: TRACE

Other option is to use a Filter in Spring Cloud Gateway and log req/resp etc with any loggers like Log4j:

routeBuilder.route(id,
                            r -> {
                                return r.path(path).and().method(requestmethod).and()
                                        .header(routmap.getRequestheaderkey(), routmap.getRequestheadervalue()).and()
                                        .readBody(String.class, requestBody -> {
                                            return true;
                                        }).filters(f -> {
                                            f.rewritePath(rewritepathregex, replacement);
                                            f.prefixPath(perfixpath);

                                            f.filter(LogFilter);
                                            return f;
                                        }).uri(uri);
                            });

This filer "LogFilter" you need to define which would have logging logic.

Sign up to request clarification or add additional context in comments.

2 Comments

Only the requests are been logged .. How about the response ?
Ok if anybody finds a reasonable understandable Filter to log Responses, for an experienced developer, rookie about reactive programming, please let us know, it's kind of impossible to understand all the things I found around, and that's the problem of this Question, without the Filter implementation the answer is useless, sorry.
0
With spring cloud gateway with webflux 


import dev.blaauwendraad.masker.json.JsonMasker
import org.reactivestreams.Publisher
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.cloud.gateway.filter.GlobalFilter
import org.springframework.core.annotation.Order
import org.springframework.http.server.reactive.ServerHttpResponseDecorator
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import org.springframework.core.io.buffer.DataBuffer
import org.springframework.core.io.buffer.DataBufferUtils
import org.springframework.http.server.reactive.ServerHttpRequestDecorator
import reactor.core.scheduler.Schedulers
import java.nio.charset.StandardCharsets


@Component
@Order(1)
class SensitiveLoggingFilter : GlobalFilter {

    private val log: Logger = LoggerFactory.getLogger(SensitiveLoggingFilter::class.java)

    private val jsonMasker = JsonMasker.getMasker(
        setOf(
           "fieldsToMask"
        )
    )

    override fun filter(exchange: ServerWebExchange, chain: org.springframework.cloud.gateway.filter.GatewayFilterChain): Mono<Void> {

        val originalRequest = exchange.request

        // Intercept Request Body for Logging
        val modifiedRequest = object : ServerHttpRequestDecorator(originalRequest) {
            override fun getBody(): Flux<DataBuffer> {
                return super.getBody().map { buffer ->
                    val requestBody = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()).toString()

                    // Asynchronously mask the request body
                    Mono.fromCallable {
                        val maskedRequestBody = jsonMasker.mask(requestBody)
                        log.info("Masked Request: $maskedRequestBody")
                    }
                        .subscribeOn(Schedulers.boundedElastic())  // Async execution
                        .doOnError { e -> log.error("Failed to mask request body", e) }
                        .subscribe()

                    // Ensure the buffer is not released prematurely
                    DataBufferUtils.retain(buffer)
                }
            }
        }
        val originalResponse = exchange.response
        val modifiedResponse = object : ServerHttpResponseDecorator(originalResponse) {
            override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> {
                val modifiedBody = Flux.from(body).doOnNext { buffer ->
                    val responseBody = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()).toString()

                    // Asynchronously mask the response body
                    Mono.fromCallable {
                        val maskedResponseBody = jsonMasker.mask(responseBody)
                        log.info("Masked Response: $maskedResponseBody")
                    }
                        .subscribeOn(Schedulers.boundedElastic())
                        .doOnError { e -> log.error("Failed to mask response body ", e) }
                        .subscribe()

                    // Ensure the buffer isn't prematurely released
                    DataBufferUtils.retain(buffer)
                }

                return super.writeWith(modifiedBody)
            }
        }
        val mutatedExchange = exchange.mutate()
            .request(modifiedRequest)
            .response(modifiedResponse)
            .build()

        return chain.filter(mutatedExchange)
    }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.