Documentation

TraceContext Manipulation

Collecting trace information is all about interacting with the tracing API and the current TraceContext to store information as it is being generated throughout your application. This section will teach all the basic operations that allow you to create, enhance and finish a TraceContext and segments related to it.

Please note that even while understanding how to manipulate a TraceContext is very important, some Kamon modules such as our Akka, Scala, Spray and Play! modules already provide bytecode instrumentation that automatically creates, propagates and finishes traces and segments in specific conditions, so, you might not need to ever manipulate a TraceContext yourself.

Creating and Finishing a TraceContext

You can create a new TraceContext by using the .newContext(..) methods available in the Kamon.tracer member. As described in the getting started section, make sure you start Kamon before using this API. When you create a new context you need to at least provide a name for it, which you can change at any point of time before finishing the trace. Additionally, you can provide a trace token to identify the trace, if you don’t, Kamon will generate one for you.

  val testTrace = Kamon.tracer.newContext("test-trace")
  val testTraceWithToken = Kamon.tracer.newContext("trace-with-token", Some("token-42"))

  // You can rename a trace before it is finished.
  testTrace.rename("cool-functinality-X")
  testTrace.finish()

  // And you can also access it's token if you want to.
  println("Test Trace Token: " + testTrace.token)
    final TraceContext testTrace = Kamon.tracer().newContext("test-trace");
    final TraceContext testTraceWithToken = Kamon.tracer().newContext("trace-with-token", Some.apply("token-42"));

    // You can rename a trace before it is finished.
    testTrace.rename("cool-functinality-X");
    testTrace.finish();

    // And you can also access it's token if you want to.
    System.out.println("Test Trace Token: " + testTrace.token());

Finishing a TraceContext, as shown in the example is just about calling the .finish() method on a context. Once a trace is finished it can no longer be renamed again.

Additionally, the Tracer companion object provides alternative .withNewTraceContext(..) methods that will not only create a new TraceContext for you, but also make it the the current trace context and optionally finishing it after the supplied piece of code finishes it’s execution as shown in the example bellow:

  Tracer.withNewContext("GetUserDetails", autoFinish = true) {
    // While this block of codes executes a new TraceContext
    // is set as the current context and finished after the
    // block returns.
    println("Current Trace Token: " + Tracer.currentContext.token)

    "Some awesome result";
  }

  // No TraceContext is present when you reach this point.
    Tracer.withNewContext("GetUserDetails", true, ()-> {
      // While this block of codes executes a new TraceContext
      // is set as the current context and finished after the
      // block returns.
      System.out.println("Current Trace Token: " + Tracer.currentContext().token());

      return "Some awesome result";
    });

    // No TraceContext is present when you reach this point.

It is really important to understand that the new TraceContext will only be available during the execution of the specified code block and removed from the trace context storage once the method returns.

TraceContext Storage

You might have noticed that we mentioned “the current trace context” in the section above. When we say this, we refer to the TraceContext associated with the trace being executed in the current thread, which is effectively stored in a internal trace-local variable. Please read the threading model considerations section for advice related to propagating a TraceContext depending on your application’s threading model.

The Tracer companion object provides the required APIs to get, set and clear the context that is available to the current thread. It is very important to ensure that you always clean up the threads once you don’t need a TraceContext as current anymore, thus, it will be better for you if instead of using the Tracer.setCurrentContext(...) and Tracer.clearCurrentContext() functions directly you work with the Tracer.withContext(...) variants which will ensure that the trace context storage is set to whatever was there before once the specified piece of code finishes execution.

  val context = Kamon.tracer.newContext("example-trace")

  Tracer.withContext(context) {
    // While this code executes, `context` is the current
    // TraceContext.
    println("Current Trace Token: " + Tracer.currentContext.token)
  }
    final TraceContext context = Kamon.tracer().newContext("example-trace");

    Tracer.withContext(context, () -> {
      // While this code executes, `context` is the current
      // TraceContext.
      System.out.println("Current Trace Token: " + Tracer.currentContext().token());

      return "Some awesome result";
    });

Note that the Tracer.withContext(...) variants will note ever try to finish a trace once they’re done.

Creating and Finishing Segments

The API for creating segments is not directly available through the Tracer companion object, but rather through the TraceContext instance that you are dealing with. Once you have access to a TraceContext you can call the .startSegment(...) method to get a segment that is tied to that TraceContext.

  Tracer.withNewContext("trace-with-segments", autoFinish = true) {
    val segment = Tracer.currentContext.startSegment("some-cool-section", "business-logic", "kamon")
    // Some application code here.
    segment.finish()
  }
    Tracer.withNewContext("trace-with-segments", true, () -> {
      final Segment segment = Tracer.currentContext().startSegment("some-cool-section", "business-logic", "kamon");
      // Some application code here.
      segment.finish();

      return "done";
    });

Remember that a segment can be finished after the correspondent trace has finished, so don’t feel forced into finishing them while still in the .withContext(...) block, the segment will know for sure what TraceContext it belongs to.

The method mentioned above is the most flexible one, as you can do whatever you want with the segment instance, but the TraceContext also provides the .withNewSegment(...) methods which create a segment and finish it automatically when the supplied code finishes execution.

  Tracer.withNewContext("trace-with-segments", autoFinish = true) {

    Tracer.currentContext.withNewSegment("some-cool-section", "business-logic", "kamon") {
      // Here is where the segment does it's job.

    }
  }
    Tracer.withNewContext("trace-with-segments", true, () -> {

      Tracer.currentContext().withNewSegment("some-cool-section", "business-logic", "kamon", () -> {
        // Here is where the segment does it's job.

        return 0;
      });

      return "done";
    });

Obviously you don’t need to get the context using Tracer.currentContext but you can start a segment in any TraceContext that you have at hand.

Finally, an additional goody for Scala developers is the .withNewAsyncSegment that can create a new segment from a piece of code that returns a Future[T] and automatically finish the segment when the future is completed. Here is how the usage looks like:

  Tracer.withNewContext("trace-with-segments", autoFinish = true) {

    Tracer.currentContext.withNewAsyncSegment("some-cool-section", "business-logic", "kamon") {
      Future {
        // Some code that will be executed asynchronously.
      }
    }
  }