In this post we’ll show how to take a simple Scalatra project and set up basic monitoring with Kamon. This is a really simplified example and also we will skip some issues related to the installation, because there are awesome tutorials about that. Having said that, let’s get to it!

Build Setup

We need to include in our Build.scala some dependencies. It should look like this:

val dependencies = Seq(
    "io.kamon"    	          %% "kamon-core"           	  % "0.4.0",
     // [Optional]
    "io.kamon"    	          %% "kamon-scala"                % "0.4.0",
     // [Optional]
    "io.kamon"    	          %% "kamon-log-reporter"   	  % "0.4.0",
     // ... and so on with all the others dependencies you need.
    )

val main = Project(appName, file(".")).settings(libraryDependencies ++= dependencies)
              .settings(defaultSettings: _*)
              .settings(aspectjSettings ++ AspectJ.aspectjSettings) //(1)

The only reason why We need to register the AspectJ weaver is to automatically propagating the TraceContext across the asynchronous operations that might be scheduled for a given Future.

Also note that there is no kamon-scalatra module, everything we are showing in this post is just using the APIs provided by kamon-core to monitor your application. This might also serve as inspiration for other people to get other web frameworks working with Kamon as well.

Create a Simple Servlet

Let’s start by creating a convenient trait that will make it easier to add Kamon instruments to our servlets:

trait KamonSupport {
  def counter(name: String) = Kamon.metrics.counter(name)
  def minMaxCounter(name: String) = Kamon.metrics.minMaxCounter(name)
  def time[A](name: String)(thunk: => A) = Latency.measure(Kamon.metrics.histogram(name))(thunk)
  def traceFuture[A](name:String)(future: => Future[A]):Future[A] =
    Tracer.withContext(Kamon.tracer.newContext(name)) {
     future.andThen { case completed ⇒ Tracer.currentContext.finish() }(SameThreadExecutionContext)
   }
}

Then we create a Servlet that will record some metrics. In order to achieve this we mix our KamonSupport to use the provided methods.

class KamonServlet extends ScalatraServlet with KamonSupport with FutureSupport {
  ...
  get("/time") {
    time("time") {
      Thread.sleep(Random.nextInt(100))
    }
  }

  get("/minMaxCounter") {
    minMaxCounter("minMaxCounter").increment()
  }

  get("/counter") {
    counter("counter").increment()
  }

  get("/async") {
    traceFuture("retrievePage") {
      Future {
        HttpClient.retrievePage()
      }
    }
  }
  ...
}

now we have 5 URLs that we can hit:

  • GET /kamon/time
  • GET /kamon/counter
  • GET /kamon/minMaxCounter
  • GET /kamon/async

Bootstrapping Scalatra with Kamon

We will need to bootstrap Scalatra and hook Kamon into it’s lifecycle and the best place for this is using ScalatraBootstrap’s init and destroy hooks as shown below:

class ScalatraBootstrap extends LifeCycle {
  override def init(context: ServletContext):Unit = {
    Kamon.start() //(1)
    context.mount(new KamonServlet(), "/kamon")
  }

  override def destroy(context: ServletContext):Unit = {
    Kamon.shutdown() //(2)
  }
}
  1. To access the metrics and tracing APIs, and to ensure that all Kamon modules are loaded you will need to start Kamon by using the Kamon.start(..) method.
  2. When you are done with Kamon, remember to shut it down using the Kamon.shutdown() method.

Select a Kamon Backend

This time will we use the kamon-log-reporter. This module is not meant to be used in production environments, but it certainly is a convenient way to test Kamon and know in a quick manner what’s going on with our application in development, moreover like all Kamon modules it will be picked up from the classpath and started at runtime, just by adding the dependency to our classpath we are good to go.

Additionally we can find more info about how to configure modules and supported backends(StatsD, Datadog, New Relic and Your Own) in the docs.

Start the Server

Scalatra uses Jetty internally, and it is in itself a simple java servlet. So what we can do is just run an embedded Jetty instance that mounts the servlet and configures it.

object EmbeddedServer extends App {
  val server = new Server(8080)
  val context: WebAppContext = new WebAppContext()

  context.setServer(server)
  context.setContextPath("/")
  context.setWar("src/webapp")
  server.setHandler(context)

  try {
    server.start()
    server.join()
  } catch {
    case e: Exception =>
      e.printStackTrace()
      System.exit(1)
  }
}

We can run this application directly by typing sbt run from the console. The output we will see will be something like this if we hit some of the endpoints we’ve set up.

  • curl http://localhost:8080/kamon/time
  • curl http://localhost:8080/kamon/counter
  • curl http://localhost:8080/kamon/minMaxCounter
+------------------------------------------------------------------------------------------------+
|                                                                                                |
|                                         Counters                                               |
|                                       -------------                                            |
|                                    counter  =>  1                                              |
|                                                                                                |
|                                        Histograms                                              |
|                                      --------------                                            |
|  time                                                                                          |
|    Min: 57671680     50th Perc: 57671680       90th Perc: 57671680       95th Perc: 57671680   |
|                      99th Perc: 57671680     99.9th Perc: 57671680             Max: 57671680   |
|                                                                                                |
|                                      MinMaxCounters                                            |
|                                    -----------------                                           |
|  minMaxCounter                                                                                 |
|          Min: 0                      Average: 0.0                         Max: 1               |
|                                                                                                |
+------------------------------------------------------------------------------------------------+

Ok, but show me the money!

So far so good. But what if my route returns a Future?. The answer is simple: Kamon has your back, the body of the future will be executed asynchronously on some other thread in a provided ExecutionContext, but Kamon, through bytecode instrumentation, will capture the TraceContext available when the Future was created and make it available while executing the future’s body.

Let’s run the application with sbt run and we measure the async operation.

  • curl http://localhost:8080/kamon/async
+------------------------------------------------------------------------------------------------+
|                                                                                                |
|    Trace: retrievePage                                                                         |
|    Count: 1                                                                                    |
|                                                                                                |
|  Elapsed Time (nanoseconds):                                                                   |
|    Min: 2164260864   50th Perc: 2164260864     90th Perc: 2164260864     95th Perc: 2164260864 |
|                      99th Perc: 2164260864   99.9th Perc: 2164260864           Max: 2164260864 |
|                                                                                                |
+------------------------------------------------------------------------------------------------+

For a more detailed explanation about the Kamon tracing module and Automatic TraceContext Propagation with Futures please start here.

Enjoy!

There it is, all your metrics data available to be sent into whatever tool you like. From here, you should be able to instrument your applications as needed.

We also encourage you to review the full source code of Scalatra Kamon Example used in this tutorial.

Thanks to Carlos Ferreyra for the review.