简体   繁体   English

如何在 Scala 中进行单子日志记录

[英]How to Monadic Logging in Scala

I often want to log or to print something without changing it.我经常想记录或打印一些东西而不改变它。

It looks like this:它看起来像这样:

val result = myResult // this could be an Option or a Future
      .map{r =>
        info(s"the result is $r")
        r
      }

These three lines are always the same.这三行总是相同的。

In a for comprehension, this can be done a bit nicer.在理解中,这可以做得更好。

But I look for a solution for the first declarative version.但我为第一个声明性版本寻找解决方案。 It should look like:它应该看起来像:

val result = myResult
      .log(info(s"the result is ${_}"))

This one-liner could be put in every place in the chain where there could be a map, like:这个单线可以放在链中可能存在 map 的每个位置,例如:

val result = myResult
      .log(info(s"1. ${_}"))
      .filter(_ > 1)
      .log(info(s"2. ${_}"))
      ...

How could this be achieved?这怎么可能实现? If possible, without a functional library.如果可能,没有功能库。

Ok so I decided to take a swing at this and I would like to retract my comment of好的,所以我决定在此使用 swing,我想收回我的评论

Maybe you can define a implicit class that has a method "log" acting on a Product?也许您可以定义一个隐式 class ,它有一个作用于产品的方法“log”?

I was confident that Future and all the monads (collections, options) shared a common ancestor, turns out I was wrong.我确信 Future 和所有的单子(集合、选项)有一个共同的祖先,结果证明我错了。 I have the following solution without using Cats.我在不使用 Cats 的情况下有以下解决方案。 This can be done in a much prettier way in cats, besides the aforementioned " flatTap ", and embelished with possibly cats.Ref or something.除了前面提到的“ flatTap ”之外,这可以在猫中以更漂亮的方式完成,并可能用 cat.Ref 或其他东西装饰。

Future is the obvious outliner here but as more exceptions come along you might need to expand this object. Future 是这里的明显大纲,但随着更多例外的出现,您可能需要扩展此 object。

import scala.concurrent._ 
import ExecutionContext.Implicits.global

object MonadicConv { 
  implicit class MonadicLog[+B <: Product, A] (val u: B){
    def log(l: String, args: List[A] = List()): B = {
      println("logging")
      println(u)
      println(l)
      u
    }  
  }



 implicit class FutureLog[T, A](val u: Future[T]){
    def log(l: String, args: List[A] = List()) : Future[T] = {
      println("logging")
      println(u)
      println(l)
      u
    }
  }
}

1) You will need to modify this with you own logging logic, I am just printing 1)您需要使用自己的日志记录逻辑来修改它,我只是在打印

2) I am not super proud of this as this is no longer a pure function. 2)我对此并不感到非常自豪,因为这不再是纯粹的 function。 I am not sure if there is a work around this in Scala without using Cats.我不确定在 Scala 中是否可以在使用 Cats 的情况下解决此问题。 (There might be) (可能有)

3) The args can be removed, just added them in case you want to pass in extra info 3) args 可以删除,只需添加它们以防您想传递额外信息

4) If you really want to combine these, you could try defining your own product, some leads: Implement product type in Scala with generic update function working on its parts 4)如果你真的想结合这些,你可以尝试定义自己的产品,一些线索: 在 Scala 中实现产品类型,通用更新 function 在其部件上工作

You can use this with你可以用这个

  import MonadicConv._


  val x = Some(5).log("").get
  val lx = List(Some(5), Some(10), Some(1)).log("list").flatten.log("x").filter(_ > 1).log("")
  val ff = Future.successful(List(Some(5), Some(10), Some(1))).log("fff").map(_.flatten.filter(_ > 1).log("inner")).log("")

This prints out这打印出来

logging
Some(5)
option test
logging
List(Some(5), Some(10), Some(1))
list test
logging
List(5, 10, 1)
flat test
logging
List(5, 10)
filter test
logging
Future(Success(List(Some(5), Some(10), Some(1))))
future test
logging
Future(<not completed>)
incomplete test
logging
List(5, 10)
inner future test

Scastie version here Scastie 版本在这里

As I have mentioned, this is really the Cats land at this point.正如我所提到的,此时这确实是猫的土地。 This is the best I could come up with in core Scala这是我在核心 Scala 中能想到的最好的

For your purpose, it is best to use treelog.出于您的目的,最好使用 treelog。 It turns the logging process and values into a Monad of DescribedComputation :它将日志记录过程和值转换为DescribedComputation的 Monad:

import treelog.LogTreeSyntaxWithoutAnnotations._
val result: DescribedComputation[YourValueType] = myResult ~> (_.fold("The result is empty")(r => s"The result is $r")

And usually to subtract the value from a DescribedComputation, use for comprehension:通常要从 DescribedComputation 中减去值,用于理解:

for {
  res <- result
} {
  doSomethingTo(res)
}

See details from https://github.com/lancewalton/treelog请参阅https 的详细信息://github.com/lancewalton/treelog


The whole example will look like:整个示例将如下所示:

val compRes = "Logging Result" ~< {
    for {
      r <- myResult ~> (_.fold("The result is empty")(r => s"The result is $r")
    } yield r
  }
}

for (res <- compRes) {
  doSomethingTo(res)
}

logger.info(logging.run.written.shows)

The output will look like: output 将如下所示:

2019-11-18 00:00:00,000 INFO Logging Result
  The result is XXX

Just for reference.仅供参考。 ZIO provides this functionality nicely. ZIO很好地提供了这个功能。

  /**
   * Returns an effect that effectfully "peeks" at the success of this effect.
   *
   * {{{
   * readFile("data.json").tap(putStrLn)
   * }}}
   */
  final def tap[R1 <: R, E1 >: E](f: A => ZIO[R1, E1, Any]): ZIO[R1, E1, A] = self.flatMap(new ZIO.TapFn(f))

There is even a version for the error case:甚至还有一个错误案例的版本:

  /**
   * Returns an effect that effectfully "peeks" at the failure of this effect.
   * {{{
   * readFile("data.json").tapError(logError(_))
   * }}}
   */
  final def tapError[R1 <: R, E1 >: E](f: E => ZIO[R1, E1, Any]): ZIO[R1, E1, A]

This makes debugging really easy:这使得调试非常容易:

   myDangerousZioFunction
      .tapError(e => putStrLn(s"Server Exception: $e"))
      .tap(r => putStrLn(s"Result is $r"))
      ....

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM