aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvlad <vlad@driver.xyz>2017-10-31 11:09:29 -0700
committervlad <vlad@driver.xyz>2017-10-31 11:09:29 -0700
commit18c68db6e446c48c5aed0010a1d057368273685c (patch)
treee31d84c7bfbf62baabab34ef8c6d18b018ada55a
parent0cb06d70bd91e1e6a4ab9d97851ef9db7aaedfd6 (diff)
downloaddriver-core-phi-safe-logging.tar.gz
driver-core-phi-safe-logging.tar.bz2
driver-core-phi-safe-logging.zip
PHI-safe logging from PDS UIphi-safe-logging
-rw-r--r--src/main/scala/xyz/driver/core/logging/phi.scala71
-rw-r--r--src/main/scala/xyz/driver/core/logging/phiLogging.scala57
-rw-r--r--src/test/scala/xyz/driver/core/logging/PhiStringContextTest.scala32
3 files changed, 160 insertions, 0 deletions
diff --git a/src/main/scala/xyz/driver/core/logging/phi.scala b/src/main/scala/xyz/driver/core/logging/phi.scala
new file mode 100644
index 0000000..224d0fe
--- /dev/null
+++ b/src/main/scala/xyz/driver/core/logging/phi.scala
@@ -0,0 +1,71 @@
+package xyz.driver.core.logging
+
+import java.net.{URI, URL}
+import java.nio.file.Path
+import java.time.{LocalDate, LocalDateTime}
+import java.util.UUID
+
+import xyz.driver.core.time.Time
+
+import scala.concurrent.duration.Duration
+
+object phi {
+
+ class NoPhiString(private[logging] val text: String) {
+ // scalastyle:off
+ @inline def +(that: NoPhiString) = new NoPhiString(this.text + that.text)
+ }
+
+ implicit class NoPhiStringContext(val sc: StringContext) extends AnyVal {
+ def noPhi(args: NoPhiString*): NoPhiString = {
+ val phiArgs = args.map(_.text)
+ new NoPhiString(sc.s(phiArgs: _*))
+ }
+ }
+
+ /**
+ * Use it with care!
+ */
+ final case class NoPhi[T](private[logging] val value: T)
+ extends NoPhiString(Option(value).map(_.toString).getOrElse("<null>"))
+
+ // DO NOT ADD!
+ // 1. phi"$fullName" is easier to write, than phi"${Unsafe(fullName)}"
+ // If you wrote the second version, it means that you know, what you doing.
+ // 2. implicit def toPhiString(s: String): PhiString = Unsafe(s)
+
+ implicit def booleanToPhiString(x: Boolean): NoPhiString = NoPhi(x.toString)
+
+ implicit def uriToPhiString(x: URI): NoPhiString = NoPhi(x.toString)
+
+ implicit def urlToPhiString(x: URL): NoPhiString = NoPhi(x.toString)
+
+ implicit def pathToPhiString(x: Path): NoPhiString = NoPhi(x.toString)
+
+ implicit def timeToPhiString(x: Time): NoPhiString = NoPhi(x.toString)
+
+ implicit def localDateTimeToPhiString(x: LocalDateTime): NoPhiString = NoPhi(x.toString)
+
+ implicit def localDateToPhiString(x: LocalDate): NoPhiString = NoPhi(x.toString)
+
+ implicit def durationToPhiString(x: Duration): NoPhiString = NoPhi(x.toString)
+
+ implicit def uuidToPhiString(x: UUID): NoPhiString = NoPhi(x.toString)
+
+ implicit def tuple2ToPhiString[T1, T2](x: (T1, T2))(implicit inner1: T1 => NoPhiString,
+ inner2: T2 => NoPhiString): NoPhiString =
+ x match { case (a, b) => noPhi"($a, $b)" }
+
+ implicit def tuple3ToPhiString[T1, T2, T3](x: (T1, T2, T3))(implicit inner1: T1 => NoPhiString,
+ inner2: T2 => NoPhiString,
+ inner3: T3 => NoPhiString): NoPhiString =
+ x match { case (a, b, c) => noPhi"($a, $b, $c)" }
+
+ implicit def optionToPhiString[T](opt: Option[T])(implicit inner: T => NoPhiString): NoPhiString = opt match {
+ case None => noPhi"None"
+ case Some(x) => noPhi"Some($x)"
+ }
+
+ implicit def iterableToPhiString[T](xs: Iterable[T])(implicit inner: T => NoPhiString): NoPhiString =
+ NoPhi(xs.map(inner(_).text).mkString("Col(", ", ", ")"))
+}
diff --git a/src/main/scala/xyz/driver/core/logging/phiLogging.scala b/src/main/scala/xyz/driver/core/logging/phiLogging.scala
new file mode 100644
index 0000000..27b6ddc
--- /dev/null
+++ b/src/main/scala/xyz/driver/core/logging/phiLogging.scala
@@ -0,0 +1,57 @@
+package xyz.driver.core.logging
+
+import java.time.{LocalDateTime, ZoneId}
+
+import org.slf4j.LoggerFactory
+import xyz.driver.core.Id
+import xyz.driver.core.auth.User
+import xyz.driver.core.logging.phi._
+
+trait PhiSafeLogger {
+
+ def error(message: NoPhiString): Unit
+
+ def warn(message: NoPhiString): Unit
+
+ def info(message: NoPhiString): Unit
+
+ def debug(message: NoPhiString): Unit
+
+ def trace(message: NoPhiString): Unit
+
+}
+
+class DefaultPhiSafeLogger(underlying: org.slf4j.Logger) extends PhiSafeLogger {
+
+ def error(message: NoPhiString): Unit = underlying.error(message.text)
+
+ def warn(message: NoPhiString): Unit = underlying.warn(message.text)
+
+ def info(message: NoPhiString): Unit = underlying.info(message.text)
+
+ def debug(message: NoPhiString): Unit = underlying.debug(message.text)
+
+ def trace(message: NoPhiString): Unit = underlying.trace(message.text)
+
+}
+
+trait PhiSafeLogging {
+
+ protected val logger: PhiSafeLogger = new DefaultPhiSafeLogger(LoggerFactory.getLogger(getClass.getName))
+
+ /**
+ * Logs the failMessage on an error level, if isSuccessful is false.
+ * @return isSuccessful
+ */
+ protected def loggedError(isSuccessful: Boolean, failMessage: NoPhiString): Boolean = {
+ if (!isSuccessful) {
+ logger.error(failMessage)
+ }
+ isSuccessful
+ }
+
+ protected def logTime(userId: Id[User], label: NoPhiString, obj: NoPhiString): Unit = {
+ val now = LocalDateTime.now(ZoneId.of("Z"))
+ logger.info(noPhi"User id=${NoPhi(userId)} performed an action at $label=$now with a $obj")
+ }
+}
diff --git a/src/test/scala/xyz/driver/core/logging/PhiStringContextTest.scala b/src/test/scala/xyz/driver/core/logging/PhiStringContextTest.scala
new file mode 100644
index 0000000..4ffe7ae
--- /dev/null
+++ b/src/test/scala/xyz/driver/core/logging/PhiStringContextTest.scala
@@ -0,0 +1,32 @@
+package xyz.driver.core.logging
+
+import org.scalatest.FreeSpecLike
+import xyz.driver.core.logging.phi._
+
+class PhiStringContextTest extends FreeSpecLike {
+
+ class Foo(x: Int, y: String) {
+ val z: Boolean = true
+ }
+
+ case class Bar(y: Boolean)
+
+ implicit def fooToPhiString(foo: Foo): NoPhiString = new NoPhiString(s"Foo(z=${foo.z})")
+
+ "should not compile if there is no PhiString implicit" in assertDoesNotCompile(
+ """val bar = Bar(true)
+ |noPhi"bar is $bar"""".stripMargin
+ )
+
+ "should compile if there is a PhiString implicit" in assertCompiles(
+ """val foo = new Foo(1, "test")
+ |println(noPhi"foo is $foo}")""".stripMargin
+ )
+
+ "should not contain private info" in {
+ val foo = new Foo(42, "test")
+ val result = noPhi"foo is $foo".text
+ assert(!result.contains("test"))
+ assert(!result.contains("42"))
+ }
+}