Skip to content

Commit

Permalink
Merge pull request #499 from kevin-lee/task/498/Ignorable-LogMessage-…
Browse files Browse the repository at this point in the history
…for-FA

Close #498 - Change the `LogMessage` parameter in the `log(F[A])` method from `NotIgnorable` to `MaybeIgnorable`
  • Loading branch information
kevin-lee authored Nov 7, 2023
2 parents 9736b8a + 2bd3d7e commit 47a0ad1
Show file tree
Hide file tree
Showing 8 changed files with 459 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,17 @@ trait Log[F[*]] {

def canLog: CanLog

def log[A](fa: F[A])(toLeveledMessage: A => LogMessage with NotIgnorable): F[A] =
def log[A](fa: F[A])(toLeveledMessage: A => LogMessage with MaybeIgnorable): F[A] =
flatMap0(fa) { a =>
toLeveledMessage(a) match {
case LogMessage.LeveledMessage(message, level) =>
flatMap0(EF.effectOf(canLog.getLogger(level)(message())))(_ => EF.pureOf(a))
case LogMessage.Ignore =>
EF.pureOf(a)
}
}

def log_[A](fa: F[A])(toLeveledMessage: A => LogMessage with NotIgnorable): F[Unit] =
def log_[A](fa: F[A])(toLeveledMessage: A => LogMessage with MaybeIgnorable): F[Unit] =
map0(log(fa)(toLeveledMessage))(_ => ())

def logS(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ trait LogSyntax {

import LogSyntax._

@inline def log[F[*], A](fa: F[A])(toLeveledMessage: A => LogMessage with NotIgnorable)(
@inline def log[F[*], A](fa: F[A])(toLeveledMessage: A => LogMessage with MaybeIgnorable)(
implicit L: Log[F]
): F[A] =
L.log(fa)(toLeveledMessage)

@inline def log_[F[*], A](fa: F[A])(toLeveledMessage: A => LogMessage with NotIgnorable)(
@inline def log_[F[*], A](fa: F[A])(toLeveledMessage: A => LogMessage with MaybeIgnorable)(
implicit L: Log[F]
): F[Unit] =
L.log_(fa)(toLeveledMessage)
Expand Down Expand Up @@ -93,10 +93,10 @@ trait LogSyntax {

object LogSyntax extends LogSyntax {
final class LogFOfASyntax[F[*], A](private val fa: F[A]) extends AnyVal {
@inline def log(toLeveledMessage: A => LogMessage with NotIgnorable)(implicit L: Log[F]): F[A] =
@inline def log(toLeveledMessage: A => LogMessage with MaybeIgnorable)(implicit L: Log[F]): F[A] =
LogSyntax.log(fa)(toLeveledMessage)

@inline def log_(toLeveledMessage: A => LogMessage with NotIgnorable)(implicit L: Log[F]): F[Unit] =
@inline def log_(toLeveledMessage: A => LogMessage with MaybeIgnorable)(implicit L: Log[F]): F[Unit] =
LogSyntax.log_(fa)(toLeveledMessage)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ import scala.annotation.implicitNotFound
// or
import loggerf.instances.cats.logF
---
-----
If this doesn't solve, you probably need a CanLog instance.
To create it, please check out the following document.
https://logger-f.kevinly.dev/docs/cats/import#canlog-logger
-----
If it doesn't solve, it's probably because of missing an Fx[F] instance.
You can simply import the Fx[F] instance of your effect library.
Please check out the message of @implicitNotFound annotation on effectie.core.Fx.
"""
)
trait Log[F[*]] {
Expand All @@ -31,15 +43,17 @@ trait Log[F[*]] {

def canLog: CanLog

def log[A](fa: F[A])(toLeveledMessage: A => LeveledMessage): F[A] =
def log[A](fa: F[A])(toLeveledMessage: A => LeveledMessage | Ignore.type): F[A] =
flatMap0(fa) { a =>
toLeveledMessage(a) match {
case LeveledMessage(message, level) =>
flatMap0(EF.effectOf(canLog.getLogger(level)(message())))(_ => EF.pureOf(a))
case Ignore =>
EF.pureOf(a)
}
}

def log_[A](fa: F[A])(toLeveledMessage: A => LeveledMessage): F[Unit] =
def log_[A](fa: F[A])(toLeveledMessage: A => LeveledMessage | Ignore.type): F[Unit] =
map0(log(fa)(toLeveledMessage))(_ => ())

def logS(message: => String)(toLeveledMessage: (String => LeveledMessage) with LeveledMessage.Leveled): F[String] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import scala.concurrent.Future
trait LogSyntax {

extension [F[*], A](fa: F[A]) {
def log(toLeveledMessage: A => LeveledMessage)(using L: Log[F]): F[A] =
def log(toLeveledMessage: A => LeveledMessage | Ignore.type)(using L: Log[F]): F[A] =
L.log(fa)(toLeveledMessage)

def log_(toLeveledMessage: A => LeveledMessage)(using L: Log[F]): F[Unit] =
def log_(toLeveledMessage: A => LeveledMessage | Ignore.type)(using L: Log[F]): F[Unit] =
L.log_(fa)(toLeveledMessage)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@ import java.util.concurrent.ExecutorService
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}

import loggerf.test_data
import loggerf.test_data.TestCases

/** @author Kevin Lee
* @since 2022-02-09
*/
object instancesSpec extends Properties {
override def tests: List[Test] = List(
property("test Log.log(F[A])", testLogFA),
property("test Log.log(F[A]) with matching cases", testLogFAWithMatchingCases),
property("test Log.log_(F[A])", testLog_FA),
property("test Log.log_(F[A]) with matching cases", testLog_FAWithMatchingCases),
property("test Log.logS(String)", testLogS),
property("test Log.logS_(String)", testLogS_()),
property("test Log.log(F[Option[A]])", testLogFOptionA),
Expand Down Expand Up @@ -56,9 +61,13 @@ object instancesSpec extends Properties {
def runLog[F[*]: Fx: Log: Monad]: F[Unit] =
(for {
_ <- Log[F].log(Fx[F].effectOf(debugMsg))(debug)
_ <- Log[F].log(Fx[F].effectOf(debugMsg))(ignoreA)
_ <- Log[F].log(Fx[F].effectOf(infoMsg))(info)
_ <- Log[F].log(Fx[F].effectOf(infoMsg))(ignoreA)
_ <- Log[F].log(Fx[F].effectOf(warnMsg))(warn)
_ <- Log[F].log(Fx[F].effectOf(warnMsg))(ignoreA)
_ <- Log[F].log(Fx[F].effectOf(errorMsg))(error)
_ <- Log[F].log(Fx[F].effectOf(errorMsg))(ignoreA)
} yield ())

val expected = LoggerForTesting(
Expand All @@ -81,6 +90,69 @@ object instancesSpec extends Properties {

}

def testLogFAWithMatchingCases: Property = for {
debugMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("debugMsg")
infoMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("infoMsg")
warnMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("warnMsg")
errorMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("errorMsg")
testCases <- test_data.Gens.genTestCases.log("testCases")
} yield {

implicit val logger: LoggerForTesting = LoggerForTesting()

def runLog[F[*]: Fx: Log: Monad]: F[Unit] = {
val fa1 = Log[F].log(Fx[F].effectOf(testCases)) {
case TestCases(_, _, true) =>
ignore
case TestCases(id, name, false) =>
debug(s"$debugMsg / id=${id.toString}, name=$name, not enabled")
}
val fa2 = Log[F].log(fa1) {
case TestCases(_, _, true) =>
ignore
case TestCases(id, name, false) =>
info(s"$infoMsg / id=${id.toString}, name=$name, not enabled")
}
val fa3 = Log[F].log(fa2) {
case TestCases(_, _, true) =>
ignore
case TestCases(id, name, false) =>
warn(s"$warnMsg / id=${id.toString}, name=$name, not enabled")
}
val fa4 = Log[F].log(fa3) {
case TestCases(_, _, true) =>
ignore
case TestCases(id, name, false) =>
error(s"$errorMsg / id=${id.toString}, name=$name, not enabled")
}
fa4 *> Fx[F].unitOf

}

val expected =
if (testCases.enabled)
LoggerForTesting()
else
LoggerForTesting(
debugMessages = Vector(s"$debugMsg / id=${testCases.id.toString}, name=${testCases.name}, not enabled"),
infoMessages = Vector(s"$infoMsg / id=${testCases.id.toString}, name=${testCases.name}, not enabled"),
warnMessages = Vector(s"$warnMsg / id=${testCases.id.toString}, name=${testCases.name}, not enabled"),
errorMessages = Vector(s"$errorMsg / id=${testCases.id.toString}, name=${testCases.name}, not enabled"),
)

implicit val es: ExecutorService = ConcurrentSupport.newExecutorService(2)
implicit val ec: ExecutionContext =
ConcurrentSupport.newExecutionContextWithLogger(es, ErrorLogger.printlnExecutionContextErrorLogger)

ConcurrentSupport.futureToValueAndTerminate(es, waitFor400Millis) {

runLog[Future]

}
logger ==== expected

}

def testLog_FA: Property = for {
debugMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("debugMsg")
infoMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("infoMsg")
Expand All @@ -93,9 +165,13 @@ object instancesSpec extends Properties {
def runLog[F[*]: Fx: Log: Monad]: F[Unit] =
Log[F]
.log_(Fx[F].effectOf(debugMsg))(debug)
.flatMap { _ => Log[F].log_(Fx[F].effectOf(debugMsg))(ignoreA) }
.flatMap { _ => Log[F].log_(Fx[F].effectOf(infoMsg))(info) }
.flatMap { _ => Log[F].log_(Fx[F].effectOf(infoMsg))(ignoreA) }
.flatMap { _ => Log[F].log_(Fx[F].effectOf(warnMsg))(warn) }
.flatMap { _ => Log[F].log_(Fx[F].effectOf(warnMsg))(ignoreA) }
.flatMap { _ => Log[F].log_(Fx[F].effectOf(errorMsg))(error) }
.flatMap { _ => Log[F].log_(Fx[F].effectOf(errorMsg))(ignoreA) }

val expected = LoggerForTesting(
debugMessages = Vector(debugMsg),
Expand All @@ -117,6 +193,65 @@ object instancesSpec extends Properties {

}

def testLog_FAWithMatchingCases: Property = for {
debugMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("debugMsg")
infoMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("infoMsg")
warnMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("warnMsg")
errorMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("errorMsg")
testCases <- test_data.Gens.genTestCases.log("testCases")
} yield {

implicit val logger: LoggerForTesting = LoggerForTesting()

def runLog[F[*]: Fx: Log: Monad]: F[Unit] = {
val fa = Fx[F].effectOf(testCases)
Log[F].log_(fa) {
case TestCases(_, _, true) =>
ignore
case TestCases(id, name, false) =>
debug(s"$debugMsg / id=${id.toString}, name=$name, not enabled")
} *> Log[F].log_(fa) {
case TestCases(_, _, true) =>
ignore
case TestCases(id, name, false) =>
info(s"$infoMsg / id=${id.toString}, name=$name, not enabled")
} *> Log[F].log_(fa) {
case TestCases(_, _, true) =>
ignore
case TestCases(id, name, false) =>
warn(s"$warnMsg / id=${id.toString}, name=$name, not enabled")
} *> Log[F].log_(fa) {
case TestCases(_, _, true) =>
ignore
case TestCases(id, name, false) =>
error(s"$errorMsg / id=${id.toString}, name=$name, not enabled")
}
}

val expected =
if (testCases.enabled)
LoggerForTesting()
else
LoggerForTesting(
debugMessages = Vector(s"$debugMsg / id=${testCases.id.toString}, name=${testCases.name}, not enabled"),
infoMessages = Vector(s"$infoMsg / id=${testCases.id.toString}, name=${testCases.name}, not enabled"),
warnMessages = Vector(s"$warnMsg / id=${testCases.id.toString}, name=${testCases.name}, not enabled"),
errorMessages = Vector(s"$errorMsg / id=${testCases.id.toString}, name=${testCases.name}, not enabled"),
)

implicit val es: ExecutorService = ConcurrentSupport.newExecutorService(2)
implicit val ec: ExecutionContext =
ConcurrentSupport.newExecutionContextWithLogger(es, ErrorLogger.printlnExecutionContextErrorLogger)

ConcurrentSupport.futureToValueAndTerminate(es, waitFor400Millis) {

runLog[Future]

}
logger ==== expected

}

def testLogS: Property = for {
debugMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("debugMsg")
infoMsg <- Gen.string(Gen.unicode, Range.linear(1, 20)).log("infoMsg")
Expand Down
Loading

0 comments on commit 47a0ad1

Please sign in to comment.