Skip to content

Commit

Permalink
Allow adding effect's contextual environment to the log context
Browse files Browse the repository at this point in the history
  • Loading branch information
iRevive committed Oct 30, 2023
1 parent 77bd29d commit 8f5d723
Show file tree
Hide file tree
Showing 9 changed files with 610 additions and 4 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: mkdir -p testing/jvm/target noop/jvm/target core/native/target testing/native/target noop/native/target core/js/target js-console/target testing/js/target noop/js/target core/jvm/target slf4j/target project/target
run: mkdir -p mtl/native/target testing/jvm/target noop/jvm/target core/native/target testing/native/target noop/native/target core/js/target js-console/target testing/js/target noop/js/target core/jvm/target mtl/js/target mtl/jvm/target slf4j/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: tar cf targets.tar testing/jvm/target noop/jvm/target core/native/target testing/native/target noop/native/target core/js/target js-console/target testing/js/target noop/js/target core/jvm/target slf4j/target project/target
run: tar cf targets.tar mtl/native/target testing/jvm/target noop/jvm/target core/native/target testing/native/target noop/native/target core/js/target js-console/target testing/js/target noop/js/target core/jvm/target mtl/js/target mtl/jvm/target slf4j/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
Expand Down
14 changes: 12 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ ThisBuild / tlVersionIntroduced := Map("3" -> "2.1.1")

val catsV = "2.10.0"
val catsEffectV = "3.5.2"
val catsMtlV = "1.4.0"
val slf4jV = "1.7.36"
val munitCatsEffectV = "2.0.0-M3"
val logbackClassicV = "1.2.12"

Global / onChangedBuildSource := ReloadOnSourceChanges

lazy val root = tlCrossRootProject.aggregate(core, testing, noop, slf4j, docs, `js-console`)
lazy val root = tlCrossRootProject.aggregate(core, mtl, testing, noop, slf4j, docs, `js-console`)

lazy val docs = project
.in(file("site"))
Expand All @@ -56,9 +57,18 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
)
.nativeSettings(commonNativeSettings)

lazy val testing = crossProject(JSPlatform, JVMPlatform, NativePlatform)
lazy val mtl = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.settings(commonSettings)
.dependsOn(core)
.settings(
name := "log4cats-mtl",
libraryDependencies += "org.typelevel" %%% "cats-mtl" % catsMtlV
)
.nativeSettings(commonNativeSettings)

