Skip to content

Instantly share code, notes, and snippets.

View vhutov's full-sized avatar

Vladyslav Hutov vhutov

View GitHub Profile
@vhutov
vhutov / example.scala
Last active February 2, 2020 12:00
IO Logging context example
def stage1(i: Int): IO[Int] = a()
def stage2(i: Int): IO[Int] = b()
def stage3(i: Int): IO[Int] = c().withContext(_.name("op"))
stage1(0)
.withContext(_.id(0)) // MDC {id: 0}
.flatMap(i =>
stage2(i)
.withContext(_.value(i)) // MDC {id: 0, value: i}
)
@vhutov
vhutov / LogContext.scala
Last active February 2, 2020 12:16
Implicit context
class LogContext {
val mdc: Map[String, String] = Map()
def update(k: String, v: String): LogContext = ???
}
@vhutov
vhutov / BusinessLogic.scala
Created February 2, 2020 12:18
BusinessLogic
trait DataStore[F[_]] {
def get(id: String)(implicit ctx: LogContext)
def update[T](id: String, t: T)(implicit ctx: LogContext)
def delete(id: String)(implicit ctx: LogContext)
}
class MongoDataStore[F[_]: Async] extends DataStore[F] {
override def get(id: String)(implicit ctx: LogContext)
override def update[T](id: String, t: T)(implicit ctx: LogContext)
override def delete(id: String)(implicit ctx: LogContext)
}
object Logging {
type IOLog[A] = WriterT[IO, Vector[String], A]
val IOLog = WriterT
}
object Test {
def loggedFunc(): IOLog[Int]
def plainFunc(i: Int): IO[String]
def work(): IOLog[String] =
@vhutov
vhutov / ThreadLocalExample.scala
Created February 2, 2020 13:28
ThreadLocalExample
trait Service {
def cleanAPI(i: Int): String
}
object ServiceImplementation {
private val ctx: ThreadLocal[String] = new ThreadLocal[String]
def setAdditionalArg(arg: String): Unit = ctx.set(arg)
}
class ServiceImplementation {
def cleanAPI(i: Int): String = {
trait Logger[F[_]] {
def error(message: => String): F[Unit]
def warn(message: => String): F[Unit]
// ... etc
def error(t: Throwable)(message: => String): F[Unit]
def warn(t: Throwable)(message: => String): F[Unit]
// ... etc
}
trait StructuredLogger[F[_]] extends Logger[F] {
def error(ctx: Map[String, String])(msg: => String): F[Unit]
def warn(ctx: Map[String, String])(msg: => String): F[Unit]
// ...
def error(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit]
def warn(ctx: Map[String, String], t: Throwable)(msg: => String): F[Unit]
}
case class LoggingContext(map: Map[String, String]) {
def id(id: String): LoggingContext = LoggingContext(map + ("id" -> id))
def tpe(tpe: String): LoggingContext = LoggingContext(map + ("type" -> tpe))
}
object LoggingContext {
def apply(): LoggingContext = LoggingContext(Map())
class MdcLogger[F[_]: Sync](logger: StructuredLogger[F]) extends Logger[F] {
@inline
private def withCtx(f: LoggingContext => F[Unit]): F[Unit] =
LoggingContext.get[F].flatMap(f)
override def error(t: Throwable)(message: => String): F[Unit] =
withCtx(c => logger.error(c.map, t)(message))
override def error(message: => String): F[Unit] =
withCtx(c => logger.error(c.map)(message))
package object logging {
implicit class ContextAwareSync[F[_], A](fa: F[A])(implicit F: Sync[F]) {
def withLogContext(f: LoggingContext => LoggingContext): F[A] =
F.bracket {
LoggingContext.update(f)
} { _ =>
fa
} { old =>