将日志记录迁移到第二代 Java 运行时

第二代运行时(Java 11 及更高版本)中的日志记录在某些方面与第一代运行时(Java 8)不同。与第一代运行时不同,在第二代运行时中,应用日志不会分组到 Logs Explorer 中的请求日志下。此外,在第二代运行时中,App Engine 会将与请求关联的每个应用日志作为单独的日志条目写入 Cloud Logging 中。

本指南介绍了如何实现在第一代运行时应用中使用的日志记录方法,并在将应用迁移到 Java 11+ 时实现几乎相同的过滤和日志记录关联结果。

主要区别

下表介绍了第一代和第二代运行时在日志记录方面的差异:

第一代运行时 (Java 8) 第二代运行时(Java 11 及更高版本)
请求日志和应用日志(也称为 application log) App Engine 会将所有应用日志嵌入请求日志中。 App Engine 不会将应用日志嵌入在请求日志中。
stdout 和 stderr App Engine 会将写入 stdout 和 stderr 的日志嵌入在请求日志中。 App Engine 不会嵌入写入 stdout 和 stderr 的日志。
App Engine 平台日志 App Engine 不会发出内部平台日志。 App Engine 会在启动或关停实例期间发出内部平台日志,日志名称为 /var/log/google_init.log。
Cloud Logging API 配额 每个请求对应于一次 Cloud Logging 写入。 与请求关联的每个应用日志条目对应于一次 Cloud Logging 写入。在第二代运行时中,Cloud Logging API 用量会增加。

请求日志和应用日志

第一代和第二代运行时中的日志记录行为在以下方面有所不同:

  • 在第一代运行时中,App Engine 将应用日志嵌入在请求日志的 protoPayload.line 字段中。您可以通过在 Logs Explorer 中展开请求来查看应用日志。

    下图展示了第一代运行时中的关联请求和应用日志:

    Java 8 中的关联日志

  • 在第二代运行时中,请求日志不包含应用日志条目,因为请求日志中不存在 protoPayload.line 字段。相反,App Engine 会将每个应用日志作为单独的条目记录。日志名称取决于您的日志记录设置。var/log/app 包含使用标准日志记录框架(例如 java.util.logging 或 Java 简易日志记录门面 [SLF4J])发出的日志。stderr 和 stdout 日志包含使用 System.err.print() 或 System.out.print() 记录的应用日志。

    下图展示了第二代运行时中的单独请求和应用日志:

    Java 11 中的单独日志

stdout 和 stderr

在第一代运行时中,App Engine 会将发送到 stdout 和 stderr 的日志视为应用日志,并自动将这些应用日志条目分组在关联的请求日志下。

在第二代运行时中,默认情况下,App Engine 不会将发送到 stdout 和 stderr 的日志关联起来。如需使用 stdout 和 stderr 将应用日志条目与请求日志一起分组,请按照将结构化日志写入 stdout 和 stderr 中的说明操作。

我们不建议将日志记录到 stdout 和 stderr,因为某些 App Engine 平台日志(JVM、Jetty、内部基础设施日志)也会发送到 stderr。应用日志和平台日志与 stderr 日志名称一起显示,这会导致歧义。这在第二代运行时中更加明显,因为 App Engine 包含更多平台日志。

App Engine 平台日志

在第一代运行时中,App Engine 不会发出平台日志。

在第二代运行时中,App Engine 会在实例启动或关停期间发出平台日志,日志名称为 /var/log/google_init.log。

您可以放心地忽略平台日志。为避免看到这些日志,请使用查询 logName="/var/log/google_init.log" 向 Logs Explorer 添加过滤条件。

下图展示了第二代运行时中的平台日志:

平台日志

Cloud Logging API 配额

在第一代运行时中,App Engine 会将请求期间发出的所有应用日志嵌入到单个请求日志中,并将嵌入的请求发送到 Cloud Logging。每个请求对应于一次 Cloud Logging 写入。

在第二代运行时中,App Engine 会以单独的日志条目将每个应用日志发送到 Cloud Logging,导致每个请求的 Cloud Logging 写入总次数增加。

Google Cloud 根据 Cloud Logging 使用的存储空间总量来确定费用。即使日志所需的存储空间总量没有显著增加,每分钟写入次数也会增加,具体取决于 App Engine 为每个请求写入的应用日志数量。如果您的应用超出了 Cloud Logging 的写入配额,您可以申请增加使用配额。

如需了解详情,请参阅 Cloud Logging 的价格。

迁移过程概览

在第二代运行时中,即使应用日志未嵌入在请求日志中,也可以保留像第一代应用一样的日志查看体验。

您可以选择以下选项之一,在第二代运行时中设置日志记录:

使用 java.util.logging (JUL) 软件包

如果使用第一代运行时的应用实现了 java.util.logging 软件包,从而满足所有日志记录要求,则迁移到第二代运行时不需要更改代码。

以下示例展示了如何在第二代运行时中使用 java.util.logging 软件包进行日志记录:

    package com.example.appengine.my_app;
    import java.io.IOException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.*;
    import java.util.logging.Logger;

    @WebServlet(name = "HelloAppEngine", value = "/")
    public class HelloAppEngine extends HttpServlet {

      // Use the java.util.logging.Logger to log messages
      private static final Logger logger = Logger.getLogger(HelloAppEngine.class.getName());

      @Override
      public void doGet(HttpServletRequest request, HttpServletResponse response)
          throws IOException {
        // Sample log messages
        logger.info("Info message");
        logger.warning("Warning message");
        logger.severe("Severe message");

        response.setContentType("text/plain");
        response.getWriter().println("Hello App Engine");
      }
    }

将应用日志与请求日志一起分组

在第二代运行时中,请求日志中的 protoPayload.line 字段不包含应用日志。Logs Explorer 使用 trace 字段对请求日志和应用日志进行分组。java.util.logging 软件包会自动将跟踪 ID 添加到所有请求和应用日志中。如需在 Logs Explorer 中查看关联日志,请参阅查看关联日志。

使用 Java 简易日志记录门面 (SLF4J)

SLF4J 是 Java 应用中最常用的日志记录接口。SLF4J 接口作为门面,是与平台无关的日志记录后端,使您能够选择任何适合应用的后端。

下图展示了两个 SLF4J 后端(包括 java.util.logging 库和 Logback 附加器)的集成选项:

SLF4J 概览

具有 java.util.logging 软件包的 SLF4J

将具有 java.util.logging 的 SLF4J 用作日志记录后端时,SLF4J 可让您将应用与 Cloud Logging 集成。默认情况下,App Engine 会将跟踪记录 ID 添加到所有请求和应用日志中。此机制支持在 Logs Explorer 中对请求和应用日志进行分组。

如需实现具有 java.util.logging 的 SLF4J,请按照以下步骤操作:

  1. 将 slf4j-jdk14.jar 依赖项添加到 pom.xml 文件中。slf4j-jdk14.jar 文件为 java.util.logging 库提供了后端集成:

    <!-- SLF4J interface -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>2.0.4</version>
    </dependency>
    <!-- JUL implementation for SLF4J -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>2.0.9</version>
    </dependency>
    
  2. 创建 WEB-INF/logging.properties 文件以添加自定义配置:

    com.example.appengine.java8.HelloAppEngine.level = INFO
    
  3. 更新您的 appengine-web.xml 文件,以添加 WEB-INF/logging.properties 的 <property> 值:

    <system-properties>
      <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
    </system-properties>
    

下图展示了具有 java.util.logging 的 SLF4J 如何自动将跟踪记录 ID 添加到应用日志中:

使用跟踪记录 ID 按请求对应用日志进行分组

如需将 Logs Explorer 视图调整为以请求为中心的视图(如第一代运行时),请参阅查看关联日志。

