= Vertx OpenTracing

Vert.x integrates with OpenTracing thanks to the Jaeger client.

You can configure Vert.x to use the Jaeger client configured via
https://github.com/jaegertracing/jaeger-client-java/blob/master/jaeger-core/README.md#configuration-via-environment[Environment]

[source,java]
----
Vertx vertx = Vertx.vertx(new VertxOptions()
  .setTracingOptions(
    new OpenTracingOptions()
  )
);
----

You can also pass a custom `Tracer` allowing for greater control
over the configuration.

[source,java]
----
Vertx vertx = Vertx.vertx(new VertxOptions()
  .setTracingOptions(
    new OpenTracingOptions(tracer)
  )
);
----

== Tracing policy

The tracing policy defines the behavior of a component when tracing is enabled:

- `link:../../apidocs/io/vertx/core/tracing/TracingPolicy.html#PROPAGATE[PROPAGATE]`: the component reports a span in the active trace
- `link:../../apidocs/io/vertx/core/tracing/TracingPolicy.html#ALWAYS[ALWAYS]`: the component reports a span in the active trace or creates a new active trace
- `link:../../apidocs/io/vertx/core/tracing/TracingPolicy.html#IGNORE[IGNORE]`: the component will not be involved in any trace.

The tracing policy is usually configured in the component options.

== HTTP tracing

The Vert.x HTTP server and client reports span around HTTP requests:

- `operationName`: the HTTP method
- tags
 - `http.method`: the HTTP method
 - `http.url`: the request URL
 - `http.status_code`: the HTTP status code

The default HTTP server tracing policy is `ALWAYS`, you can configure the policy with `link:../../apidocs/io/vertx/core/http/HttpServerOptions.html#setTracingPolicy-io.vertx.core.tracing.TracingPolicy-[setTracingPolicy]`

[source,java]
----
HttpServer server = vertx.createHttpServer(new HttpServerOptions()
  .setTracingPolicy(TracingPolicy.IGNORE)
);
----

The default HTTP client tracing policy is `PROPAGATE`, you can configure the policy with `link:../../apidocs/io/vertx/core/http/HttpClientOptions.html#setTracingPolicy-io.vertx.core.tracing.TracingPolicy-[setTracingPolicy]`

[source,java]
----
HttpClient client = vertx.createHttpClient(new HttpClientOptions()
  .setTracingPolicy(TracingPolicy.IGNORE)
);
----

To initiate a trace for a client call, you need to create it first and make Vert.x
aware of it by using `OpenTracingUtil.setSpan`:

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

In an HTTP scenario between two Vert.x services, a span will be created client-side, then
the trace context will be propagated server-side and another span will be added to the trace.

== EventBus tracing

The Vert.x EventBus reports spans around message exchanges.

The default sending policy is `PROPAGATE`, you can configure the policy with `link:../../apidocs/io/vertx/core/eventbus/DeliveryOptions.html#setTracingPolicy-io.vertx.core.tracing.TracingPolicy-[setTracingPolicy]`.

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

== Obtain current Span

Vert.x stores current `Span` object in local context.
To obtain it, use method `OpenTracingUtil.getSpan()`.

This method will work only on Vert.x threads (instances of `VertxThread`).
Obtaining from non-Vert.x thread doesn't work by design, method will return null.

== Coroutines support

There is no direct support for coroutines, but it can be achieved with minimal instrumentation.

There are several steps to achieve this.

1. Use `CoroutineVerticle`.
2. Convert *every route handler* you have to a coroutine.
3. Use CoroutineContext to store `Tracer` and current `Span` object


Example code:

[source,kotlin]
----
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. Creates a coroutine handler with `coroutineHandler` extension method.
2. Creates usual async handler, which is then wrapped to a coroutine.
3. Extension method to convert `Handler<RoutingContext>` to suspendable function.
4. Extension method which creates and launches a coroutine on Vert.x EventLoop.
5. Get current `Span` from Vert.x local context (populated automatically).
6. Create a wrapper coroutine, add current Span to `CoroutineContext`.
7. Retrieve a `Span` from coroutine context.
8. Either reuse `span` or create a new span with `tracer.buildSpan("").asChildOf(span).start()`.
9. Put a new `Span` to a context.
10. Finish `childSpan`, if you created a new one.

Helper code, your implementation may vary:

[source,kotlin]
----
/**
* 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"
----