Skip to content

Commit

Permalink
Move logging to kernel-only dependency (#710)
Browse files Browse the repository at this point in the history
* Kernelize Atom and QVar

* Fix tuple syntax for 2.12

* Move logging to kernel-only dependency
  • Loading branch information
Odomontois authored Jul 20, 2021
1 parent 04672b0 commit ba409fb
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 50 deletions.
9 changes: 4 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ lazy val loggingStr = project
defaultSettings,
libraryDependencies ++= Seq(
catsCore,
catsEffect,
circeCore,
tethys,
tethysJackson,
Expand All @@ -95,7 +94,7 @@ lazy val loggingStr = project
catsTagless
),
)
.dependsOn(core, concurrent)
.dependsOn(kernel)

lazy val loggingDer = project
.in(file("modules/logging/derivation"))
Expand All @@ -111,7 +110,7 @@ lazy val loggingLayout = project
.in(file("modules/logging/layout"))
.settings(
defaultSettings,
libraryDependencies ++= Seq(catsCore, catsEffect, logback, slf4j),
libraryDependencies ++= Seq(catsCore, logback, slf4j),
name := "tofu-logging-layout"
)
.dependsOn(loggingStr)
Expand All @@ -121,9 +120,9 @@ lazy val loggingUtil = project
.settings(
defaultSettings,
name := "tofu-logging-util",
libraryDependencies += slf4j,
libraryDependencies ++= Vector(slf4j, catsEffect),
)
.dependsOn(loggingStr, concurrent)
.dependsOn(loggingStr)

