Skip to content

Commit

Permalink
Close #461 - Add LoggerFLogHandler to support doobie's LogHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin-lee committed Nov 25, 2024
1 parent 97818cd commit b334b94
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 36 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ jobs:
strategy:
matrix:
scala:
- { name: "Scala 2", version: "2.12.13", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.13.6", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }
- { name: "Scala 3", version: "3.0.2", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.12.18", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.13.11", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }
- { name: "Scala 3", version: "3.3.0", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
scala:
- { name: "Scala 2", version: "2.13.6", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }
- { name: "Scala 2", version: "2.13.11", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" }

steps:
- uses: actions/checkout@v4
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ jobs:
strategy:
matrix:
scala:
- { name: "Scala 2", version: "2.12.13", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.13.6", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 3", version: "3.0.2", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.12.18", binary-version: "2.12", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 2", version: "2.13.11", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "" }
- { name: "Scala 3", version: "3.3.0", binary-version: "3", java-version: "11", java-distribution: "temurin", report: "" }

steps:
- uses: actions/checkout@v4
Expand Down
43 changes: 33 additions & 10 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ lazy val loggerF = (project in file("."))
catsJs,
logbackMdcMonix3Jvm,
logbackMdcMonix3Js,
doobie1Jvm,
doobie1Js,
testKitJvm,
testKitJs,
catsEffectJvm,
Expand Down Expand Up @@ -276,6 +278,28 @@ lazy val logbackMdcMonix3 = module(ProjectName("logback-mdc-monix3"), crossPr
lazy val logbackMdcMonix3Jvm = logbackMdcMonix3.jvm
lazy val logbackMdcMonix3Js = logbackMdcMonix3.js

lazy val doobie1 = module(ProjectName("doobie1"), crossProject(JVMPlatform, JSPlatform))
.settings(
description := "Logger for F[_] - for Doobie v1",
libraryDependencies ++= Seq(
libs.doobieFree,
libs.tests.effectieCatsEffect3,
libs.tests.extrasHedgehogCatsEffect3,
) ++ libs.tests.hedgehogLibs,
libraryDependencies := libraryDependenciesRemoveScala3Incompatible(
scalaVersion.value,
libraryDependencies.value,
),
)
.dependsOn(
core,
cats,
testKit % Test,
slf4jLogger % Test,
)
lazy val doobie1Jvm = doobie1.jvm
lazy val doobie1Js = doobie1.js

lazy val testKit =
module(ProjectName("test-kit"), crossProject(JVMPlatform, JSPlatform))
.settings(
Expand Down Expand Up @@ -514,8 +538,8 @@ lazy val props =
final val GitHubUsername = "Kevin-Lee"
final val RepoName = "logger-f"

final val Scala3Versions = List("3.0.2")
final val Scala2Versions = List("2.13.6", "2.12.13")
final val Scala3Versions = List("3.3.0")
final val Scala2Versions = List("2.13.11", "2.12.18")

// final val ProjectScalaVersion = Scala3Versions.head
final val ProjectScalaVersion = Scala2Versions.head
Expand Down Expand Up @@ -550,6 +574,8 @@ lazy val props =

val Monix3Version = "3.4.0"

val Doobie1Version = "1.0.0-RC4"

final val LoggerF1Version = "1.20.0"

final val ExtrasVersion = "0.25.0"
Expand Down Expand Up @@ -593,10 +619,14 @@ lazy val libs =

lazy val logbackScalaInterop = "io.kevinlee" % "logback-scala-interop" % props.LogbackScalaInteropVersion

lazy val doobieFree = "org.tpolecat" %% "doobie-free" % props.Doobie1Version

lazy val tests = new {

lazy val monix = "io.monix" %% "monix" % props.Monix3Version % Test

lazy val effectieCatsEffect3 = "io.kevinlee" %% "effectie-cats-effect3" % props.EffectieVersion % Test

lazy val effectieMonix3 = "io.kevinlee" %% "effectie-monix3" % props.EffectieVersion % Test

lazy val hedgehogLibs: List[ModuleID] = List(
Expand Down Expand Up @@ -625,14 +655,7 @@ def prefixedProjectName(name: String) = s"${props.RepoName}${if (name.isEmpty) "
def libraryDependenciesRemoveScala3Incompatible(
scalaVersion: String,
libraries: Seq[ModuleID],
): Seq[ModuleID] =
(
if (scalaVersion.startsWith("3."))
libraries
.filterNot(props.removeDottyIncompatible)
else
libraries
)
): Seq[ModuleID] = libraries

lazy val mavenCentralPublishSettings: SettingsDefinition = List(
/* Publish to Maven Central { */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import loggerf.core.ToLog
* @since 2022-02-19
*/
trait show {
inline given showToLog[A: Show]: ToLog[A] = Show[A].show(_)
given showToLog[A: Show]: ToLog[A] with {
inline def toLogMessage(a: A): String = Show[A].show(a)
}
}

object show extends show
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ object ToLog {
@SuppressWarnings(Array("org.wartremover.warts.ToString"))
def fromToString[A]: ToLog[A] = _.toString

inline given stringToLog: ToLog[String] = identity(_)
given stringToLog: ToLog[String] with {
inline def toLogMessage(a: String): String = a
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package loggerf.doobie1

import cats.syntax.all._
import doobie.util.log
import doobie.util.log.LogHandler
import loggerf.core.Log
import loggerf.syntax.all._

/** @author Kevin Lee
* @since 2023-07-28
*/
object LoggerFLogHandler {
def apply[F[*]: Log]: LogHandler[F] = new LoggerFLogHandlerF[F]

// format: off
final private class LoggerFLogHandlerF[F[*]: Log] extends LogHandler[F] {
override def run(logEvent: log.LogEvent): F[Unit] = logEvent match {
case log.Success(sql, args, label, e1, e2) =>
logS_(
show"""Successful Statement Execution:
|
| ${sql.linesIterator.dropWhile(_.trim.isEmpty).mkString("\n ")}
|
| arguments = [${args.mkString(", ")}]
| label = $label
| elapsed = ${e1.toMillis} ms exec + ${e2.toMillis} ms processing (${(e1 + e2).toMillis} ms total)
|""".stripMargin
)(info)

case log.ProcessingFailure(sql, args, label, e1, e2, failure) =>
logS_(
show"""Failed Resultset Processing:
|
| ${sql.linesIterator.dropWhile(_.trim.isEmpty).mkString("\n ")}
|
| arguments = [${args.mkString(", ")}]
| label = $label
| elapsed = ${e1.toMillis} ms exec + ${e2.toMillis} ms processing (failed) (${(e1 + e2).toMillis} ms total)
| failure = ${failure.getMessage}
|""".stripMargin
)(error)

case log.ExecFailure(sql, args, label, e1, failure) =>
logS_(
show"""Failed Statement Execution:
|
| ${sql.linesIterator.dropWhile(_.trim.isEmpty).mkString("\n ")}
|
| arguments = [${args.mkString(", ")}]
| label = $label
| elapsed = ${e1.toMillis} ms exec (failed)
| failure = ${failure.getMessage}
|""".stripMargin
)(error)
}
}
// format: on
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package loggerf.doobie1

import cats.effect._
import doobie.util.log.{ExecFailure, ProcessingFailure, Success}
import effectie.instances.ce3.fx.ioFx
import extras.hedgehog.ce3.syntax.runner._
import hedgehog._
import hedgehog.runner._
import loggerf.instances.cats.logF
import loggerf.testing.CanLog4Testing
import loggerf.testing.CanLog4Testing.OrderedMessages

import scala.concurrent.duration._

/** @author Kevin Lee
* @since 2023-07-29
*/
object LoggerFLogHandlerSpec extends Properties {

override def tests: List[Prop] = List(
property("testSuccess", testSuccess),
property("testExecFailure", testExecFailure),
property("testProcessingFailure", testProcessingFailure),
)

def testSuccess: Property =
for {
columns <- Gen.string(Gen.alpha, Range.linear(3, 10)).list(Range.linear(1, 5)).log("columns")
table <- Gen.string(Gen.alpha, Range.linear(3, 10)).log("table")
args <- Gen.string(Gen.alpha, Range.linear(3, 10)).list(Range.linear(0, 5)).log("args")
label <- Gen.string(Gen.alpha, Range.linear(3, 10)).log("label")
exec <- Gen.int(Range.linear(10, 3000)).log("exec")
processing <- Gen.int(Range.linear(10, 3000)).log("processing")
} yield runIO {
implicit val canLog: CanLog4Testing = CanLog4Testing()

val expected =
OrderedMessages(
Vector(
(
0,
loggerf.Level.info,
s"""Successful Statement Execution:
|
| SELECT ${columns.mkString(", ")} FROM $table
|
| arguments = ${args.mkString("[", ", ", "]")}
| label = $label
| elapsed = ${exec.toString} ms exec + ${processing.toString} ms processing (${(exec + processing).toString} ms total)
|""".stripMargin,
)
)
)

LoggerFLogHandler[IO]
.run(
Success(
s"SELECT ${columns.mkString(", ")} FROM $table",
args,
label,
exec.milliseconds,
processing.milliseconds,
)
) *> IO {
val actual = canLog.getOrderedMessages
actual ==== expected
}
}

def testExecFailure: Property =
for {
columns <- Gen.string(Gen.alpha, Range.linear(3, 10)).list(Range.linear(1, 5)).log("columns")
table <- Gen.string(Gen.alpha, Range.linear(3, 10)).log("table")
args <- Gen.string(Gen.alpha, Range.linear(3, 10)).list(Range.linear(0, 5)).log("args")
label <- Gen.string(Gen.alpha, Range.linear(3, 10)).log("label")
exec <- Gen.int(Range.linear(10, 3000)).log("exec")
errMessage <- Gen.string(Gen.alpha, Range.linear(3, 10)).log("errMessage")
} yield runIO {
implicit val canLog: CanLog4Testing = CanLog4Testing()

val expectedException = new RuntimeException(errMessage)

val expected =
OrderedMessages(
Vector(
(
0,
loggerf.Level.error,
s"""Failed Statement Execution:
|
| SELECT ${columns.mkString(", ")} FROM $table
|
| arguments = ${args.mkString("[", ", ", "]")}
| label = $label
| elapsed = ${exec.toString} ms exec (failed)
| failure = ${expectedException.getMessage}
|""".stripMargin,
)
)
)

LoggerFLogHandler[IO]
.run(
ExecFailure(
s"SELECT ${columns.mkString(", ")} FROM $table",
args,
label,
exec.milliseconds,
expectedException,
)
) *> IO {
val actual = canLog.getOrderedMessages
actual ==== expected
}
}

def testProcessingFailure: Property =
for {
columns <- Gen.string(Gen.alpha, Range.linear(3, 10)).list(Range.linear(1, 5)).log("columns")
table <- Gen.string(Gen.alpha, Range.linear(3, 10)).log("table")
args <- Gen.string(Gen.alpha, Range.linear(3, 10)).list(Range.linear(0, 5)).log("args")
label <- Gen.string(Gen.alpha, Range.linear(3, 10)).log("label")
exec <- Gen.int(Range.linear(10, 3000)).log("exec")
processing <- Gen.int(Range.linear(10, 3000)).log("processing")
errMessage <- Gen.string(Gen.alpha, Range.linear(3, 10)).log("errMessage")
} yield runIO {
implicit val canLog: CanLog4Testing = CanLog4Testing()

val expectedException = new RuntimeException(errMessage)

val expected =
OrderedMessages(
Vector(
(
0,
loggerf.Level.error,
s"""Failed Resultset Processing:
|
| SELECT ${columns.mkString(", ")} FROM $table
|
| arguments = ${args.mkString("[", ", ", "]")}
| label = $label
| elapsed = ${exec.toString} ms exec + ${processing.toString} ms processing (failed) (${(exec + processing).toString} ms total)
| failure = ${expectedException.getMessage}
|""".stripMargin,
)
)
)

LoggerFLogHandler[IO]
.run(
ProcessingFailure(
s"SELECT ${columns.mkString(", ")} FROM $table",
args,
label,
exec.milliseconds,
processing.milliseconds,
expectedException,
)
) *> IO {
val actual = canLog.getOrderedMessages
actual ==== expected
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,18 @@ object Monix3MdcAdapter extends Monix3MdcAdapterOps

trait Monix3MdcAdapterOps {

@SuppressWarnings(Array("org.wartremover.warts.Null"))
@SuppressWarnings(Array("org.wartremover.warts.Null", "scalafix:DisableSyntax.null"))
protected def initialize0(monix3MdcAdapter: Monix3MdcAdapter): Monix3MdcAdapter = {
val field = classOf[MDC].getDeclaredField("mdcAdapter")
field.setAccessible(true)
field.set(null, monix3MdcAdapter) // scalafix:ok DisableSyntax.null
field.set(null, monix3MdcAdapter)
field.setAccessible(false)
monix3MdcAdapter
}

@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf"))
@SuppressWarnings(Array("org.wartremover.warts.AsInstanceOf", "scalafix:DisableSyntax.asInstanceOf"))
protected def getLoggerContext(): LoggerContext =
LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext] // scalafix:ok DisableSyntax.asInstanceOf
LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]

def initialize(): Monix3MdcAdapter =
initializeWithMonix3MdcAdapterAndLoggerContext(new Monix3MdcAdapter, getLoggerContext())
Expand Down
Loading

0 comments on commit b334b94

Please sign in to comment.