繁体   English   中英

如何将 Open Telemetry span 传播到 Spring Boot 中的 Executor threads(或者 span 是否有可能存在于 THREAD_LOCAL 模式下)

[英]How to propagate Open Telemetry span to Executor threads in Spring Boot (or Is it possible for the span to exist in THREAD_LOCAL mode)

我正在尝试设置一个开放的遥测跟踪器以传播到线程,该线程由配置如下的 ThreadPoolTaskExecutor 生成

private DgsDataLoaderAsyncExecutor() {
  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  executor.setCorePoolSize(1);
  executor.setMaxPoolSize(100);
  executor.setQueueCapacity(500);
  executor.setThreadNamePrefix("threadAsync");
  executor.setWaitForTasksToCompleteOnShutdown(true);
  executor.setTaskDecorator(new DataLoaderTaskDecorator());
  executor.initialize();
  executorInstance = executor;
  // spring tracer is obtained here to extract the current span later on 
  tracer = GlobalOpenTelemetry.getTracer("org.springframework.boot");
}

后来我这样称呼执行者:

/**
 * Only one (or no) Executor will live in the service at all times. It will be retrieved in a thread safe manner
 * @return executor to be used in the DataLoader
 */
public static synchronized Executor get() {
  if (instance == null) {
    instance = new DgsDataLoaderAsyncExecutor();
  }
  return instance.getExecutorInstance();
}

static class DataLoaderTaskDecorator implements TaskDecorator {
  /**
   * Currently, tenant, security, and locale contexts are passed
   */
  @Override
  public Runnable decorate(Runnable runnable) {
    // Current Web Thread context to be passed ot async
    var currentSpanContext = Context.current();
    var currentSpan = Span.current();
    Locale locale = LocaleContextHolder.getLocale();

    log.debug("Saving information for async thread...");
    return () -> {
      // this span shows up in the parent trace as expected 
      Span asyncSpan = tracer.spanBuilder("DGS Executor async op")
        .setParent(currentSpanContext.with(currentSpan))
        .startSpan();
      try {
        // Async thread context
        LocaleContextHolder.setLocale(locale);
        log.debug("Restoring information for async thread...");
        // however the spans in the thread fail to obtain the parent trace and are not linked to it
        runnable.run();
      } catch (Exception e) {
        log.error("Error in async task", e);
      } finally {
        asyncSpan.end();
        log.debug("DgsDataLoader has finished async execution");
      }
    };
  }
}

但是,跨度无法传播到线程,并且会为在该线程中创建的跨度创建一个新的父级。

我已经能够通过创建一个自定义上下文来解决这个问题,我在其中打包当前跨度

Span asyncSpan = tracer.spanBuilder("DGS Executor async op")
  .setParent(currentSpanContext.with(currentSpan))
  .startSpan();
try {
  SpanThreadContextHolder.setTracingSpan(asyncSpan);
  // Async thread context
  LocaleContextHolder.setLocale(locale);
  log.debug("Restoring information for async thread...");
  runnable.run();
} catch (Exception e) {
  log.error("Error in async task", e);
} finally {
  SpanThreadContextHolder.removeTracingSpan();

但这会带来副作用,下游代码必须意识到它并通过

// inside runnable.run()
var span = SpanThreadContextHolder.getOpenTracingSpan()
if (span!= null) {
    Context.current().with(span).makeCurrent()
}

完成所有这些故事后,我的问题是:是否有可能将跨度自动传播到从主线程生成的其他线程,让跨度存在于 THREAD_LOCAL 模式中?

我终于在这里找到了答案。

执行者必须用 otel 上下文包装:

public ExecutorService wrapExecutor(ExecutorService executor) {
  return Context.taskWrapping(executor);
} 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM