Vertx OpenTracing

已弃用

Vert.x 借助 Jaeger 客户端集成了 OpenTracing。

你可以将 Vert.x 配置为使用通过环境变量配置的 Jaeger 客户端。

Vertx vertx = Vertx.vertx(new VertxOptions()
  .setTracingOptions(
    new OpenTracingOptions()
  )
);

你也可以传入一个自定义的 Tracer,以便对配置进行更精细的控制。

Vertx vertx = Vertx
  .builder()
  .withTracer(o -> new OpenTracingTracer(false, tracer))
  .build();

追踪策略

追踪策略定义了当追踪启用时组件的行为。

  • PROPAGATE:组件在当前活跃的追踪中报告一个 span。

  • ALWAYS:组件在当前活跃的追踪中报告一个 span,或者创建一个新的活跃追踪。

  • IGNORE:组件将不参与任何追踪。

追踪策略通常在组件选项中配置。

HTTP 追踪

Vert.x HTTP 服务器和客户端围绕 HTTP 请求报告 span。

  • operationName:HTTP 方法

  • 标签

  • http.method:HTTP 方法

  • http.url:请求 URL

  • http.status_code:HTTP 状态码

默认的 HTTP 服务器追踪策略是 ALWAYS,你可以通过 setTracingPolicy 来配置该策略。

HttpServer server = vertx.createHttpServer(new HttpServerOptions()
  .setTracingPolicy(TracingPolicy.IGNORE)
);

默认的 HTTP 客户端追踪策略是 PROPAGATE,你可以通过 setTracingPolicy 来配置该策略。

HttpClient client = vertx.createHttpClient(new HttpClientOptions()
  .setTracingPolicy(TracingPolicy.IGNORE)
);

要为客户端调用启动追踪,你需要首先创建它,并通过使用 OpenTracingUtil.setSpan 让 Vert.x 感知到它。

Span span = tracer.buildSpan("my-operation")
  .withTag("some-key", "some-value")
  .start();
OpenTracingUtil.setSpan(span);
// Do something, e.g. client request
span.finish();

在两个 Vert.x 服务之间的 HTTP 场景中,一个 span 将在客户端创建,然后追踪上下文将传播到服务端,并向追踪添加另一个 span。

EventBus 追踪

Vert.x EventBus 围绕消息交换报告 span。

默认的发送策略是 PROPAGATE,你可以通过 setTracingPolicy 来配置该策略。

DeliveryOptions options = new DeliveryOptions().setTracingPolicy(TracingPolicy.ALWAYS);
vertx.eventBus().send("the-address", "foo", options);

获取当前 Span

Vert.x 在本地上下文中存储当前的 Span 对象。要获取它,请使用方法 OpenTracingUtil.getSpan()

此方法仅在 Vert.x 线程(VertxThread 的实例)上有效。从非 Vert.x 线程获取 span 在设计上是无效的,方法将返回 null。

协程支持

没有对协程的直接支持,但可以通过最少的插桩来实现。

实现此目标有几个步骤。

  1. 使用 CoroutineVerticle

  2. 将你拥有的每个路由处理器转换为协程。

  3. 使用 CoroutineContext 存储 Tracer 和当前 Span 对象。

示例代码

class TracedVerticle(private val tracer: Tracer): CoroutineVerticle() {
    override suspend fun start() {
        val router = Router.router(vertx)

        router.route("/hello1")
            .method(HttpMethod.GET)
            .coroutineHandler { ctx ->                          // (1)
                launch { println("Hello to Console") }
                ctx.end("Hello from coroutine handler")
            }

        router.route("/hello2")
            .method(HttpMethod.GET)
            .coroutineHandler(::nonSuspendHandler)              // (2)

        vertx.createHttpServer()
            .requestHandler(router)
            .listen(8080)
            .await()
    }

    private fun nonSuspendHandler(ctx: RoutingContext) {
        ctx.end("Hello from usual handler")
    }

    private fun Route.coroutineHandler(handler: Handler<RoutingContext>): Route = // (3)
        this.coroutineHandler(handler::handle)

    private fun Route.coroutineHandler(                                           // (4)
        handler: suspend (RoutingContext) -> (Unit)
    ): Route = handler { ctx ->
        val span: Span = OpenTracingUtil.getSpan()                                // (5)
        launch(ctx.vertx().dispatcher() + SpanElement(tracer, span)) {            // (6)
            val spanElem = coroutineContext[SpanElement]                          // (7)
            if (spanElem == null) {
                handler(ctx)
            } else {
                val span = spanElem.span
                val tracer = spanElem.tracer
                val childSpan = span                                                // (8)
                try {
                    withContext(SpanElement(tracer, childSpan)) { handler(ctx) }    // (9)
                } finally {
                    // childSpan.finish()                                           // (10)
                }
            }
            // OR create a helper method for further reuse
            withContextTraced(coroutineContext) {
                try {
                    handler(ctx)
                } catch (t: Throwable) {
                    ctx.fail(t)
                }
            }
        }
    }
}
  1. 使用 coroutineHandler 扩展方法创建协程处理器。

  2. 创建常规的异步处理器,然后将其封装到协程中。

  3. Handler<RoutingContext> 转换为可挂起函数的扩展方法。

  4. 在 Vert.x EventLoop 上创建并启动协程的扩展方法。

  5. 从 Vert.x 本地上下文获取当前 Span(自动填充)。

  6. 创建一个包装协程,将当前 Span 添加到 CoroutineContext

  7. 从协程上下文检索 Span

  8. 要么重用 span,要么使用 tracer.buildSpan("").asChildOf(span).start() 创建一个新 span。

  9. 将新的 Span 放入上下文。

  10. 如果你创建了新的 childSpan,请完成它。

辅助代码,你的实现可能有所不同

/**
* Keeps references to a tracer and current Span inside CoroutineContext
*/
class SpanElement(val tracer: Tracer, val span: Span) :
    ThreadContextElement<Scope>,
    AbstractCoroutineContextElement(SpanElement) {

    companion object Key : CoroutineContext.Key<SpanElement>

    /**
    *  Will close current [Scope] after continuation's pause.
    */
    override fun restoreThreadContext(context: CoroutineContext, oldState: Scope) {
        oldState.close()
    }

    /**
    * Will create a new [Scope] after each continuation's resume, scope is activated with provided [span] instance.
    */
    override fun updateThreadContext(context: CoroutineContext): Scope {
        return tracer.activateSpan(span)
    }
}

/**
* Advanced helper method with a few options, also shows how to use MDCContext to pass a Span to a logger.
*/
suspend fun <T> withContextTraced(
    context: CoroutineContext,
    reuseParentSpan: Boolean = true,
    block: suspend CoroutineScope.() -> T
): T {
    return coroutineScope {
        val spanElem = this.coroutineContext[SpanElement]

        if (spanElem == null) {
            logger.warn { "Calling 'withTracer', but no span found in context" }
            withContext(context, block)
        } else {
            val childSpan = if (reuseParentSpan) spanElem.span
            else spanElem.tracer.buildSpan("").asChildOf(spanElem.span).start()

            try {
                val mdcSpan = mapOf(MDC_SPAN_KEY to childSpan.toString())
                withContext(context + SpanElement(spanElem.tracer, childSpan) + MDCContext(mdcSpan), block)
            } finally {
                if (!reuseParentSpan) childSpan.finish()
            }
        }
    }
}
private const val MDC_SPAN_KEY = "request.span.id"