使用具有 Logback 附加器的 SLF4J

如果第一代运行时应用使用具有 Logback 的 SLF4J 的内置实现,则您必须更新源代码,并实施其他步骤对请求和应用日志进行分组。

如需将应用与 Cloud Logging 集成,请按照以下步骤操作:

  1. 将 google-cloud-logging-logback 附加器添加到 pom.xml 文件中,以便为 Logback 安装日志记录附加器:

     <!-- SLF4J interface -->
     <dependency>
           <groupId>org.slf4j</groupId>
           <artifactId>slf4j-api</artifactId>
           <version>2.0.4</version>
     </dependency>
    
     <!-- Logback JARs -->
     <dependency>
           <groupId>ch.qos.logback</groupId>
           <artifactId>logback-classic</artifactId>
           <version>1.3.6</version>
     </dependency>
     <dependency>
           <groupId>ch.qos.logback</groupId>
           <artifactId>logback-core</artifactId>
           <version>1.3.5</version>
     </dependency>
    
     <!-- Google Cloud logging appender for Logback -->
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-logging-logback</artifactId>
     </dependency>
    
  2. 创建日志记录增强器,以将跟踪记录 ID 添加到每个 LogEntry 字段中。以下 TraceIdLoggingEnhancer 类使用 ApiProxy API 来检索与请求关联的跟踪记录 ID:

      import com.google.appengine.api.utils.SystemProperty;
      import com.google.cloud.logging.LogEntry;
      import com.google.cloud.logging.LoggingEnhancer;
    
      import com.google.apphosting.api.ApiProxy;
      import com.google.apphosting.api.ApiProxy.Environment;
    
      // Add trace ID to the log entry
      public class TraceIdLoggingEnhancer implements LoggingEnhancer {
    
        @Override
        public void enhanceLogEntry(LogEntry.Builder logEntry) {
          final String PROJECT_ID = SystemProperty.applicationId.get();
    
          Environment environment = ApiProxy.getCurrentEnvironment();
    
          if (environment instanceof ApiProxy.EnvironmentWithTrace) {
            ApiProxy.EnvironmentWithTrace environmentWithTrace = (ApiProxy.EnvironmentWithTrace) environment;
            environmentWithTrace
                .getTraceId()
                .ifPresent(
                    id ->
                      logEntry.setTrace(String.format("projects/%s/traces/%s", PROJECT_ID, id)));
          }
        }
      }
      // [END logging_enhancer]
    
  3. 在 logback.xml 文件中添加 Cloud Logging 附加器配置,以配置 Logback。Logback 框架通过 WEB-INF/classes 中的 logback.xml 文件来管理配置:

    <configuration>
      <appender name="CLOUD" class="com.google.cloud.logging.logback.LoggingAppender">
          <!-- This should be set to the new Logging Enhancer in the app code. -->
          <enhancer>com.example.appengine.my_app.enhancers.TraceIdLoggingEnhancer</enhancer>
          <resourceType>gae_app</resourceType>
      </appender>
      <root level="info">
          <appender-ref ref="CLOUD" />
      </root>
    </configuration>
    

    下图展示了最终的应用目录结构:

    目录结构

查看关联日志

您可以使用每个日志条目中的跟踪记录 ID 字段在 Logs Explorer 中查看关联日志。如需在 Logs Explorer 中查看关联日志,请执行以下操作:

  1. 在 Google Cloud 控制台的导航面板中,选择 Logging,然后选择 Logs Explorer:

    前往 Logs Explorer

  2. 在资源类型中,选择 GAE 应用。

  3. 如需查看和关联请求日志,请在日志名称中选择 request_log。 或者,如需按请求日志进行关联,请点击关联依据,然后选择 request_log:

    关联日志

  4. 在查询结果窗格中,如需展开日志条目,请点击展开。展开后,每个请求日志都会显示关联的应用日志。