All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.janstenpickle.trace4cats.http4s.server.ServerTracer.scala Maven / Gradle / Ivy

package io.janstenpickle.trace4cats.http4s.server

import cats.Monad
import cats.data.{Kleisli, OptionT}
import cats.effect.Bracket
import cats.syntax.apply._
import cats.syntax.flatMap._
import cats.syntax.functor._
import io.janstenpickle.trace4cats.Span
import io.janstenpickle.trace4cats.http4s.common.{
  Http4sHeaders,
  Http4sRequestFilter,
  Http4sSpanNamer,
  Http4sStatusMapping
}
import io.janstenpickle.trace4cats.inject.{EntryPoint, LiftTrace, Provide}
import io.janstenpickle.trace4cats.model.SpanKind
import org.http4s.util.CaseInsensitiveString
import org.http4s.{HttpApp, HttpRoutes, Request, Response}

object ServerTracer {
  def injectRoutes[F[_]: Bracket[*[_], Throwable], G[_]](
    routes: HttpRoutes[G],
    entryPoint: EntryPoint[F],
    spanNamer: Http4sSpanNamer,
    requestFilter: Http4sRequestFilter,
    dropHeadersWhen: CaseInsensitiveString => Boolean
  )(implicit provide: Provide[F, G], lift: LiftTrace[F, G]): HttpRoutes[F] =
    Kleisli[OptionT[F, *], Request[F], Response[F]] { req =>
      val filter = requestFilter.lift(req.covary).getOrElse(true)
      val headers = req.headers.toList.map(h => h.name.value -> h.value).toMap
      val spanR =
        if (filter) entryPoint.continueOrElseRoot(spanNamer(req.covary), SpanKind.Server, headers) else Span.noop[F]

      OptionT[F, Response[F]] {
        spanR.use { span =>
          val low = provide.fk(span)

          span.putAll(Http4sHeaders.requestFields(req, dropHeadersWhen): _*) *> routes
            .run(req.mapK(lift.fk))
            .mapK(low)
            .map(_.mapK(low))
            .semiflatMap { resp =>
              span.setStatus(Http4sStatusMapping.toSpanStatus(resp.status)) *>
                span
                  .putAll(Http4sHeaders.responseFields(resp, dropHeadersWhen): _*)
                  .as(resp)
            }
            .value
        }
      }
    }

  def injectApp[F[_]: Bracket[*[_], Throwable], G[_]: Monad](
    app: HttpApp[G],
    entryPoint: EntryPoint[F],
    spanNamer: Http4sSpanNamer,
    requestFilter: Http4sRequestFilter,
    dropHeadersWhen: CaseInsensitiveString => Boolean
  )(implicit provide: Provide[F, G], lift: LiftTrace[F, G]): HttpApp[F] = Kleisli[F, Request[F], Response[F]] { req =>
    val filter = requestFilter.lift(req.covary).getOrElse(true)
    val headers = req.headers.toList.map(h => h.name.value -> h.value).toMap
    val spanR =
      if (filter) entryPoint.continueOrElseRoot(spanNamer(req.covary), SpanKind.Server, headers) else Span.noop[F]

    spanR.use { span =>
      val low = provide.fk(span)
      span.putAll(Http4sHeaders.requestFields(req, dropHeadersWhen): _*) *> low(
        app
          .run(req.mapK(lift.fk))
          .map(_.mapK(low))
      ).flatMap { resp =>
        span.setStatus(Http4sStatusMapping.toSpanStatus(resp.status)) *>
          span
            .putAll(Http4sHeaders.responseFields(resp, dropHeadersWhen): _*)
            .as(resp)
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy