Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue related to Spring Boot WebFlux and micrometer while the system uses different executors #643

Open
ForItInNet opened this issue Mar 27, 2024 · 3 comments

Comments

@ForItInNet
Copy link

ForItInNet commented Mar 27, 2024

We tried to use micrometer tracing with WebFlux gRPC and, it looks like, the context doesn't populate between executors during the one request.

public Flux<AuthorityDto> getAuthority(@NonNull Long accountId){
        return Mono.just(accountId)
                .doOnNext(ignore -> log.info("========== Message 1 =========="))
                .flatMapMany(authorityRepository::getAuthority) <=== Request to DB to select a flux of authorities
                .take(1) // just for sample
                .doOnNext(ignore -> log.info("========== Message 2 =========="))
                ...;
 }

Result

INFO [6603fd55c4be0cc0d99287a970446e4a] --- [ault-executor-0] AuthorityService    : ========== Message 1 ==========
INFO [] --- [actor-tcp-nio-2] AuthorityService    : ========== Message 2 ==========

We can see that before DB-operation the log has a trace ID and executor ault-executor-0 but after selection, the race ID is empty and, as expected, we have another executor actor-tcp-nio-2.

There are dependencies which are used:

implementation 'org.springframework.boot:spring-boot-starter-actuator:3.2.4'
implementation 'io.micrometer:micrometer-tracing-bridge-brave:1.2.4'
implementation 'io.micrometer:context-propagation:1.1.1'

Spring Boot 3.1.5
The main method also has Hooks.enableAutomaticContextPropagation

public static void main(String[] args) {
        Hooks.enableAutomaticContextPropagation();
        SpringApplication.run(AuthorityServiceApplication.class, args);
}
@ForItInNet
Copy link
Author

ForItInNet commented Mar 28, 2024

I have found that it should be propagated by hand like in this tutorial https://www.youtube.com/watch?v=6OKS36PSpho
It can be something like this

public static void main(String[] args) {
        Hooks.enableAutomaticContextPropagation();
        SpringApplication.run(AuthorityServiceApplication.class, args);
    }

Interceptor for trace ID creation

@Slf4j
@RequiredArgsConstructor
@GrpcGlobalServerInterceptor
public class TracerInterceptor implements ServerInterceptor, ClientInterceptor {

    private final String TRACE_ID = "traceId";
    private final Metadata.Key<String> TRACE_ID_KEY = Metadata.Key.of(TRACE_ID, Metadata.ASCII_STRING_MARSHALLER);

    private final Tracer tracer;

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
        Optional.ofNullable(headers.get(TRACE_ID_KEY)).ifPresentOrElse(traceId -> MDC.put(TRACE_ID, traceId), () -> tracer.withSpan(tracer.nextSpan()));
        ContextRegistry.getInstance().registerThreadLocalAccessor(TRACE_ID, () -> MDC.get(TRACE_ID), traceId -> MDC.put(TRACE_ID, traceId), () -> MDC.remove(TRACE_ID));
        return next.startCall(call, headers);
    }
}

And add data into the context of your chain:

...
.contextCapture();

Is it expected that gRPC doesn't create trace ID automatically?
How can we correctly propagate tracer context?

Thank you.

@marcingrzejszczak
Copy link
Contributor

marcingrzejszczak commented May 6, 2024

cc @chemicL @bclozel

@chemicL
Copy link
Contributor

chemicL commented May 6, 2024

AFAIK, Spring has no official gRPC support. Whichever community supported project that the GrpcGlobalServerInterceptor comes from would need to support Observation just like Spring does for this to work without user configuration.

Some remarks regarding the code above:

  1. spring.reactor.context-propagation=auto Boot config can be used instead of directly calling Hooks.enableAutomaticContextPropagation()
  2. You should register a ThreadLocalAccessor once per app lifecycle instead of registering one on every call.
  3. I don't know about reactive support in your gRPC integration library but in reactive endpoints you should avoid directly writing to MDC and using contextCapture() afterwards, but instead write directly to the Reactor Context.
  4. Now you can use a dedicated ThreadLocalAccessor for such a use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants