Logging zu Java-Laufzeiten der zweiten Generation migrieren

Einige Aspekte des Loggings in den Laufzeiten der zweiten Generation (Java 11+) unterscheiden sich von der Laufzeit der ersten Generation (Java 8). Im Gegensatz zur Laufzeit der ersten Generation werden Anwendungslogs in der Laufzeit der zweiten Generation im Log-Explorer nicht unter den Anfragelogs gruppiert. Darüber hinaus schreibt App Engine in den Laufzeiten der zweiten Generation jedes mit einer Anfrage verknüpfte Anwendungslog als separaten Logeintrag in Cloud Logging.

In diesem Leitfaden wird beschrieben, wie Sie dieselben Logging-Methoden implementieren, die Sie in Ihren Laufzeitanwendungen der ersten Generation verwendet haben, und bei der Migration Ihrer Anwendung zu Java 11+ fast dieselben Filter- und Logging-Korrelationsergebnisse erhalten.

Wichtige Unterschiede

In der folgenden Tabelle werden die Unterschiede beim Logging zwischen Laufzeiten der ersten und zweiten Generation behandelt:

Laufzeit der ersten Generation (Java 8) Laufzeiten der zweiten Generation (Java 11+)
Anfrage- und Anwendungslogs (auch Anwendungslogs genannt) App Engine bettet alle Anwendungslogs in das Anfragelog ein. App Engine bettet keine Anwendungslogs in das zugehörige Anfragelog ein.
stdout und stderr App Engine bettet Logs, die in stdout und stderr geschrieben wurden, in das Anfragelog ein. App Engine bettet keine Logs ein, die in stdout und stderr geschrieben wurden.
App Engine-Plattformlogs Die App Engine gibt keine internen Plattformlogs aus. App Engine gibt beim Starten oder Herunterfahren einer Instanz mit dem Lognamen /var/log/google_init.log interne Plattformlogs aus.
Kontingente für die Cloud Logging API Jede Anfrage entspricht einem Cloud Logging-Schreibvorgang. Jeder Anwendungslogeintrag, der mit einer Anfrage verknüpft ist, entspricht einem Cloud Logging-Schreibvorgang. Die Nutzung der Cloud Logging API steigt in den Laufzeiten der zweiten Generation.

Anfragelogs und Anwendungslogs

Das Logging-Verhalten in Laufzeiten der ersten und zweiten Generation unterscheidet sich in folgender Weise:

  • In der Laufzeit der ersten Generation bettet App Engine Anwendungslogs in das Feld protoPayload.line der Anfragelogs ein. Sie können Anwendungslogs aufrufen, indem Sie eine Anfrage im Log-Explorer maximieren.

    Die folgende Abbildung zeigt korrelierte Anfrage- und Anwendungslogs in den Laufzeiten der ersten Generation:

    Korrelierte Logs in Java 8

  • In den Laufzeiten der zweiten Generation enthalten Anfragelogs keine Anwendungslogeinträge, da das Feld protoPayload.line im Anfragelog fehlt. Stattdessen zeichnet die App Engine jedes Anwendungslog als separaten Eintrag auf. Der Logname hängt von Ihrer Logeinrichtung ab. var/log/app enthält Logs, die mit Standard-Logging-Frameworks wie java.util.logging oder der Simple Logging Facade for Java (SLF4J) ausgegeben werden. Die stderr- und stdout-Logs enthalten Anwendungslogs, die mit System.err.print() oder System.out.print() protokolliert werden.

    Die folgende Abbildung zeigt separate Anfrage- und Anwendungslogs in den Laufzeiten der zweiten Generation:

    Separate Logs in Java 11

stdout und stderr

In der Laufzeit der ersten Generation betrachtet App Engine Logs, die an stdout und stderr ausgegeben werden, als Anwendungslogs und gruppiert diese Anwendungslogeinträge automatisch unter dem zugehörigen Anfragelog.

In den Laufzeiten der zweiten Generation korreliert App Engine standardmäßig keine Logs, die an stdout und stderr gesendet werden. Folgen Sie der Anleitung unter Strukturierte Logs in stdout und stderr schreiben, um Anwendungslogentitäten mit dem Anfragelog mithilfe von stdout und stderr zu gruppieren.

