如何将我的 java 应用程序日志记录事件映射到 GCP Flexible 非兼容 App Engine 中相应的云日志记录事件级别?

How do I map my java app logging events to corresponding cloud logging event levels in GCP Felexible non-compat App Engine?

我是 GCP AppEngine 的新手,出于多种原因我选择了灵活的环境。但是,我震惊地发现灵活环境的非 "compatible" 运行时似乎不允许我将我的应用程序的日志事件映射到云日志中适当的日志 级别 。我读对了吗? https://cloud.google.com/appengine/docs/flexible/java/writing-application-logs#writing_application_logs_1

而且这个页面真的没有帮助。 https://cloud.google.com/java/getting-started/logging-application-events

这是在阅读了几个小时的 GAE 日志记录问题并试图确定哪些适用于标准环境与灵活环境之后。据我所知,事件级别映射在标准环境中是可能的。

However, for more fine-grained control of the log level display in the Cloud Platform Console, the logging framework must use a java.util.logging adapter. https://cloud.google.com/appengine/docs/java/how-requests-are-handled#Java_Logging

好的。这是一个模糊的参考,但我想我在其他地方看到了更清楚的东西。

无论如何,这在 "flexible" 环境中不应该更容易吗?谁不想按日志级别轻松过滤事件?

更新:我澄清了问题表明我问的是GAE flexible环境中的不兼容运行时。

java.util.logging 提供的日志级别将映射到 Cloud Logging 中的相应日志级别。在灵活运行时上登录基本上与在标准运行时上一样。

编辑:“Writing Application Logs”页面的基本原理似乎是 Cloud Logging 映射不适用于所有运行时。但是,它们目前似乎至少适用于“-compat”运行时和 Java 自定义运行时。文档中的其他地方提供了其他解决方法(见下文):

推荐的登录 Java 应用程序的默认方法是使用 java.util.logging(对于 Python,它是 'logging' 模块,对于 Go,它是 'log' 包,所有这些都提供映射到 Cloud Logging 级别的日志级别)。我会要求更新这些页面。

您链接的其他文档提供了有关 Java 日志记录的准确信息。关于您引用的部分,full paragraph it was pulled from provides context. It's saying any logging framework that writes to stderr or stdout will work, but it needs to use 'java.util.logging' if you want more fine-grained log levels other than 'INFO' or 'WARNING'. A full code sample for using 'java.util.logging' is provided directly underneath the quoted section, and others are provided on the other document you mentioned, 'Logging Application Events with Java'.

更新:'Getting Started' 指南包含有关如何为每个运行时配置日志记录的具体细节:

Java
https://cloud.google.com/java/getting-started/logging-application-events#understanding_the_code

Python
https://cloud.google.com/python/getting-started/logging-application-events#understanding_the_code


https://cloud.google.com/go/getting-started/logging-application-events

NodeJS
https://cloud.google.com/nodejs/getting-started/logging-application-events#understanding_the_code

Ruby
https://cloud.google.com/ruby/getting-started/logging-application-events#application_structure

PHP
https://cloud.google.com/php/getting-started/logging-application-events

以下是我如何使用 SLF4J 进行云日志记录。这适用于不兼容的 Java GAE Flex 环境。

logback.xml

<configuration debug="true">
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>/var/log/app_engine/custom_logs/app.log.json</file>
        <append>true</append>
        <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout
                class="putyourpackagenamehere.GCPCloudLoggingJSONLayout">
                <pattern>%-4relative [%thread] %-5level %logger{35} - %msg</pattern>
            </layout>
        </encoder>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="FILE" />
    </root>
</configuration>

这是 PatternLayout class 我用来在日志文件中的一行上生成 JSON。

import static ch.qos.logback.classic.Level.DEBUG_INT;
import static ch.qos.logback.classic.Level.ERROR_INT;
import static ch.qos.logback.classic.Level.INFO_INT;
import static ch.qos.logback.classic.Level.TRACE_INT;
import static ch.qos.logback.classic.Level.WARN_INT;

import java.util.Map;

import org.json.JSONObject;

import com.homedepot.ta.wh.common.logging.GCPCloudLoggingJSONLayout.GCPCloudLoggingEvent.GCPCloudLoggingTimestamp;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;

