deltaprojects / lagom-opentracing

OpenTracing helpers for the Lagom Framework

GitHub

OpenTracing for the Lagom Framework

Maven Central

Note that this software comes without warranty. It is used in production at Delta Projects, but it may not be suited for your use case. Lightbend provides commercial OpenTracing support in the form of Lightbend Telemetry https://developer.lightbend.com/docs/cinnamon/2.5.x/home.html

Lagom

Lagom is a microservices framework by Lightbend for Scala and Java based on Akka and Play. For more information, visit https://www.lagomframework.com/.

OpenTracing

OpenTracing is a vendor neutral specification for tracing distributed systems. For more information, visit http://opentracing.io/.

Lagom + OpenTracing

This package provides simple helper methods for dealing with the OpenTracing headers in Lagom service calls.

SBT

libraryDependencies += "com.deltaprojects" %% "lagom-opentracing" % "0.2.3"

When loading your services you should register a global tracer

val tracer: Tracer = ... // your favourite Tracer implementation

GlobalTracer.register(tracer)

HTTP requests

Server Usage

class HelloServiceImpl extends HelloService {
  override def sayHello = ServiceCall { name =>
    Future.successful(s"Hello $name!")
  }
}
  override def yourServiceHandler: ServerServiceCall[Request, Response] =
    trace("OPERATION_NAME") { scope =>
      ServerServiceCall { request =>
        scope.span.setBaggageItem("user", request.user)
        ...
      }
    }

Note that there is no need to close the scope manually, it will be closed automatically when the handler returns.

Client Usage

val scope = tracer.buildSpan("Handling your service").startActive(true)

yourServiceClient
.handleRequestHeaders(addTracingHeaders)
.yourServiceHandler
.invoke()
.map(response => {
    scope.span.log("Received response")
    ...
    scope.close()
})

CQRS

You can also trace commands and domain events. Commands and events have the same API. The examples below are from the official Lagom documentation, sprinkled with some tracing.

Command and Event Usage

final case class AddPost(content: PostContent) extends BlogCommand with TracedCommand[BlogCommand] with ReplyType[AddPostDone]

sealed trait BlogEvent extends AggregateEvent[BlogEvent] with TracedEvent[BlogEvent] {
  override def aggregateTag: AggregateEventShards[BlogEvent] = BlogEvent.Tag
}

final case class PostAdded(postId: String, content: PostContent) extends BlogEvent


override def addPost(id: String) = ServiceCall { request =>
  val ref = persistentEntities.refFor[Post](id)
  ref.ask(request.withTracing).map(ack => "OK") // with tracing!
}
...

override def behavior: Behavior =
  Actions()
    .onCommand[AddPost, AddPostDone] {
      case (com@AddPost(content), ctx, state) if state.isEmpty =>
        val scope = com.extractScope("AddPost")
        ctx.thenPersist(PostAdded(entityId, content).withTracing) { evt => // with more tracing!
          scope.close()
          ctx.reply(AddPostDone(entityId))
        }
    }
    .onEvent {
      case (ev@PostAdded(postId, content), state) =>
        val scope = ev.extractScope("PostAdded")
        scope.span.setBaggageItem("content", content)
        scope.close()
        BlogState(Some(content), published = false)
    }
    .onReadOnlyCommand[GetPost.type, PostContent] {
      case (com@GetPost, ctx, state) if !state.isEmpty =>
        val scope = com.extractScope("GetPost")
        scope.close()
        ctx.reply(state.content.get)
    }

TODO

  • Support HTTP tracing
  • Support CQRS tracing
  • Implement Tests
  • Support adding arbitrary OpenTracing header Tags
  • Support the Java API

Pull requests welcome! :)