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

bayou.Trace.scala Maven / Gradle / Ivy

There is a newer version: 0.1-4fb42c8
Show newest version
/*
 * Copyright 2022 Arman Bilge
 *
 * 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 bayou

import cats.Applicative
import cats.data.EitherT
import cats.data.Kleisli
import cats.data.OptionT
import cats.data.StateT
import cats.effect.IO
import cats.effect.IOLocal
import cats.effect.kernel.MonadCancel
import cats.effect.kernel.Resource
import cats.syntax.all._
import natchez.Kernel
import natchez.Span
import natchez.TraceValue

import java.net.URI

trait Trace[F[_]] extends natchez.Trace[F] {

  /**
   * Create a new span, and within it run the returned resource.
   */
  def spanR(name: String): Resource[F, Unit]

}

object Trace {
  def apply[F[_]](implicit ev: Trace[F]): ev.type = ev

  /**
   * A `Trace` instance that uses `IOLocal` internally.
   */
  def ioTrace(rootSpan: Span[IO]): IO[IOTrace] =
    IOLocal(rootSpan).map(new IOTrace(_))

  final class IOTrace private[bayou] (local: IOLocal[Span[IO]]) extends Trace[IO] {

    def put(fields: (String, TraceValue)*): IO[Unit] =
      local.get.flatMap(_.put(fields: _*))

    def kernel: IO[Kernel] =
      local.get.flatMap(_.kernel)

    def span[A](name: String)(k: IO[A]): IO[A] =
      spanR(name).use(_ => k)

    /**
     * Run the continuation `k` within the provided span.
     */
    def span[A](span: Span[IO])(k: IO[A]): IO[A] =
      spanR(span).use(_ => k)

    def spanR(name: String): Resource[IO, Unit] =
      Resource.eval(local.get).flatMap { parent =>
        parent.span(name).flatMap { child =>
          Resource.make(local.set(child))(_ => local.set(parent))
        }
      }

    /**
     * Run the returned resource within the provided span.
     */
    def spanR(span: Span[IO]): Resource[IO, Unit] =
      Resource.eval(local.get).flatMap { root =>
        Resource.make(local.set(span))(_ => local.set(root))
      }

    def traceId: IO[Option[String]] =
      local.get.flatMap(_.traceId)

    def traceUri: IO[Option[URI]] =
      local.get.flatMap(_.traceUri)

  }

  object Implicits {

    /**
     * A no-op `Trace` implementation is freely available for any applicative effect. This lets
     * us add a `Trace` constraint to most existing code without demanding anything new from the
     * concrete effect type.
     */
    implicit def noop[F[_]: Applicative]: Trace[F] =
      new Trace[F] {
        final val void = ().pure[F]
        val kernel: F[Kernel] = Kernel(Map.empty).pure[F]
        def put(fields: (String, TraceValue)*): F[Unit] = void
        def span[A](name: String)(k: F[A]): F[A] = k
        def spanR(name: String): Resource[F, Unit] = Resource.unit
        def traceId: F[Option[String]] = none.pure[F]
        def traceUri: F[Option[URI]] = none.pure[F]
      }

  }

  implicit def liftKleisli[F[_], E](
      implicit F: MonadCancel[F, _],
      trace: Trace[F]): Trace[Kleisli[F, E, *]] =
    new Trace[Kleisli[F, E, *]] {

      def put(fields: (String, TraceValue)*): Kleisli[F, E, Unit] =
        Kleisli.liftF(trace.put(fields: _*))

      def kernel: Kleisli[F, E, Kernel] =
        Kleisli.liftF(trace.kernel)

      def span[A](name: String)(k: Kleisli[F, E, A]): Kleisli[F, E, A] =
        Kleisli(e => trace.span[A](name)(k.run(e)))

      def spanR(name: String): Resource[Kleisli[F, E, *], Unit] =
        trace.spanR(name).mapK(Kleisli.liftK)

      def traceId: Kleisli[F, E, Option[String]] =
        Kleisli.liftF(trace.traceId)

      def traceUri: Kleisli[F, E, Option[URI]] =
        Kleisli.liftF(trace.traceUri)
    }

  implicit def liftStateT[F[_], S](
      implicit F: MonadCancel[F, _],
      trace: Trace[F]): Trace[StateT[F, S, *]] =
    new Trace[StateT[F, S, *]] {

      def put(fields: (String, TraceValue)*): StateT[F, S, Unit] =
        StateT.liftF(trace.put(fields: _*))

      def kernel: StateT[F, S, Kernel] =
        StateT.liftF(trace.kernel)

      def span[A](name: String)(k: StateT[F, S, A]): StateT[F, S, A] =
        StateT(s => trace.span[(S, A)](name)(k.run(s)))

      def spanR(name: String): Resource[StateT[F, S, *], Unit] =
        trace.spanR(name).mapK(StateT.liftK)

      def traceId: StateT[F, S, Option[String]] =
        StateT.liftF(trace.traceId)

      def traceUri: StateT[F, S, Option[URI]] =
        StateT.liftF(trace.traceUri)
    }

  implicit def liftEitherT[F[_], E](
      implicit F: MonadCancel[F, _],
      trace: Trace[F]): Trace[EitherT[F, E, *]] =
    new Trace[EitherT[F, E, *]] {

      def put(fields: (String, TraceValue)*): EitherT[F, E, Unit] =
        EitherT.liftF(trace.put(fields: _*))

      def kernel: EitherT[F, E, Kernel] =
        EitherT.liftF(trace.kernel)

      def span[A](name: String)(k: EitherT[F, E, A]): EitherT[F, E, A] =
        EitherT(trace.span(name)(k.value))

      def spanR(name: String): Resource[EitherT[F, E, *], Unit] =
        trace.spanR(name).mapK(EitherT.liftK)

      def traceId: EitherT[F, E, Option[String]] =
        EitherT.liftF(trace.traceId)

      def traceUri: EitherT[F, E, Option[URI]] =
        EitherT.liftF(trace.traceUri)
    }

  implicit def liftOptionT[F[_]](
      implicit F: MonadCancel[F, _],
      trace: Trace[F]): Trace[OptionT[F, *]] =
    new Trace[OptionT[F, *]] {

      def put(fields: (String, TraceValue)*): OptionT[F, Unit] =
        OptionT.liftF(trace.put(fields: _*))

      def kernel: OptionT[F, Kernel] =
        OptionT.liftF(trace.kernel)

      def span[A](name: String)(k: OptionT[F, A]): OptionT[F, A] =
        OptionT(trace.span(name)(k.value))

      def spanR(name: String): Resource[OptionT[F, *], Unit] =
        trace.spanR(name).mapK(OptionT.liftK)

      def traceId: OptionT[F, Option[String]] =
        OptionT.liftF(trace.traceId)

      def traceUri: OptionT[F, Option[URI]] =
        OptionT.liftF(trace.traceUri)
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy