Skip to content

Commit

Permalink
Merge pull request #14 from gvolpe/series/1.0
Browse files Browse the repository at this point in the history
Series/1.0
  • Loading branch information
gvolpe authored Oct 8, 2018
2 parents 4bbe827 + 31b594d commit 3de850f
Show file tree
Hide file tree
Showing 22 changed files with 215 additions and 80 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ libraryDependencies += "com.github.gvolpe" %% "http4s-tracer" % Version

| Dependency | Version |
| ------------ |:----------:|
| cats | 1.1.1 |
| cats-effect | 0.10.1 |
| fs2 | 0.10.4 |
| cats | 1.3.1 |
| cats-effect | 1.0.0 |
| fs2 | 1.0.0-RC1 |
| gfc-timeuuid | 0.0.8 |
| http4s | 0.18.12 |
| http4s | 0.19.0-M3 |

### Credits

Expand Down
5 changes: 2 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ name := """https-tracer-root"""

organization in ThisBuild := "com.github.gvolpe"

version in ThisBuild := "0.2"

crossScalaVersions in ThisBuild := Seq("2.11.12", "2.12.6")
crossScalaVersions in ThisBuild := Seq("2.11.12", "2.12.7")

sonatypeProfileName := "com.github.gvolpe"

Expand Down Expand Up @@ -101,6 +99,7 @@ lazy val noPublish = Seq(

lazy val `http4s-tracer` = project.in(file("core"))
.settings(commonSettings: _*)
.settings(libraryDependencies += Libraries.http4sClient % Test)
.enablePlugins(AutomateHeaderPlugin)

lazy val examples = project.in(file("examples"))
Expand Down
26 changes: 26 additions & 0 deletions core/src/main/scala/com/github/gvolpe/tracer/KFX.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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

import cats.data.Kleisli
import Tracer.TraceId

object KFX {
type KFX[F[_], A] = Kleisli[F, TraceId, A]

def apply[F[_], A](run: TraceId => F[A]): KFX[F, A] = Kleisli[F, TraceId, A](run)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import cats.data.{Kleisli, OptionT}
import cats.syntax.flatMap._
import cats.syntax.functor._
import com.github.gvolpe.tracer.Tracer.TraceId
import org.http4s.{HttpService, Request, Response}
import org.http4s.{HttpRoutes, Request, Response}

object TracedHttpRoute {
case class TracedRequest[F[_]](traceId: TraceId, request: Request[F])

def apply[F[_]: Monad](pf: PartialFunction[TracedRequest[F], F[Response[F]]]): HttpService[F] =
def apply[F[_]: Monad](pf: PartialFunction[TracedRequest[F], F[Response[F]]]): HttpRoutes[F] =
Kleisli[OptionT[F, ?], Request[F], Response[F]] { req =>
OptionT {
Tracer
Expand Down
30 changes: 16 additions & 14 deletions core/src/main/scala/com/github/gvolpe/tracer/Tracer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
package com.github.gvolpe.tracer

import cats.Applicative
import cats.data.{Kleisli, OptionT}
import cats.data.Kleisli
import cats.effect.Sync
import cats.syntax.all._
import com.gilt.timeuuid.TimeUuid
import org.http4s.syntax.StringSyntax
import org.http4s.{Header, HttpService, Request, Response}
import org.http4s.{Header, HttpApp, Request}

/**
* `org.http4s.server.HttpMiddleware` that either tries to get a Trace-Id from the headers or otherwise
Expand All @@ -42,30 +42,32 @@ import org.http4s.{Header, HttpService, Request, Response}
* */
object Tracer extends StringSyntax {

private val DefaultTraceIdHeader = "Trace-Id"
private var TraceIdHeader = DefaultTraceIdHeader
import KFX._

private[tracer] val DefaultTraceIdHeader = "Trace-Id"
private var TraceIdHeader = DefaultTraceIdHeader

final case class TraceId(value: String) extends AnyVal

type KFX[F[_], A] = Kleisli[F, TraceId, A]
type Tracer = Tracer.type

// format: off
def apply[F[_]](service: HttpService[F], headerName: String = DefaultTraceIdHeader)
(implicit F: Sync[F], L: TracerLog[KFX[F, ?]]): HttpService[F] =
Kleisli[OptionT[F, ?], Request[F], Response[F]] { req =>
def apply[F[_]](http: HttpApp[F], headerName: String = DefaultTraceIdHeader)
(implicit F: Sync[F], L: TracerLog[KFX[F, ?]]): HttpApp[F] =
Kleisli { req =>
val createId: F[(Request[F], TraceId)] =
for {
id <- F.delay(TraceId(TimeUuid().toString))
tr <- F.delay(req.putHeaders(Header(TraceIdHeader, id.value)))
} yield (tr, id)

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ import scala.reflect.ClassTag

trait TracerLog[F[_]] {
def info[A: ClassTag](value: String): F[Unit]
def error[A: ClassTag](error: Exception): F[Unit]
def error[A: ClassTag](value: String): F[Unit]
def warn[A: ClassTag](value: String): F[Unit]
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@

package com.github.gvolpe.tracer.instances

import cats.data.Kleisli
import cats.effect.Sync
import com.github.gvolpe.tracer.Tracer.KFX
import cats.syntax.flatMap._
import com.github.gvolpe.tracer.KFX
import com.github.gvolpe.tracer.KFX._
import com.github.gvolpe.tracer.TracerLog
import org.slf4j.{Logger, LoggerFactory}

Expand All @@ -28,19 +29,19 @@ object tracerlog {

implicit def defaultLog[F[_]](implicit F: Sync[F]): TracerLog[KFX[F, ?]] =
new TracerLog[KFX[F, ?]] {
def logger[A](implicit ct: ClassTag[A]): Logger =
LoggerFactory.getLogger(ct.runtimeClass)
def logger[A](implicit ct: ClassTag[A]): F[Logger] =
F.delay(LoggerFactory.getLogger(ct.runtimeClass))

override def info[A: ClassTag](value: String): KFX[F, Unit] = Kleisli { id =>
F.delay(logger[A].info(s"$id >> $value"))
override def info[A: ClassTag](value: String): KFX[F, Unit] = KFX { id =>
logger[A].flatMap(log => F.delay(log.info(s"$id >> $value")))
}

override def error[A: ClassTag](error: Exception): KFX[F, Unit] = Kleisli { id =>
F.delay(logger[A].error(s"$id >> ${error.getMessage}"))
override def error[A: ClassTag](value: String): KFX[F, Unit] = KFX { id =>
logger[A].flatMap(log => F.delay(log.error(s"$id >> $value")))
}

override def warn[A: ClassTag](value: String): KFX[F, Unit] = Kleisli { id =>
F.delay(logger[A].warn(s"$id >> $value"))
override def warn[A: ClassTag](value: String): KFX[F, Unit] = KFX { id =>
logger[A].flatMap(log => F.delay(log.warn(s"$id >> $value")))
}
}

Expand Down
24 changes: 24 additions & 0 deletions core/src/test/scala/com/github/gvolpe/tracer/IOAssertion.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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

import cats.effect.IO
import cats.syntax.functor._

object IOAssertion {
def apply[A](ioa: IO[A]): Unit = ioa.void.unsafeRunSync
}
81 changes: 81 additions & 0 deletions core/src/test/scala/com/github/gvolpe/tracer/TracerSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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

import cats.effect.IO
import com.github.gvolpe.tracer.instances.tracerlog._
import org.http4s._
import org.http4s.client.dsl.io._
import org.http4s.dsl.io._
import org.scalatest.FunSuite
import org.scalatest.prop.PropertyChecks

class TracerSpec extends FunSuite with TracerFixture {

forAll(examples) { (name, request, tracer, assertions) =>
test(name) {
IOAssertion {
for {
req <- request
resp <- tracer(req)
_ <- assertions(resp)
} yield ()
}
}
}

}

// format: off
trait TracerFixture extends PropertyChecks {

val customHeaderName = "Test-Id"
val customHeaderValue = "my-custom-value"

val httpApp: HttpApp[IO] = TestHttpRoute.routes.orNotFound
val tracerApp: HttpApp[IO] = Tracer(httpApp)
val customTracerApp: HttpApp[IO] = Tracer(http = httpApp, headerName = customHeaderName)

def defaultAssertion(traceHeaderName: String): Response[IO] => IO[Unit] = resp =>
IO {
assert(resp.status == Status.Ok)
assert(resp.headers.toList.map(_.name.value).contains(traceHeaderName))
}

def customAssertion(traceHeaderName: String): Response[IO] => IO[Unit] = resp =>
IO {
assert(resp.status == Status.Ok)
assert(resp.headers.toList.map(_.name.value).contains(traceHeaderName))
assert(resp.headers.toList.map(_.value).contains(customHeaderValue))
}

val examples = Table(
("name", "request", "tracer", "assertions"),
("Default TraceId header is created", GET(Uri.uri("/")), tracerApp, defaultAssertion(Tracer.DefaultTraceIdHeader)),
("TraceId header is passed in the request (no TraceId created)", GET(Uri.uri("/"), Header(Tracer.DefaultTraceIdHeader, customHeaderValue)), tracerApp, customAssertion(Tracer.DefaultTraceIdHeader)),
("Custom TraceId header (Test-Id) is created", GET(Uri.uri("/")), customTracerApp, defaultAssertion(customHeaderName)),
("TraceId header (Test-Id) is passed in the request", GET(Uri.uri("/"), Header(customHeaderName, customHeaderValue)), customTracerApp, customAssertion(customHeaderName))
)

}

object TestHttpRoute extends Http4sTracerDsl[IO] {
val routes: HttpRoutes[IO] = TracedHttpRoute[IO] {
case GET -> Root using traceId =>
Ok(traceId.value)
}
}
11 changes: 6 additions & 5 deletions examples/src/main/scala/com/github/gvolpe/tracer/Module.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
package com.github.gvolpe.tracer

import cats.effect.Sync
import com.github.gvolpe.tracer.Tracer.KFX
import com.github.gvolpe.tracer.KFX._
import com.github.gvolpe.tracer.algebra.UserAlgebra
import com.github.gvolpe.tracer.http.UserRoutes
import com.github.gvolpe.tracer.interpreter.UserTracerInterpreter
import com.github.gvolpe.tracer.instances.tracerlog._
import com.github.gvolpe.tracer.repository.UserTracerRepository
import com.github.gvolpe.tracer.repository.algebra.UserRepository
import org.http4s.HttpService
import org.http4s.{HttpApp, HttpRoutes}
import org.http4s.implicits._

class Module[F[_]: Sync] {

Expand All @@ -34,10 +35,10 @@ class Module[F[_]: Sync] {
private val service: UserAlgebra[KFX[F, ?]] =
new UserTracerInterpreter[F](repo)

private val httpRoutes: HttpService[F] =
private val httpRoutes: HttpRoutes[F] =
new UserRoutes[F](service).routes

val routes: HttpService[F] =
Tracer(httpRoutes, headerName = "Flow-Id") // Header name is optional, default to "Trace-Id"
val httpApp: HttpApp[F] =
Tracer(httpRoutes.orNotFound, headerName = "Flow-Id") // Header name is optional, default to "Trace-Id"

}
31 changes: 13 additions & 18 deletions examples/src/main/scala/com/github/gvolpe/tracer/Server.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,21 @@

package com.github.gvolpe.tracer

import cats.effect.{Effect, IO}
import fs2.StreamApp.ExitCode
import fs2.{Scheduler, Stream, StreamApp}
import org.http4s.server.blaze.BlazeBuilder
import cats.effect.{ExitCode, IO, IOApp}
import cats.syntax.functor._
import org.http4s.server.blaze.BlazeServerBuilder

import scala.concurrent.ExecutionContext.Implicits.global
object Server extends IOApp {

object Server extends HttpServer[IO]
private val ctx = new Module[IO]

class HttpServer[F[_]: Effect] extends StreamApp[F] {

override def stream(args: List[String], requestShutdown: F[Unit]): Stream[F, ExitCode] =
Scheduler(corePoolSize = 2).flatMap { implicit scheduler =>
for {
ctx <- Stream(new Module[F])
exitCode <- BlazeBuilder[F]
.bindHttp(8080, "0.0.0.0")
.mountService(ctx.routes)
.serve
} yield exitCode
}
override def run(args: List[String]): IO[ExitCode] =
BlazeServerBuilder[IO]
.bindHttp(8080, "0.0.0.0")
.withHttpApp(ctx.httpApp)
.serve
.compile
.drain
.as(ExitCode.Success)

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

import cats.effect.Sync
import com.github.gvolpe.tracer.Tracer.KFX
import com.github.gvolpe.tracer.KFX._
import com.github.gvolpe.tracer.algebra.UserAlgebra
import com.github.gvolpe.tracer.auth.{AuthTracedHttpRoute, Http4sAuthTracerDsl}
import io.circe.generic.auto._
Expand All @@ -38,7 +38,7 @@ class AuthRoutes[F[_]: Sync](userService: UserAlgebra[KFX[F, ?]]) extends Http4s

lazy val authMiddleware: AuthMiddleware[F, String] = ???

lazy val routes: HttpService[F] = Router(
lazy val routes: HttpRoutes[F] = Router(
PathPrefix -> authMiddleware(httpRoutes)
)

Expand Down
Loading

0 comments on commit 3de850f

Please sign in to comment.