Code Monkey home page Code Monkey logo

scala-opentracing's Introduction

Scala Opentracing

Get Scala Opentracing

Download from maven

Scala-opentracing

Scala Opentracing

Scala Opentracing is a Scala wrapper around the Opentracing library for Java, along with utilities to monitor Http4s applications. It was originally developed to address our needs for Datadog APM instrumentation at Colisweb.

It is being used in production at Colisweb.

Goals

  • Provide Datadog instrumentation for our Scala applications : this tracing library should work with any Opentracing compatible tracer, like Jaeger, but it has only been tested with Datadog so far.
  • Stay out of the way : we don't want the tracing system to get in the way of our business logic, so
  • we try to reduce to a minimum the amount of boilerplate required to use this library. Furthermore, Spans are automatically closed by a Resource from Cats effect for safety and convenience.
  • Be idiomatic : we try to follow the principles of functional programming in Scala, and wrap all side-effects of the Java libraries into algebraic effects.

Installation

And add the core library to your dependencies :

  libraryDependencies += "com.colisweb" %% "scala-opentracing-core" % "2.5.0"

Usage

Creating a TracingContext explicitly

A TracingContext[F[_]] represents some unit of work, which can spawn child units of work.

This library provides multiples instances of TracingContext:

  • OpenTracingContext
  • DDTracingContext
  • LoggingTracingContext
  • NoOpTracingContext

All of these contexts can be added to your project by adding the following import:

  libraryDependencies += "com.colisweb" %% "scala-opentracing-context" % "2.5.0"

If you use Datadog, create a DDTracingContext, otherwise use OpenTracingContext. Both will rely on some Tracer, provided by whatever tracing library you use (Jaeger, Datadog ...).

In the following examples, we'll use IO from Cats effect as our F but you can use any monad that satisfies the Sync Typeclass, like Task from Monix.

import io.opentracing._
import com.colisweb.tracing.context._

val tracer: Tracer = ???
val tracingContextBuilder: IO[TracingContextBuilder[IO]] = OpenTracingContext.builder[IO, Tracer, Span](tracer)

All contexts possess a builder method which return a F[TracingContextBuilder[F]] Arguments that needs to be passed may differ based on the implementation. However, TracingContextBuilder[F] possess a single method which take the operation name and some tags.

Creating a TracingContext for Datadog

Datadog requires that you create a tracer and then register it as the "Global tracer" for your application.

For convenience and purity, this library creates and register the tracer itself.

import com.colisweb.tracing.context.datadog._

val tracingContextBuilder: IO[TracingContextBuilder[IO]] = DDTracingContext.builder[IO]("your-service-name")

Don't evaluate this effect multiple times, as it will recreate a new tracer every time. Instead, use it in the main of your application, and propagate the TracingContextBuilder down where you need it.

Logging all your traces for development

For development or fast prototyping purposes, you can also use LoggingTracingContext, that will log all your operations to the standard output, along with the time every operation took in milliseconds.

val tracingContextBuilder: IO[TracingContextBuilder[IO]] = LoggingTracingContext.builder[IO]()

To see the logs, make sur the trace level is enabled in SLF4J.

Once you have a TracingContextBuilder[F[_]], you can use to wrap your computations.

// You can pass tags as a Tags
val result: IO[Int] = tracingContextBuilder("Heavy mathematics", Map.empty) use { _ =>
  IO { 42 - 5 }
}

Notice how the context is wrapped inside a Resource[IO, TracingContext]. This means the span will automatically closed at the end of the computation. You can use the TracingContext you get from the Resource to create a child computation :

val result: IO[Int] = tracingContextBuilder("Parent context", Map.empty) use { parentCtx =>
  // Some work here ...
  parentCtx.span("Child context") use { _ =>
    IO { /* And some work there */ }
  } *> parentCtx.span("Sibling context") use { _ =>
    IO { 20 + 20 }
  }
}

// Result value will 20. The Spans graph will look like this :
// <------------------ Parent context ------------------>
//   <---- Child context ----> <---- Sibling context -->

If you don't need to create child contexts, you can import the wrap convenience method by importing com.colisweb.tracing.implicits._.

import com.colisweb.tracing.core.implicits._

val result: IO[Int] = tracingContextBuilder("Parent context", Map.empty) wrap IO {
  // Some work
  78 + 12
}

Working with monad transformers (OptionT, EitherT)

Sometimes, you will need to trace a computation and get back an EitherT[F, Error, B] or OptionT[F, A] instead of a regular F[A]. For convinience, this library also provides the either and option operations on Resource[F, A].

import com.colisweb.tracing.core.implicits._

val computation: Option[IO, Int] = ???

val result: OptionT[IO, Int] = tracingContextBuilder("Parent context", Map.empty) option computation
import com.colisweb.tracing.core.implicits._

val computation: EitherT[IO, Throwable, Int] = ???

val result: EitherT[IO, Throwable, Int] = tracingContextBuilder("Parent context", Map.empty) either computation

Tracing Http4s services

This library provides TracedHttpRoutes[F[_]], a function that works just like HttpRoutes.of from Http4s, except in wraps all requests in a tracing context, which you can retrieve in your routes to instrument subsequent computations.

To create a TracedHttpRoutes[F[_]], you will need an implicit TracingContextBuilder[F[_]] in scope. You can then retrieve the tracing context with the using extractor from com.colisweb.tracing.TracedHttpRoutes._

