如何使用 stdout Go 1.11 记录由跟踪 ID 关联的 Stackdriver 日志消息

How to log Stackdriver log messages correlated by trace id using stdout Go 1.11

我正在使用 Google App Engine 标准环境和 Go 1.11 运行时。 Go 1.11 的文档说 "Write your application logs using stdout for output and stderr for errors"。从 Go 1.9 迁移指南还建议不要直接调用 Google Cloud Logging 库,而是通过 stdout 进行日志记录。 https://cloud.google.com/appengine/docs/standard/go111/writing-application-logs

考虑到这一点,我编写了一个小型 HTTP 服务(下面的代码)来试验使用 JSON 输出到 stdout 来记录到 Stackdriver。

当我打印纯文本消息时,它们按预期显示在 textPayload 下的日志查看器面板中。当我传递 JSON 字符串时,它们出现在 jsonPayload 下。到目前为止,还不错。

因此,我在输出字符串中添加了一个 severity 字段,Stackdriver Log Viewer 成功地根据分级日志记录 NOTICE、WARNING 等对消息进行了分类。 https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry

文档说要设置 trace 标识符以将日志条目与原始请求日志相关联。 trace ID是从容器设置的X-Cloud-Trace-Contextheader中提取的。

使用curl -v -H 'X-Cloud-Trace-Context: 1ad1e4f50427b51eadc9b36064d40cc2/8196282844182683029;o=1' http://localhost:8080/

在本地模拟

但是,这不会导致消息按请求进行线程化,而是 trace 属性 出现在日志的 jsonPayload object 中。 (见下文)。

请注意,severity 已按预期解释,并未出现在 jsonPayload 中。我原以为 trace 也会发生同样的情况,但它似乎未被处理。

如何在原始请求日志消息中实现嵌套消息? (这必须在 Go 1.11 上使用 stdout 完成,因为我不想直接使用 Google 云日志包进行日志记录)。

GAE 究竟做了什么来解析来自我的 运行 进程的标准输出流? (在 GCE 上的 VM 设置指南中,有关于安装代理程序以充当 Stackdriver 日志记录管道的内容 - 这是 GAE 安装的吗?)

app.yaml 文件如下所示:

runtime: go111

handlers:
- url: /.*
  script: auto



package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
    "strings"
)

var projectID = "glowing-market-234808"

func parseXCloudTraceContext(t string) (traceID, spanID, traceTrue string) {
    slash := strings.Index(t, "/")
    semi := strings.Index(t, ";")
    equal := strings.Index(t, "=")
    return t[0:slash], t[slash+1 : semi], t[equal+1:]
}

func sayHello(w http.ResponseWriter, r *http.Request) {
    xTrace := r.Header.Get("X-Cloud-Trace-Context")
    traceID, spanID, _ := parseXCloudTraceContext(xTrace)
    trace := fmt.Sprintf("projects/%s/traces/%s", projectID, traceID)

    warning := fmt.Sprintf(`{"trace":"%s","spanId":"%s", "severity":"WARNING","name":"Andy","age":45}`, trace, spanID)
    fmt.Fprintf(os.Stdout, "%s\n", warning)

    message := "Hello"
    w.Write([]byte(message))
}

func main() {
    http.HandleFunc("/", sayHello)
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
}

日志查看器中显示的输出:

...,
jsonPayload: {
  age:  45   
  name:  "Andy"   
  spanId:  "13076979521824861986"   
  trace:  "projects/glowing-market-234808/traces/e880a38fb5f726216f94548a76a6e474"   
},
severity:  "WARNING",
...

我通过调整程序以使用 logging.googleapis.com/trace 代替 tracelogging.googleapis.com/spanId 代替 spanId 来解决这个问题。

    warning := fmt.Sprintf(`{"logging.googleapis.com/trace":"%s","logging.googleapis.com/spanId":"%s", "severity":"WARNING","name":"Andy","age":45}`, trace, spanID)
    fmt.Fprintf(os.Stdout, "%s\n", warning)

GAE 似乎正在使用日志代理 google-fluentdfluentd 日志数据收集器的修改版本。)

查看此 link 以获得完整解释。 https://cloud.google.com/logging/docs/agent/configuration#special-fields

[更新] 2019 年 6 月 25 日:我编写了一个 Logrus 插件,它有助于通过 HTTP 请求对日志条目进行线程化。它在 GitHub https://github.com/andyfusniak/stackdriver-gae-logrus-plugin.

下可用

[更新]] 2020 年 4 月 3 日:我已经改用 Cloud 运行 并且 Logrus 插件似乎也适用于该平台。