结构化日志记录

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

本文档讨论了结构化日志记录的概念,以及向日志条目载荷字段添加结构的方法。当日志载荷的格式设置为 JSON 对象,并且该对象存储在 jsonPayload 字段中时,日志条目称为结构化日志。对于这些日志,您可以构建用于搜索特定 JSON 路径的查询,并且可以将日志载荷中的特定字段编入索引。相反,当日志载荷的格式设置为字符串并存储在 textPayload 字段中时,日志条目将是非结构化的。您可以搜索文本字段,但无法将其内容编入索引。

您可以通过多种方式将结构化日志写入 Logging:

  • 使用 Cloud Logging API 写入日志条目
  • 使用 Google Cloud CLI 写入日志条目
  • 使用 BindPlane 服务注入日志
  • 向 Logging 代理提供序列化 JSON 对象

以下各部分介绍了每种方法。

使用 Logging API

如果您使用 Cloud Logging API 写入日志条目,则可以通过向 Cloud Logging API 发送带有 jsonPayload 的完整 LogEntry 结构来控制载荷的结构。

如需了解详情,请参阅 entries.write 参考文档。 如需查看代码示例,请参阅写入结构化日志

使用 gcloud CLI

如果您使用 gcloud CLI 写入日志条目,则可以将具有 jsonPayload 的完整 LogEntry 结构发送到 Cloud Logging API,从而控制载荷的结构。

如需查看代码示例,请参阅 gcloud logging write 参考文档。

使用 BindPlane

如果您使用 BindPlane 服务来注入日志,则您的载荷采用 JSON 格式,并根据源系统进行结构化。如需了解如何查找和查看通过 BindPlane 提取的日志,请参阅有关如何查找日志数据的 BindPlane 文档。

使用 Logging 代理

如果使用 Cloud Logging 代理获取日志条目,则可以指定 Logging 代理将载荷转换为 JSON 格式。

如果您使用的是 Google Kubernetes Engine 或 App Engine 柔性环境,则可以将结构化日志以 JSON 格式进行序列化,并在一行写入 stdoutstderr。然后,Logging 代理会将结构化日志作为 LogEntry 结构的 jsonPayload 发送到 Cloud Logging。

JSON 对象中的某些字段会被 Logging 代理识别为特殊字段,并提取到 LogEntry 结构中。这些特殊的 JSON 字段可用于设置 LogEntry 中的以下字段:

  • severity
  • spanId
  • labels 由用户定义
  • httpRequest

由于 JSON 比文本行更精确且更全能,因此您可以使用 JSON 对象编写多行消息并添加元数据。

如需使用简化格式为您的应用创建结构化日志条目,请参阅下表,其中列出了 JSON 格式的字段及其值:

JSON 日志字段 LogEntry 字段 Cloud Logging 代理函数 示例值
severity severity Logging 代理尝试匹配各种常见严重性字符串,其中包括 Logging API 识别的 LogSeverity 字符串列表。 "severity":"ERROR"
message textPayload(或 jsonPayload 的一部分) 日志浏览器中的日志条目行显示的消息。 "message":"There was an error in the application."

