用 Scala 登录

在 Scala 应用程序中做日志记录的好方法是什么?符合语言哲学、不会使代码混乱、低维护和不引人注目的东西。下面是一个基本要求列表:

  • 很简单
  • 不会使代码混乱。Scala 非常简洁。我不希望我的一半代码是日志语句
  • 日志格式可以改变,以适应其余的企业日志和监控软件
  • 支持日志记录级别(即调试、跟踪、错误)
  • 可以同时登录到磁盘和其他目的地(例如套接字、控制台等)
  • 最低配置(如果有的话)
  • 在容器(即网络服务器)中工作
  • (可选,但很高兴有)可以作为语言的一部分,或者作为一个专业的工件,所以我不必黑客我的构建来使用它

我知道我可以使用现有的 Java 日志记录解决方案,但它们至少在上述两个方面失败,即混乱和配置。

谢谢你的回复。

126682 次浏览

Slf4j 包装纸

大多数 Scala 的日志库都是一些包装 Java日志框架(slf4j,log4j 等) ,但是到2015年3月,现存的日志库都是 slf4j。这些日志库提供某种类型的 log对象,您可以对其调用 info(...)debug(...)等。我不是 slf4j 的超级粉丝,但是现在它似乎是主要的日志框架。以下是对 SLF4J的描述:

这个 SLF4J 或(SLF4J)作为各种日志框架的简单外观或抽象,例如 java.util.log、 log4j 和 logback,允许终端用户在部署时插入所需的日志框架。

在部署时更改底层日志库的能力为整个 slf4j 日志记录器家族带来了独特的特性,您需要注意:

  1. 将类路径作为配置 方法。Slf4j 知道您正在使用哪个底层日志库的方法是通过使用某个名称来加载类。我遇到过这样的问题,当类加载器被定制时,slf4j 无法识别我的日志记录器。
  2. 因为 简单的表象试图成为公分母,所以它仅限于实际的日志调用。换句话说,配置不能通过代码来完成。

在大型项目中,如果每个人都使用 slf4j,那么实际上可以很方便地控制传递依赖项的日志记录行为。

Scala 日志

Scala Logging 由 Heiko Seeberger 编写,作为他的 SLF4的继承者。它使用宏将调用扩展为 if 表达式,以避免可能开销很大的日志调用。

Scala Logging 是一个方便且高性能的日志库,它包含了 SLF4J 等日志库以及其他可能的日志库。

历史记录员

  • Logula ,由 Coda Hale 编写的 Log4J 包装。以前喜欢这个,但现在已经废弃了。
  • Configgy ,这是一个 java.util.log 包装器,在早期的 Scala 中很流行,现在已经废弃了。

您应该看一下 scalax 库: Http://scalax.scalaforge.org/ 在这个库中,有一个 Logging trait,使用 sl4j 作为后端。 通过使用这个 trait,您可以非常容易地记录日志(只需使用 logger 字段即可) 在继承 trait 的类中)。

还没有尝试过,但是 Configgy 在配置和日志方面看起来很有前途:

Http://github.com/robey/configgy/tree/master

我从 scalaxLogging特性中提取了一些工作,创建了一个也集成了 MessageFormat-based库的特性。

然后东西看起来像这样:

class Foo extends Loggable {
info( "Dude, I'm an {0} with {1,number,#}", "Log message", 1234 )
}

到目前为止,我们喜欢这种方式。

实施方法:

trait Loggable {


val logger:Logger = Logging.getLogger(this)


def checkFormat(msg:String, refs:Seq[Any]):String =
if (refs.size > 0) msgfmtSeq(msg, refs) else msg


def trace(msg:String, refs:Any*) = logger trace checkFormat(msg, refs)


def trace(t:Throwable, msg:String, refs:Any*) = logger trace (checkFormat(msg, refs), t)


def info(msg:String, refs:Any*) = logger info checkFormat(msg, refs)


def info(t:Throwable, msg:String, refs:Any*) = logger info (checkFormat(msg, refs), t)


def warn(msg:String, refs:Any*) = logger warn checkFormat(msg, refs)


def warn(t:Throwable, msg:String, refs:Any*) = logger warn (checkFormat(msg, refs), t)


def critical(msg:String, refs:Any*) = logger error checkFormat(msg, refs)


def critical(t:Throwable, msg:String, refs:Any*) = logger error (checkFormat(msg, refs), t)


}


/**
* Note: implementation taken from scalax.logging API
*/
object Logging {


def loggerNameForClass(className: String) = {
if (className endsWith "$") className.substring(0, className.length - 1)
else className
}


def getLogger(logging: AnyRef) = LoggerFactory.getLogger(loggerNameForClass(logging.getClass.getName))
}

使用 slf4j 和一个包装器是很好的,但是当您有两个以上的值要插值时,内置插值的使用就会崩溃,因为那时您需要创建一个要插值的数组值。

一个更像 Scala 的解决方案是使用一个 thunk 或集群来延迟错误消息的串联。Lift 的日志记录器就是一个很好的例子

Log.scala Slf4jLog.scala

看起来像这样:

class Log4JLogger(val logger: Logger) extends LiftLogger {
override def trace(msg: => AnyRef) = if (isTraceEnabled) logger.trace(msg)
}