All of these functionnalities are present in the http package of the library.

libraryDependencies += "com.colisweb" %% "scala-opentracing-http4s-server-tapir" % "2.5.0"
import org.http4s.dsl.io._
import com.colisweb.tracing.http.server.TracedHttpRoutes
import com.colisweb.tracing.http.server.TracedHttpRoutes._
import com.colisweb.tracing.core.TracingContextBuilder

object MyRoutes {
  def routes(implicit tracingContextBuilder: TracingContextBuilder[IO]): HttpRoutes[IO] =
    TracedHttpRoutes[IO] {
      case (req @ POST -> Root / "endpoint") using tracingContext =>
        val result = tracingContext.span("Some computation") wrap IO {
          // Something here ...
        }

        result.flatMap(Ok(_))
    }
}

Using Tapir

tapir, or Typed API descRiptions is a fantastic library that allows you to define http endpoints using Scala's type system, and separate those definitions from your actual business logic. Tapir definitions can be interpreted into http4s HttpRoutes.

The package scala-opentracing-tapir provides a small integration layer that allows you to create traced http endpoints from tapir Endpoint definitions.

import cats.effect.IO
import tapir._
import com.colisweb.tracing.http.server._

val myEndpoint: Endpoint[Unit, Unit, String, Nothing] =
  endpoint.get.in("/hello").out(stringBody)

val routes: HttpRoutes[IO] = myEndpoint.toTracedRoute[IO](
  (input, ctx) => ctx.span("Some description") use { _ =>
    IO.pure(Right("OK"))
  }
)

You will need an implicit ContextShift[F] in scope for this to work. Take a look at the tests for further examples.

If your error type extends Throwable, you can also use toTracedRouteRecoverErrors, a traced equivalent of toTracedRoute from tapir-http4s-server.

Correlating your logs

If you want to link your traces and your logs together, you can do it manually by adding the correlationId of the context to your messages. A PureLogger is available in the core module. It wraps a logger with a Sync[_].

import com.colisweb.tracing.core.PureLogger
import org.slf4j.{Logger, LoggerFactory}

val logger: Logger = LoggerFactory.getLogger(getClass)
val pureLogger: PureLogger[IO] = PureLogger(logger)

val result: IO[Int] = tracingContextBuilder("Heavy mathematics", Map.empty) use { ctx =>
    pureLogger.debug("Doing stuff. correlation id : {}", ctx.correlationId) *> IO {
    // Some computation here
  }
} 

Automatic correlation for Datadog

DDTracingContext[F[_]] possess internally a spanId and a traceId. We provide a wrapper around Logger that is TracingLogger from SLF4J that will automatically add the spanId and traceId to your logs. The logging side-effect is already wrapped in F for purity.

You can directly use the TracingLogger to set your own markers. These will be added to all logs.

import org.http4s.dsl.io._
import com.typesafe.scalalogging.StrictLogging
import com.colisweb.tracing.http.server.TracedHttpRoutes
import com.colisweb.tracing.http.server.TracedHttpRoutes._
import com.colisweb.tracing.core._

object MyRoutes extends StrictLogging {
  // You will need an implicit Logger from slf4j to use the logging facility
  implicit val slf4jLogger: org.slf4j.Logger = logger.underlying

  def routes(implicit tracingContextBuilder: TracingContextBuilder[IO]): HttpRoutes[IO] =
    TracedHttpRoutes[IO] {
      case (req @ POST -> Root / "endpoint") using tracingContext =>
        val result = tracingContext.span("Some computation") use { ctx =>
          ctx.logger.debug("Doing stuff") *> IO {
            // Something here ...
          }
        } 

        result.flatMap(Ok(_))
    }
}

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Make sure to follow our Code of Conduct.

Here are some ideas of improvements :

  • Right now this library does not support distributed tracing, i.e the ability to continue a serialized trace from another application and/or send an unfinished trace to another application.
  • The LoggingTracingContext does not support tags at the moment. Adding tags to a LoggingTracingContext will have no effect.

License

MIT

scala-opentracing's People

Contributors

colisweb-gitlab avatar cverdier avatar gbogard avatar litlbro avatar tyrcho avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scala-opentracing's Issues

Better dependency management

This project makes a number of assumptions about the dependencies that will be used with tapir. I think it would be cleaner if core had fewer dependencies - for example, I think it should rely on slf4j-api instead of logback-classic. In addition, there should be a few subprojects to support tracing + http4s and tracing + akka http.

Integration with cats-effect-3

Cats effect 3 is going to release in mid march. There is already a RC2 release. I could not find a branch for this. Are there plans to migrate to ce3?

Update README

Current README seems to be outdated. I had to spent half an hour to actually figure out how to compile http4s example (You can see the updated example in #4). It looks like that there was a quite a lot refactoring and moving thing around (no more scala-opentracing ) and even new packages (scala-opentracing-core) but none of those thing actually mentioned anywhere.

I would suggest to:

  • List available packages, add latest version badges and describe their usage so user doesn't have to dig https://bintray.com/colisweb/maven
  • Revisit example codes and fix the ones that are broken

Question: Context Propagation

I've been testing out this library with datadog but I can't get the traces between client and server to relate at all in datadog's UI. I was able to view the spans individually by tagging them all with a unique id, but I don't see how this correlates the traces without passing on the trace id, etc. like b3-propagation does. Any thoughts?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.