📝

Media CDN の 2 つのリクエストログ

2024/12/10に公開

この記事は Google Cloud Japan Advent Calendar 2024 7 日目の記事です。

TL;DR

Cloud Logging に出力される Media CDN のアクセスログは クライアントから Media CDN(EdgeCacheService)へのリクエストログMedia CDN(EdgeCacheService)からオリジンへのリクエストログ の両方が含まれている。

クライアントから Media CDN へのリクエストログMedia CDN からオリジンへのリクエストログ の判別には、例えば以下ようなフィールドを利用できる。

jsonPayload.cacheMode
Media CDN からオリジンへのリクエストログ にはこのフィールドが存在しないことを利用して判別可能。

jsonPayload.requestUrl
FQDN が Media CDN のものかオリジンのものかで判別可能。

リクエスト先 フィールドの値の例
Media CDN "requestUrl": "http://media-cdn-4.naraoka.demo.altostrat.com/10kib"
オリジン "requestUrl": "https://argolis-naraoka-origin-public.storage.googleapis.com/10kib"

jsonPayload.proxyStatus
"proxyStatus": "Google-Edge-Cache" もしくは Google-Edge-Cache で終わるもの。
(例:"proxyStatus": "Google-Edge-Cache-Origin; next-hop=argolis-naraoka-origin-public.storage.googleapis.com; next-protocol=h2; received-status=200, Google-Edge-Cache")
クライアントから Media CDN へのリクエストログ となる。

きっかけ

https://cloud.google.com/media-cdn/docs/logging

Media CDN logs each HTTP request to Cloud Logging. These requests include client requests to the EdgeCacheService resource and requests from the EdgeCacheOrigin resource to the configured origin for cache fills.

ドキュメントによると Cloud Logging に出力される Media CDN のアクセスログは クライアントから EdgeCacheService(Media CDN)へのリクエストEdgeCacheService(Media CDN)からオリジンへのリクエスト の両方が含まれているとのこと。

ということは、例えば「アクセスログを BigQuery に Sink して分析する」ようなことを考えた時、クライアントからのリクエストのみを対象とするのが自然なので、何かしらの方法でこれら 2 つを判別する必要が出てきます。

クライアントからのリクエストのみを判別/取り出すために、まずは Media CDN に対して「どのようなアクセス」によって「どのようなログが出力されるのか」を確認してみます。

キャッシュされていないオブジェクトに対するリクエストログ

リクエストとそのレスポンスヘッダ

キャッシュされていないオブジェクト(まだリクエストされていないもの)に対して、以下のように curl コマンドを用いてリクエストしてみました。

% curl -s -w '%{stderr}%{header_json}' http://media-cdn-4.naraoka.demo.altostrat.com/20mib 1>/dev/null
{"date":["Wed, 27 Nov 2024 15:04:28 GMT"],
"last-modified":["Wed, 27 Nov 2024 12:47:44 GMT"],
"etag":["\"f33e0a188d9f07d97bdb483d398f127b\""],
"x-goog-generation":["1732711664124815"],
"x-goog-metageneration":["1"],
"x-goog-stored-content-encoding":["identity"],
"x-goog-stored-content-length":["20971520"],
"content-type":["application/octet-stream"],
"x-goog-hash":["crc32c=96JIqA==","md5=8z4KGI2fB9l720g9OY8Sew=="],
"x-goog-storage-class":["STANDARD"],
"accept-ranges":["bytes"],
"content-length":["20971520"],
"x-guploader-uploadid":["AFiumC7jcrwblOHtTVdKq9Srtnv4wEkwLzCmt9yMrk9wlvvTD18BPhj1oqf_AQzkv5K_4C5Tah8zDWYvgw"],
"server":["Google-Edge-Cache"],
"x-request-id":["1fbe26d7-62f7-4099-abbf-5fb7ed95a322"],
"x-xss-protection":["0"],
"x-frame-options":["SAMEORIGIN"],
"x-cdn":["GCP nrt;miss, nrt;uncacheable"],
"alt-svc":["h3=\":443\"; ma=2592000, h3-29=\":443\"; ma=2592000"],
"cache-control":["public,max-age=60"]
}

