diff --git a/examples/ce3/src/main/scala-2/tofu/example/doobie/TofuDoobieExample.scala b/examples/ce3/src/main/scala-2/tofu/example/doobie/TofuDoobieExample.scala index c4bdcf31a..8fead2428 100644 --- a/examples/ce3/src/main/scala-2/tofu/example/doobie/TofuDoobieExample.scala +++ b/examples/ce3/src/main/scala-2/tofu/example/doobie/TofuDoobieExample.scala @@ -2,7 +2,7 @@ package tofu.example.doobie import cats.data.ReaderT import cats.effect.std.Dispatcher -import cats.effect.{Async, IO, IOApp, Sync} +import cats.effect.{Async, IO, IOApp} import cats.tagless.syntax.functorK._ import cats.{Apply, Monad} import derevo.derive @@ -10,10 +10,9 @@ import doobie._ import doobie.implicits._ import doobie.util.log.LogHandler import tofu.doobie.LiftConnectionIO -import tofu.doobie.log.{EmbeddableLogHandler, LogHandlerF} import tofu.doobie.transactor.Txr +import tofu.higherKind.RepresentableK import tofu.higherKind.derived.representableK -import tofu.kernel.types.PerformThrow import tofu.lift.Lift import tofu.logging.derivation.{loggable, loggingMidTry} import tofu.logging.{Logging, LoggingCompanion} @@ -45,11 +44,10 @@ trait PersonSql[F[_]] { } object PersonSql extends LoggingCompanion[PersonSql] { - def make[DB[_]: Monad: LiftConnectionIO: EmbeddableLogHandler]: PersonSql[DB] = { - EmbeddableLogHandler[DB].embedLift(implicit lh => new Impl) - } + def make[DB[_]: Monad: LiftConnectionIO]: PersonSql[DB] = + RepresentableK[PersonSql].mapK(Impl)(Lift[ConnectionIO, DB].liftF) - final class Impl(implicit lh: LogHandler) extends PersonSql[ConnectionIO] { + final private object Impl extends PersonSql[ConnectionIO] { def init: ConnectionIO[Unit] = lsql"create table if not exists person(id int8, name varchar(50), dept_id int8)".update.run.void def create(p: Person): ConnectionIO[Unit] = @@ -70,11 +68,10 @@ trait DeptSql[F[_]] { } object DeptSql extends LoggingCompanion[DeptSql] { - def make[DB[_]: Monad: LiftConnectionIO: EmbeddableLogHandler]: DeptSql[DB] = { - EmbeddableLogHandler[DB].embedLift(implicit lh => new Impl) - } + def make[DB[_]: Monad: LiftConnectionIO]: DeptSql[DB] = + RepresentableK[DeptSql].mapK(Impl)(Lift[ConnectionIO, DB].liftF) - final class Impl(implicit lh: LogHandler) extends DeptSql[ConnectionIO] { + final private object Impl extends DeptSql[ConnectionIO] { def init: ConnectionIO[Unit] = lsql"create table if not exists department(id int8, name varchar(50))".update.run.void def create(d: Dept): ConnectionIO[Unit] = @@ -118,23 +115,22 @@ object TofuDoobieExample extends IOApp.Simple { runF[IO, ReaderT[IO, Ctx, *]] } - def runF[I[_]: Async, F[_]: Sync: PerformThrow: WithRun[*[_], I, Ctx]]: I[Unit] = { + def runF[I[_], F[_]: Async: WithRun[*[_], I, Ctx]]: I[Unit] = { // Simplified wiring below implicit val loggingF = Logging.Make.contextual[F, Ctx] - val transactor = Transactor.fromDriverManager[I]( + val transactor = Transactor.fromDriverManager[F]( driver = "org.h2.Driver", - url = "jdbc:h2:./test" + url = "jdbc:h2:./test", + logHandler = Some(LogHandler.loggable[F](Logging.Debug)) ) - implicit val txr = Txr.continuational(transactor.mapK(Lift.trans[I, F])) + implicit val txr = Txr.continuational(transactor) def initStorage[ - DB[_]: Tries: Txr[F, *[_]]: Delay: Monad: LiftConnectionIO: WithLocal[*[_], Ctx]: PerformThrow + DB[_]: Tries: Txr[F, *[_]]: Delay: Monad: LiftConnectionIO: WithLocal[*[_], Ctx] ]: PersonStorage[F] = { implicit val loggingDB = Logging.Make.contextual[DB, Ctx] - implicit val elh = EmbeddableLogHandler.async(LogHandlerF.loggable[DB](Logging.Debug)) - val personSql = PersonSql.make[DB].attachErrLogs val deptSql = DeptSql.make[DB].attachErrLogs diff --git a/modules/doobie/core-ce3/src/main/scala-2/tofu/doobie/log/EmbeddableLogHandler.scala b/modules/doobie/core-ce3/src/main/scala-2/tofu/doobie/log/EmbeddableLogHandler.scala deleted file mode 100644 index 52cad9ac0..000000000 --- a/modules/doobie/core-ce3/src/main/scala-2/tofu/doobie/log/EmbeddableLogHandler.scala +++ /dev/null @@ -1,80 +0,0 @@ -package tofu.doobie.log - -import _root_.doobie.LogHandler -import cats.tagless.FunctorK -import cats.tagless.syntax.functorK._ -import cats.{Applicative, FlatMap, Functor, ~>} -import tofu.concurrent.Exit -import tofu.higherKind.Embed -import tofu.kernel.types.PerformThrow -import tofu.lift.Lift -import tofu.syntax.embed._ -import tofu.syntax.monadic._ - -import scala.concurrent.duration.Duration -import scala.concurrent.{Await, Promise} - -/** A holder for a [[doobie.util.log.LogHandler]] instance wrapped in an effect `F[_]`. Only useful when `F[_]` is - * contextual. Allows one to create a context-aware `LogHandler` and embed it into a database algebra that requires it - * for logging SQL execution events. - */ -final class EmbeddableLogHandler[F[_]](val self: F[LogHandler]) extends AnyVal { - def embedMapK[A[_[_]]: Embed: FunctorK, G[_]](impl: LogHandler => A[G])(fk: G ~> F)(implicit F: FlatMap[F]): A[F] = - self.map(impl(_).mapK(fk)).embed - - def embedLift[A[_[_]]: Embed: FunctorK, G[_]]( - impl: LogHandler => A[G] - )(implicit F: FlatMap[F], L: Lift[G, F]): A[F] = embedMapK(impl)(L.liftF) - - def embed[A[_[_]]: Embed](impl: LogHandler => A[F])(implicit F: FlatMap[F]): A[F] = - self.map(impl).embed -} - -/** `EmbeddableLogHandler[F]` has two main constructors from `LogHandlerF[F]`: `async` and `sync`. Both require - * `UnliftIO[F]` and need to perform IO unsafely under the hood due to the impurity of `doobie.util.log.LogHandler`. - */ -object EmbeddableLogHandler { - def apply[F[_]](implicit elh: EmbeddableLogHandler[F]): EmbeddableLogHandler[F] = elh - - /** Preferably use `async` as its underlying unsafe run is potentially less harmful. - */ - def async[F[_]: Functor](logHandlerF: LogHandlerF[F])(implicit P: PerformThrow[F]): EmbeddableLogHandler[F] = - new EmbeddableLogHandler(P.performer.map { perf => - LogHandler { event => - val _ = perf.perform((exit: Exit[Throwable, Unit]) => - exit match { - case Exit.Canceled => throw Exit.CanceledException - case Exit.Error(e) => throw e - case Exit.Completed(_) => () - } - )(logHandlerF.run(event)) - } - }) - - /** Only use `sync` if you are sure your logging logic doesn't contain async computations and cannot block the - * execution thread. - */ - def sync[F[_]: Functor](logHandlerF: LogHandlerF[F])(implicit P: PerformThrow[F]): EmbeddableLogHandler[F] = - new EmbeddableLogHandler(P.performer.map { performer => - LogHandler { event => - val promise = Promise[Unit]() - val _ = performer.perform((exit: Exit[Throwable, Unit]) => - exit match { - case Exit.Canceled => promise.failure(Exit.CanceledException) - case Exit.Error(e) => promise.failure(e) - case Exit.Completed(_) => promise.success(()) - } - )(logHandlerF.run(event)) - Await.result(promise.future, Duration.Inf) - } - }) - - /** Use `nop` in tests. - */ - def nop[F[_]: Applicative]: EmbeddableLogHandler[F] = new EmbeddableLogHandler(LogHandler.nop.pure[F]) - - implicit val embeddableLogHandlerFunctorK: FunctorK[EmbeddableLogHandler] = new FunctorK[EmbeddableLogHandler] { - def mapK[F[_], G[_]](af: EmbeddableLogHandler[F])(fk: F ~> G): EmbeddableLogHandler[G] = - new EmbeddableLogHandler(fk(af.self)) - } -} diff --git a/modules/doobie/core-ce3/src/main/scala-2/tofu/doobie/log/LogHandlerF.scala b/modules/doobie/core-ce3/src/main/scala-2/tofu/doobie/log/LogHandlerF.scala deleted file mode 100644 index 24bd45240..000000000 --- a/modules/doobie/core-ce3/src/main/scala-2/tofu/doobie/log/LogHandlerF.scala +++ /dev/null @@ -1,16 +0,0 @@ -package tofu.doobie.log - -import derevo.derive -import doobie.util.log.LogEvent -import tofu.higherKind.derived.representableK - -/** A pure analog of [[doobie.util.log.LogHandler]] that logs SQL execution events in an effect `F[_]`. - */ -@derive(representableK) -trait LogHandlerF[F[_]] { - def run(event: LogEvent): F[Unit] -} - -object LogHandlerF { - def apply[F[_]](run: LogEvent => F[Unit]): LogHandlerF[F] = run(_) -} diff --git a/modules/doobie/logging-ce3/src/main/scala-2/tofu/doobie/log/instances.scala b/modules/doobie/logging-ce3/src/main/scala-2/tofu/doobie/log/instances.scala index 73b5357cf..86be0f010 100644 --- a/modules/doobie/logging-ce3/src/main/scala-2/tofu/doobie/log/instances.scala +++ b/modules/doobie/logging-ce3/src/main/scala-2/tofu/doobie/log/instances.scala @@ -19,50 +19,56 @@ object instances { } def logShow(ev: LogEvent): String = ev match { - case Success(s, a, e1, e2) => + case Success(s, a, l, e1, e2) => s"""Successful Statement Execution: | | ${multiline(s)} | | arguments = ${loggedArgs(a)} + | label = $l | elapsed = ${e1.toMillis} ms exec + ${e2.toMillis} ms processing (${(e1 + e2).toMillis} ms total) """.stripMargin - case ProcessingFailure(s, a, e1, e2, _) => + case ProcessingFailure(s, a, l, e1, e2, _) => s"""Failed Resultset Processing: | | ${multiline(s)} | | arguments = ${loggedArgs(a)} + | label = $l | elapsed = ${e1.toMillis} ms exec + ${e2.toMillis} ms processing (failed) (${(e1 + e2).toMillis} ms total) """.stripMargin - case ExecFailure(s, a, e1, _) => + case ExecFailure(s, a, l, e1, _) => s"""Failed Statement Execution: | | ${multiline(s)} | | arguments = ${loggedArgs(a)} + | label = $l | elapsed = ${e1.toMillis} ms exec (failed) """.stripMargin } def fields[I, V, R, S](ev: LogEvent, i: I)(implicit r: LogRenderer[I, V, R, S]): R = ev match { - case Success(s, a, e1, e2) => + case Success(s, a, l, e1, e2) => i.field("sql-event-type", "Success") |+| + i.field("sql-label", l) |+| i.field("sql-statement", oneline(s)) |+| i.field("sql-args", loggedArgs(a)) |+| i.field("sql-exec-ms", e1.toMillis) |+| i.field("sql-processing-ms", e2.toMillis) |+| i.field("sql-total-ms", (e1 + e2).toMillis) - case ProcessingFailure(s, a, e1, e2, _) => + case ProcessingFailure(s, a, l, e1, e2, _) => i.field("sql-event-type", "ProcessingFailure") |+| + i.field("sql-label", l) |+| i.field("sql-statement", oneline(s)) |+| i.field("sql-args", loggedArgs(a)) |+| i.field("sql-exec-ms", e1.toMillis) |+| i.field("sql-processing-ms", e2.toMillis) |+| i.field("sql-total-ms", (e1 + e2).toMillis) - case ExecFailure(s, a, e1, _) => + case ExecFailure(s, a, l, e1, _) => i.field("sql-event-type", "ExecFailure") |+| + i.field("sql-label", l) |+| i.field("sql-statement", oneline(s)) |+| i.field("sql-args", loggedArgs(a)) |+| i.field("sql-exec-ms", e1.toMillis) diff --git a/modules/doobie/logging-ce3/src/main/scala-2/tofu/syntax/doobie/log/handler.scala b/modules/doobie/logging-ce3/src/main/scala-2/tofu/syntax/doobie/log/handler.scala index 438f081b4..9793f03ce 100644 --- a/modules/doobie/logging-ce3/src/main/scala-2/tofu/syntax/doobie/log/handler.scala +++ b/modules/doobie/logging-ce3/src/main/scala-2/tofu/syntax/doobie/log/handler.scala @@ -1,15 +1,14 @@ package tofu.syntax.doobie.log import doobie.util.log._ -import tofu.doobie.log.LogHandlerF import tofu.doobie.log.instances._ import tofu.kernel.types.AnyK import tofu.logging.Logging object handler { - implicit class MkLogHandlerF(private val lhf: LogHandlerF.type) extends AnyVal { - def loggable[F[_]: Logging.Make](level: Logging.Level): LogHandlerF[F] = { evt => - val logging: Logging[F] = Logging.Make[F].forService[LogHandlerF[AnyK]] + implicit class MkLogHandlerF(private val lhf: LogHandler.type) extends AnyVal { + def loggable[F[_]: Logging.Make](level: Logging.Level): LogHandler[F] = { evt => + val logging: Logging[F] = Logging.Make[F].forService[LogHandler[AnyK]] evt match { case _: Success => logging.write(level, "{}", evt) case e: ProcessingFailure => logging.errorCause("{}", e.failure, evt) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 1d2939a71..b16b54a17 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -62,7 +62,7 @@ object Dependencies { val doobie = "0.13.4" - val doobieCE3 = "1.0.0-RC2" + val doobieCE3 = "1.0.0-RC5" // Compile time only val macroParadise = "2.1.1"