構造化ロギング

このドキュメントでは、構造化ロギングのコンセプトと、ログエントリのペイロード フィールドに構造を追加する方法について説明します。ログ ペイロードが JSON オブジェクトとしてフォーマットされ、そのオブジェクトが jsonPayload フィールドに保存される場合、ログエントリは構造化ログと呼ばれます。これらのログについて、特定の JSON パスを検索するクエリを作成し、ログペイロード内の特定のフィールドのインデックスを作成できます。一方、ログ ペイロードが文字列としてフォーマットされ、textPayload フィールドに格納されている場合、ログエントリは非構造化になります。 テキスト フィールドを検索することはできますが、コンテンツをインデックスに登録することはできません。

構造化ログエントリを作成するには、次のいずれかを行います。

  • entries.write API メソッドを呼び出し、完全なフォーマットの LogEntry を指定します。
  • 構造化ログを書き込む Cloud Logging クライアント ライブラリを使用します。
  • gcloud logging write コマンドを使用します。
  • BindPlane サービスを使用します。
  • エージェントを使用してログを書き込みます:

    • 一部の Google Cloud サービスには、stdout または stderr に書き込まれたデータを Cloud Logging にログとして送信する統合 Logging エージェントが含まれています。この方法は、Google Kubernetes Engine、App Engine フレキシブル環境、Cloud Functions などの Google Cloud サービスに使用できます。

    • Compute Engine 仮想マシン(VM)の場合は、Ops エージェントまたは以前の 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 エージェントまたは以前の Cloud Logging エージェントを使用します。どちらのエージェントもサードパーティ アプリケーションから指標を収集でき、どちらのエージェントも構造化ロギングをサポートしています。

  • Ops エージェントは、Compute Engine インスタンスからテレメトリーを収集する推奨エージェントです。このエージェントは、ロギングと指標を単一のエージェントに統合します。また、YAML ベースの構成を提供し、高スループット ロギングを行います。

    構造化ロギングをサポートするように Ops エージェントを構成するか、構造化ログの形式をカスタマイズする方法については、Ops エージェントの構成をご覧ください。

  • 以前の 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 エージェントが他の特殊フィールドを移動した後に唯一残ったフィールドであり、かつ detect_json が有効でない場合、messagetextPayload として保存されます。これに該当しない場合、messagejsonPayload に残ります。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 ログエントリに関連付けられているトレース内のスパン 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] 形式にする必要があります。これにより、ログ エクスプローラとトレース ビューアで、ログエントリをグループ化してトレースと一緒に表示できます。autoformat_stackdriver_trace が true で、[V]ResourceTracetraceId の形式と一致する場合、LogEntry の trace フィールドの値は projects/[PROJECT-ID]/traces/[V] になります。
logging.googleapis.com/trace_sampled traceSampled このフィールドの値は true または false のいずれかにする必要があります。詳細については、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-fluentd は、Fluentd ログデータ コレクタの Cloud Logging 専用パッケージです。Logging エージェントにはデフォルトの Fluentd 構成があります。Logging エージェントは、Fluentd 入力プラグインを使用して外部ソース(ディスク上のファイルなど)からイベントログを pull するか、受信ログレコードを解析します。

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 エージェントのデフォルト構成を変更する必要があります。構造化ロギングを有効にすると、以前にリストされた構成ファイルが置き換えられますが、エージェント自体のオペレーションは変更されません。

構造化ロギングを有効にすると、構造化ログを有効にする前と異なる形式にログエントリが変換されます。ログが Logging の外部の宛先に転送されている場合は、この変更が後処理のアプリケーションに影響を及ぼす可能性があります。たとえば、ログを BigQuery に転送する場合、BigQuery は変更を行った時刻以降の当日の残りの期間について、新しいログエントリを誤ったスキーマを有するものとして拒否します。

以前の Logging エージェントをインストールして構造化ロギングを有効にする手順については、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"
  },
  ...
}

また、特定のフィールドを httpRequest フィールドに抽出するように以前の Logging エージェントを構成することもできます。例:

{
  "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"
  },
  ...
}

また、特定のフィールドを httpRequest フィールドに抽出するように以前の Logging エージェントを構成することもできます。例:

{
  "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 エージェントのインストールや操作に関する一般的な問題のトラブルシューティングについては、エージェントのトラブルシューティングをご覧ください。

次のステップ