结构化日志记录

在 Cloud Logging 中,结构化日志是指使用 jsonPayload 字段为其载荷添加结构的日志条目。

如果您使用 Cloud Logging API 或 gcloud 命令行工具,则可以控制载荷的结构。如需查看示例,请参阅写入结构化日志

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

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

在 JSON 载荷中使用特殊字段来通过 Logging 代理构建日志结构

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

  • 将包含 jsonPayload 的完整 LogEntry 结构发送到 Cloud Logging API
  • 向 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 中。如果您的日志条目包含异常堆栈轨迹,则应在此 message JSON 日志字段中设置异常堆栈轨迹,以便系统可以解析该异常堆栈跟踪并将其保存到 Error Reporting 之中。
log(仅限旧版 Google Kubernetes Engine) textPayload 仅适用于旧版 Google Kubernetes Engine:如果在删除特殊用途字段后,只留下一个日志字段,则该日志将另存为 textPayload
httpRequest httpRequest 采用 LogEntry HttpRequest 字段格式的结构化记录。 "httpRequest":{"requestMethod":"GET"}
与时间相关的字段 timestamp 如需了解详情,请参阅与时间相关的字段 "timestamp":"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 代理时更改其默认配置。启用结构化日志记录会替换之前列出的配置文件。 代理本身的运行不会发生变化。

启用结构化日志记录后,列出的日志将转换为与启用结构化日志之前的格式不同的日志条目。如果正在将日志从 Cloud 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 字段有助于跟踪:Cloud Console 在父子层次结构中显示给定 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 字段有助于跟踪:Cloud Console 在父子层次结构中显示给定 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>

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

自行编写解析器

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

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

  • 日志记录中的日志行:

    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"
     }
    

查看结构化日志

如需使用 Cloud Console 日志浏览器搜索日志和查看日志条目,请参阅使用日志浏览器

要使用 gcloud 命令行工具或 API 读取日志条目,请参阅读取日志条目

问题排查

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