Wir empfehlen, das Logging in stdout und stderr nicht, da bestimmte App Engine-Plattformlogs (JVM, Jetty, interne Infrastrukturlogs) ebenfalls an stderr ausgegeben werden. Anwendungs- und Plattformlogs werden zusammen mit dem Lognamen stderr angezeigt, was zu Mehrdeutigkeit führt. Bei Laufzeiten der zweiten Generation ist das Problem noch ausgeprägter, da die App Engine mehr Plattformlogs generiert.

App Engine-Plattformlogs

In der Laufzeit der ersten Generation gibt App Engine keine Plattformlogs aus.

In den Laufzeiten der zweiten Generation gibt App Engine Plattformlogs beim Starten oder Herunterfahren der Instanz mit dem Lognamen /var/log/google_init.log aus.

Sie können die Plattformlogs ignorieren. Wenn Sie diese Logs nicht sehen möchten, fügen Sie dem Log-Explorer einen Filter mit der Abfrage logName="/var/log/google_init.log" hinzu.

Die folgende Abbildung zeigt Plattformlogs in den Laufzeiten der zweiten Generation an:

Plattform-Logs

Kontingente für die Cloud Logging API

In der Laufzeit der ersten Generation bettet App Engine alle Anwendungslogs, die während einer Anfrage ausgegeben werden, in ein einzelnes Anfragelog ein und sendet die eingebettete Anfrage an Cloud Logging. Jede Anfrage entspricht einem Cloud Logging-Schreibvorgang.

Bei Laufzeiten der zweiten Generation sendet App Engine jedes Anwendungslog in separaten Logeinträgen an Cloud Logging. Dadurch steigt die Gesamtzahl der Cloud Logging-Schreibvorgänge pro Anfrage.

Die Abrechnung in Google Cloud richtet sich nach dem Gesamtspeicher, den Cloud Logging verwendet. Auch wenn der Gesamtspeicherplatz für Logs nicht wesentlich zunimmt, steigt die Anzahl der Schreibvorgänge pro Minute, je nachdem, wie viele Anwendungslogs App Engine pro Anfrage schreibt. Wenn Ihre Anwendung das Schreibkontingent von Cloud Logging überschreitet, können Sie eine Erhöhung Ihres Nutzungskontingents anfordern.

Weitere Informationen finden Sie in der Preisübersicht für Cloud Logging.

Übersicht über den Migrationsprozess

Auch wenn Anwendungslogs in den Laufzeiten der zweiten Generation nicht in die Anfragelogs eingebettet sind, können Sie die Logs wie bei Anwendungen der ersten Generation aufrufen.

Sie können eine der folgenden Optionen auswählen, um das Logging in den Laufzeiten der zweiten Generation einzurichten:

Paket java.util.logging (JUL) verwenden

Wenn Ihre Anwendungen, die die Laufzeit der ersten Generation verwenden, das Paket java.util.logging für alle Logging-Anforderungen implementieren, erfordert die Migration zu Laufzeiten der zweiten Generation keine Codeänderungen.

Das folgende Beispiel zeigt, wie Sie das Paket java.util.logging zum Logging in Laufzeiten der zweiten Generation verwenden können:

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

Anwendungslogs mit dem Anfragelog gruppieren

In den Laufzeiten der zweiten Generation enthält das Feld protoPayload.line im Anfragelog keine Anwendungslogs. Im Log-Explorer werden Anfrage- und Anwendungslogs anhand des Felds trace gruppiert. Das Paket java.util.logging fügt die Trace-ID automatisch allen Anfrage- und Anwendungslogs hinzu. Informationen zum Anzeigen korrelierter Logs im Log-Explorer finden Sie unter Korrelierte Logs ansehen.

Simple Logging Facade for Java (SLF4J) verwenden

SLF4J ist die gängigste Logging-Schnittstelle in Java-Anwendungen. Da die SLF4J-Schnittstelle eine Fassade ist, ist sie plattformunabhängig vom Logging-Backend, sodass Sie jedes für Ihre Anwendungen geeignete Backend auswählen können.