lazy val loggingShapeless = project
.in(file("modules/logging/interop/shapeless"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,73 +1,79 @@
package tofu.logging

import cats.effect.SyncIO
import cats.effect.concurrent.Ref
import cats.instances.list._
import cats.instances.unit._
import tofu.syntax.monadic._
import cats.syntax.foldable._
import cats.syntax.traverse._
import scala.collection.mutable

import cats.{Applicative, Monoid}
import io.circe.Json
import cats.Eval

sealed trait LogTree {
import LogTree._
def json: SyncIO[Json] = this match {
case LogDict(values) =>
values.get.flatMap { d => d.toList.traverse { case (name, t) => t.json.map(name -> _) }.map(Json.obj(_: _*)) }
def json: Eval[Json] = this match {
case dict: LogDict =>
dict.getList.flatMap { _.traverse { case (name, t) => t.json.map(name -> _) }.map(Json.obj(_: _*)) }
case LogArr(items) => items.toList.traverse(_.json).map(Json.arr(_: _*))
case value: LogParamValue => value.jsonVal.pure[SyncIO]
case value: LogParamValue => value.jsonVal.pure[Eval]
}
}

// pretty much unsafe mutable builder for circe.Json from logs
object LogTree extends LogBuilder[Json] {
implicit def monoid: Monoid[Output] = Applicative.monoid

type Output = SyncIO[Unit]
type ValRes = SyncIO[Boolean]
type Value = Ref[SyncIO, LogTree]
type Top = LogDict
type Output = Eval[Unit]
type ValRes = Eval[Boolean]
class Value(private var tree: LogTree) {
def get = Eval.always(tree)
def set(value: LogTree) = Eval.always { tree = value }
}
type Top = LogDict

final case class LogDict(values: Ref[SyncIO, Map[String, LogTree]]) extends LogTree {
def add(name: String, tree: LogTree): SyncIO[Unit] = values update (_ + (name -> tree))
class LogDict(private val values: mutable.Map[String, LogTree]) extends LogTree {
def add(name: String, tree: LogTree): Eval[Unit] = Eval.always(values.update(name, tree))
def getList: Eval[List[(String, LogTree)]] = Eval.always(values.toList)
}

final case class LogArr(values: Iterable[LogTree]) extends LogTree

private val newdict = Ref[SyncIO].of(Map.empty[String, LogTree]).map(LogDict)
private val newtree = Ref[SyncIO].of(NullValue: LogTree)
private val newdict = Eval.always(mutable.Map.empty[String, LogTree]).map(new LogDict(_))
private val newtree = Eval.always(new Value(NullValue))

val receiver: LogRenderer[LogDict, Value, Output, ValRes] = new LogRenderer[LogDict, Value, Output, ValRes] {

def coalesce(f: Value => ValRes, g: Value => ValRes, v: Value): ValRes =
f(v).flatMap(written => if (written) SyncIO(true) else g(v))
def zero(v: Ref[SyncIO, LogTree]): SyncIO[Boolean] = SyncIO(false)
def noop(i: LogDict): SyncIO[Unit] = SyncIO.unit
def combine(x: SyncIO[Unit], y: SyncIO[Unit]): SyncIO[Unit] = x *> y
def putValue(value: LogParamValue, input: Ref[SyncIO, LogTree]): SyncIO[Boolean] = input.set(value) as true
def sub(name: String, input: LogDict)(receive: Ref[SyncIO, LogTree] => SyncIO[Boolean]): SyncIO[Unit] =
def coalesce(f: Value => ValRes, g: Value => ValRes, v: Value): ValRes =
f(v).flatMap(written => if (written) Eval.now(true) else g(v))
def zero(v: Value): Eval[Boolean] = Eval.now(false)
def noop(i: LogDict): Eval[Unit] = Eval.now(())
def combine(x: Eval[Unit], y: Eval[Unit]): Eval[Unit] = x *> y
def putValue(value: LogParamValue, input: Value): Eval[Boolean] = input.set(value) as true

def sub(name: String, input: LogDict)(receive: Value => Eval[Boolean]): Eval[Unit] =
for {
t <- newtree
_ <- receive(t)
v <- t.get
_ <- input.add(name, v)
} yield ()

def list(size: Int, input: Ref[SyncIO, LogTree])(receive: (Value, Int) => SyncIO[Boolean]): SyncIO[Boolean] =
def list(size: Int, input: Value)(receive: (Value, Int) => Eval[Boolean]): Eval[Boolean] =
for {
ts <- newtree.replicateA(size)
_ <- ts.zipWithIndex.traverse_(receive.tupled)
vs <- ts.traverse(_.get)
_ <- input.set(LogArr(vs))
} yield true
def dict(input: Ref[SyncIO, LogTree])(receive: LogDict => SyncIO[Unit]): SyncIO[Boolean] =
def dict(input: Value)(receive: LogDict => Eval[Unit]): Eval[Boolean] =
newdict flatMap input.set as true
}

def buildJson(buildTree: LogDict => Output): SyncIO[Json] = newdict flatTap buildTree flatMap (_.json)
def buildJson(buildTree: LogDict => Output): Eval[Json] = newdict flatTap buildTree flatMap (_.json)

def make(f: LogDict => SyncIO[Unit]): Json = buildJson(f).unsafeRunSync()
def make(f: LogDict => Eval[Unit]): Json = buildJson(f).value
}

sealed trait LogParamValue extends LogTree {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ trait LoggableContext[F[_]] {
object LoggableContext {
def of[F[_]] = new LoggableContextPA[F]
private[logging] final class LoggableContextPA[F[_]](private val dummy: Boolean = true) extends AnyVal {
def instance[C](implicit ctx: F HasContext C, ctxLog: Loggable[C]): LoggableContext[F] = new LoggableContext[F] {
def instance[C](implicit ctx: F WithContext C, ctxLog: Loggable[C]): LoggableContext[F] = new LoggableContext[F] {
type Ctx = C
val loggable: Loggable[C] = ctxLog
val context: F WithContext C = ctx.asWithContext
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package tofu.logging

import cats.data.Tuple2K
import cats.effect.Sync
import cats.kernel.Monoid
import cats.tagless.syntax.functorK._
import cats.tagless.{ApplyK, FunctorK}
Expand All @@ -24,6 +23,7 @@ import tofu.logging.impl.UniversalLogs
import tofu.Delay
import tofu.WithContext
import tofu.logging.impl.UniversalContextLogs
import tofu.concurrent.MakeQVar

/** A helper for creating instances of [[tofu.logging.Logging]], defining a way these instances will behave while doing logging.
* Can create instances either on a by-name basic or a type tag basic.
Expand Down Expand Up @@ -88,21 +88,21 @@ object Logs extends LogsInstances0 {
/** Returns an instance of [[tofu.logging.Logs]] that requires [[cats.effect.Sync]] to perform logging side-effects.
* Has no notion of context.
*/
def sync[I[_]: Sync, F[_]: Sync]: Logs[I, F] = new Logs[I, F] {
def byName(name: String): I[Logging[F]] = Sync[I].delay(new SyncLogging[F](LoggerFactory.getLogger(name)))
def sync[I[_]: Delay, F[_]: Delay: Monad]: Logs[I, F] = new Logs[I, F] {
def byName(name: String): I[Logging[F]] = Delay[I].delay(new SyncLogging[F](LoggerFactory.getLogger(name)))
}

def universal[F[_]: Delay]: Logs.Universal[F] = new UniversalLogs
def contextual[F[_]: FlatMap: Delay, C: Loggable](implicit FC: F WithContext C): Logs.Universal[F] =
new UniversalContextLogs[F, C]

def withContext[I[_]: Sync, F[_]: Sync](implicit ctx: LoggableContext[F]): Logs[I, F] = {
def withContext[I[_]: Delay, F[_]: Monad: Delay](implicit ctx: LoggableContext[F]): Logs[I, F] = {
import ctx.loggable
new Logs[I, F] {
override def forService[Svc: ClassTag]: I[Logging[F]] =
Sync[I].delay(new ContextSyncLoggingImpl[F, ctx.Ctx](ctx.context, loggerForService[Svc]))
Delay[I].delay(new ContextSyncLoggingImpl[F, ctx.Ctx](ctx.context, loggerForService[Svc]))
override def byName(name: String): I[Logging[F]] =
Sync[I].delay(new ContextSyncLoggingImpl[F, ctx.Ctx](ctx.context, LoggerFactory.getLogger(name)))
Delay[I].delay(new ContextSyncLoggingImpl[F, ctx.Ctx](ctx.context, LoggerFactory.getLogger(name)))
}
}

Expand Down Expand Up @@ -138,7 +138,7 @@ object Logs extends LogsInstances0 {
}

final implicit class LogsOps[I[_], F[_]](private val logs: Logs[I, F]) extends AnyVal {
def cached(implicit IM: Monad[I], IQ: QVars[I], IG: Guarantee[I]): I[Logs[I, F]] =
def cached(implicit IM: Monad[I], IQ: MakeQVar[I, I], IG: Guarantee[I]): I[Logs[I, F]] =
QVars[I]
.of(Map.empty[String, Logging[F]])
.map2(
Expand All @@ -149,7 +149,7 @@ object Logs extends LogsInstances0 {

def cachedUniversal(implicit
IM: Monad[I],
IQ: QVars[I],
IQ: MakeQVar[I, I],
IG: Guarantee[I],
il: Lift[I, F],
F: FlatMap[F]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import cats.Monad
import tofu.Guarantee
import tofu.concurrent.QVar
import tofu.logging.{LoggedValue, Logging, Logs}
import tofu.syntax.bracket._
import tofu.syntax.guarantee._
import tofu.syntax.monadic._

import scala.reflect.{ClassTag, classTag}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import cats.Applicative
import cats.syntax.applicative._
import org.slf4j.{Logger, Marker}

class ContextLoggingImpl[F[_]: Applicative, C: Loggable, Service](context: F HasContext C, logger: Logger)
class ContextLoggingImpl[F[_]: Applicative, C: Loggable, Service](context: F WithContext C, logger: Logger)
extends LoggingImpl[F](logger) {

override def trace(message: String, values: LoggedValue*) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package tofu
package logging
package impl

import cats.effect.Sync
import cats.syntax.applicative._
import org.slf4j.{Logger, Marker}
import tofu.syntax.monadic._
import cats.Monad

class ContextSyncLoggingImpl[F[_], C: Loggable](context: F HasContext C, logger: Logger)(implicit F: Sync[F])
class ContextSyncLoggingImpl[F[_]: Monad, C: Loggable](context: F WithContext C, logger: Logger)(implicit F: Delay[F])
extends LoggingImpl[F](logger) {

override def trace(message: String, values: LoggedValue*) =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package tofu.logging
package impl

import cats.effect.Sync
import cats.syntax.applicative._
import org.slf4j.{Logger, Marker}
import tofu.syntax.monadic._
import cats.Monad
import tofu.Delay

class SyncLogging[F[_]](logger: Logger)(implicit F: Sync[F]) extends LoggingImpl[F](logger) {
class SyncLogging[F[_]: Monad](logger: Logger)(implicit F: Delay[F]) extends LoggingImpl[F](logger) {
override def trace(message: String, values: LoggedValue*): F[Unit] =
F.delay(logger.trace(message, values: _*)).whenA(traceEnabled)
override def debug(message: String, values: LoggedValue*): F[Unit] =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package tofu.logging

import tofu.HasContext
import tofu.compat.unused
import tofu.WithContext

object LoggableContextSuite {

def summonInstance[R: Loggable, F[_]: *[_] HasContext R](): Unit = {
def summonInstance[R: Loggable, F[_]: *[_] WithContext R](): Unit = {
LoggableContext.of[F].instance
()
}

@unused
def summonInstanceUnambiguously[R1: Loggable, R2: Loggable, F[_]: *[_] HasContext R1: *[_] HasContext R2](): Unit = {
def summonInstanceUnambiguously[R1: Loggable, R2: Loggable, F[_]: *[_] WithContext R1: *[_] WithContext R2]()
: Unit = {
LoggableContext.of[F].instance[R1]
()
}
Expand Down

0 comments on commit ba409fb

Please sign in to comment.