diff --git a/modules/logging/derivation/src/main/scala/tofu/logging/derivation/masking.scala b/modules/logging/derivation/src/main/scala/tofu/logging/derivation/masking.scala index f96607682..e91c0ea15 100644 --- a/modules/logging/derivation/src/main/scala/tofu/logging/derivation/masking.scala +++ b/modules/logging/derivation/src/main/scala/tofu/logging/derivation/masking.scala @@ -20,6 +20,9 @@ object MaskMode { def this(length: Int) = this(0, length) } case class Regexp(pattern: Regex) extends MaskMode + + /** Allows to define a custom masker function */ + case class Custom(f: String => String) extends MaskMode } object masking { @@ -54,6 +57,8 @@ object masking { .getOrElse(shown) case MaskMode.ForLength(offset, maxLength) => loop(shown.toCharArray, shown.length min (offset max 0), if (maxLength == -1) shown.length else maxLength) + case MaskMode.Custom(f) => + f(shown) } } 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 index 5edb541e1..09ac962c4 100644 --- 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 @@ -4,6 +4,7 @@ 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 { @@ -85,6 +86,18 @@ class DerivedLoggableSuite extends AnyFlatSpec with Matchers { "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 { @@ -120,4 +133,17 @@ object DerivedLoggableSuite { @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/DerivedLoggableSuite.scala b/modules/logging/derivation/src/test/scala-3/DerivedLoggableSuite.scala index 09bb6a386..8e87a00bd 100644 --- a/modules/logging/derivation/src/test/scala-3/DerivedLoggableSuite.scala +++ b/modules/logging/derivation/src/test/scala-3/DerivedLoggableSuite.scala @@ -3,6 +3,7 @@ package derivation import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import tofu.logging.derivation.MaskMode.Custom class DerivedLoggableSuite extends AnyFlatSpec with Matchers { @@ -67,6 +68,35 @@ class DerivedLoggableSuite extends AnyFlatSpec with Matchers { 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 { @@ -89,4 +119,25 @@ object DerivedLoggableSuite { 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 + }