当前位置: 首页 > 文档资料 > Play 中文文档 >

The Logging API

优质
小牛编辑
122浏览
2023-12-01

在你的应用中使用日志有助于监控、调试、错误跟踪以及商业智能分析。Play 提供了日志 API,可以通过 Logger 对象来使用,Play 使用 Logback 作为日志引擎。

日志体系结构

日志 API 提供了一组构件来帮助你实现高效的日志记录策略。

Logger

你的应用可以定义 Logger 实例来发送日志信息。每个 Logger 有一个名字,它会出现在日志信息中,该名字是用于配置层级关系的。

Logger 根据它们的名字会有一个层级继承结构。如果一个 logger 的名字后面加个点后是另外一个 logger 名字的前缀,那么我们就说第一个 logger 是第二个 logger 的祖先。例如,名为 “com.foo” 的 logger 是名为 “com.foo.bar.Baz” 的 logger 的祖先。所有的 logger 都继承自根 logger。Logger 继承机制的一个好处就是,如果你想配置一组 logger,那你只需要配置它们的共同祖先即可。

Play 应用有一个默认的 logger,叫 “application”。当然,你也可以创建你自己的 logger。Play 库使用的 logger 名叫 “play”,还有一些第三方库会自己命名 logger。

日志级别

日志级别是用来区分日志消息的严重性的。当你写日志请求时,你需要指定严重程度,它会出现在产生的日志消息里。

以下是一组可用的日志级别,以严重性的降序列出。

  • OFF - 关闭日志,不作为消息分类。
  • ERROR - 运行时错误,或不可预料的情况。
  • WARN - 用在一些不建议使用的 API 提示上,或是其它一些不可预料但又算不上错误的运行时情况。
  • INFO - 用在你感兴趣的运行时事件上,比如应用启动和关闭时。
  • DEBUG - 系统工作流程上的一些细节信息。
  • TRACE - 大部分细节信息。

日志级别是用来配置 logger 和 appender 打印日志的阈值的。例如,如果你把 logger 的日志级别设置为 INFO,则会打印 INFO 及严重性更高的日志(INFOWARNERROR),而忽略严重性比它低的日志(DEBUGTRACE)。使用 OFF 会忽略所有的日志请求。

Appenders

日志 API 允许日志请求打印到一个或多个叫做 appender 的目标输出。appender 在配置中指定,可选的有:控制台,文件,数据库或其它输出。

appender 和 logger 组合可以帮助你路由及过滤日志信息。例如,你可以使用一个 appender 打印有用的信息用于分析,用另一个 appender 打印错误信息,用于运维团队的监控。

注意:想了解更多关于日志体系结构的信息,请移步:Logback 文档

使用 Logger

首先导入 Logger 类及其伴生对象:

  1. import play.api.Logger

默认 Logger

Logger 对象是默认的 logger,名为 “application”。你可以使用它来打印日志:

  1. / Log some debug info
  2. Logger.debug("Attempting risky calculation.")
  3. try {
  4. val result = riskyCalculation
  5. // Log result if successful
  6. Logger.debug(s"Result=$result")
  7. } catch {
  8. case t: Throwable => {
  9. // Log error with message and Throwable.
  10. Logger.error("Exception with riskyCalculation", t)
  11. }
  12. }

使用 Play 默认的日志配置,上面的语句会产生类似下面的控制台输出:

  1. [debug] application - Attempting risky calculation.
  2. [error] application - Exception with riskyCalculation
  3. java.lang.ArithmeticException: / by zero
  4. at controllers.Application$.controllers$Application$$riskyCalculation(Application.scala:32) ~[classes/:na]
  5. at controllers.Application$$anonfun$test$1.apply(Application.scala:18) [classes/:na]
  6. at controllers.Application$$anonfun$test$1.apply(Application.scala:12) [classes/:na]
  7. at play.api.mvc.ActionBuilder$$anonfun$apply$17.apply(Action.scala:390) [play_2.10-2.3-M1.jar:2.3-M1]
  8. at play.api.mvc.ActionBuilder$$anonfun$apply$17.apply(Action.scala:390) [play_2.10-2.3-M1.jar:2.3-M1]

输出包含了日志级别(debug,error),logger 名称(application),消息,如果抛出异常的话,还会有堆栈跟踪信息。

创建你自己的 logger

也许你想在所有地方都用默认 logger,但这种使用方式并不好。用一个不同的名字创建你自己的 logger,这样配置起来会更灵活,可以过滤你的日志输出,并且精确知道日志的来源。

你可以使用 Logger.apply 工厂方法来创建一个新 logger,它需要一个名字作为参数:

  1. val accessLogger: Logger = Logger("access")

一个常用的策略是,为每个类配置一个不同的 logger,并且用类名来命名它。你可以使用另一个工厂方法,它接受一个类作为参数:

  1. val logger: Logger = Logger(this.getClass())

日志模式

高效地使用 logger 可以帮助你达到许多目标:

  1. import scala.concurrent.Future
  2. import play.api.Logger
  3. import play.api.mvc._
  4. trait AccessLogging {
  5. val accessLogger = Logger("access")
  6. object AccessLoggingAction extends ActionBuilder[Request] {
  7. def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
  8. accessLogger.info(s"method=${request.method} uri=${request.uri} remote-address=${request.remoteAddress}")
  9. block(request)
  10. }
  11. }
  12. }
  13. object Application extends Controller with AccessLogging {
  14. val logger = Logger(this.getClass())
  15. def index = AccessLoggingAction {
  16. try {
  17. val result = riskyCalculation
  18. Ok(s"Result=$result")
  19. } catch {
  20. case t: Throwable => {
  21. logger.error("Exception with riskyCalculation", t)
  22. InternalServerError("Error in calculation: " + t.getMessage())
  23. }
  24. }
  25. }
  26. }

这个例子使用了 action 组合 来定义 AccessLoggingAction,它会把日志打印到一个名为 “access” 的 logger。Application 控制器使用了这个 action,并且它使用了自己的 logger 来记录该应用里的事件。在配置中,你就可以将这些 logger 路由到不同的 appender,例如访问日志和应用日志。

如果你只想为指定 action 打印请求数据,那么上面的设计就足够了。想要打印所有的请求,则最好使用过滤器

  1. import scala.concurrent.ExecutionContext.Implicits.global
  2. import scala.concurrent.Future
  3. import play.api.Logger
  4. import play.api.mvc._
  5. import play.api._
  6. object AccessLoggingFilter extends Filter {
  7. val accessLogger = Logger("access")
  8. def apply(next: (RequestHeader) => Future[Result])(request: RequestHeader): Future[Result] = {
  9. val resultFuture = next(request)
  10. resultFuture.foreach(result => {
  11. val msg = s"method=${request.method} uri=${request.uri} remote-address=${request.remoteAddress}" +
  12. s" status=${result.header.status}";
  13. accessLogger.info(msg)
  14. })
  15. resultFuture
  16. }
  17. }
  18. object Global extends WithFilters(AccessLoggingFilter) {
  19. override def onStart(app: Application) {
  20. Logger.info("Application has started")
  21. }
  22. override def onStop(app: Application) {
  23. Logger.info("Application has stopped")
  24. }
  25. }

上面的代码中,我们在打印日志请求里加入了响应状态,当 Future[Result] 完成时会打印输出。注意,对于像应用开启和应用关闭这种事件,用全局对象来使用默认 logger 是一种明智的选择。

配置

详见日志配置