GKE&;Stackdriver:Java日志记录格式?

GKE&;Stackdriver:Java日志记录格式?,java,google-cloud-platform,kubernetes,logback,stackdriver,Java,Google Cloud Platform,Kubernetes,Logback,Stackdriver,我有一个项目在Kubernetes上的docker映像中运行Java。fluentd代理会自动接收日志,并最终保存在Stackdriver中 但是,日志的格式是错误的:多行日志被放入Stackdriver中的单独日志行中,并且所有日志都具有“INFO”日志级别,即使它们实际上是警告或错误 我一直在搜索有关如何配置logback以输出正确格式以使其正常工作的信息,但在google Stackdriver或GKE文档中找不到此类指南 我的猜测是,我应该输出某种形式的JSON,但我在哪里可以找到关于该

我有一个项目在Kubernetes上的docker映像中运行Java。fluentd代理会自动接收日志,并最终保存在Stackdriver中

但是,日志的格式是错误的:多行日志被放入Stackdriver中的单独日志行中,并且所有日志都具有“INFO”日志级别,即使它们实际上是警告或错误

我一直在搜索有关如何配置logback以输出正确格式以使其正常工作的信息,但在google Stackdriver或GKE文档中找不到此类指南

我的猜测是,我应该输出某种形式的JSON,但我在哪里可以找到关于该格式的信息,甚至是关于如何正确设置此管道的指南


谢谢

这个答案包含了我需要的大部分信息:

我已经调整了答案,以适应我的确切问题,并修复了一些奇怪的导入和似乎已被弃用的代码

logback.xml:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="my.package.logging.GCPCloudLoggingJSONLayout">
                <pattern>%-4relative [%thread] %-5level %logger{35} - %msg</pattern>
            </layout>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

%-4相对[%thread]-5级别%logger{35}-%msg
GCPCloudLoggingJSONLayout:

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Map;

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;

/**
 * GKE fluentd ingestion detective work:
 * https://cloud.google.com/error-reporting/docs/formatting-error-messages#json_representation
 * http://google-cloud-python.readthedocs.io/en/latest/logging-handlers-container-engine.html
 * http://google-cloud-python.readthedocs.io/en/latest/_modules/google/cloud/logging/handlers/container_engine.html#ContainerEngineHandler.format
 * https://github.com/GoogleCloudPlatform/google-cloud-python/blob/master/logging/google/cloud/logging/handlers/_helpers.py
 * https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
 */
public class GCPCloudLoggingJSONLayout extends PatternLayout {

    private static final ObjectMapper objectMapper = new ObjectMapper();

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

    /**
     * For testing without having to deal wth the complexity of super.doLayout()
     * Uses formattedMessage instead of event.getMessage()
     */
    private String doLayoutInternal(String formattedMessage, ILoggingEvent event) {
        GCPCloudLoggingEvent gcpLogEvent =
            new GCPCloudLoggingEvent(formattedMessage, convertTimestampToGCPLogTimestamp(event.getTimeStamp()),
                mapLevelToGCPLevel(event.getLevel()), event.getThreadName());

        try {
            // 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 objectMapper.writeValueAsString(gcpLogEvent) + "\n";
        } catch (JsonProcessingException e) {
            return "";
        }
    }