请注意,msg 是一个按名称调用的函数,除非 isTraceEnable 为 true,否则不会计算它,因此生成一个好的消息字符串没有成本。这是围绕 slf4j 的插值机制进行的,它需要解析错误消息。使用这个模型,您可以向错误消息中插入任意数量的值。

如果您有一个单独的 trait,将这个 Log4JLogger 混合到您的类中,那么您可以这样做

trace("The foobar from " + a + " doesn't match the foobar from " +
b + " and you should reset the baz from " + c")

而不是

info("The foobar from {0} doesn't match the foobar from {1} and you should reset the baz from {c},
Array(a, b, c))

WriterMonoidMonad实现。

在使用 slf4s 和 logula 一段时间后,我编写了 loglady,这是一个包装 slf4j 的简单日志特性。

它提供了一个类似于 Python 日志库的 API,这使得常见情况(基本字符串、简单格式化)变得简单,并且避免了格式化样板。

Http://github.com/dln/loglady/

我使用 SLF4J + Logback 经典,并应用如下:

trait Logging {
lazy val logger = LoggerFactory.getLogger(getClass)


implicit def logging2Logger(anything: Logging): Logger = anything.logger
}

然后你可以使用任何更适合你的风格:

class X with Logging {
logger.debug("foo")
debug("bar")
}

但是这种方法当然会为每个类实例使用一个日志记录器实例。

别用 Logula

我实际上已经遵循了 Eugene 的建议,并尝试了它,发现它有一个笨拙的配置,并受到错误,这是不能得到修复(如 这个)。它看起来没有得到很好的维护,它 不支持 Scala 2.10

使用 slf4s + slf4j-simple

主要好处:

  • 支持最新的 Scala 2.10 < em > (到目前为止是 M7)
  • 配置是通用的,但不能再简单了。它是通过 系统属性完成的,您可以通过将类似 -Dorg.slf4j.simplelogger.defaultlog=trace的内容附加到执行命令或脚本中的硬代码 System.setProperty("org.slf4j.simplelogger.defaultlog", "trace")来设置它。不需要管理垃圾配置文件!
  • 很适合 IDE。例如,要在 IDEA 的特定运行配置中将日志记录级别设置为“ trace”,只需转到 Run/Debug Configurations并将 -Dorg.slf4j.simplelogger.defaultlog=trace添加到 VM options
  • 简单的设置: 只需要在这个答案的底部添加依赖项

以下是你需要与 Maven 一起运行它的步骤:

<dependency>
<groupId>com.weiglewilczek.slf4s</groupId>
<artifactId>slf4s_2.9.1</artifactId>
<version>1.0.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.6</version>
</dependency>

使用 Scala 2.10 + 考虑使用类型安全的 ScalaLogging

Https://github.com/typesafehub/scala-logging

引用他们维基百科上的话:

幸运的是,Scala 宏可以使我们的生活变得更加轻松: ScalaLogging 提供了带有轻量级日志记录方法的类 Logger,这些方法将扩展到上面的习惯用法。所以我们要写的就是:

logger.debug(s"Some ${expensiveExpression} message!")

宏应用完毕后,代码将被转换成上面描述的习惯用法。

此外,ScalaLogging 还提供了 trait Logging,它方便地提供了一个 Logger实例,该实例初始化时将类的名称混合到:

import com.typesafe.scalalogging.slf4j.LazyLogging


class MyClass extends LazyLogging {
logger.debug("This is very convenient ;-)")
}

我发现使用某种 java 日志记录器(例如 sl4j)和简单的 scala 包装器非常方便,这给我带来了这样的语法

val #! = new Logger(..) // somewhere deep in dsl.logging.


object User with dsl.logging {


#! ! "info message"
#! dbg "debug message"
#! trace "var a=true"


}

在我看来,java 验证过的日志框架和 scala 奇特的语法非常有用。

这就是我如何让 Scala 日志为我工作的:

把这个放进你的 build.sbt:

libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.7.2",
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"

然后,在执行 sbt update之后,输出一条友好的日志消息:

import com.typesafe.scalalogging._
object MyApp extends App with LazyLogging {
logger.info("Hello there")
}

如果使用 Play,当然可以简单地使用 import play.api.Logger来编写日志消息: Logger.debug("Hi")

有关更多信息,请参见 医生

简单快捷的表格。

Scala 2.10及以上版本:

import com.typesafe.scalalogging.slf4j.Logger
import org.slf4j.LoggerFactory
val logger = Logger(LoggerFactory.getLogger("TheLoggerName"))
logger.debug("Useful message....")

建造:

libraryDependencies += "com.typesafe" %% "scalalogging-slf4j" % "1.1.0"

Scala 2.11 + 和更新版本:

import import com.typesafe.scalalogging.Logger
import org.slf4j.LoggerFactory
val logger = Logger(LoggerFactory.getLogger("TheLoggerName"))
logger.debug("Useful message....")

建造:

libraryDependencies += "com.typesafe.scala-logging" %% "scala-logging" % "3.1.0"

2020年的伐木业

我真的很惊讶,我在工作中使用的 抄写员日志框架甚至没有在这里提到。更重要的是,它甚至没有出现在谷歌搜索“ Scala 日志”的第一页。但是这个页面在谷歌搜索的时候出现了!让我把它放在这里。

抄写员的主要优点: