Grafana Loki に Cloudflare Logpush する

# Grafana Loki とは

- [Grafana Lokiで始めるログ管理 - Speaker Deck](https://speakerdeck.com/nutslove/grafana-lokideshi-meruroguguan-li?slide=29)

<script defer class="speakerdeck-embed" data-id="a95c056b7d43486da5d6b4ff770cef1a" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script>

# Grafana Loki に HTTP POST するには

- [HTTP API - Push log entries to Loki | Grafana Loki documentation](https://grafana.com/docs/loki/latest/api/#push-log-entries-to-loki)

`/loki/api/v1/push` に `Content-Type: application/json` で JSON ボディをポストできます。

```json:sample.json
{
  "streams": [
    {
      "stream": {
        "label": "value"
      },
      "values": [
          [ "<unix epoch in nanoseconds>", "<log line>" ],
          [ "1677514484485207000", "{"BotScore":99,"BotScoreSrc":"Machine Learning","CacheCacheStatus":"miss","CacheResponseBytes":2478}" ]
      ]
    }
  ]
}
```

- [HTTP API - Timestamp formats | Grafana Loki documentation](https://grafana.com/docs/loki/latest/api/#timestamp-formats)

タイムスタンプは、Cloudflare Logpush でも利用できる以下の形式にしましょう。

```text
1628819445609000000 (unixnano)
```

# Grafana Cloud で Loki を使う

「https://grafana.com/products/cloud/ 」から無料アカウントを作成します。

![image-20230216172040540](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20230216172040540.png)

「Forever Free」の枠があるので無料アカウントでも、それなりに使えます。
Loki には 14日間保持で 50GB logs を保存できます。

![image-20230216172103405](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20230216172103405.png)

すると Loki データソースが自動で構成されるので、URL と User の情報を使います。

![image-20230228002256939](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20230228002256939.png)

最後に「https://grafana.com/profile/org/api-keys?src=hg_home 」から API KEY を発行します。

![image-20230228010811925](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20230228010811925.png)

得られた情報を使ってテストデータを POST します。

```shell
export LOKI_HOST='YOUR_LOKI_HOST'
export LOKI_USER='YOUR_LOKI_USER'
export LOKI_API_KEY='YOUR_LOKI_API_KEY'
```

```shell
% curl -v -H "Content-Type: application/json" -XPOST -s \
"https://$LOKI_USER:$LOKI_API_KEY@$LOKI_HOST/loki/api/v1/push" --data-raw \
  '{"streams": [{ "stream": { "foo": "bar2" }, "values": [ [ "1677514484485207000", "fizzbuzz" ] ] }]}'
*   Trying 34.110.200.173:443...
* Connected to logs-prod-011.grafana.net (34.110.200.173) port 443 (#0)
...
> POST /loki/api/v1/push HTTP/2
...
> user-agent: curl/7.88.1-DEV
> accept: */*
> content-type: application/json
> content-length: 99
> 
* We are completely uploaded and fine
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/2 204 
< date: Mon, 27 Feb 2023 16:18:21 GMT
< via: 1.1 google
< alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
< 
* Connection #0 to host xxx.grafana.net left intact
```

データソースを Explore すると、ラベルフィルタ指定でテスト POST データが以下のように確認できます。

![image-20230228012206568](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20230228012206568.png)

# Workers デプロイ

- [kyouheicf/logpush-grafana-loki](https://github.com/kyouheicf/logpush-grafana-loki)

まず、こちらのコードをクローンします。

```shell
git clone https://github.com/kyouheicf/logpush-grafana-loki.git && cd $(basename $_ .git)
```

先ほど取得した各パラメータを Secret にセットします。

（また、`src/index.js` スクリプト内の `PRESHARED_AUTH_HEADER_VALUE` も必要に応じて変更してください。）

```shell
wrangler secret put LOKI_USER
wrangler secret put LOKI_API_KEY
wrangler secret put LOKI_HOST
```

先にローカルでテストしたい場合は、以下のようにすると確認できます。

```shell:.dev.vars
LOKI_HOST='logs-prod-xxxxxx.grafana.net'
LOKI_USER='XXXXXX'
LOKI_API_KEY='eyJxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=='
```

```shell
wrangler dev

curl "http://localhost:8787/" -X POST \
--data-raw '{"content":"test","filename":"test.txt"}' \
-H "Content-Type: application/json" \
-H "X-Logpush-Auth: mypresharedkey"

echo '[{"BotScore":99,"BotScoreSrc":"Machine Learning","CacheCacheStatus":"miss","CacheResponseBytes":2478}, {"BotScore":1,"BotScoreSrc":"Heuristics","CacheCacheStatus":"dynamic","CacheResponseBytes":8346}]' | jq -c '.[]'| perl -p -e 'chomp if eof'| gzip > body.gz
curl "http://localhost:8787/" -X POST \
--data-binary @body.gz \
-H "Content-Type: application/json" \
-H "Content-Encoding: gzip" \ 
-H "X-Logpush-Auth: mypresharedkey"
```

最後に `wrangler publish src/index.js` で Workers をデプロイします。

```shell
% wrangler publish src/index.js 
 ⛅️ wrangler 2.11.0 (update available 2.11.1)
-------------------------------------------------------
Total Upload: 2.20 KiB / gzip: 0.88 KiB
Uploaded logpush-grafana-loki (1.31 sec)
Published logpush-grafana-loki (4.14 sec)
  https://logpush-grafana-loki.example.workers.dev
Current Deployment ID: xxx-xxx-xxx-xxx-xxx
```

これで Logpush を待ち受けて、Grafana Loki (on Grafana Cloud) に Logpush する準備ができました。

# Cloudflare Logpush 構成

以下のコマンドで、Workers をターゲットとした Logpush ジョブを構成します。

```shell
export EMAIL='YOUR_EMAIL'
export APIKEY='YOUR_APIKEY'
export ZONE_ID='YOUR_ZONE_ID'

# 全フィールドを指定
export FIELDS=$(curl -s \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $APIKEY" \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/logpush/datasets/http_requests/fields" | jq -r '.result | keys | join(",")')
# echo $FIELDS

# example.workers.dev と example.com 等は適宜変更
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/logpush/jobs" -X POST -d '
{
  "name": "grafana-loki",                                   
  "logpull_options": "fields='$FIELDS'",
  "destination_conf": "https://logpush-grafana-loki.example.workers.dev?header_X-Logpush-Auth=mypresharedkey&tags=host:example.com,dataset:http_requests",
  "max_upload_bytes": 5000000,
  "max_upload_records": 1000,
  "dataset": "http_requests",
  "enabled": true
}' \
-H "X-Auth-Email: $EMAIL" \
-H "X-Auth-Key: $APIKEY"
```

# Grafana Loki データソース確認

以下のクエリで Logpush されていることが確認できます。

また、各フィールドも JSON がパースされ、認識されていることがわかります。

```shell
{job="cloudflare-logpush-http-requests"} | json | __error__ = "" 
```

ただし `json` のみ記述だと、 `BotDetectionIDs` や `FirewallMatchesActions` などの Array はスキップされるので、Array フィールドを活用したい場合は、 `BotDetectionIDs[0]` として個別に取得する必要があります。

>参考：Log queries | Grafana Loki documentation
>https://grafana.com/docs/loki/latest/logql/log_queries/#json
>Note: Arrays are skipped.

![image-20230228121415744](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20230228121415744.png)

# Grafana ダッシュボードで可視化

- [Grafana Loki Dashboard for NGINX Service Mesh | Grafana Labs](https://grafana.com/grafana/dashboards/12559-grafana-loki-dashboard-for-nginx-service-mesh/)

上記のダッシュボードを参考に少しフィールドを微調整すれば、以下のように可視化できます。

- https://github.com/kyouheicf/logpush-grafana-loki/blob/main/grafana-loki-dash-cloudflare-logpush.json

![image-20230228132130924](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20230228132130924.png)

# 感想

Grafana Cloud を活用して、無料でこれだけのことができるとテンション爆上がりですね。

Cloudflare の Logpush が無料プランでも使えるようになる日を祈って楽しみに待ちましょう。

# 参考

- [Cloudflare Logpush - Personal Website | David Tofan](https://davidtofan.com/articles/cloudflare-logpush/)
- [【JavaScript】Fetch API で Basic 認証 | 高木のブログ](https://takagi.blog/fetch-api-with-basic-authentication-in-javascript/)
- [Cloudflare Logpush を Axiom に Ingest する - Qiita](https://qiita.com/khayama/items/fbb4bc8e0f3d15e1ccc8)
- [Go by Example: Epoch](https://gobyexample.com/epoch)