The Futures instrumentation ensures that the same Context
available when a Future is created will be available while
the Future body runs. Same goes for all transformations applied to a Future, like .map
, .flatmap
, and so on.
In the example below, the body of the future will be executed asynchronously on a thread provided by the
ExecutionContext
. When the code runs, Kamon will capture the Context
available when the future was created and make
it available while executing the Future’s body.
Kamon.runWithContextEntry(userID, Some("1234")) {
// The userID Context key is available here,
Future {
// is available here as well.
"Hello Kamon"
}.map(_.length)
.flatMap(len => Future(len.toString))
.map(s => Kamon.currentContext().get(userID))
.map(println)
// And through all async callbacks, even though
// they are executed in different threads!
}
The same Context
propagation works when you transform a Future by calling map/flatMap/filter and friends, or you
directly register a callback on a Future using onComplete/onSuccess/onFailure. Kamon will capture the current Context
available when transforming the Future and make it available while executing the given callback.
The code snippet above would print the same userID
that was available when creating the future, even though it will
happen asynchronously and (probably) in another thread.
Since Kamon 2.1.21 you can use the Kamon.span
function to
wrap a code block and create a Span out of it, regardless of whether the code returns a Future instance or not:
import kamon.Kamon.span
span("myOperationName") {
// Do your stuff here
}
The span
function creates a new Span with the operation name you passed as argument, and additionally it will:
Context
while the wrapped code block executes. This ensures that other Spans you
create later on will be connected to the same trace as the current Span.Future
or a CompletionStage
, then the Span will be finished when the asynchronous computation
finishes.Beware that there is a difference between wrapping an entire Future with Kamon.span
, or only wrapping the code inside
the Future. Take these two example methods:
/**
* The Span counts the Future body's execution time
* and any time spent waiting for the Future to start
* running
*/
def wrappingTheFuture(id: Long): Future[Result] = {
span("getCachedRecord") {
Future {
Result()
}
}
}
/**
* The Span only counts the Future body's execution time,
* ignoring any time spent waiting for the Future to start
* running
*/
def wrappingTheBody(id: Long): Future[Result] = {
Future {
span("getCachedRecord") {
Result()
}
}
}
Scala Futures are always scheduled for execution right away, but that doesn’t mean they will actually run immediately, it all depends on how busy the ExecutionContext
is at the moment. From the code example above:
The Future Chaining instrumentation was deprecated in Kamon 2.2.0 and will be completely removed in Kamon 2.3.0. This is for legacy reference only. Please, don’t write any new code using these functions.
The recommended way to create Spans from Futures is using the Kamon.span
function as described in the section above.
If you need to enable the Future Chaining instrumentation in Kamon 2.2.0+, you must add these settings to your
application.conf
file:
kanela.modules {
executor-service {
exclude += "scala.concurrent.impl.*"
}
scala-future {
enabled = true
}
akka-http {
instrumentations += "kamon.instrumentation.akka.http.FastFutureInstrumentation"
}
}
It is possible to create Spans that represent an entire asynchronous computation, a Future’s body or asynchronous transformations applied to them by using these functions:
trace(operationName: String) { ... }
which accepts a call-by-name block that produces a Future and creates a Span
that will be automatically finished when the Future finishes.traceBody(operationName: String) { ... }
which accepts a call-by-name block that can be used to wrap the Future body
and creates a Span that will be automatically finished when the Future’s body finishes executing.traceFunc(operationName: String) { ... }
which accepts a function that can be used to wrap any of the transformations
than can be applied on a Future and creates a Span that will be automatically finished when the transformation finishes
executing.The traceBody
and traceFunc
functions work very similarly, the only reason for them not being the same is the
mismatch between the types expected when creating a Future and the types expected when applying transformations to them.
Let’s see this in a small example:
import kamon.instrumentation.futures.scala.ScalaFutureInstrumentation.{traceBody, traceFunc}
Future(traceBody("future-body") {
// Here goes the actual future work, same as usual.
"Hello Kamon"
}).map(traceFunc("calculate-length")(_.length))
.flatMap(traceFunc("to-string")(len => Future(len.toString)))
.map(_ => Kamon.currentContext().get(userID))
.map(println)
// And through all async callbacks, even though
// they are executed in different threads!
In the code example above, the instrumentation will automatically create a Span called future-body
that measures how
long did it take to execute the Future’s body, as well as additional Spans for the calculate-length
and to-string
steps of the computation. Note that even though the rest of the transformations in this example will not get dedicated
Spans, they will still benefit from Context propagation, just as before!
Do not use these functions if you are not running your application without the instrumentation agent, since that will lead to threads not being properly cleaned up from previous request’s Contexts.
In case you are not using the Kamon Bundle, add the dependency below to your build.
libraryDependencies += "io.kamon" %% "kamon-scala-future" % "2.5.9"
<dependency>
<groupId>io.kamon</groupId>
<artifactId>kamon-scala-future_2.13</artifactId>
<version>2.5.9</version>
</dependency>
implementation 'io.kamon:kamon-scala-future_2.13:2.5.9'
You must start your application with the instrumentation agent for this module to work properly.