惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
小众软件
小众软件
S
SegmentFault 最新的问题
人人都是产品经理
人人都是产品经理
博客园 - 【当耐特】
博客园 - 三生石上(FineUI控件)
C
Check Point Blog
S
Schneier on Security
Microsoft Azure Blog
Microsoft Azure Blog
N
Netflix TechBlog - Medium
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
罗磊的独立博客
有赞技术团队
有赞技术团队
V
V2EX
Y
Y Combinator Blog
博客园 - 叶小钗
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
F
Fortinet All Blogs
W
WeLiveSecurity
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Stack Overflow Blog
Stack Overflow Blog
The Cloudflare Blog
S
Security @ Cisco Blogs
TaoSecurity Blog
TaoSecurity Blog
MyScale Blog
MyScale Blog
Hugging Face - Blog
Hugging Face - Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
PCI Perspectives
PCI Perspectives
H
Heimdal Security Blog
Schneier on Security
Schneier on Security
Security Latest
Security Latest
AWS News Blog
AWS News Blog
月光博客
月光博客
Security Archives - TechRepublic
Security Archives - TechRepublic
Recent Announcements
Recent Announcements
Google DeepMind News
Google DeepMind News
博客园 - Franky
Cisco Talos Blog
Cisco Talos Blog
T
Threat Research - Cisco Blogs
M
MIT News - Artificial intelligence
T
Troy Hunt's Blog
N
News and Events Feed by Topic
Cloudbric
Cloudbric
Scott Helme
Scott Helme
云风的 BLOG
云风的 BLOG
Attack and Defense Labs
Attack and Defense Labs

Scala

这个分类好久没新帖子了,记录下最新用 scala 3 写的项目 - V2EX sbt run 的时候,能不能跳过 compile 直接 run? - V2EX 还有必要用 scala 吗 - V2EX 有 scala 兄弟吗?有招 scala 的公司吗? - V2EX Scala 单元测试 - V2EX scala 模型导出问题 - V2EX 想用 Springboot 做后端接口,但组里 scala 大佬希望我用 Akka Http 怎么办 - V2EX 可能是家庭背景最深厚的程序员写的书... - V2EX 集合类型 filter 后执行 foreach 的效率 比 foreach 里写 if 来的慢 - V2EX sbt 为什么能这么垃圾? - V2EX sbt 1.3.0 released, 依赖管理从 ivy 变为 Coursier - V2EX 如何停止已经提交到 Spark 集群上的任务? - V2EX 关于《Scala 编程(3rd)》中, 20.6 抽象类型 P424 的疑问 - V2EX 请教下关于用 Flume+kafka+Spark 实现实时监控错误日志报警的功能 - V2EX 闲话 Scala Native: 初次见面,请多多关照 - V2EX Scala 中不带具体实现的 object 是什么用法 - V2EX scala 命令行删除字符不能正常显示 spark 技术面试被问到个问题,有点问懵逼了,求指点 - V2EX scala 中 _ 的语法问题 - V2EX [小广告] scala 中文 telegram 群组: t.me/scala_zh Scala 中国社区、微信群? - V2EX Scala's types of types 译文 - V2EX Scala 自学笔记-参考书籍 Programming in Scala 第三版 - V2EX scala 如何实现某个代码段同一时刻只能有一个进程在读,其他进程只能等待。 请问该怎样结合实践去学习 Scala,谢谢! - V2EX 欢迎来访开放源码的 PlayScala 社区(http://www.playscala.cn) - V2EX 请问我下载了乌云的 digatalAllData,说明上写着用 case class 解析出数据,那有没有详细的方法? - V2EX 嘿,Scala 这货你们都应用在什么项目上了,打算接触试试呢~ - V2EX sbt package 将 scala 源代码一并打包进去? - V2EX 大家都用什么 orm 或 mysql db library - V2EX 询问有关 Scala 的实习, 工业应用等 - V2EX sbt 的自定义 resolvers 被-Dsbt.override.build.repos=true 覆盖掉了 - V2EX 谁能说说 scala 和 python 的区别,有木有必要学习 scala? - V2EX IDEA 的 sbt 依赖解决问题 - V2EX 话说 V2 里有搞 Scala 的同学吗? - V2EX 还有必要学 scala 吗? - V2EX Scala China 的 Telegram 群 - V2EX Scala 2.12 will require Java 8. - V2EX 仿照 scrapy 的设计用 scala 写了一个爬虫框架 - V2EX V2EX 里有 scala 爱好者吗? - V2EX 此处有 Scala 爱好者吗? - V2EX 10 Scala One Liners to Impress Your Friends - V2EX 有什么方法,能够使得主程序在 Actor 执行了 exit 后才继续执行么~? - V2EX 李光耀似乎有个能称为 Scala 大神的孙子! - V2EX Scala on Android - V2EX scala 值得学习么?被逼着学习这货了:( - V2EX Scala 怎么做如下变换:(A,B,C) => (A,(B,C)) (B,(A,C)) (C,(A,B)) - V2EX Scala By Example,Scala 之父 Martin 今天刚更新/发布的教程 - V2EX
Scala 元编程:在日志库中的应用 - V2EX
sadhen · 2019-07-17 · via Scala

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 元编程:在日志库中的应用