Description
When using webflux in conjunction with open-telemetry with micrometer context propagation, it is expected that the trace context should be accessible throughout the controller. However, after updating to 3.1.6 (or 3.2.0) the traceId is lost within the controller at various spots.
What is even more odd, is simply adding a @RequestBody
param to the controller "fixes" the issue in 3.1.6 / 3.2.0.
Run the attached demo application and using postman or similar, call the endpoints using header (or any valid traceparent syntax header):
- traceparent = 00-11111111111111111111111111111111-2222222222222222-01
In 3.1.5, the traceId will always be available. In 3.1.6 the traceId will not be available in the below root endpoint -- but in the /body endpoint it will be available.
As far as I can tell (via downgrading micrometer, otel-bridge, netty, etc when using spring-boot 3.1.6/3.2.0) the issue does not seem to come from a lower level library.
@Slf4j
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
Hooks.enableAutomaticContextPropagation();
}
static String getTraceId(Tracer tracer) {
return Optional.ofNullable(tracer.currentSpan())
.map(Span::context)
.map(TraceContext::traceId)
.orElse(null);
}
@RestController
@RequiredArgsConstructor
static class DemoController {
private final Tracer tracer;
@GetMapping
Mono<String> getRoot() {
// These are null in 3.1.6+
log.info("Current traceId={}", getTraceId(tracer));
return Mono.error(() -> {
log.info("error supplier traceId={}", getTraceId(tracer));
return new RuntimeException("returning error");
});
}
@GetMapping("body")
Mono<String> getWithBody(@RequestBody(required=false) String body) {
// These calls propagate fine
log.info("Current traceId={}", getTraceId(tracer));
return Mono.error(() -> {
log.info("error supplier traceId={}", getTraceId(tracer));
return new RuntimeException("returning error");
});
}
}
@RestControllerAdvice
@RequiredArgsConstructor
static class ErrorHandler {
private final Tracer tracer;
@ExceptionHandler(Exception.class)
ResponseEntity<String> handleError(Exception ex) {
log.info("Encountered error: traceId={}", getTraceId(tracer));
return ResponseEntity.ok("Error: " + ex.getMessage());
}
}
}