diff --git a/modules/logging/derivation/src/test/scala-2/tofu/logging/derivation/DerivedLoggableSamples.scala b/modules/logging/derivation/src/test/scala-2/tofu/logging/derivation/DerivedLoggableSamples.scala new file mode 100644 index 000000000..ca4006e21 --- /dev/null +++ b/modules/logging/derivation/src/test/scala-2/tofu/logging/derivation/DerivedLoggableSamples.scala @@ -0,0 +1,55 @@ +package tofu.logging.derivation + +import java.time.LocalDate +import java.util.UUID + +import derevo.derive +import tofu.logging.derivation.MaskMode.Custom + +object DerivedLoggableSamples { + @derive(loggable) + final case class Foo(lol: String, kek: Option[Long]) + + @derive(loggable) + final case class Bar( + @hidden foo1: Option[Foo] = None, + @unembed foo2: Option[Foo] = None, + foo3: Option[Foo] = None + ) + + @derive(loggable) + final case class Jak( + @masked(MaskMode.Erase) one: String, + @masked(MaskMode.ForLength(1)) two: Long, + @masked(MaskMode.Regexp("\\d*\\.(\\d*)".r)) three: Double, + @masked(MaskMode.Regexp("-?\\d*\\.(\\d*)".r)) four: List[Double], + ) + + @derive(loggable) + final case class Baz(foos: List[Foo] = Nil, ys: Vector[Int] = Vector(), zs: Option[List[List[String]]] = None) + + @derive(loggable) + final case class MaskedBaz(@masked kek: Option[String], @ignoreOpt a: Option[String] = None) + + @derive(loggable) + final case class MaskedOptBaz( + @masked maybeStr: Option[String], + @masked maybeInt: Option[Int], + @masked maybeBool: Option[Boolean], + @masked maybeDouble: Option[Double], + @masked maybeStr2: Option[String] + ) + + @derive(loggable) + final case class MaskedCustom( + @masked(Custom(_ => "*")) sensitiveField: String, + @masked(Custom(name => name.take(1) + "***")) firstName: Option[String], + @masked(Custom(i => "*" * i.length())) age: Int + ) + + @derive(loggable) + final case class MaskedContra( + @masked(MaskMode.Erase) id: UUID, + @masked(MaskMode.ForLength(4)) date: LocalDate, + ) +} diff --git a/modules/logging/derivation/src/test/scala-2/tofu/logging/derivation/DerivedLoggableSuite.scala b/modules/logging/derivation/src/test/scala-2/tofu/logging/derivation/DerivedLoggableSuite.scala deleted file mode 100644 index 09ac962c4..000000000 --- a/modules/logging/derivation/src/test/scala-2/tofu/logging/derivation/DerivedLoggableSuite.scala +++ /dev/null @@ -1,149 +0,0 @@ -package tofu.logging -package derivation - -import derevo.derive -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -import tofu.logging.derivation.MaskMode.Custom - -class DerivedLoggableSuite extends AnyFlatSpec with Matchers { - - import DerivedLoggableSuite._ - - val foo = Foo("zaz", Some(1)) - - def json[A: Loggable](a: A) = TethysBuilder(a) - - "Foo logging" should "not rendered None" in { - json(foo.copy(kek = None)) shouldBe """{"lol":"zaz"}""" - } - it should "not rendered Some flat" in { - json(foo) shouldBe """{"lol":"zaz","kek":1}""" - } - - "Bar logging" should "hide fields" in { - json(Bar(foo1 = Some(foo))) shouldBe "{}" - } - - it should "unembed fields" in { - json(Bar(foo2 = Some(foo))) shouldBe """{"lol":"zaz","kek":1}""" - } - - it should "embed fields" in { - json(Bar(foo3 = Some(foo))) shouldBe """{"foo3":{"lol":"zaz","kek":1}}""" - } - - "Baz logging" should "respect object collections" in { - json(Baz(foos = List(foo, foo))) shouldBe """{"foos":[{"lol":"zaz","kek":1},{"lol":"zaz","kek":1}],"ys":[]}""" - } - - it should "respect primitive collections" in { - json(Baz(ys = Vector(1, 2, 3, 4))) shouldBe """{"foos":[],"ys":[1,2,3,4]}""" - } - - it should "respect complex primitive collections" in { - json( - Baz(zs = Some(List(List("one", "two"), List("three")))) - ) shouldBe """{"foos":[],"ys":[],"zs":[["one","two"],["three"]]}""" - } - - it should "respect masking" in { - json( - Jak("one", 4567, 3.123456, List(1.234, 5.678)) - ) shouldBe """{"one":"...","two":"4###","three":"3.######","four":["1.###","5.###"]}""" - } - - "MaskedBaz" should "emit Some" in { - json( - MaskedBaz(Some("auf")) - ) shouldBe """{"kek":"***"}""" - } - - it should "not emit None" in { - json( - MaskedBaz(None) - ) shouldBe """{}""" - } - - it should "not show for None with ignoreOpt" in { - Loggable[MaskedBaz].logShow(MaskedBaz(None)) shouldBe "MaskedBaz{kek=}" - } - - it should "show mask Option values in logShow" in { - val maybeMasked = MaskedOptBaz( - maybeStr = Some("str"), - maybeInt = Some(123), - maybeBool = Some(true), - maybeDouble = Some(100.001), - maybeStr2 = None - ) - Loggable[MaskedOptBaz].logShow(maybeMasked) shouldBe - "MaskedOptBaz{" + - "maybeStr=Some(***)," + - "maybeInt=Some(###)," + - "maybeBool=Some(****)," + - "maybeDouble=Some(###.###)," + - "maybeStr2=" + - "}" - } - - it should "show mask fields with custom masker function" in { - val maskedCustom = MaskedCustom( - sensitiveField = "som sensitive data", - firstName = Some("John"), - age = 42 - ) - - json(maskedCustom) shouldBe """{"sensitiveField":"*","firstName":"J***","age":"**"}""" - Loggable[MaskedCustom].logShow(maskedCustom) shouldBe - "MaskedCustom{sensitiveField=*,firstName=Some(J***),age=**}" - } -} - -object DerivedLoggableSuite { - @derive(loggable) - final case class Foo(lol: String, kek: Option[Long]) - - @derive(loggable) - final case class Bar( - @hidden foo1: Option[Foo] = None, - @unembed foo2: Option[Foo] = None, - foo3: Option[Foo] = None - ) - - @derive(loggable) - final case class Jak( - @masked(MaskMode.Erase) one: String, - @masked(MaskMode.ForLength(1)) two: Long, - @masked(MaskMode.Regexp("\\d*\\.(\\d*)".r)) three: Double, - @masked(MaskMode.Regexp("-?\\d*\\.(\\d*)".r)) four: List[Double], - ) - - @derive(loggable) - final case class Baz(foos: List[Foo] = Nil, ys: Vector[Int] = Vector(), zs: Option[List[List[String]]] = None) - - @derive(loggable) - final case class MaskedBaz(@masked kek: Option[String], @ignoreOpt a: Option[String] = None) - - @derive(loggable) - final case class MaskedOptBaz( - @masked maybeStr: Option[String], - @masked maybeInt: Option[Int], - @masked maybeBool: Option[Boolean], - @masked maybeDouble: Option[Double], - @masked maybeStr2: Option[String] - ) - - @derive(loggable) - final case class MaskedCustom( - @masked(Custom(_ => "*")) sensitiveField: String, - @masked(Custom(maskName)) firstName: Option[String], - @masked(Custom(maskAge)) age: Int - ) - - def maskName(name: String): String = - name.take(1) + "***" - - def maskAge(i: String): String = - "*" * i.length -} diff --git a/modules/logging/derivation/src/test/scala-3/DerivedLoggableSamples.scala b/modules/logging/derivation/src/test/scala-3/DerivedLoggableSamples.scala new file mode 100644 index 000000000..53fd51211 --- /dev/null +++ b/modules/logging/derivation/src/test/scala-3/DerivedLoggableSamples.scala @@ -0,0 +1,47 @@ +package tofu.logging +package derivation + +import java.time.LocalDate +import java.util.UUID + +import tofu.logging.derivation.MaskMode.Custom + +object DerivedLoggableSamples: + final case class Foo(lol: String, kek: Option[Long]) derives Loggable + + final case class Bar( + @hidden foo1: Option[Foo] = None, + @unembed foo2: Option[Foo] = None, + foo3: Option[Foo] = None + ) derives Loggable + + final case class Jak( + @masked(MaskMode.Erase) one: String, + @masked(MaskMode.ForLength(1)) two: Long, + @masked(MaskMode.Regexp("\\d*\\.(\\d*)".r)) three: Double, + @masked(MaskMode.Regexp("-?\\d*\\.(\\d*)".r)) four: List[Double], + ) derives Loggable + + final case class Baz(foos: List[Foo] = Nil, ys: Vector[Int] = Vector(), zs: Option[List[List[String]]] = None) + derives Loggable + + final case class MaskedBaz(@masked kek: Option[String], @ignoreOpt a: Option[String] = None) derives Loggable + + final case class MaskedOptBaz( + @masked maybeStr: Option[String], + @masked maybeInt: Option[Int], + @masked maybeBool: Option[Boolean], + @masked maybeDouble: Option[Double], + @masked maybeStr2: Option[String] + ) derives Loggable + + final case class MaskedCustom( + @masked(Custom(_ => "*")) sensitiveField: String, + @masked(Custom(name => name.take(1) + "***")) firstName: Option[String], + @masked(Custom(i => "*" * i.length())) age: Int + ) derives Loggable + + final case class MaskedContra( + @masked(MaskMode.Erase) id: UUID, + @masked(MaskMode.ForLength(4)) date: LocalDate, + ) derives Loggable diff --git a/modules/logging/derivation/src/test/scala-3/DerivedLoggableSuite.scala b/modules/logging/derivation/src/test/scala/tofu/logging/derivation/DerivedLoggableSuite.scala similarity index 65% rename from modules/logging/derivation/src/test/scala-3/DerivedLoggableSuite.scala rename to modules/logging/derivation/src/test/scala/tofu/logging/derivation/DerivedLoggableSuite.scala index 8e87a00bd..7508e6a7d 100644 --- a/modules/logging/derivation/src/test/scala-3/DerivedLoggableSuite.scala +++ b/modules/logging/derivation/src/test/scala/tofu/logging/derivation/DerivedLoggableSuite.scala @@ -1,13 +1,15 @@ package tofu.logging package derivation +import java.time.LocalDate +import java.util.UUID + import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers -import tofu.logging.derivation.MaskMode.Custom class DerivedLoggableSuite extends AnyFlatSpec with Matchers { - import DerivedLoggableSuite._ + import DerivedLoggableSamples._ val foo = Foo("zaz", Some(1)) @@ -97,47 +99,10 @@ class DerivedLoggableSuite extends AnyFlatSpec with Matchers { Loggable[MaskedCustom].logShow(maskedCustom) shouldBe "MaskedCustom{sensitiveField=*,firstName=Some(J***),age=**}" } -} - -object DerivedLoggableSuite { - final case class Foo(lol: String, kek: Option[Long]) derives Loggable - - final case class Bar( - @hidden foo1: Option[Foo] = None, - @unembed foo2: Option[Foo] = None, - foo3: Option[Foo] = None - ) derives Loggable - - final case class Jak( - @masked(MaskMode.Erase) one: String, - @masked(MaskMode.ForLength(1)) two: Long, - @masked(MaskMode.Regexp("\\d*\\.(\\d*)".r)) three: Double, - @masked(MaskMode.Regexp("-?\\d*\\.(\\d*)".r)) four: List[Double], - ) derives Loggable - - final case class Baz(foos: List[Foo] = Nil, ys: Vector[Int] = Vector(), zs: Option[List[List[String]]] = None) - derives Loggable - - final case class MaskedBaz(@masked kek: Option[String], @ignoreOpt a: Option[String] = None) derives Loggable - - final case class MaskedOptBaz( - @masked maybeStr: Option[String], - @masked maybeInt: Option[Int], - @masked maybeBool: Option[Boolean], - @masked maybeDouble: Option[Double], - @masked maybeStr2: Option[String] - ) derives Loggable - - final case class MaskedCustom( - @masked(Custom(_ => "*")) sensitiveField: String, - @masked(Custom(maskName)) firstName: Option[String], - @masked(Custom(maskAge)) age: Int - ) derives Loggable - - def maskName(name: String): String = - name.take(1) + "***" - - def maskAge(i: String): String = - "*" * i.length + "MaskedContra logging" should "mask fields" in { + json( + MaskedContra(UUID.randomUUID(), LocalDate.of(2023, 12, 1)) + ) shouldBe """{"id":"...","date":"2023-##-##"}""" + } } diff --git a/modules/logging/structured/src/main/scala/tofu/logging/impl/ContramapLoggable.scala b/modules/logging/structured/src/main/scala/tofu/logging/impl/ContramapLoggable.scala index ed53fe380..3b7197cdb 100644 --- a/modules/logging/structured/src/main/scala/tofu/logging/impl/ContramapLoggable.scala +++ b/modules/logging/structured/src/main/scala/tofu/logging/impl/ContramapLoggable.scala @@ -7,9 +7,14 @@ class ContramapLoggable[A, B](val self: Loggable.Base[A], val f: B => A) extends def fields[I, V, R, M](b: B, input: I)(implicit receiver: LogRenderer[I, V, R, M]): R = self.fields(f(b), input) def putValue[I, V, R, M](a: B, v: V)(implicit r: LogRenderer[I, V, R, M]): M = self.putValue(f(a), v) - override def putField[I, V, R, M](a: B, name: String, input: I)(implicit receiver: LogRenderer[I, V, R, M]): R = + override def putField[I, V, R, M](a: B, name: String, input: I)(implicit receiver: LogRenderer[I, V, R, M]): R = self.putField(f(a), name, input) - override def logVia(a: B, addParam: (String, Any) => Unit): Unit = self.logVia(f(a), addParam) - override def logShow(a: B): String = self.logShow(f(a)) - override def contramap[C](g: C => B): Loggable[C] = self.contramap(AndThen(g).andThen(f)) + override def logVia(a: B, addParam: (String, Any) => Unit): Unit = self.logVia(f(a), addParam) + override def logShow(a: B): String = self.logShow(f(a)) + override def contramap[C](g: C => B): Loggable[C] = self.contramap(AndThen(g).andThen(f)) + override def putMaskedValue[I, V, R, M](b: B, v: V)(m: String => String)(implicit r: LogRenderer[I, V, R, M]): M = + self.putMaskedValue(f(b), v)(m) + override def putMaskedField[I, V, R, M](b: B, name: String, input: I)(m: String => String)(implicit + receiver: LogRenderer[I, V, R, M] + ): R = self.putMaskedField(f(b), name, input)(m) }