Skip to content

Commit

Permalink
Merge pull request #17 from gvolpe/refactor/removing-ref-from-tracer
Browse files Browse the repository at this point in the history
Making creation of Tracer non-effectful
  • Loading branch information
gvolpe authored Nov 30, 2018
2 parents 3fd8507 + ed377ea commit 3f3bd9a
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 43 deletions.
20 changes: 9 additions & 11 deletions core/src/main/scala/com/github/gvolpe/tracer/Tracer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@

package com.github.gvolpe.tracer

import cats.Applicative
import cats.data.Kleisli
import cats.effect.Sync
import cats.effect.concurrent.Ref
import cats.syntax.all._
import com.gilt.timeuuid.TimeUuid
import org.http4s.syntax.StringSyntax
Expand Down Expand Up @@ -50,35 +50,33 @@ object Tracer extends StringSyntax {

def apply[F[_]](implicit ev: Tracer[F]): Tracer[F] = ev

def create[F[_]: Sync](headerName: String = DefaultTraceIdHeader): F[Tracer[F]] =
Ref.of[F, String](headerName).map(ref => new Tracer[F](ref))
def create[F[_]](headerName: String = DefaultTraceIdHeader): Tracer[F] = new Tracer[F](headerName)

}

class Tracer[F[_]: Sync] private (ref: Ref[F, String]) {
class Tracer[F[_]] private (headerName: String) {

import Trace._, Tracer._

// format: off
def middleware(http: HttpApp[F])(implicit L: TracerLog[Trace[F, ?]]): HttpApp[F] =
def middleware(http: HttpApp[F])(implicit F: Sync[F], L: TracerLog[Trace[F, ?]]): HttpApp[F] =
Kleisli { req =>
val createId: F[(Request[F], TraceId)] =
for {
id <- Sync[F].delay(TraceId(TimeUuid().toString))
tr <- ref.get.map(h => req.putHeaders(Header(h, id.value)))
id <- F.delay(TraceId(TimeUuid().toString))
tr <- F.delay(req.putHeaders(Header(headerName, id.value)))
} yield (tr, id)

for {
mi <- getTraceId(req)
(tr, id) <- mi.fold(createId){ id => (req, id).pure[F] }
_ <- L.info[Tracer[F]](s"$req").run(id)
header <- ref.get
rs <- http(tr).map(_.putHeaders(Header(header, id.value)))
rs <- http(tr).map(_.putHeaders(Header(headerName, id.value)))
_ <- L.info[Tracer[F]](s"$rs").run(id)
} yield rs
}

def getTraceId(request: Request[F]): F[Option[TraceId]] =
ref.get.map(hn => request.headers.get(hn.ci).map(h => TraceId(h.value)))
def getTraceId(request: Request[F])(implicit F: Applicative[F]): F[Option[TraceId]] =
F.pure(request.headers.get(headerName.ci).map(h => TraceId(h.value)))

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2018 com.github.gvolpe
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.gvolpe.tracer
package instances

object tracer {
implicit def defaultTracer[F[_]]: Tracer[F] = Tracer.create[F]()
}
5 changes: 2 additions & 3 deletions core/src/test/scala/com/github/gvolpe/tracer/TracerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ trait TracerFixture extends PropertyChecks {
val customHeaderName = "Test-Id"
val customHeaderValue = "my-custom-value"

// yolo
val tracer: Tracer[IO] = Tracer.create[IO]().unsafeRunSync
val customTracer: Tracer[IO] = Tracer.create[IO](customHeaderName).unsafeRunSync()
val tracer: Tracer[IO] = Tracer.create[IO]()
val customTracer: Tracer[IO] = Tracer.create[IO](customHeaderName)

val tracerApp: HttpApp[IO] = tracer.middleware(TestHttpRoute.routes(tracer).orNotFound)
val customTracerApp: HttpApp[IO] = customTracer.middleware(TestHttpRoute.routes(customTracer).orNotFound)
Expand Down
24 changes: 12 additions & 12 deletions examples/src/main/scala/com/github/gvolpe/tracer/Server.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,28 @@ import org.http4s.server.blaze.BlazeServerBuilder

object Server extends IOApp {

// For a default instance with header name "Trace-Id" just use `import com.github.gvolpe.tracer.instances.tracer._`
implicit val tracer = Tracer.create[IO]("Flow-Id")

override def run(args: List[String]): IO[ExitCode] =
new Main[IO].server.as(ExitCode.Success)

}

class Main[F[_]: ConcurrentEffect: Timer] {
class Main[F[_]: ConcurrentEffect: Timer: Tracer] {

val server: F[Unit] =
LiveRepositories[F].flatMap { repositories =>
val tracedRepos = new TracedRepositories[F](repositories)
val tracedPrograms = new TracedPrograms[F](tracedRepos)

Tracer.create[F]("Flow-Id").flatMap { implicit tracer => // Header name is optional, default to "Trace-Id"
val httpApi = new HttpApi[F](tracedPrograms)

BlazeServerBuilder[F]
.bindHttp(8080, "0.0.0.0")
.withHttpApp(httpApi.httpApp)
.serve
.compile
.drain
}
val httpApi = new HttpApi[F](tracedPrograms)

BlazeServerBuilder[F]
.bindHttp(8080, "0.0.0.0")
.withHttpApp(httpApi.httpApp)
.serve
.compile
.drain
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package com.github.gvolpe.tracer.http

import cats.effect.IO
import com.github.gvolpe.tracer.Trace._
import com.github.gvolpe.tracer.Tracer
import com.github.gvolpe.tracer.instances.tracer._
import com.github.gvolpe.tracer.model.user.{User, Username}
import com.github.gvolpe.tracer.program.UserProgram
import com.github.gvolpe.tracer.repository.TestUserRepository
Expand All @@ -29,8 +29,6 @@ import org.scalatest.prop.TableFor3

class UserRoutesSpec extends HttpRoutesSpec {

implicit val tracer: Tracer[IO] = Tracer.create[IO]().unsafeRunSync // yolo

private val repo = new TestUserRepository[Trace[IO, ?]]
private val program = new UserProgram[Trace[IO, ?]](repo)
private val routes = new UserRoutes[IO](program).routes
Expand Down
25 changes: 12 additions & 13 deletions site/src/main/tut/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ class UserRoutes[F[_]: Sync: Tracer](users: UserAlgebra[Trace[F, ?]]) extends Ht
There are a couple of things going on here:

- We require an `UserAlgebra[Trace[F, ?]]` instead of a plain `UserAlgebra[F]`.
- We need an instance of `Trace[F]` in scope.
- We need an instance of `Tracer[F]` in scope to make sure we are getting the right header.

This is necessary to pass the "Trace-Id" along and we'll soon see how to do it.

Expand Down Expand Up @@ -290,29 +290,28 @@ Writing these tracers is the most tedious part as we need to write quite some bo

#### Main entry point

This is where we instantiate our modules and create our `Tracer` instance to make it available implicitly.
This is where we instantiate our modules and create our `Tracer` instance. For a default instance with header name "Trace-Id" just use `import com.github.gvolpe.tracer.instances.tracer._`.

```tut:book:silent
import com.github.gvolpe.tracer.instances.tracerlog._
import org.http4s.server.blaze.BlazeServerBuilder
class Main[F[_]: ConcurrentEffect: Timer] {
implicit val tracer: Tracer[F] = Tracer.create[F]("Flow-Id")
val server: F[Unit] =
LiveRepositories[F].flatMap { repositories =>
val tracedRepos = new TracedRepositories[F](repositories)
val tracedPrograms = new TracedPrograms[F](tracedRepos)
Tracer.create[F]("Flow-Id").flatMap { implicit tracer => // Header name is optional, default to "Trace-Id"
val httpApi = new HttpApi[F](tracedPrograms)
BlazeServerBuilder[F]
.bindHttp(8080, "0.0.0.0")
.withHttpApp(httpApi.httpApp)
.serve
.compile
.drain
}
val httpApi = new HttpApi[F](tracedPrograms)
BlazeServerBuilder[F]
.bindHttp(8080, "0.0.0.0")
.withHttpApp(httpApi.httpApp)
.serve
.compile
.drain
}
}
Expand Down
16 changes: 15 additions & 1 deletion site/src/main/tut/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ the different components in between such as external http calls, database access
Its design is very simple and minimalistic. These are main components:

- `Trace[F]` monad which is just an alias for `Kleisli[F, TraceId, ?]`.
- `Tracer[F]` http middleware.
- `Tracer[F]` typeclass that let's you access the http middleware.
- `Http4sTracerDsl[F]` as a replacement for `Http4sDsl[F]`.
- `TracedHttpRoute[F]` as a replacement for `HttpRoutes.of[F]`.
- `TracerLog[F]` typeclass for structured logging with tracing information.

With all this machinery in place, the system will trace the call-chain of every single request by either adding a `Trace-Id` header with a unique identifier or by passing around the one received. Note that the header name is customizable.

Expand All @@ -31,3 +32,16 @@ The tracing-aware components should only be your `HttpRoutes`. It is not require
- Easy to test components in isolation.

This separation of concerns is at the core of `http4s-tracer`'s design.

### Structured logging

A normal application commonly serves hundreds of requests concurrently and being able to trace the call-graph of a single one could be invaluable in failure scenarios. Here's an example of how the logs look like when using the default `TracerLog` instance:

```bash
10:50:16.587 [ec-1] INFO c.g.g.t.Tracer - [Trace-Id] -[490bd050-f442-11e8-a46d-578226236d02] - Request(method=POST, uri=/users, ...)
10:50:16.768 [ec-1] INFO c.g.g.t.a.UserAlgebra - [Trace-Id] - [490bd050-f442-11e8-a46d-578226236d02] - About to persist user: gvolpe
10:50:16.769 [ec-1] INFO c.g.g.t.r.a$UserRepository - [Trace-Id] - [490bd050-f442-11e8-a46d-578226236d02] - Find user by username: gvolpe
10:50:16.770 [ec-1] INFO c.g.g.t.r.a$UserRepository - [Trace-Id] - [490bd050-f442-11e8-a46d-578226236d02] - Persisting user: gvolpe
10:50:16.773 [ec-1] INFO c.g.g.t.Tracer - [Trace-Id] - [490bd050-f442-11e8-a46d-578226236d02] - Response(status=201, ...)
```

0 comments on commit 3f3bd9a

Please sign in to comment.