    private static GCPCloudLoggingEvent.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 GCPCloudLoggingEvent.GCPCloudLoggingTimestamp(seconds, nanos);
    }

    private 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 Jackson JSON conversion */
    public static class GCPCloudLoggingEvent {
        private String message;
        private GCPCloudLoggingTimestamp timestamp;
        private String thread;
        private String severity;

        public GCPCloudLoggingEvent(String message, GCPCloudLoggingTimestamp timestamp, String severity,
            String thread) {
            super();
            this.message = message;
            this.timestamp = timestamp;
            this.thread = thread;
            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 getThread() {
            return thread;
        }

        public void setThread(String thread) {
            this.thread = thread;
        }

        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;
    }
}
导入ch.qos.logback.classic.Level;
导入ch.qos.logback.classic.PatternLayout;
导入ch.qos.logback.classic.spi.ILoggingEvent;
导入com.fasterxml.jackson.core.JsonProcessingException;
导入com.fasterxml.jackson.databind.ObjectMapper;
导入java.util.Map;
导入静态ch.qos.logback.classic.Level.DEBUG\u INT;
导入静态ch.qos.logback.classic.Level.ERROR\u INT;
导入静态ch.qos.logback.classic.Level.INFO\u INT;
导入静态ch.qos.logback.classic.Level.TRACE\u INT;
导入静态ch.qos.logback.classic.Level.WARN\u INT;
/**
*GKE fluentd摄入检测工作:
* https://cloud.google.com/error-reporting/docs/formatting-error-messages#json_representation
* http://google-cloud-python.readthedocs.io/en/latest/logging-handlers-container-engine.html
* http://google-cloud-python.readthedocs.io/en/latest/_modules/google/cloud/logging/handlers/container_engine.html#ContainerEngineHandler.format
* https://github.com/GoogleCloudPlatform/google-cloud-python/blob/master/logging/google/cloud/logging/handlers/_helpers.py
* https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
*/
公共类GCPCloudLoggingJSONLayout扩展了PatternLayout{
私有静态最终ObjectMapper ObjectMapper=新ObjectMapper();
@凌驾
公共字符串布局(ILOGINGEVENT事件){
字符串formattedMessage=super.doLayout(事件);
返回dolayouternal(格式化消息、事件);
}
/**
*用于测试,无需处理super.doLayout()的复杂性
*使用formattedMessage而不是event.getMessage()
*/
私有字符串dolayouternal(字符串格式化消息,ILOGINGEVENT事件){
GCPCloudLoggingEvent gcpLogEvent=
新的GCPCloudLoggingEvent(formattedMessage,ConvertTimestampTogcLogTimeStamp(event.getTimeStamp()),
MaplevelToGCLevel(event.getLevel()),event.getThreadName();
试一试{
//添加一个换行符,使每个JSON日志条目位于其自己的行上。
//请注意,JSON日志项不跨越多行也很重要。
返回objectMapper.writeValueAsString(gcpLogEvent)+“\n”;
}捕获(JsonProcessingException e){
返回“”;
}
}
专用静态GCPCloudLoggingEvent.GCPCloudLoggingTimestamp转换器Timestamp TogCplogTimestamp(
长百万分之一世纪){
奈米=
((int)(millisSinceEpoch%1000))*1_000_000;//仅去掉毫秒并转换为纳秒
long seconds=millisSinceEpoch/1000L;//删除毫秒
返回新的GCPCloudLoggingEvent.GCPCloudLoggingTimestamp(秒,纳秒);
}
私有静态字符串MaplevelToGCLevel(级别){
开关(level.toInt()){
案例跟踪信息:
返回“跟踪”;
案例调试\u INT:
返回“调试”;
案件信息:
返回“信息”;
案例警告:
返回“警告”;
案例错误_INT:
返回“错误”;
违约:
返回null;/*这应该映射到GCP云日志中的no级别*/
}
}
/*对于Jackson JSON转换,必须是公共的*/
公共静态类GCPCloudLoggingEvent{
私有字符串消息;
专用GCPCloudLoggingTimestamp时间戳;
私有字符串线程;
私有字符串严重性;
公共GCPCloudLoggingEvent(字符串消息、GCPCloudLoggingTimestamp时间戳、字符串严重性、,
(细线){
超级();
this.message=消息;
this.timestamp=时间戳;
this.thread=线程;
严重性=严重性;
}
公共字符串getMessage(){
返回消息;
}
公共无效设置消息(字符串消息){
this.message=消息;
}
公共GCPCloudLoggingTimestamp getTimestamp()的时间戳{
返回时间戳;
}
public void setTimestamp(GCPCloudLoggingTimestamp时间戳){
this.timestamp=时间戳;
}
公共字符串getThread(){
返回线程;
}
公共void setThread(字符串线程){
this.thread=线程;
}
公共字符串getSeverity(){
返回严重性;
}
公共无效设置严重性(字符串严重性){
严重性=严重性;
}
/*对于JSON编组逻辑,必须是公共的*/
公共静态类GCPCloudLoggingTimestamp{
私人长秒;
私有int nanos;
公共GCPCloudLoggingTimestamp(长秒,整数纳秒){
超级();
这个。秒=秒;
<dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>google-cloud-logging-logback</artifactId>
        <version>0.116.0-alpha</version>
    </dependency>
    <springProfile name="prod-gae">
    <appender name="CLOUD"
        class="com.google.cloud.logging.logback.LoggingAppender">
        <log>clicktrade.log</log>
        <loggingEventEnhancer>com.jimmy.clicktrade.arch.CommonEnhancer</loggingEventEnhancer>
        <flushLevel>WARN</flushLevel>
    </appender>
    <root level="info">
        <appender-ref ref="CLOUD" />
    </root>
</springProfile>
    public class CommonEnhancer implements LoggingEventEnhancer {

    @Override
    public void enhanceLogEntry(Builder builder, ILoggingEvent e) {
        builder.addLabel("thread", e.getThreadName());
    }

}