Code Monkey home page Code Monkey logo

spring-webflux-mdc's Introduction

Spring Webflux MDC

This library is a utility for adding MDC logs to a Spring Webflux application (with Spring Boot version 3).

Import the library

With Maven:

<dependency>
   <groupId>com.vincenzoracca</groupId>
   <artifactId>spring-webflux-mdc</artifactId>
   <version>1.0.2</version>
</dependency>

or with Gradle:

dependencies {
	implementation 'com.vincenzoracca:spring-webflux-mdc:1.0.2'
}

Usage

In your Spring WebFlux Application:

  1. import the SpringMDC class in a @Configuration class or in the main class (@SpringBootApplication), in this way:
    @Configuration
    @Import(SpringMDC.class)
    public class AppConfig {
    }
  2. add in your application.properties the headers keys and the relatives mdc keys in this way:
    spring.mdc.headers.<header_key_1>=<mdc_key_1> 
    spring.mdc.headers.<header_key_2>=<mdc_key_2>
    
  3. Optionally, if the HTTP request headers don't contain the <header_key>, the library can add the related MDC with a UUID value. If you want me to do this, in your application.properties add this:
    spring.mdc.defaults=<header_key_1>,<header_key_2>
    
    The spring.mdc.defaults property accepts a list of string (the header keys), with the comma as the delimiter.

See the example in application.properties

Usage with programmatically MDC (without headers)

If you need to add a MDC key programmatically, in the most "external" method that contains the MDC key:

  1. wrap the method using the wrapMDC method of MDCUtil Example:
    Original method:
@GetMapping("test-client-programmatically-2")
public Mono<ResponseEntity<MessageResponse>> getMDCProgrammaticallyExampleTwo(@RequestHeader("an-header-not-registered") String anHeader) {
  log.info("[{}] Called getMDCExample with header but without MDC because it is not wrapped:", anHeader);
  return Mono.just("test-another-product")
          .delayElement(Duration.ofMillis(1))
          .flatMap(product ->
                  Flux.concat(
                                  addProduct(product, anHeader),
                                  notifyShop(product, anHeader))
                          .then(Mono.just(ResponseEntity.ok(new MessageResponse("Hello World!")))));

}

With MDC:

@GetMapping("test-client-programmatically-2")
public Mono<ResponseEntity<MessageResponse>> getMDCProgrammaticallyExampleTwo(@RequestHeader("an-header-not-registered") String anHeader) {
  log.info("[{}] Called getMDCExample with header but without MDC because it is not wrapped:", anHeader);
  Mono<ResponseEntity<MessageResponse>> responseEntityMono = Mono.just("test-another-product")
          .delayElement(Duration.ofMillis(1))
          .flatMap(product ->
                  Flux.concat(
                                  addProduct(product, anHeader),
                                  notifyShop(product, anHeader))
                          .then(Mono.just(ResponseEntity.ok(new MessageResponse("Hello World!")))));

  return MDCUtil.wrapMDC(responseEntityMono, "my-mdc-key", anHeader);
}

If you need to pass more MDC keys, you can use the wrapMDC method that accepts a Map of <mdc_key, mdc_value>.

Test with an HTTP client manually with the IDE

  1. Run the SpringMDCApplication main class from your IDE
  2. Run the test-client.sh script to execute 100 HTTP calls or make an HTTP call manually with the localhost:8080/test-client endpoint with the X-Amzn-Trace-Id request header
  3. Watch the logs in the tests.log file

spring-webflux-mdc's People

Contributors

vincenzo-racca avatar

Stargazers

Prajwal Raju P avatar Hans Lindner avatar  avatar Erick Belluci Tedeschi avatar 李志博 avatar Monika Szumna avatar Saurav Sahu avatar

Watchers

 avatar

Forkers

mszumna

spring-webflux-mdc's Issues

Thank you so much for this project.

