结构化日志记录

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

如需创建结构化日志条目,请执行以下任一操作:

  • 调用 entries.write API 方法,并提供完全格式化的 LogEntry
  • 使用可写入结构化日志的 Cloud Logging 客户端库。
  • 使用 gcloud logging write 命令。
  • 使用 BindPlane 服务。
  • 使用代理写入日志

    • 某些 Google Cloud 服务包含集成式日志记录代理,该代理会将写入 stdoutstderr 的数据作为日志发送到 Cloud Logging。您可以将此方法用于 Google Cloud 服务,例如 Google Kubernetes Engine、App Engine 柔性环境和 Cloud Functions。

    • 对于 Compute Engine 虚拟机 (VM),您可以安装和配置 Ops Agent 或旧版 Logging 代理,然后使用已安装的代理将日志发送到 Cloud Logging。

如需详细了解这些方法,请参阅以下部分。

使用客户端库或 API 写入日志

您可以使用调用 Cloud Logging API 的 Cloud Logging 客户端库或直接调用 Cloud Logging API 来写入日志数据。客户端库可以自动捕获某些信息并提供用于适当填充字段的接口,从而简化特殊 JSON 字段的填充。但是,如需完全控制载荷的结构,请直接调用 Cloud Logging API 并将完整的 LogEntry 结构传递给 Cloud Logging API。

如需了解详情,请参阅 entries.write 参考文档。

如需查看代码示例,请参阅编写结构化日志

使用 gcloud CLI 写入日志

您可以使用 gcloud CLI 写入日志数据。该界面支持非结构化日志和结构化日志。如果要编写结构化日志,请为命令提供序列化 JSON 对象。

如需查看快速入门,请参阅使用 Google Cloud CLI 写入和查询日志条目

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

使用 BindPlane 写入日志

您可以使用 BindPlane 服务将日志发送到 Logging。 这些日志的载荷采用 JSON 格式,并根据源系统进行结构化。如需了解如何查找和查看使用 BindPlane 注入的日志,请参阅 BindPlane 快速入门指南

使用代理写入日志

如需从 Compute Engine 实例获取日志,您可以使用 Ops Agent旧版 Cloud Logging 代理。这两种代理都可以从第三方应用收集指标,并且都支持结构化日志记录:

  • 建议使用 Ops Agent 从 Compute Engine 实例收集遥测数据。此代理将日志记录和指标组合到单个代理中,提供基于 YAML 的配置,并具有高吞吐量日志记录。

    如需了解如何配置 Ops Agent 以支持结构化日志记录或如何自定义结构化日志的形式,请参阅配置 Ops Agent

  • 旧版 Cloud Logging 代理用于收集日志。此代理不会收集其他形式的遥测数据。

本部分的其余内容专门介绍旧版 Logging 代理。

Logging 代理:特殊 JSON 字段

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"}
与时间相关的字段 timestamp 如需了解详情,请参阅与时间相关的字段 "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": "2020-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-fluentdFluentd 日志数据收集器的 Cloud Logging 专用软件包。Logging 代理默认采用 Fluentd 配置,并使用 Fluentd 输入插件从外部源(例如磁盘上的文件)拉取事件日志,或者使用该插件解析传入的日志记录。

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

通过使用 format [PARSER_NAME] 配置日志源,您可以在 Fluentd 提供的内置解析器的基础上进行构建。如需了解如何配置旧版 Logging 代理,请参阅配置 Logging 代理

以下代码示例展示了 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 文档

Logging 代理:默认启用标准解析器

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

解析器名称 配置文件
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 代理时更改该代理的默认配置。启用结构化日志记录会替换之前列出的配置文件,但不会更改代理本身的操作。

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

如需了解如何安装旧版 Logging 代理和启用结构化日志记录,请参阅安装 Logging 代理

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

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>

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

Logging 代理:配置 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:  "2023-03-21T01:47:11.475065313Z"
    resource: {
      labels: {
        instance_id:  "3914079432219560274"
        project_id:  "my-sample-project-12345"
        zone:  "us-central1-c"
      }
      type:  "gce_instance"
    }
    timestamp:  "2023-03-21T01:47:05.051902169Z"
    }
    

问题排查

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

后续步骤