文档

§日志记录 API

在您的应用程序中使用日志记录对于监控、调试、错误跟踪和商业智能非常有用。Play 使用 SLF4J 作为日志记录外观,使用 Logback 作为默认日志记录引擎。

§日志记录架构

日志记录 API 使用一组组件来帮助您实现有效的日志记录策略。

§日志记录器

您的应用程序可以定义日志记录器来发送日志消息请求。每个日志记录器都有一个名称,该名称将出现在日志消息中,并用于配置。

日志记录器根据其命名遵循分层继承结构。如果一个日志记录器的名称后跟一个点是后代日志记录器名称的前缀,则称该日志记录器是另一个日志记录器的祖先。例如,名为“com.foo”的日志记录器是名为“com.foo.bar.Baz”的日志记录器的祖先。所有日志记录器都继承自根日志记录器。日志记录器继承允许您通过配置一个共同祖先来配置一组日志记录器。

我们建议为每个类创建单独命名的日志记录器。遵循此约定,Play 库使用命名空间为“play”的日志记录器,许多第三方库将使用基于其类名的日志记录器。

§日志级别

日志级别用于对日志消息的严重程度进行分类。当您编写日志请求语句时,您将指定严重程度,这将出现在生成的日志消息中。

这是可用日志级别的集合,按严重程度降序排列。

除了对消息进行分类外,日志级别还用于配置日志记录器和附加程序的严重性阈值。例如,将日志记录器设置为级别INFO将记录级别为INFO或更高(INFOWARNERROR)的任何请求,但会忽略级别较低的请求(DEBUGTRACE)。使用OFF将忽略所有日志请求。

§附加程序

日志记录 API 允许将日志记录请求打印到一个或多个称为“附加程序”的输出目标。附加程序在配置中指定,并且存在用于控制台、文件、数据库和其他输出的选项。

附加程序与日志记录器相结合可以帮助您路由和过滤日志消息。例如,您可以为一个日志记录器使用一个附加程序来记录有用的分析数据,而为另一个附加程序使用另一个附加程序来记录由运维团队监控的错误。

注意:有关架构的更多信息,请参阅Logback 文档

§使用日志记录器

首先导入Logger

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

§创建日志记录器

您可以使用LoggerFactoryname参数创建一个新的日志记录器

final Logger accessLogger = LoggerFactory.getLogger("access");

记录应用程序事件的常用策略是使用每个类一个不同的日志记录器,使用类名。日志记录 API 使用一个接受类参数的工厂方法来支持这一点

final Logger log = LoggerFactory.getLogger(this.getClass());

然后,您可以使用Logger来编写日志语句

// Log some debug info
logger.debug("Attempting risky calculation.");

try {
  final int result = riskyCalculation();

  // Log result if successful
  logger.debug("Result={}", result);
} catch (Throwable t) {
  // Log error with message and Throwable.
  logger.error("Exception with riskyCalculation", t);
}

使用 Play 的默认日志记录配置,这些语句将生成类似于以下内容的控制台输出

[debug] c.e.s.MyClass - Attempting risky calculation.
[error] c.e.s.MyClass - Exception with riskyCalculation
java.lang.ArithmeticException: / by zero
    at controllers.Application.riskyCalculation(Application.java:20) ~[classes/:na]
    at controllers.Application.index(Application.java:11) ~[classes/:na]
    at Routes$$anonfun$routes$1$$anonfun$applyOrElse$1$$anonfun$apply$1.apply(routes_routing.scala:69) [classes/:na]
    at Routes$$anonfun$routes$1$$anonfun$applyOrElse$1$$anonfun$apply$1.apply(routes_routing.scala:69) [classes/:na]
    at play.core.Router$HandlerInvoker$$anon$8$$anon$2.invocation(Router.scala:203) [play_2.10-2.3-M1.jar:2.3-M1]

请注意,消息具有日志级别、日志记录器名称(在本例中为类名,以缩写形式显示)、消息以及如果在日志请求中使用了Throwable,则包含堆栈跟踪。

还有一些play.Logger静态方法允许您访问名为application的日志记录器,但它们在 Play 2.7.0 及更高版本中已弃用。您应该使用上面定义的策略之一声明自己的日志记录器实例。

§使用标记

SLF4J API 有一个标记的概念,它充当丰富日志消息和将消息标记为特别感兴趣的标记。标记对于触发和过滤特别有用 - 例如,OnMarkerEvaluator可以在看到标记时发送电子邮件,或者可以将特定流程标记到其自己的附加程序。

标记非常有用,因为它们可以为日志记录器携带额外的上下文信息。例如,使用Logstash Logback 编码器,请求信息可以自动编码到日志语句中

import static net.logstash.logback.marker.Markers.append;

private Marker requestMarker(Http.Request request) {
  return append("host", request.host()).and(append("path", request.path()));
}

public Result index(Http.Request request) {
  logger.info(requestMarker(request), "Rendering index()");
  return ok("foo");
}

请注意,标记对于“追踪子弹”式日志记录也非常有用,在这种情况下,您希望在特定请求上进行日志记录,而无需显式更改日志级别。例如,您可以在满足特定条件时添加标记

public class JavaTracerController extends Controller {

  private final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(this.getClass());

  private static final Marker tracerMarker = org.slf4j.MarkerFactory.getMarker("TRACER");

  private Marker tracer(Http.Request request) {
    Marker marker = MarkerFactory.getDetachedMarker("dynamic"); // base do-nothing marker...
    request.queryString("trace").ifPresent(s -> marker.add(tracerMarker));
    return marker;
  }

  public Result index(Http.Request request) {
    logger.trace(tracer(request), "Only logged if queryString contains trace=true");
    return ok("hello world");
  }
}

然后在logback.xml中使用以下 TurboFilter 触发日志记录

<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
  <Name>TRACER_FILTER</Name>
  <Marker>TRACER</Marker>
  <OnMatch>ACCEPT</OnMatch>
</turboFilter>

此时,您可以根据输入动态设置调试语句。

有关在日志记录中使用标记的更多信息,请参阅 Logback 手册中的 TurboFilters基于标记的触发 部分。

§日志模式

有效使用日志记录器可以帮助您使用相同的工具实现许多目标。

import java.util.concurrent.CompletionStage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import play.mvc.Action;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Result;
import play.mvc.With;

public class Application extends Controller {

  private static final Logger logger = LoggerFactory.getLogger(Application.class);

  @With(AccessLoggingAction.class)
  public Result index() {
    try {
      final int result = riskyCalculation();
      return ok("Result=" + result);
    } catch (Throwable t) {
      logger.error("Exception with riskyCalculation", t);
      return internalServerError("Error in calculation: " + t.getMessage());
    }
  }

  private static int riskyCalculation() {
    return 10 / (new java.util.Random()).nextInt(2);
  }
}

class AccessLoggingAction extends Action.Simple {

  private static final Logger accessLogger = LoggerFactory.getLogger(AccessLoggingAction.class);

  public CompletionStage<Result> call(Http.Request request) {
    accessLogger.info(
        "method={} uri={} remote-address={}",
        request.method(),
        request.uri(),
        request.remoteAddress());

    return delegate.call(request);
  }
}

此示例使用 操作组合 来定义一个 AccessLoggingAction,它将请求数据记录到名为“access”的日志记录器。Application 控制器使用此操作,并且它还使用自己的日志记录器(以其类命名)来记录应用程序事件。在配置中,您可以将这些日志记录器路由到不同的附加程序,例如访问日志和应用程序日志。

如果您只想为特定操作记录请求数据,则上述设计效果很好。要记录所有请求,最好使用 过滤器 或扩展 play.http.DefaultHttpRequestHandler

§配置

有关配置的详细信息,请参阅 配置日志记录

下一步:高级主题


在此文档中发现错误?此页面的源代码可以在 此处 找到。在阅读 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?转到 我们的社区论坛 与社区开始对话。