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

Posted

技术标签:

【中文标题】如何将我的 java 应用程序日志记录事件映射到 GCP Felexible 非兼容 App Engine 中相应的云日志记录事件级别?【英文标题】:How do I map my java app logging events to corresponding cloud logging event levels in GCP Felexible non-compat App Engine? 【发布时间】:2016-09-22 01:40:48 【问题描述】:

我是 GCP AppEngine 的新手,我选择柔性环境有几个原因。但是,令我震惊的是,灵活环境的非“兼容”运行时似乎不允许我将应用程序的日志记录事件映射到云日志记录中的适当日志级别。我读对了吗? 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 日志记录问题并尝试确定适用于标准环境与灵活环境的几个小时之后。尽我所能,事件级别映射在标准环境中是可能的。

但是,为了更细粒度地控制日志级别显示在 Cloud Platform Console,日志框架必须使用 java.util.logging 适配器。 https://cloud.google.com/appengine/docs/java/how-requests-are-handled#Java_Logging

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

无论如何,这在“灵活”的环境中不是更容易吗?谁不想通过 Logging 级别轻松过滤事件?

更新:我澄清了这个问题,表明我在询问 GAE 柔性环境中不兼容的运行时。

【问题讨论】:

【参考方案1】:

这是我使用 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 %logger35 - %msg</pattern>
            </layout>
        </encoder>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="FILE" />
    </root>
</configuration>

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

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;
       

【讨论】:

感谢您发布此信息 - 为我解决了一个大问题。有没有想过把它放在 GitHub 上以便我们改进它?一件不起作用的事情是将单个请求的所有日志折叠到一个组中,这是 GAE 经典的工作方式。【参考方案2】:

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

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

推荐的在 Java 应用中记录日志的默认方法是使用 java.util.logging(对于 Python,它是“日志”模块,对于 Go,它是“日志”包,所有这些都提供映射到 Cloud Logging 的日志级别水平)。我会请求更新这些页面。

您链接的其他文档以提供有关 Java 日志记录的准确信息。关于您引用的部分,full paragraph it was pulled from 提供了上下文。它是说任何写入 stderr 或 stdout 的日志框架都可以工作,但如果您想要除“INFO”或“WARNING”之外的更细粒度的日志级别,它需要使用“java.util.logging”。引用部分下方直接提供了使用“java.util.logging”的完整代码示例,您提到的另一个文档“Logging Application Events with Java”中提供了其他示例。

更新:“入门”指南包含有关如何为每个运行时配置日志记录的具体详细信息:

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

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

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

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

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

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

【讨论】:

你试过了吗?我相信我做到了,但它对我不起作用。 IIRC,日志级别根本没有映射,多行日志事件也没有保存在一起。我最终使用了一个日志事件格式化程序(我使用了 SLF4J)将我的日志事件格式化为一个没有正式指定的单行 JSON 文档。我在 GCP 松弛社区的某个人指出的一些 GO 代码中找到了格式。 我已经使用 app.yaml 中的“runtime: custom”在 java-compat、jetty9-compat 和 python-compat-multicore 运行时上进行了测试。 app.yaml 中的默认 'runtime: java' 运行时选择可能没有云日志连接器的 'Java 8 / Jetty 9.3 Runtime'。 谢谢,亚当。我澄清了我的问题,指出我没有使用“兼容”运行时。根据我看到的所有文档,我没有意识到日志记录会有所不同。

以上是关于如何将我的 java 应用程序日志记录事件映射到 GCP Felexible 非兼容 App Engine 中相应的云日志记录事件级别?的主要内容,如果未能解决你的问题,请参考以下文章

Spring引导微服务使用Graylog进行日志记录

Kubernetes 事件日志到 elasticsearch

如何将我的节点 winston JSON 输出更改为单行

如何将我的 JSON 数据文件的结果映射到 quasar 表中?

如何将我的 JSON 数据映射到 Angular 组件 .HTML 表

如何在Wildfly 8.2.0中的server.log文件中禁用日志记录应用程序日志