Skip to content

Commit 4559b15

Browse files
committed
Add logger with Local semantics
1 parent b607fbb commit 4559b15

File tree

4 files changed

+455
-2
lines changed

4 files changed

+455
-2
lines changed

build.sbt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@ ThisBuild / tlVersionIntroduced := Map("3" -> "2.1.1")
2828

2929
val catsV = "2.11.0"
3030
val catsEffectV = "3.6.0"
31+
val catsMtlV = "1.4.0"
3132
val slf4jV = "1.7.36"
3233
val munitCatsEffectV = "2.1.0"
3334
val logbackClassicV = "1.2.13"
3435

36+
val tempOtel4sV = "0.11.0"
37+
3538
Global / onChangedBuildSource := ReloadOnSourceChanges
3639

3740
lazy val root = tlCrossRootProject.aggregate(core, testing, noop, slf4j, docs, `js-console`)
@@ -46,8 +49,10 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
4649
.settings(
4750
name := "log4cats-core",
4851
libraryDependencies ++= Seq(
49-
"org.typelevel" %%% "cats-core" % catsV,
50-
"org.typelevel" %%% "cats-effect-std" % catsEffectV
52+
"org.typelevel" %%% "cats-core" % catsV,
53+
"org.typelevel" %%% "cats-effect-std" % catsEffectV,
54+
"org.typelevel" %%% "cats-mtl" % catsMtlV,
55+
"org.typelevel" %%% "otel4s-core-common" % tempOtel4sV
5156
),
5257
libraryDependencies ++= {
5358
if (tlIsScala3.value) Seq.empty
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2018 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.typelevel.log4cats
18+
19+
import cats.mtl.{Ask, Local}
20+
import cats.syntax.functor.*
21+
import cats.syntax.traverse.*
22+
import cats.{~>, Applicative, Show}
23+
import org.typelevel.otel4s.KindTransformer
24+
25+
import scala.collection.immutable.ArraySeq
26+
27+
sealed trait LocalLogContext[F[_]] {
28+
private[log4cats] def currentLogContext: F[Map[String, String]]
29+
30+
private[log4cats] def withAddedContext[A](ctx: Map[String, String])(fa: F[A]): F[A]
31+
32+
private[log4cats] final def withAddedContext[A](ctx: (String, Show.Shown)*)(fa: F[A]): F[A] =
33+
withAddedContext {
34+
ctx.view.map { case (k, v) => k -> v.toString }.toMap
35+
}(fa)
36+
37+
def withHighPriorityAskedContext(ask: Ask[F, Map[String, String]]): LocalLogContext[F]
38+
39+
def withLowPriorityAskedContext(ask: Ask[F, Map[String, String]]): LocalLogContext[F]
40+
41+
def mapK[G[_]: Applicative](implicit kt: KindTransformer[F, G]): LocalLogContext[G]
42+
}
43+
44+
object LocalLogContext {
45+
private[this] type AskContext[F[_]] = Ask[F, Map[String, String]]
46+
47+
private[this] final class MappedKLocal[F[_], G[_], E](
48+
localF: Local[F, E]
49+
)(implicit
50+
val applicative: Applicative[G],
51+
kt: KindTransformer[F, G]
52+
) extends Local[G, E] {
53+
def ask[E2 >: E]: G[E2] =
54+
kt.liftK(localF.ask[E2])
55+
def local[A](ga: G[A])(f: E => E): G[A] =
56+
kt.limitedMapK(ga) {
57+
new (F ~> F) {
58+
def apply[B](fb: F[B]): F[B] = localF.local(fb)(f)
59+
}
60+
}
61+
}
62+
63+
private[this] final class MappedKAsk[F[_], G[_], E](
64+
askF: Ask[F, E],
65+
fk: F ~> G
66+
)(implicit val applicative: Applicative[G])
67+
extends Ask[G, E] {
68+
def ask[E2 >: E]: G[E2] = fk(askF.ask[E2])
69+
}
70+
71+
private[this] final class MultiAskContext[F[_]] private[MultiAskContext] (
72+
asks: Seq[AskContext[F]] /* never empty */
73+
) extends AskContext[F] {
74+
implicit def applicative: Applicative[F] = asks.head.applicative
75+
def ask[E2 >: Map[String, String]]: F[E2] =
76+
asks
77+
.traverse(_.ask[Map[String, String]])
78+
.map(_.reduceLeft(_ ++ _))
79+
def prependLowPriority(ask: AskContext[F]): MultiAskContext[F] =
80+
new MultiAskContext(ask +: asks)
81+
def appendHighPriority(ask: AskContext[F]): MultiAskContext[F] =
82+
new MultiAskContext(asks :+ ask)
83+
}
84+
85+
private[this] object MultiAskContext {
86+
def apply[F[_]](ask: AskContext[F]): MultiAskContext[F] =
87+
ask match {
88+
case multi: MultiAskContext[F] => multi
89+
case other => new MultiAskContext(ArraySeq(other))
90+
}
91+
}
92+
93+
private[this] final class Impl[F[_]](
94+
localCtx: Local[F, Map[String, String]],
95+
askCtx: AskContext[F]
96+
) extends LocalLogContext[F] {
97+
private[log4cats] def currentLogContext: F[Map[String, String]] =
98+
askCtx.ask[Map[String, String]]
99+
private[log4cats] def withAddedContext[A](ctx: Map[String, String])(fa: F[A]): F[A] =
100+
localCtx.local(fa)(_ ++ ctx)
101+
102+
def withHighPriorityAskedContext(ask: Ask[F, Map[String, String]]): LocalLogContext[F] =
103+
new Impl(
104+
localCtx,
105+
MultiAskContext(askCtx).appendHighPriority(ask)
106+
)
107+
108+
def withLowPriorityAskedContext(ask: Ask[F, Map[String, String]]): LocalLogContext[F] =
109+
new Impl(
110+
localCtx,
111+
MultiAskContext(askCtx).prependLowPriority(ask)
112+
)
113+
114+
def mapK[G[_]](implicit G: Applicative[G], kt: KindTransformer[F, G]): LocalLogContext[G] = {
115+
val localF = localCtx
116+
val askF = askCtx
117+
val localG = new MappedKLocal(localF)
118+
val askG =
119+
if (askF eq localF) localG
120+
else new MappedKAsk(askF, kt.liftK)
121+
new Impl(localG, askG)
122+
}
123+
}
124+
125+
def fromLocal[F[_]](implicit localCtx: Local[F, Map[String, String]]): LocalLogContext[F] =
126+
new Impl(localCtx, localCtx)
127+
}

0 commit comments

Comments
 (0)