注意:如果在 Logging 代理移动其他特殊用途字段之后只留下了一个 message 字段并且 detect_json 未启用,则该 message 将保存为 textPayload,否则 message 仍保留在 jsonPayload 中。detect_json 不适用于 Google Kubernetes Engine 等代管式日志记录环境。如果您的日志条目包含异常堆栈轨迹,则应在此 message JSON 日志字段中设置异常堆栈轨迹,以便系统可以解析该异常堆栈跟踪并将其保存到 Error Reporting 之中。
log(仅限旧版 Google Kubernetes Engine) textPayload 仅适用于旧版 Google Kubernetes Engine:如果在移动特殊用途字段后,只留下一个 log 字段,则该字段将保存为 textPayload
httpRequest httpRequest 采用 LogEntry HttpRequest 字段格式的结构化记录。 "httpRequest":{"requestMethod":"GET"}
与时间相关的字段 timetimestamp 等等 如需了解详情,请参阅与时间相关的字段 "time":"2020-10-12T07:20:50.52Z"
logging.googleapis.com/insertId insertId 如需了解详情,请参阅 LogEntry 页面上的 insertId "logging.googleapis.com/insertId":"42"
logging.googleapis.com/labels labels 此字段的值必须为结构化记录。如需了解详情,请参阅 LogEntry 页面上的 labels "logging.googleapis.com/labels": {"user_label_1":"value_1","user_label_2":"value_2"}
logging.googleapis.com/operation operation 此字段的值也可用于日志浏览器对相关日志条目进行分组。如需了解详情,请参阅 LogEntry 页面上的 operation "logging.googleapis.com/operation": {"id":"get_data","producer":"github.com/MyProject/MyApplication", "first":"true"}
logging.googleapis.com/sourceLocation sourceLocation 与日志条目关联的源代码位置信息(如果有)。如需了解详情,请参阅 LogEntry 页面上的 LogEntrySourceLocation "logging.googleapis.com/sourceLocation": {"file":"get_data.py","line":"142","function":"getData"}
logging.googleapis.com/spanId spanId 与日志条目相关联的跟踪记录内的 Span ID。如需了解详情,请参阅 LogEntry 页面上的 spanId "logging.googleapis.com/spanId":"000000000000004a"
logging.googleapis.com/trace trace 与日志条目关联的跟踪记录的资源名称(如果有)。如需了解详情,请参阅 LogEntry 页面上的 trace "logging.googleapis.com/trace":"projects/my-projectid/traces/0679686673a"

注意:如果未向 stdoutstderr 写入数据,此字段的值应设置为 projects/[PROJECT-ID]/traces/[TRACE-ID] 格式,以便日志浏览器和 Trace Viewer 可以使用此字段对日志条目进行分组,并且将日志条目与跟踪记录一起显示。如果 autoformat_stackdriver_trace 为 true,并且 [V]ResourceTrace traceId 的格式相匹配,则 LogEntry trace 字段的值将是 projects/[PROJECT-ID]/traces/[V]
logging.googleapis.com/trace_sampled traceSampled 此字段的值必须是 truefalse。如需了解详情,请参阅 LogEntry 页面上的 traceSampled "logging.googleapis.com/trace_sampled": false

如需创建简化格式的日志条目,请使用字段创建条目的 JSON 表示。所有字段均为可选字段。

以下是简化版 JSON 日志条目的示例:

{
  "severity":"ERROR",
  "message":"There was an error in the application.",
  "httpRequest":{
    "requestMethod":"GET"
  },
  "times":"2020-10-12T07:20:50.52Z",
  "logging.googleapis.com/insertId":"42",
  "logging.googleapis.com/labels":{
    "user_label_1":"value_1",
    "user_label_2":"value_2"
  },
  "logging.googleapis.com/operation":{
    "id":"get_data",
    "producer":"github.com/MyProject/MyApplication",
     "first":"true"
  },
  "logging.googleapis.com/sourceLocation":{
    "file":"get_data.py",
    "line":"142",
    "function":"getData"
  },
  "logging.googleapis.com/spanId":"000000000000004a",
  "logging.googleapis.com/trace":"projects/my-projectid/traces/06796866738c859f2f19b7cfb3214824",
  "logging.googleapis.com/trace_sampled":false
}

以下是生成的日志条目的示例:

{
  "insertId": "42",
  "jsonPayload": {
    "message": "There was an error in the application",
    "times": "2019-10-12T07:20:50.52Z"
  },
  "httpRequest": {
    "requestMethod": "GET"
  },
  "resource": {
    "type": "k8s_container",
    "labels": {
      "container_name": "hello-app",
      "pod_name": "helloworld-gke-6cfd6f4599-9wff8",
      "project_id": "stackdriver-sandbox-92334288",
      "namespace_name": "default",
      "location": "us-west4",
      "cluster_name": "helloworld-gke"
    }
  },
  "timestamp": "2020-11-07T15:57:35.945508391Z",
  "severity": "ERROR",
  "labels": {
    "user_label_2": "value_2",
    "user_label_1": "value_1"
  },
  "logName": "projects/stackdriver-sandbox-92334288/logs/stdout",
  "operation": {
    "id": "get_data",
    "producer": "github.com/MyProject/MyApplication",
    "first": true
  },
  "trace": "projects/my-projectid/traces/06796866738c859f2f19b7cfb3214824",
  "sourceLocation": {
    "file": "get_data.py",
    "line": "142",
    "function": "getData"
  },
  "receiveTimestamp": "2020-11-07T15:57:42.411414059Z",
  "spanId": "000000000000004a"
}