lazy val testing = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.settings(commonSettings)
.dependsOn(core, mtl % Test)
.settings(
name := "log4cats-testing",
libraryDependencies ++= Seq(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2018 Typelevel
*
* 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 org.typelevel.log4cats.mtl

import cats.Applicative
import cats.mtl.Ask

/**
* Represents the ability to access a contextual environment as a map of key-value pairs.
*
* @example
* {{{
* case class LogContext(logId: String)
*
* // how to transform the contextual environment into the log context
* implicit val toLogContext: ToContext[LogContext] =
* ctx => Map("log_id" -> ctx.logId)
*
* // available out of the box for Kleisli, Reader, etc
* implicit val askLogContext: Ask[F, LogContext] = ???
*
* implicit val contextual: Contextual[F] = Contextual.fromAsk
* }}}
*
* @tparam F
* the higher-kinded type of a polymorphic effect
*/
@scala.annotation.implicitNotFound("""
Couldn't find `Contextual` for type `${F}`. Make sure you have the following implicit instances:
1) `org.typelevel.log4cats.mtl.ToContext[Ctx]`
2) `cats.mtl.Ask[${F}, Ctx]`
""")
trait Contextual[F[_]] {

/**
* Retrieves the current contextual environment as a map of key-value pairs.
*/
def current: F[Map[String, String]]
}

object Contextual {

def apply[F[_]](implicit ev: Contextual[F]): Contextual[F] = ev

/**
* Creates a [[Contextual]] instance that always returns the given `ctx`.
*/
def const[F[_]: Applicative](ctx: Map[String, String]): Contextual[F] =
new Contextual[F] {
val current: F[Map[String, String]] = Applicative[F].pure(ctx)
}

implicit def fromAsk[F[_], A: ToContext](implicit A: Ask[F, A]): Contextual[F] =
new Contextual[F] {
def current: F[Map[String, String]] = A.reader(ToContext[A].extract)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.typelevel.log4cats
package mtl

import cats.FlatMap
import cats.syntax.functor._
import org.typelevel.log4cats.mtl.syntax._

object ContextualLoggerFactory {

/**
* Creates a new [[LoggerFactory]] that returns [[SelfAwareStructuredLogger]] that adds
* information captured by `Contextual` to the context.
*
* @example
* {{{
* case class LogContext(logId: String)
*
* implicit val toLogContext: ToContext[LogContext] =
* ctx => Map("log_id" -> ctx.logId)
*
* implicit val askLogContext: Ask[F, LogContext] = ???
*
* val loggerFactory: LoggerFactory[F] = ??? // the general factory, e.g. Slf4jFactory
* val contextual: LoggerFactory[F] = ContextualLoggerFactory(loggerFactory)
* }}}
*/
def apply[F[_]: Contextual: FlatMap](factory: LoggerFactory[F]): LoggerFactory[F] =
new LoggerFactory[F] {
def getLoggerFromName(name: String): SelfAwareStructuredLogger[F] =
factory.getLoggerFromName(name).contextual

def fromName(name: String): F[SelfAwareStructuredLogger[F]] =
factory.fromName(name).map(logger => logger.contextual)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright 2018 Typelevel
*
* 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 org.typelevel.log4cats
package mtl

import cats.FlatMap
import cats.syntax.flatMap._
import cats.syntax.functor._

private[mtl] class ContextualSelfAwareStructuredLogger[F[_]: Contextual: FlatMap](
sl: SelfAwareStructuredLogger[F]
) extends SelfAwareStructuredLogger[F] {

private val defaultCtx: F[Map[String, String]] = Contextual[F].current

private def modify(ctx: Map[String, String]): F[Map[String, String]] =
defaultCtx.map(c => c ++ ctx)

def isTraceEnabled: F[Boolean] = sl.isTraceEnabled
def isDebugEnabled: F[Boolean] = sl.isDebugEnabled
def isInfoEnabled: F[Boolean] = sl.isInfoEnabled
def isWarnEnabled: F[Boolean] = sl.isWarnEnabled
def isErrorEnabled: F[Boolean] = sl.isErrorEnabled

def error(message: => String): F[Unit] =
defaultCtx.flatMap(sl.error(_)(message))

def warn(message: => String): F[Unit] =
defaultCtx.flatMap(sl.warn(_)(message))

def info(message: => String): F[Unit] =
defaultCtx.flatMap(sl.info(_)(message))

def debug(message: => String): F[Unit] =
defaultCtx.flatMap(sl.debug(_)(message))

def trace(message: => String): F[Unit] =
defaultCtx.flatMap(sl.trace(_)(message))

def error(t: Throwable)(message: => String): F[Unit] =
defaultCtx.flatMap(sl.error(_, t)(message))

def warn(t: Throwable)(message: => String): F[Unit] =
defaultCtx.flatMap(sl.warn(_, t)(message))

def info(t: Throwable)(message: => String): F[Unit] =
defaultCtx.flatMap(sl.info(_, t)(message))

def debug(t: Throwable)(message: => String): F[Unit] =
defaultCtx.flatMap(sl.debug(_, t)(message))

def trace(t: Throwable)(message: => String): F[Unit] =
defaultCtx.flatMap(sl.trace(_, t)(message))

def trace(ctx: Map[String, String])(msg: => String): F[Unit] =
modify(ctx).flatMap(sl.trace(_)(msg))

def debug(ctx: Map[String, String])(msg: => String): F[Unit] =
modify(ctx).flatMap(sl.debug(_)(msg))

def info(ctx: Map[String, String])(msg: => String): F[Unit] =
modify(ctx).flatMap(sl.info(_)(msg))

def warn(ctx: Map[String, String])(msg: => String): F[Unit] =
modify(ctx).flatMap(sl.warn(_)(msg))

def error(ctx: Map[String, String])(msg: => String): F[Unit] =
modify(ctx).flatMap(sl.error(_)(msg))

def error(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] =
modify(ctx).flatMap(sl.error(_, t)(message))

def warn(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] =
modify(ctx).flatMap(sl.warn(_, t)(message))

def info(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] =
modify(ctx).flatMap(sl.info(_, t)(message))

def debug(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] =
modify(ctx).flatMap(sl.debug(_, t)(message))

def trace(ctx: Map[String, String], t: Throwable)(message: => String): F[Unit] =
modify(ctx).flatMap(sl.trace(_, t)(message))
}

object ContextualSelfAwareStructuredLogger {

/**
* Creates a new [[SelfAwareStructuredLogger]] that adds information captured by `Contextual` to
* the context.
*
* @example
* {{{
* case class LogContext(logId: String)
*
* implicit val toLogContext: ToContext[LogContext] =
* ctx => Map("log_id" -> ctx.logId)
*
* implicit val askLogContext: Ask[F, LogContext] = ???
*
* val logger: SelfAwareStructuredLogger[F] = ??? // the general logger, e.g. Slf4jLogger
* val contextual: SelfAwareStructuredLogger[F] = ContextualSelfAwareStructuredLogger(logger)
* }}}
*/
def apply[F[_]: Contextual: FlatMap](
logger: SelfAwareStructuredLogger[F]
): SelfAwareStructuredLogger[F] =
new ContextualSelfAwareStructuredLogger[F](logger)

}
Loading

0 comments on commit 8f5d723

Please sign in to comment.