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

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

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

主要区别

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

第一代运行时 (Java 8) 第二代运行时(Java 11 及更高版本)
请求日志和应用日志(也称为 application log) App Engine 会将所有应用日志嵌入请求日志中。 App Engine 不会将应用日志嵌入在请求日志中。
stdoutstderr App Engine 会将写入 stdoutstderr 的日志嵌入在请求日志中。 App Engine 不会嵌入写入 stdoutstderr 的日志。
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])发出的日志。stderrstdout 日志包含使用 System.err.print()System.out.print() 记录的应用日志。

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

    Java 11 中的单独日志

stdoutstderr

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

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

我们不建议将日志记录到 stdoutstderr,因为某些 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

    转到日志浏览器

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

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

    关联日志

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