Вопрос или проблема
Я пытаюсь использовать автоматическую инструментализацию opentelemetry. Но, похоже, след теряется при использовании CompletableFuture.handle. Не могли бы вы помочь мне с этой проблемой?
@GetMapping("/future3")
public CompletableFuture<ResponseEntity<String>> future3() {
LOGGER.info("future1 "); // <- у нас есть след здесь
LOGGER.info("trace={}", Span.current().getSpanContext().getTraceId()); // <- у нас есть след здесь
SqsAsyncClient sqsAsyncClient = SqsAsyncClient.builder().build();
CompletableFuture<ResponseEntity<String>> aa = sqsAsyncClient
.sendMessage(build -> build.queueUrl(queueUrl).messageBody("hello"))
.handle((response, error) -> {
LOGGER.info("trace response={}", Span.current().getSpanContext().getTraceId()); // <- мы потеряли след
LOGGER.info("test response={}", response); // <- мы потеряли след
return new ResponseEntity<>("success", HttpStatus.OK);
});
LOGGER.info("future1_1"); // <- у нас есть идентификатор следа
return aa;
}
У меня была такая же проблема, и, к счастью, я могу предоставить вам решение. Из вашего кода я вижу, что проблема, с которой вы столкнулись, связана с потерей контекста следа при использовании CompletableFuture.handle. В качестве решения я предлагаю использовать Context или, возможно, Span, что является хорошим выбором для явной передачи функции OpenTelemetry, чтобы гарантировать, что контекст следа передается в асинхронный обратный вызов.
Во-первых, Context может захватывать и передавать явно асинхронным задачам. Я предлагаю захватить текущий контекст в начале метода и затем использовать этот захваченный контекст внутри вашего метода .handle
Хорошо, я также внес некоторые изменения в код, чтобы помочь вам быстрее получить результат, и вот этот код:
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
@GetMapping("/future3")
public CompletableFuture<ResponseEntity<String>> future3() {
LOGGER.info("future1 ");
LOGGER.info("trace={}", Span.current().getSpanContext().getTraceId());
SqsAsyncClient sqsAsyncClient = SqsAsyncClient.builder().build();
// Захват текущего контекста OpenTelemetry
Context currentContext = Context.current();
CompletableFuture<ResponseEntity<String>> aa = sqsAsyncClient
.sendMessage(build -> build.queueUrl(queueUrl).messageBody("hello"))
.handle((response, error) -> {
// Сделать контекст OpenTelemetry текущим внутри этого блока handle
try (Scope scope = currentContext.makeCurrent()) {
LOGGER.info("trace response={}", Span.current().getSpanContext().getTraceId());
LOGGER.info("test response={}", response);
return new ResponseEntity<>("success", HttpStatus.OK);
}
});
LOGGER.info("future1_1");
return aa;
}
Это все, чем я могу помочь, надеюсь, это сработает. Удачи 😉🙏
Ответ или решение
Решение проблемы потери трассировки в OpenTelemetry при использовании CompletableFuture.handle
При использовании автоматического инструментирования OpenTelemetry, особенно в обработках асинхронных задач, существует вероятность потери контекста трассировки. Это часто встречается при работе с методами, такими как CompletableFuture.handle()
, когда текущий контекст OpenTelemetry не передается в асинхронные колбеки.
Проблема
В приведённом коде метода future3
вы можете заметить, что при вызове метода handle
мы теряем информацию о трассировке:
LOGGER.info("trace response={}", Span.current().getSpanContext().getTraceId()); // <- трасса потеряна
Причина
Когда вызывается CompletableFuture.handle
, новый контекст создается в другом потоке, и поэтому информация о текущей трассировке не доступна. Это приводит к тому, что вызовы логирования внутри блока handle
не содержат идентификаторы трассировки.
Решение
Для решения этой проблемы необходимо явно захватить текущий контекст OpenTelemetry и использовать его в асинхронном колбеке. Это достигается путем использования класса Context
для сохранения текущего состояния и восстановления его в нужном месте.
Пример исправленного кода
Ниже приведён модифицированный код, который сохраняет контекст и восстанавливает его внутри метода handle
:
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
@GetMapping("/future3")
public CompletableFuture<ResponseEntity<String>> future3() {
LOGGER.info("future1 ");
LOGGER.info("trace={}", Span.current().getSpanContext().getTraceId());
SqsAsyncClient sqsAsyncClient = SqsAsyncClient.builder().build();
// Захватываем текущий контекст OpenTelemetry
Context currentContext = Context.current();
CompletableFuture<ResponseEntity<String>> aa = sqsAsyncClient
.sendMessage(build -> build.queueUrl(queueUrl).messageBody("hello"))
.handle((response, error) -> {
// Восстанавливаем контекст OpenTelemetry в блоке handle
try (Scope scope = currentContext.makeCurrent()) {
LOGGER.info("trace response={}", Span.current().getSpanContext().getTraceId());
LOGGER.info("test response={}", response);
return new ResponseEntity<>("success", HttpStatus.OK);
}
});
LOGGER.info("future1_1");
return aa;
}
Пояснение изменений
- Захват контекста: В начале метода мы сохраняем текущий контекст с помощью
Context.current()
. - Восстановление контекста: Внутри блока
handle
мы используемtry (Scope scope = currentContext.makeCurrent())
, чтобы установить ранее сохранённый контекст как текущий. Это гарантирует, что любые вызовы OpenTelemetry в этом блоке будут работать с правильной трассировкой.
Заключение
Использование контекста OpenTelemetry в асинхронных обработках — важный шаг к корректному мониторингу и трассировке приложений. Применяя данный подход, вы сможете избежать потери информации о трассировке и улучшить мониторинг производительности вашего приложения.
Если у вас возникнут дополнительные вопросы или потребуется помощь с реализацией, не стесняйтесь задавать их. Удачи в ваших проектах!