Logging 代理配置

Logging 代理 google-fluentd 是 Cloud Logging 专属的 Fluentd 日志数据收集器的程序包。Logging 代理默认采用 Fluentd 配置,并使用 Fluentd 输入插件从外部源(例如磁盘上的文件)拉取事件日志,或者使用该插件解析传入的日志记录。

Fluentd 支持多种解析器,这些解析器可提取日志并将日志转换为结构化 (JSON) 负载。

使用 format [PARSER_NAME] 配置日志源之后,您便可以利用 Fluentd 提供的内置解析器。

以下代码示例展示了 Fluentd 配置、输入日志记录,以及属于 Cloud Logging 日志条目一部分的输出结构化载荷:

  • Fluentd 配置:

      <source>
        @type tail
    
        format syslog # <--- This uses a predefined log format regex named
                      # `syslog`. See details at https://docs.fluentd.org/parser/syslog.
    
        path /var/log/syslog
        pos_file /var/lib/google-fluentd/pos/syslog.pos
        read_from_head true
        tag syslog
      </source>
    
  • 日志记录(输入):

      <6>Feb 28 12:00:00 192.168.0.1 fluentd[11111]: [error] Syslog test
    
  • 结构化负载(输出):

        jsonPayload: {
            "pri": "6",
            "host": "192.168.0.1",
            "ident": "fluentd",
            "pid": "11111",
            "message": "[error] Syslog test"
        }
    

如需详细了解 syslog 解析器的工作原理,请参阅详细的 Fluentd 文档

默认启用的标准解析器

下表列出了启用结构化日志记录后代理中包含的标准解析器:

解析器名称 配置文件
syslog /etc/google-fluentd/config.d/syslog.conf
nginx /etc/google-fluentd/config.d/nginx.conf
apache2 /etc/google-fluentd/config.d/apache.conf
apache_error /etc/google-fluentd/config.d/apache.conf

如需了解如何在安装 Logging 代理时启用结构化日志记录,请参阅安装部分。

安装

如需启用结构化日志记录,您必须在安装或重新安装 Logging 代理时更改其默认配置。如果启用结构化日志记录,则系统会替换之前列出的配置文件,但不会更改代理本身的操作。

启用结构化日志记录后,列出的日志将转换为与启用结构化日志之前的格式不同的日志条目。如果正在将日志路由到 Logging 之外的目标位置,发生的更改可能会影响所有后处理应用。例如,如果将日志路由到 BigQuery,则 BigQuery 会因架构错误拒绝当天剩余时间内的新日志条目。

有关安装 Logging 代理和启用结构化日志记录的说明,请参阅安装 Logging 代理

您可以在 /etc/google-fluentd/config.d/ 下找到 Logging 代理配置文件,现在其中应该已经包括了默认启用的标准解析器

配置 Apache 访问日志格式

默认情况下,Logging 代理将 Apache 访问日志数据存储在 jsonPayload 字段中。例如:

{
  "logName": ...,
  "resource": ...,
  "httpRequest": ...,
  "jsonPayload": {
    "user"   : "some-user",
    "method" : "GET",
    "code"   : 200,
    "size"   : 777,
    "host"   : "192.168.0.1",
    "path"   : "/some-path",
    "referer": "some-referer",
    "agent"  : "Opera/12.0"
  },
  ...
}

或者,您可以配置 Logging 代理以将某些字段提取到 httpRequest 字段。例如:

{
  "logName": ...,
  "resource": ...,
  "httpRequest": {
    "requestMethod": "GET",
    "requestUrl": "/some-path",
    "requestSize": "777",
    "status": "200",
    "userAgent": "Opera/12.0",
    "serverIp": "192.168.0.1",
    "referrer":"some-referrer",
  },
  "jsonPayload": {
    "user":"some-user"
  },
  ...
}

如前面的示例所示,配置 httpRequest 字段有助于跟踪:Google Cloud 控制台在父级-子级层次结构中显示给定 HTTP 请求的所有日志。

要配置此提取,请将以下内容添加到 /etc/google-fluentd/config.d/apache.conf 的末尾:

  <filter apache-access>
    @type record_transformer
    enable_ruby true
    <record>
      httpRequest ${ {"requestMethod" => record['method'], "requestUrl" => record['path'], "requestSize" => record['size'], "status" => record['code'], "userAgent" => record['agent'], "serverIp" => record['host'],
      "referer" => record['referer']} }
    </record>
    remove_keys method, path, size, code, agent, host, referer
  </filter>