Die folgende Abbildung zeigt Integrationsoptionen für zwei SLF4J-Backends, einschließlich der java.util.logging-Bibliothek und des Logback-Appender:

SLF4J-Übersicht

SLF4J mit java.util.logging-Paket

Mit SLF4J können Sie Ihre Anwendung in Cloud Logging einbinden, wenn Sie SLF4J mit java.util.logging als Logging-Backend verwenden. Standardmäßig fügt App Engine allen Anfrage- und Anwendungslogs eine Trace-ID hinzu. Mit diesem Mechanismus können Anfrage- und Anwendungslogs im Log-Explorer gruppiert werden.

So implementieren Sie SLF4J mit java.util.logging:

  1. Fügen Sie der Datei pom.xml die Abhängigkeit slf4j-jdk14.jar hinzu. Die Datei slf4j-jdk14.jar enthält die Backend-Integration für die Bibliothek 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. Erstellen Sie eine WEB-INF/logging.properties-Datei, um benutzerdefinierte Konfigurationen hinzuzufügen:

    com.example.appengine.java8.HelloAppEngine.level = INFO
    
  3. Aktualisieren Sie die Datei appengine-web.xml, um den Wert <property> für WEB-INF/logging.properties aufzunehmen:

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

Die folgende Abbildung zeigt, wie SLF4J mit java.util.logging automatisch die Trace-ID in ein Anwendungslog einfügt:

Anwendungslogs nach Anfrage mit Trace-ID gruppieren

Informationen zum Anpassen der Log-Explorer-Ansicht auf eine anfrageorientierte Ansicht wie bei der Laufzeit der ersten Generation finden Sie unter Korrelierte Logs ansehen.

SLF4J mit Logback-Appender verwenden

Wenn Ihre Laufzeitanwendungen der ersten Generation die integrierte Implementierung von SLF4J mit Logback verwenden, müssen Sie Ihren Quellcode aktualisieren und zusätzliche Schritte zum Gruppieren von Anfrage- und Anwendungslogs implementieren.

So integrieren Sie Ihre Anwendung in Cloud Logging:

  1. Fügen Sie der Datei pom.xml den google-cloud-logging-logback-Appender hinzu, um den Logging-Appender für Logback zu installieren:

     <!-- 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. Erstellen Sie eine Logging-Optimierung, um jedem LogEntry-Feld eine Trace-ID hinzuzufügen. Die folgende TraceIdLoggingEnhancer-Klasse verwendet die ApiProxy API, um die mit einer Anfrage verknüpfte Trace-ID abzurufen:

      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. Fügen Sie die Cloud Logging-Appender-Konfiguration in der Datei logback.xml hinzu, um das Logback zu konfigurieren. Das Logback-Framework verwaltet die Konfiguration über die Datei logback.xml in WEB-INF/classes:

    <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>
    

    Die folgende Abbildung zeigt die endgültige Verzeichnisstruktur der Anwendung:

    Verzeichnisstruktur

Korrelierte Logs ansehen

Sie können korrelierte Logs im Log-Explorer mithilfe des Felds „Trace-ID“ in jedem Logeintrag aufrufen. So rufen Sie korrelierte Logs im Log-Explorer auf:

  1. Wählen Sie im Navigationsbereich der Google Cloud Console Logging und anschließend Log-Explorer aus:

    Zum Log-Explorer

  2. Wählen Sie unter Ressourcentyp die Option GAE-Anwendung aus.

  3. Wählen Sie unter Logname die Option request_log aus, um Anfragelogs aufzurufen und zu korrelieren. Wenn Sie nach Anfragelogs korrelieren möchten, klicken Sie alternativ auf Korrelieren nach und wählen Sie request_log aus.

    Logs korrelieren

  4. Klicken Sie im Bereich Abfrageergebnisse auf Maximieren, um einen Logeintrag zu maximieren. Beim Maximieren werden in jedem Anfragelog die zugehörigen Anwendungslogs angezeigt.