Cloud Logging 上のログ

レスポンスヘッダに含まれる x-request-id を利用することで、リクエストに関連するログをトレースすることができます。
Cloud Logging からは以下のようにクエリします。

resource.type="edgecache.googleapis.com/EdgeCacheRouteRule"
jsonPayload.requestId="1fbe26d7-62f7-4099-abbf-5fb7ed95a322"

結果から

  • Media CDN からオリジン への HEAD リクエスト
  • Media CDN からオリジン への GET リクエスト
  • クライアントから Media CDN への GET リクエスト

の 3 つのログが記録されており、ドキュメントに記載のとおり、クライアントから Media CDN へのリクエストログMedia CDN からオリジンへのリクエストログ の両方が記録されていることが確認できました。

それぞれのログは以下のようになっていました。

Media CDN からオリジンへの HEAD リクエストログ
{
  "insertId": "11121692654816502897@a1",
  "jsonPayload": {
    "requestId": "1fbe26d7-62f7-4099-abbf-5fb7ed95a322",
    "proxyStatus": "Google-Edge-Cache-Origin; next-hop=argolis-naraoka-origin-public.storage.googleapis.com; next-protocol=h2; received-status=200",
    "@type": "type.googleapis.com/google.cloud.edgecache.v1.EdgeCacheLogEntry",
    "backendInitialLatency": "0.103685617s",
    "backendLatency": "0.103903027s",
    "originIp": "142.250.199.123"
  },
  "httpRequest": {
    "requestMethod": "HEAD",
    "requestUrl": "https://argolis-naraoka-origin-public.storage.googleapis.com/20mib",
    "requestSize": "1303",
    "status": 200,
    "responseSize": "739",
    "protocol": "HTTP/2"
  },
  "resource": {
    "type": "edgecache.googleapis.com/EdgeCacheRouteRule",
    "labels": {
      "location": "global",
      "route_destination": "argolis-naraoka-origin-public.storage.googleapis.com",
      "path_matcher_name": "path-matcher-0",
      "resource_container": "projects/555151815269",
      "route_type": "ORIGIN",
      "matched_path": "/",
      "service_name": "edge-cache-service-4"
    }
  },
  "timestamp": "2024-11-27T15:04:28.693165946Z",
  "logName": "projects/argolis-naraoka/logs/edgecache.googleapis.com%2Fedge_cache_request",
  "receiveTimestamp": "2024-11-27T15:04:29.222416914Z"
}
Media CDN からオリジンへの GET リクエスト
{
  "insertId": "15797176116043924213@a1",
  "jsonPayload": {
    "@type": "type.googleapis.com/google.cloud.edgecache.v1.EdgeCacheLogEntry",
    "originIp": "142.251.42.219",
    "requestId": "1fbe26d7-62f7-4099-abbf-5fb7ed95a322",
    "backendLatency": "0.326347731s",
    "backendInitialLatency": "0.142634215s",
    "proxyStatus": "Google-Edge-Cache-Origin; next-hop=argolis-naraoka-origin-public.storage.googleapis.com; next-protocol=h2; received-status=200"
  },
  "httpRequest": {
    "requestMethod": "GET",
    "requestUrl": "https://argolis-naraoka-origin-public.storage.googleapis.com/20mib",
    "requestSize": "1302",
    "status": 200,
    "responseSize": "20972259",
    "protocol": "HTTP/2"
  },
  "resource": {
    "type": "edgecache.googleapis.com/EdgeCacheRouteRule",
    "labels": {
      "location": "global",
      "matched_path": "/",
      "service_name": "edge-cache-service-4",
      "route_type": "ORIGIN",
      "route_destination": "argolis-naraoka-origin-public.storage.googleapis.com",
      "resource_container": "projects/555151815269",
      "path_matcher_name": "path-matcher-0"
    }
  },
  "timestamp": "2024-11-27T15:04:29.028765726Z",
  "logName": "projects/argolis-naraoka/logs/edgecache.googleapis.com%2Fedge_cache_request",
  "receiveTimestamp": "2024-11-27T15:04:29.785011078Z"
}
クライアントから Media CDN への GET リクエスト
{
  "insertId": "6ee4f56f-0000-2344-b4a3-3c286d502bba@a1",
  "jsonPayload": {
    "metroIataCode": "NRT",
    "proxyRegionCode": "JP",
    "proxyStatus": "Google-Edge-Cache-Origin; next-hop=argolis-naraoka-origin-public.storage.googleapis.com; next-protocol=h2; received-status=200, Google-Edge-Cache",
    "cacheId": "nrt, nrt",
    "origin": "gcs-origin-public",
    "enforcedSecurityPolicy": {
      "configuredAction": "ACCEPT",
      "name": "no_policy",
      "priority": 2147483647,
      "outcome": "ACCEPT"
    },
    "@type": "type.googleapis.com/google.cloud.edgecache.v1.EdgeCacheLogEntry",
    "originIp": "142.251.222.27",
    "cacheStatus": "miss,uncacheable",
    "clientRegionCode": "JP",
    "cacheMode": "FORCE_CACHE_ALL",
    "httpTtfb": "0.264827318s",
    "clientCity": "Narashino",
    "clientAsn": "2516",
    "requestId": "1fbe26d7-62f7-4099-abbf-5fb7ed95a322",
    "cacheKeyFingerprint": "85c47f33735acd78",
    "tlsVersion": "NONE",
    "latency": "0.832139515s"
  },
  "httpRequest": {
    "requestMethod": "GET",
    "requestUrl": "http://media-cdn-4.naraoka.demo.altostrat.com/20mib",
    "requestSize": "2779",
    "status": 200,
    "responseSize": "20972274",
    "userAgent": "curl/8.7.1",
    "remoteIp": "113.149.199.161",
    "protocol": "HTTP/1.1"
  },
  "resource": {
    "type": "edgecache.googleapis.com/EdgeCacheRouteRule",
    "labels": {
      "resource_container": "projects/555151815269",
      "route_destination": "projects/555151815269/locations/global/edgeCacheOrigins/gcs-origin-public",
      "path_matcher_name": "path-matcher-0",
      "matched_path": "/",
      "route_type": "ORIGIN",
      "location": "asia-southeast1",
      "service_name": "edge-cache-service-4"
    }
  },
  "timestamp": "2024-11-27T15:04:29.416472515Z",
  "logName": "projects/argolis-naraoka/logs/edgecache.googleapis.com%2Fedge_cache_request",
  "trace": "projects/555151815269/traces/1fbe26d7-62f7-4099-abbf-5fb7ed95a322",
  "receiveTimestamp": "2024-11-27T15:04:34.966748234Z"
}