如需详细了解如何配置日志条目,请参阅修改日志记录

配置 nginx 访问日志格式

默认情况下,Logging 代理将 nginx 访问日志数据存储在 jsonPayload 字段中。例如:

  {
    "logName": ...,
    "resource": ...,
    "httpRequest": ...,
    "jsonPayload": {
      "remote":"127.0.0.1",
      "host":"192.168.0.1",
      "user":"some-user",
      "method":"GET",
      "path":"/some-path",
      "code":"200",
      "size":"777",
      "referrer":"some-referrer",
      "agent":"Opera/12.0",
      "http_x_forwarded_for":"192.168.3.3"
    },
    ...
  }

或者,您可以配置 Logging 代理以将某些字段提取到 httpRequest 字段。例如:

  {
    "logName": ...,
    "resource": ...,
    "httpRequest": {
      "requestMethod": "GET",
      "requestUrl": "/some-path",
      "requestSize": "777",
      "status": "200",
      "userAgent": "Opera/12.0",
      "remoteIp": "127.0.0.1",
      "serverIp": "192.168.0.1",
      "referrer":"some-referrer",
    },
    "jsonPayload": {
      "user":"some-user",
      "http_x_forwarded_for":"192.168.3.3"
    },
    ...
  }

如前面的示例所示,配置 httpRequest 字段有助于跟踪:Google Cloud 控制台在父级-子级层次结构中显示给定 HTTP 请求的所有日志。

要配置此提取,请将以下内容添加到 /etc/google-fluentd/config.d/nginx.conf 的末尾:

<filter nginx-access>
  @type record_transformer
  enable_ruby true
  <record>
    httpRequest ${ {"requestMethod" => record['method'], "requestUrl" => record['path'], "requestSize" => record['size'], "status" => record['code'], "userAgent" => record['agent'], "remoteIp" => record['remote'], "serverIp" => record['host'], "referer" => record['referer']} }
  </record>
  remove_keys method, path, size, code, agent, remote, host, referer
</filter>

如需详细了解如何配置日志条目,请参阅修改日志记录

编写您自己的解析器

如果标准解析器不支持您的日志,您可以编写自己的解析器。解析器包含一个用于匹配日志记录并将标签应用于各部分的正则表达式。

以下代码示例分别显示了日志记录中的日志行、包含指示日志行格式的正则表达式的配置,以及提取的日志条目:

  • 日志记录中的日志行:

    REPAIR CAR $500
    
  • 包含指示日志行格式的正则表达式的配置:

    $ sudo vim /etc/google-fluentd/config.d/test-structured-log.conf
    $ cat /etc/google-fluentd/config.d/test-structured-log.conf
    <source>
      @type tail
    
      # Format indicates the log should be translated from text to
      # structured (JSON) with three fields, "action", "thing" and "cost",
      # using the following regex:
      format /(?<action>\w+) (?<thing>\w+) \$(?<cost>\d+)/
      # The path of the log file.
      path /tmp/test-structured-log.log
      # The path of the position file that records where in the log file
      # we have processed already. This is useful when the agent
      # restarts.
      pos_file /var/lib/google-fluentd/pos/test-structured-log.pos
      read_from_head true
      # The log tag for this log input.
      tag structured-log
    </source>
    
  • 生成的日志条目:

     {
      insertId:  "eps2n7g1hq99qp"
      jsonPayload: {
        "action": "REPAIR"
        "thing": "CAR"
        "cost": "500"
      }
      labels: {
        compute.googleapis.com/resource_name:  "add-structured-log-resource"
      }
      logName:  "projects/my-sample-project-12345/logs/structured-log"
      receiveTimestamp:  "2018-03-21T01:47:11.475065313Z"
      resource: {
        labels: {
          instance_id:  "3914079432219560274"
          project_id:  "my-sample-project-12345"
          zone:  "us-central1-c"
        }
        type:  "gce_instance"
      }
      timestamp:  "2018-03-21T01:47:05.051902169Z"
     }
    

问题排查

如需排查在安装 Logging 代理或与之交互时出现的常见问题,请参阅排查代理问题

后续步骤