Scala 元编程:在日志库中的应用 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
sadhen
V2EX    Scala

Scala 元编程:在日志库中的应用

  •  
  •   sadhen 2019 年 7 月 17 日 6438 次点击
    这是一个创建于 2437 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Scala 中常用的第三方日志库,我这边了解的有 log4s^1和 Scala Logging^2两个。

    在 Scala Logging 中:

    logger.debug(s"Some $expensive message!") 

    会被 Scala 的宏转换成:

    if (logger.isDebugEnabled) logger.debug(s"Some $expensive message!") 

    因为在实际代码运行时,实际上会先做字符串插值,然后在看日志级别为 DEBUG 的日志是否需要输出。所以我们通过 if 语句,防止不必要的字符串操作,进而改善性能。

    那么 Scala Logging 是如何做到改写表达式的呢?

    在上一篇^3实现 lombok.Data 的时候,我们实际上是通过注解告诉编译器,我们需要在该注解所作用的类上面生成 getter 和 setter。说白了,就是注解 @data 让我们定位具体的类,然后我们再插入代码。而这个例子实际上是直接将生成代码的规则和具体的方法衔接起来。

    完整的实现如下所示:

    final class Logger private (val underlying: org.slf4j.Logger) { def debug(message: String): Unit = macro LoggerMacro.debugMessage } private object LoggerMacro { type LoggerCOntext= blackbox.Context {type PrefixType = Logger} private def deconstructInterpolatedMessage(c: LoggerContext) (message: c.Expr[String]) = { import c.universe._ message.tree match { case q"scala.StringContext.apply(..$parts).s(..$args)" => val format = parts.iterator.map({ case Literal(Constant(str: String)) => str }) // Emulate standard interpolator escaping .map(StringContext.treatEscapes) // Escape literal slf4j format anchors if the resulting call will require a format string .map(str => if (args.nonEmpty) str.replace("{}", "\{}") else str) .mkString("{}") val formatArgs = args.map(t => c.Expr[Any](t)) (c.Expr(q"$format"), formatArgs) case _ => (message, Seq.empty) } } private def formatArgs(c: LoggerContext)(args: c.Expr[Any]*) = { import c.universe._ args.map { arg => c.Expr[AnyRef]( if (arg.tree.tpe <:< weakTypeOf[AnyRef]) arg.tree else q"$arg.asInstanceOf[_root_.scala.AnyRef]" ) } } def debugMessageArgs(c: LoggerContext) (message: c.Expr[String], args: c.Expr[Any]*): c.universe.Tree = { import c.universe._ val underlying = q"${c.prefix}.underlying" val anyRefArgs = formatArgs(c)(args: _*) if (args.length == 2) q"if ($underlying.isDebugEnabled) $underlying.debug($message, _root_.scala.Array(${anyRefArgs.head}, ${anyRefArgs(1)}): _*)" else q"if ($underlying.isDebugEnabled) $underlying.debug($message, ..$anyRefArgs)" } def debugMessage(c: LoggerContext) (message: c.Expr[String]): c.universe.Tree = { val (messageFormat, args) = deconstructInterpolatedMessage(c)(message) debugMessageArgs(c)(messageFormat, args: _*) } } 

    首先,blackbox.Context 事实上限定了这个宏的作用域即在类 Logger 之中。可以观察到,单例 LoggerMacro 的每一个方法都带有 LoggerContext 这个参数,每一个方法的具体实现,也和 LoggerContext 有一定的关系。

    debugMessage 函数首先将字符串插值这个表达式通过 deconstructInterpolateMessage 解构成 messageFormat 和 args。下面这段代码可以非常明确的解释,什么是 messageFormat 以及什么是 args:

    logger.info("Info :{}" , user.getName()) 

    如果是 Scala 的字符串插值的话,就是 s"Info :${user.getName}"。

    解构之后,我们只需要通过 Quasiquote 将带有条件语句的代码重新构造起来就可以了。

    编译期和运行时

    另外一个需要注意的点是,在使用 @data 的时候,我们实际上需要在工程中开启 Paradise 插件,而我们在使用 Scala Logging 的时候,实际上直接依赖 Scala Logging 就可以了,不需要开启 Paradise 插件。这就涉及到一个问题:我们在上一节中做了详细解释的代码,到底是在哪个环节执行的。

    很简单,我们可以通过在 debugMessage 增加日志的方式,确定这个细节。

    最终发现,实际上,我们依赖了 Scala Logging,但是项目自身没有使用编译插件,在编译过程中,编译器遇到 Scala Logging 中会生成代码的方法时,实际上还是会去利用编译插件,生成代码。

    总结

    实际上,这一篇的内容虽然在宏的具体使用接口上和 lombok.Data 那一篇有细节上的差异,但实际上最终生成代码的还是在使用 Quasiquote,所以如何高效地在 REPL 中尝试 Quasiquote 至关重要。Quasiquote 是伊甸园元编程中最枯燥最耗时的一个环节,而通过何种方式去将常规的代码和宏生成的代码衔接起来,则是伊甸园中一扇隐秘的大门。

    阅读原文:Scala 元编程:在日志库中的应用

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1166 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 17:57 PVG 01:57 LAX 10:57 JFK 13:57
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86