/**
 * Format a LoggingEvent as a single line JSON object  
 * 
 *  <br>https://cloud.google.com/appengine/docs/flexible/java/writing-application-logs
 *  
 *  <br>From https://cloud.google.com/appengine/articles/logging
 *  <quote>
 *  Applications using the flexible environment should write custom log files to the VM's log directory at 
 *  /var/log/app_engine/custom_logs. These files are automatically collected and made available in the Logs Viewer. 
 *  Custom log files must have the suffix .log or .log.json. If the suffix is .log.json, the logs must be in JSON 
 *  format with one JSON object per line. If the suffix is .log, log entries are treated as plain text.
 *  </quote>
 *  
 *  Nathan: I can't find a reference to this format on the google pages but I do remember getting the format from some
 *  GO code that a googler on the community slack channel referred me to.   
 */
public class GCPCloudLoggingJSONLayout extends PatternLayout {

    @Override
    public String doLayout(ILoggingEvent event) {
        String formattedMessage = super.doLayout(event);
        return doLayout_internal(formattedMessage, event);
    }

    /* for testing without having to deal wth the complexity of super.doLayout() 
     * Uses formattedMessage instead of event.getMessage() */
    String doLayout_internal(String formattedMessage, ILoggingEvent event) {
        GCPCloudLoggingEvent gcpLogEvent = new GCPCloudLoggingEvent(formattedMessage
                                                                    , convertTimestampToGCPLogTimestamp(event.getTimeStamp())
                                                                    , mapLevelToGCPLevel(event.getLevel())
                                                                    , null);
        JSONObject jsonObj = new JSONObject(gcpLogEvent);
        /* Add a newline so that each JSON log entry is on its own line.
         * Note that it is also important that the JSON log entry does not span multiple lines.
         */
        return jsonObj.toString() + "\n"; 
    }

    static GCPCloudLoggingTimestamp convertTimestampToGCPLogTimestamp(long millisSinceEpoch) {
        int nanos = ((int) (millisSinceEpoch % 1000)) * 1_000_000; // strip out just the milliseconds and convert to nanoseconds
        long seconds = millisSinceEpoch / 1000L; // remove the milliseconds
        return new GCPCloudLoggingTimestamp(seconds, nanos);
    }

    static String mapLevelToGCPLevel(Level level) {
        switch (level.toInt()) {
        case TRACE_INT:
            return "TRACE";
        case DEBUG_INT:
            return "DEBUG";
        case INFO_INT:
            return "INFO";
        case WARN_INT:
            return "WARN";
        case ERROR_INT:
            return "ERROR";
        default:
            return null; /* This should map to no level in GCP Cloud Logging */
        }
    }

    /* Must be public for JSON marshalling logic */
    public static class GCPCloudLoggingEvent {
        private String message;
        private GCPCloudLoggingTimestamp timestamp;
        private String traceId;
        private String severity;

        public GCPCloudLoggingEvent(String message, GCPCloudLoggingTimestamp timestamp, String severity,
                String traceId) {
            super();
            this.message = message;
            this.timestamp = timestamp;
            this.traceId = traceId;
            this.severity = severity;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public GCPCloudLoggingTimestamp getTimestamp() {
            return timestamp;
        }

        public void setTimestamp(GCPCloudLoggingTimestamp timestamp) {
            this.timestamp = timestamp;
        }

        public String getTraceId() {
            return traceId;
        }

        public void setTraceId(String traceId) {
            this.traceId = traceId;
        }

        public String getSeverity() {
            return severity;
        }

        public void setSeverity(String severity) {
            this.severity = severity;
        }

        /* Must be public for JSON marshalling logic */
        public static class GCPCloudLoggingTimestamp {
            private long seconds;
            private int nanos;

            public GCPCloudLoggingTimestamp(long seconds, int nanos) {
                super();
                this.seconds = seconds;
                this.nanos = nanos;
            }

            public long getSeconds() {
                return seconds;
            }

            public void setSeconds(long seconds) {
                this.seconds = seconds;
            }

            public int getNanos() {
                return nanos;
            }

            public void setNanos(int nanos) {
                this.nanos = nanos;
            }

        }       
    }

    @Override
    public Map<String, String> getDefaultConverterMap() {
        return PatternLayout.defaultConverterMap;
    }   
}