功能性scala日志积累器

问题描述 投票:1回答:1

我正在做一个Scala项目,主要使用cats库。在那里,我们有像这样的调用

for {
   _ <- initSomeServiceAndLog("something from a far away service")
   _ <- initSomeOtherServiceAndLog("something from another far away service")
   a <- b()
   c <- d(a)
  } yield c

想象一下,b也会记录一些东西,或者可能会抛出一个业务错误(我知道,我们在Scala中避免抛出,但现在不是这样)。我正在寻找一个解决方案来积累日志,并在最后将它们全部打印在一条消息中。为了寻求一条幸福的道路,我看到Cats中的Writer Monad可能是一个可以接受的解决方案.但是如果b方法抛出呢?我们的要求是记录所有的东西--所有之前的日志和错误信息,在一个单一的信息中,用某种独特的跟踪ID。先谢谢你

scala logging scala-cats
1个回答
1
投票

使用单元变换器(如Writer(WriterT)或State(StateT))实现功能日志(即使发生错误也能保留日志)是很难的。然而,如果我们不对FP的方法进行分析,我们可以做以下事情。

  • 使用一些IO单体
  • 用它来创建一些类似于日志的内存存储空间
  • 但在功能上实现

就我个人而言,我会选择以下两种 cats.effect.concurrent.Refmonix.eval.TaskLocal.

使用Ref(和Task)的例子。

type Log = Ref[Task, Chain[String]]
type FunctionalLogger = String => Task[Unit]
val createLog: Task[Log] = Ref.of[Task, Chain[String]](Chain.empty)
def createAppender(log: Log): FunctionalLogger =
  entry => log.update(chain => chain.append(entry))
def outputLog(log: Log): Task[Chain[String]] = log.get

用这样的助手,我可以。

def doOperations(logger: FunctionalLogger) = for {
  _ <- operation1(logger) // logging is a side effect managed by IO monad
  _ <- operation2(logger) // so it is referentially transparent
} yield result

createLog.flatMap { log =>
  doOperations(createAppender(log))
    .recoverWith(...)
    .flatMap { result =>
       outputLog(log)
       ...
    }
}

然而,确保输出被调用是一件很麻烦的事 所以我们可以使用某种形式的... ... BracketResource 来处理它。

val loggerResource: Resource[Task, FunctionalLogger] = Resource.make {
  createLog // acquiring resource - IO operation that accesses something
} { log =>
  outputLog(log) // releasing resource - works like finally in try-catchso it should
    .flatMap(... /* log entries or sth */) // be called no matter if error occured
}.map(createAppender)

loggerResource.use { logger =>
  doSomething(logger)
}

如果你不喜欢把这个appender显式地传给别人 你可以用Kleisli来注入它:

type WithLogger[A] = Kleisli[Task, FunctionalLogger, A]

// def operation1: WithLogger[A]
// def operation2: WithLogger[B]

def doSomething: WithLogger[C] = for {
  a <- operation1
  b <- operation2
} yield c

loggerResource.use { logger =>
  doSomething(logger)
}

TaskLocal 将以非常相似的方式使用。

在一天结束的时候,你最终会得到。

  • 类型说,它正在记录
  • 通过IO管理的突变性,所以引用的透明度不会丧失。
  • 确保即使IO失败,日志也会被保存下来,并将结果发送到

相信有些纯粹的人不会喜欢这个方案,但是它有FP的所有好处,所以我个人会用它。

© www.soinside.com 2019 - 2024. All rights reserved.