Vertx vertx = Vertx.vertx(new VertxOptions()
.setTracingOptions(
new OpenTracingOptions()
)
);
Vertx OpenTracing
Vert.x 借助 Jaeger 客户端集成了 OpenTracing。
你可以将 Vert.x 配置为使用通过环境变量配置的 Jaeger 客户端。
你也可以传入一个自定义的 Tracer
,以便对配置进行更精细的控制。
Vertx vertx = Vertx
.builder()
.withTracer(o -> new OpenTracingTracer(false, tracer))
.build();
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。
协程支持
没有对协程的直接支持,但可以通过最少的插桩来实现。
实现此目标有几个步骤。
-
使用
CoroutineVerticle
。 -
将你拥有的每个路由处理器转换为协程。
-
使用 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)
}
}
}
}
}
-
使用
coroutineHandler
扩展方法创建协程处理器。 -
创建常规的异步处理器,然后将其封装到协程中。
-
将
Handler<RoutingContext>
转换为可挂起函数的扩展方法。 -
在 Vert.x EventLoop 上创建并启动协程的扩展方法。
-
从 Vert.x 本地上下文获取当前
Span
(自动填充)。 -
创建一个包装协程,将当前 Span 添加到
CoroutineContext
。 -
从协程上下文检索
Span
。 -
要么重用
span
,要么使用tracer.buildSpan("").asChildOf(span).start()
创建一个新 span。 -
将新的
Span
放入上下文。 -
如果你创建了新的
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"