クライアントから Media CDN へのリクエストログMedia CDN からオリジンへのリクエストログ を判別するという観点でこれらのログを見てみると、

  • httpRequest.requestUrl
  • jsonPayload.cacheMode
  • jsonPayload.proxyStatus

あたりに違いが見て取れます。

jsonPayload.requestUrl は FQDN が Media CDN のものかオリジンのものかで判別ができそうです。

jsonPayload.cacheModeクライアントから Media CDN へのリクエストログ にのみ存在するので、フィールドの存在有無をチェックをすることで判別できそうです。

jsonPayload.proxyStatus については、以下のドキュメントと RFC から
"proxyStatus": "Google-Edge-Cache" もしくは Google-Edge-Cache で終わるもの
クライアントから Media CDN へのリクエストログ と言えますので、これをチェックすることで判別できそうです。

https://cloud.google.com/media-cdn/docs/custom-headers#header-variables

A list of intermediary HTTP proxies in the response path. The value is defined by RFC 9209. An EdgeCacheService resource is represented by Google-Edge-Cache. If the response was fetched from the origin, an EdgeCacheOrigin resource is represented by Google-Edge-Cache-Origin.

(意訳)proxyStatus には RFC 9209 で定義されたレスポンスパスが格納されており、Google-Edge-Cache は EdgeCacheService(注:わかりやすく表現すると Media CDN)からのレスポンスであることを表している。