I am posting here just to thank this project. I am using the webflux + security technology stack, and I want to set the logged-in username in MDC. I have been researching for almost a day without success until I found this library, it saved me!

Losing MDC values in some scenarios

Hi @vincenzo-racca

I have been struggling with adding MDC support to my Webflux project for a while now, so it is really great to see this library, thank you for your work!

I am still having issues where I lose the MDC values in certain scenarios and I was wondering if you can help me out. I have modified your example a bit to demonstrate my issue

    @GetMapping("test-client")
    public Mono<ResponseEntity<MessageResponse>> getMDCExample(@RequestHeader("X-Amzn-Trace-Id") String awsTraceId,
                                                               @RequestParam String fileKey) {
        log.info("[{}] Called getMDCExample with header:", awsTraceId);
        return Mono.just("test-product")
                .delayElement(Duration.ofMillis(1))
                .flatMap(x -> getImage(fileKey))
                .then(Mono.just(ResponseEntity.ok(new MessageResponse("Hello World!"))));
    }

    public Mono<String> getImage(String fileKey) {
        // Has MDC
        log.info("Inside getImage()");

        return getDocumentMetadata(fileKey)
                .flatMap(this::downloadImage);
    }

    private Mono<DocumentMetadata> getDocumentMetadata(String fileKey) {
        // Has MDC
        log.info("Inside getDocumentMetadata()");
        return documentMetadataRepository.findByFileKey(fileKey);
    }

    private Mono<String> downloadImage(DocumentMetadata documentMetadata) {
        // No MDC
        log.info("Inside downloadImage()");
        return Mono.just("example");
    }

Where documentMetadataRepository is a standard ReactiveCrudRepository

public interface DocumentMetadataRepository extends ReactiveCrudRepository<DocumentMetadata, String> {
    Mono<DocumentMetadata> findByFileKey(String fileKey);
}

This issue I am having is that my log inside downloadImage() does not have any MDC values, and I do not understand why.

Logs:

{"@timestamp":"2024-01-10T16:21:49.1136988Z","@version":"1","message":"Inside getImage()","logger_name":"com.vincenzoracca.webflux.mdc.api.MockApi","thread_name":"parallel-7","level":"INFO","level_value":20000,"trace_id":"test"}
{"@timestamp":"2024-01-10T16:21:49.1147004Z","@version":"1","message":"Inside getDocumentMetadata()","logger_name":"com.vincenzoracca.webflux.mdc.api.MockApi","thread_name":"parallel-7","level":"INFO","level_value":20000,"trace_id":"test"}
{"@timestamp":"2024-01-10T16:21:51.2039685Z","@version":"1","message":"Inside downloadImage()","logger_name":"com.vincenzoracca.webflux.mdc.api.MockApi","thread_name":"reactor-tcp-nio-2","level":"INFO","level_value":20000}

And it seems to be related to the repository, as if I replace this with just basic Mono.just(), my downloadImage() method then has MDC values..

    private Mono<DocumentMetadata> getDocumentMetadata(String fileKey) {
        // Has MDC
        log.info("Inside getDocumentMetadata()");
        return Mono.just(new DocumentMetadata());
    }

Have you any idea why this happens? Appreciate any info you can provide!

This may lead to thread polluting and memory leaks.

hi friend

base : Spring 6 Webflux Web application

Since I added this dependency package to the project , when the application has a high concurrency ,the following error occurs:

[15:09:58.214] [] [reactor-http-epoll-57] [WARN] [i.m.o.c.ObservationThreadLocalAccessor] Observation [io.micrometer.observation.NoopObservation@159d17a9](mailto:io.micrometer.observation.NoopObservation@159d17a9) to which we're restoring is not the same as the one set as this scope's parent observation <{name=null(null), error=null, context=name='null', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[], parentObservation=null}>. Most likely a manually created Observation has a scope opened that was never closed. This may lead to thread polluting and memory leaks.
Do you have this issue there? What shoud I do ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.