登录Scala


168

在Scala应用程序中进行登录的好方法是什么?符合语言哲学,不会使代码混乱,维护成本低且不引人注意的事物。这是一个基本需求列表:

  • 简单
  • 不会使代码混乱。Scala简洁明了。我不希望我的一半代码成为记录语句
  • 可以更改日志格式以适合我的企业日志和监视软件的其余部分
  • 支持日志记录级别(即调试,跟踪,错误)
  • 可以登录到磁盘以及其他目标位置(即套接字,控制台等)
  • 最低配置(如果有)
  • 在容器(即Web服务器)中工作
  • (可选,但很高兴)作为语言的一部分或作为Maven工件而出现,因此我不必破解我的构建即可使用它

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

多谢您的回覆。

Answers:


119

slf4j包装器

Scala的大多数日志库都是围绕Java日志框架(slf4j,log4j等)的一些包装程序,但截至2015年3月,尚存的日志库均为slf4j。这些日志库提供了log您可以调用info(...)debug(...)等等的某种对象。我不是slf4j的忠实拥护者,但是现在看来它是主要的日志记录框架。这是SLF4J的描述:

Java或(SLF4J)的简单日志记录外观可以用作各种日志记录框架(例如java.util.logging,log4j和logback)的简单外观或抽象,从而允许最终用户在部署时插入所需的日志记录框架。

在部署时更改基础日志库的功能为整个slf4j记录器系列带来了独特的特性,您需要注意以下几点:

  1. 类路径作为配置方法。slf4j知道使用哪个底层日志记录库的方法是通过按名称加载类。自定义类加载器时,slf4j无法识别记录器时出现了问题。
  2. 因为简单的外观试图成为公分母,所以它仅限于实际的日志调用。换句话说,无法通过代码完成配置。

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

Scala记录

Scala日志记录由Heiko Seeberger撰写,作为其slf4s的继承者。它使用宏将调用扩展为if表达式,以避免潜在的昂贵日志调用。

Scala日志记录是一种方便且高性能的日志记录库,它包装了SLF4J之类的日志记录库以及其他可能的日志记录库。

历史记录员

  • Logula,Coda Hale编写的Log4J包装器。曾经喜欢这个,但是现在已经废弃了。
  • configgy,一个java.util.logging包装器,在Scala的早期就很流行。现在被遗弃了。

1
总得说,我爱Logula ...最后是一个不会让我想用螺丝刀刺入耳朵的伐木机。没有xml,没有BS,只是启动时的scala配置
-Arg

Heiko Seeberger的SLF4S似乎不再维护。我制定了自己的目标Scala 2.10和最新的SLF4J。http://slf4s.org/
马特·罗伯茨

3
Logula根据其在Github上的自述文件而被废弃。
艾里克·卡普伦2014年

Oncue Journal在概念上与Scala日志记录类似,但是日志消息被发送到actor,该actor从专用线程池调用SLF4J。用CPU时间换取(更好)的延迟。github.com/oncue/journal
Gary Coady

64

对于Scala 2.10+,请考虑使用Typesafe进行ScalaLogging。使用宏来提供非常干净的API

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

从他们的维基报价:

幸运的是,可以使用Scala宏使我们的生活更轻松:ScalaLogging为该类Logger提供了轻量级的日志记录方法,这些方法将扩展为上述习惯用法。所以我们要做的就是:

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

应用宏后,代码将被转换为上述惯用语。

另外,ScalaLogging提供了特征Logging,该特征可以方便地提供一个Logger实例,该实例使用以下类的名称进行初始化:

import com.typesafe.scalalogging.slf4j.LazyLogging

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

12
使用class MyClass with Logging
Andrzej Jozwik

1
请注意,使用登录特征可以使特征在记录器中具有与其混合到的类相同的名称。我认为您可以手动获取记录器,而不是使用宏来方便地手动提供记录器名称(如果需要)。但是,如果我错了,请纠正我。
mkko

1
这是否意味着MyClass具有用于调试,信息警告等的公共方法?如果是这样,我宁愿进行额外的手动工作,实例化该类的私有记录器。记录方法不应泄漏到类接口中。
ChucK 2013年

2
您将获得一个记录器字段,您可以在闲暇时使用它。见github.com/typesafehub/scalalogging/blob/master/...的细节
fracca

2
2.x需要Scala 2.11; 希望没有太大的实际差异;对于Scala 2.10.x,有"com.typesafe" %% "scalalogging-slf4j" % "1.1.0"
埃里克·卡普伦

14

使用slf4j和包装器是不错的选择,但是当您要插入两个以上的值时,使用内置的插值功能会失败,因为从那时起,您需要创建一个要插入值的数组。

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

Log.scala Slf4jLog.scala

看起来像这样:

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

请注意,msg是一个按名称进行的调用,除非isTraceEnabled为true,否则不会进行评估,因此无需花费任何费用即可生成一个不错的消息字符串。这可以解决slf4j的内插机制,该机制需要解析错误消息。使用此模型,您可以将任意数量的值插值到错误消息中。

如果您有将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))

2
是否可以获取准确的行号以记录scala类中的语句?
jpswain 2011年

13

不要使用Logula

实际上,我遵循了Eugene的建议并进行了尝试,发现它的配置笨拙,并存在无法修复的错误(例如,此错误)。它看起来维护得不好,并且不支持Scala 2.10

使用slf4s + slf4j-简单

主要优点:

  • 支持最新的Scala 2.10 (迄今为止是M7)
  • 配置是通用的,但再简单不过了。这是通过系统属性完成的,您可以通过-Dorg.slf4j.simplelogger.defaultlog=trace在脚本中添加类似于执行命令或硬编码的方式来进行设置:System.setProperty("org.slf4j.simplelogger.defaultlog", "trace")。无需管理垃圾配置文件!
  • 非常适合IDE。例如,要在IDEA中的特定运行配置中将日志记录级别设置为“ trace”,只需转到Run/Debug Configurations并添加-Dorg.slf4j.simplelogger.defaultlog=traceVM 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>

您在哪里找到支持2.10的slf4s?我在看github.com/weiglewilczek/slf4s,看到“支持的Scala版本是2.8.0、2.8.1、2.9.0-1和2.9.1”。..我看错地方了吗?
nairbv 2012年

@Brian使用答案中建议的2.9.1。我测试了它在2.10.0-M7上的正常工作
Nikita Volkov 2012年

我使用的是2.9.1,我只是对突出显示的“关键利益”及其含义感到好奇。
nairbv 2012年

在仔细搜索了日志库的优缺点的答案之后,我选择了一个告诉我要导入什么依赖项的库。谢谢先生。
teo 2012

11

这就是我让Scala Logging为我工作的方式:

把它放在你的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")

有关更多信息,请参阅文档


仅在我log4j.properties在项目资源文件夹中创建后为我工作。
Nadjib Mami,

7

我从的Logging特质中抽出了一些工作scalax,并创建了一个集成了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配置)始终看起来像以下内容,而不是实际扩展Loggable的类:Jul 22,2011 3:02:17 AM redacted.util.Loggable $ class信息信息:一些消息...您是否遇到了这个机会?
Tomer Gabel

6

我使用SLF4J + Logback classic并按以下方式应用它:

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")
}

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


4

您应该看看scalax库:http ://scalax.scalaforge.org/ 在这个库中,有一个Logging特性,使用sl4j作为后端。通过使用此特征,您可以非常轻松地进行记录(只需使用继承该特征的类中的logger字段)。




3

快速简便的表格。

Scala 2.10及更高版本:

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

并build.sbt:

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....")

并build.sbt:

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

2

使用slf4s和logula一段时间后,我写道 loglady了一个简单的日志记录特征,包装了slf4j。

它提供了类似于Python日志记录库的API,从而使常见情况(基本字符串,简单格式)变得微不足道,并且避免了格式化样板。

http://github.com/dln/loglady/


2

我发现使用某种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的高级语法非常有用。


@sschaef,哦,是的。抱歉。我几乎忘记了。几天来,这是一场艰苦的斗争,但看起来这真的是不可能的。我用过#!!而是“信息消息”。我将编辑编辑我的答案。
Alex Povar
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.