https://www.rfc-editor.org/rfc/rfc9209#name-the-proxy-status-http-field

Each member of the List represents an intermediary that has handled the response. The first member represents the intermediary closest to the origin server, and the last member represents the intermediary closest to the user agent.

(意訳)一番最後の値はユーザーから一番近いものを表している。

キャッシュされているオブジェクト

次にキャッシュされているオブジェクトに対するリクエストのログについても、同じように確認してみます。

リクエストとそのレスポンスヘッダ

% curl -s -w '%{stderr}%{header_json}' http://media-cdn-4.naraoka.demo.altostrat.com/20mib 1>/dev/null
{"date":["Wed, 27 Nov 2024 16:44:33 GMT"],
"last-modified":["Wed, 27 Nov 2024 12:47:44 GMT"],
"etag":["\"f33e0a188d9f07d97bdb483d398f127b\""],
"x-goog-generation":["1732711664124815"],
"x-goog-metageneration":["1"],
"x-goog-stored-content-encoding":["identity"],
"x-goog-stored-content-length":["20971520"],
"content-type":["application/octet-stream"],
"x-goog-hash":["md5=8z4KGI2fB9l720g9OY8Sew=="],
"x-goog-storage-class":["STANDARD"],
"accept-ranges":["bytes"],
"content-length":["20971520"],
"x-guploader-uploadid":["AFiumC42sJOnuTZ2N8wj95Ex_o0P0JQhHDRRpq3Y7ij93fcSb5ykBBp_9u4iFxREmtu6ml_C"],
"server":["Google-Edge-Cache"],
"x-request-id":["152167a4-d884-4be9-9733-fcf70b586c3c"],
"x-xss-protection":["0"],
"x-frame-options":["SAMEORIGIN"],
"age":["45"],
"x-content-type-options":["nosniff"],
"x-cdn":["GCP nrt-91464d1d; hit, nrt;uncacheable"],
"alt-svc":["h3=\":443\"; ma=2592000, h3-29=\":443\"; ma=2592000"],
"cache-control":["public,max-age=60"]
}

Cloud Logging 上のログ

Cloud Logging のクエリ

resource.type="edgecache.googleapis.com/EdgeCacheRouteRule"
jsonPayload.requestId="152167a4-d884-4be9-9733-fcf70b586c3c"

(キャッシュされているので、ある意味当然とも言えますが)クライアントから Media CDN への GET リクエストのログのみが記録されていました。

実際のログ
{
  "insertId": "734e3be3-0000-2d0d-b1f4-94eb2c181de2@a1",
  "jsonPayload": {
    "requestId": "152167a4-d884-4be9-9733-fcf70b586c3c",
    "originalRequestId": "a54515a3-a558-40e4-98e9-c6fd18a6f789",
    "cacheStatus": "hit,uncacheable",
    "enforcedSecurityPolicy": {
      "outcome": "ACCEPT",
      "configuredAction": "ACCEPT",
      "name": "no_policy",
      "priority": 2147483647
    },
    "cacheMode": "FORCE_CACHE_ALL",
    "clientCity": "Narashino",
    "httpTtfb": "0.010450876s",
    "clientAsn": "2516",
    "proxyRegionCode": "JP",
    "cacheKeyFingerprint": "85c47f33735acd78",
    "cacheId": "nrt-91464d1d, nrt",
    "origin": "gcs-origin-public",
    "originIp": "216.58.220.123",
    "latency": "0.440056761s",
    "metroIataCode": "NRT",
    "tlsVersion": "NONE",
    "clientRegionCode": "JP",
    "@type": "type.googleapis.com/google.cloud.edgecache.v1.EdgeCacheLogEntry",
    "proxyStatus": "Google-Edge-Cache"
  },
  "httpRequest": {
    "requestMethod": "GET",
    "requestUrl": "http://media-cdn-4.naraoka.demo.altostrat.com/20mib",
    "requestSize": "2390",
    "status": 200,
    "responseSize": "20972281",
    "userAgent": "curl/8.7.1",
    "remoteIp": "113.149.199.161",
    "protocol": "HTTP/1.1"
  },
  "resource": {
    "type": "edgecache.googleapis.com/EdgeCacheRouteRule",
    "labels": {
      "service_name": "edge-cache-service-4",
      "resource_container": "projects/555151815269",
      "route_type": "ORIGIN",
      "route_destination": "projects/555151815269/locations/global/edgeCacheOrigins/gcs-origin-public",
      "matched_path": "/",
      "location": "asia-southeast1",
      "path_matcher_name": "path-matcher-0"
    }
  },
  "timestamp": "2024-11-27T16:45:18.662477761Z",
  "logName": "projects/argolis-naraoka/logs/edgecache.googleapis.com%2Fedge_cache_request",
  "trace": "projects/555151815269/traces/152167a4-d884-4be9-9733-fcf70b586c3c",
  "receiveTimestamp": "2024-11-27T16:45:22.306934961Z"
}

キャッシュされているオブジェクトに対するリクエストのログについても、

  • httpRequest.requestUrl の FQDN が Media CDN のものかどうか
  • jsonPayload.cacheMode フィールドが存在するかどうか
  • jsonPayload.proxyStatusGoogle-Edge-Cache もしくは Google-Edge-Cache で終わっているかどうか

で判別することができそうです。

Cloud Logging でのフィルタ例

判別に利用できそうなフィールドがわかったので、実際に Cloud Logging で クライアントから Media CDN へのリクエストログ のみを取得するフィルタの例を考えてみたいと思います。

httpRequest.requestUrl の FQDN でチェックする方法は(環境によって異なるのと)Media CDN で使用する FQDN が変わった際にフィルタもあわせて変更しなければならないのと、jsonPayload.proxyStatus を利用してのチェックはフィルタがやや複雑になるので、ここでは jsonPayload.cacheMode フィールドの有無をチェックしてフィルタしてみたいと思います。

Cloud Logging には、フィールドの有無をチェックすることが可能な演算子 :* (field-exists operator [1] )が存在します。

これを利用すると Media CDN のログから jsonPayload.cacheMode が存在する行のみを選択するフィルタを以下のように記述することができます。

resource.type="edgecache.googleapis.com/EdgeCacheRouteRule"
jsonPayload.cacheMode:*

このフィルタを実際に試してみます。

フィルタ前、

  • クライアントから Media CDN へのリクエストログ:8 行
  • Media CDN からオリジンへのリクエストログ:10 行

である上記 Media CDN のログに対して、

resource.type="edgecache.googleapis.com/EdgeCacheRouteRule"
jsonPayload.cacheMode:*

でフィルタを行うと、

このように クライアントから Media CDN へのリクエストログ の 8 行のみを選択することができました。

おわりに

Cloud Logging に出力される Media CDN の 2 つのアクセスログ、
クライアントから Media CDN へのリクエストログMedia CDN からオリジンへのリクエストログ
を判別するために、「どのようなアクセス」によって「どのようなログが出力されるのか」を確認し、実際に Cloud Logging でフィルタする際の例を考えてみました。

やってみただけの記事になってしまいましたが、何かの参考になれば幸いです。

脚注
  1. https://cloud.google.com/logging/docs/view/logging-query-language?hl=en ↩︎

Google Cloud Japan

Discussion