Cloudflare Logpush を Axiom に Ingest する

# 目的

Cloudflare の Logpush が無料プランで使えるようになるとの発表を受け、無料のログ SaaS と連携する方法を探しました。

> https://blog.cloudflare.com/announcing-logs-engine/
> Finally, Logpush was previously limited to Enterprise plans. Soon, we will start offering these services to customers at any scale, regardless of plan type or how they choose to pay.

Workers Launchpad プログラムに参加している「[Axiom](https://www.axiom.co/)」という会社がログ SaaS として無料プランを提供しており、良さそうでしたので連携してみます。

> https://blog.cloudflare.com/ja-jp/launchpad-fall-22-ja-jp/
> ![img](https://blog.cloudflare.com/content/images/2022/11/image3-26.png)

# Axiom とは

- [How is Axiom different than other logging solutions?](https://www.axiom.co/docs/get-help/faq#how-is-axiom-different-than-other-logging-solutions?)

https://www.axiom.co/ - Observability を提供する会社でログの取得先も幅広く対応しているようです。

クエリ対応を充実しており、ログを取得して分析するまでを1つのプラットフォームで完結できる。

https://www.axiom.co/play のプレイグラウンドから試すことでわかりますが、動作が速い。

https://www.axiom.co/pricing にプランがありますが、ログに関する柔軟なプライシングの考え方も魅力です。

> All plans include bandwidth, api access, and unlimited data sources - subject to our [Fair Use Policy.](https://www.axiom.co/terms)

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

実際に Hobby ライセンスで無料アカウントを作成してみたところ、以下の機能範囲が有効化されていましたので、無料の範囲でも十分使えそうです。

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

# Axiom Ingest API

- [Ingest API - Axiom Documentation](https://www.axiom.co/docs/send-data/ingest#ingest-api)

上記の Ingest API により、`json`, `ndjson`, `csv` でログを投げて取り込むことができます。

（`ndjson` に対応しているあたり、Logpush に関してはコード量が削減できて都合が良い）

# Cloudflare Logpush --> Workers --> Axiom

Cloudflare の Logpush には、任意の HTTP エンドポイントにログ出力を行うことができます。

- [Cloudflare Logpush を Slack にポストする - Qiita](https://qiita.com/khayama/items/2b1913ef8bac51e6d5eb)

上記の記事を参考に、Cloudflare Logpush --> Cloudflare Workers --> Axiom という構成を実装します。

# Axiom API トークン作成

以下の画面で `AXIOM_API_TOKEN` を作成します。

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

# Axiom データセット作成

以下の画面で `AXIOM_DATASET_NAME` を作成します。

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

# Workers コード

GitHub にコードをアップロードしておきました。

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

コード作成の際には、gzip 解凍時に DecompressionStream が使えるようになったので

> https://blog.cloudflare.com/standards-compliant-workers-api/
> CompressionStream and DecompressionStream
> Streaming compression and decompression is also now supported in the runtime using the standard CompressionStream and DecompressionStream APIs.

下記の内容を参考にしました。

- [compression/explainer.md at main · WICG/compression](https://github.com/WICG/compression/blob/main/explainer.md)

```javascript:src/index.js
export default {
	async fetch(request, env, ctx) {
		// Check pre-shared key header
		const PRESHARED_AUTH_HEADER_KEY = 'X-Logpush-Auth';
		const PRESHARED_AUTH_HEADER_VALUE = 'mypresharedkey';
		const psk = request.headers.get(PRESHARED_AUTH_HEADER_KEY);
		if (psk !== PRESHARED_AUTH_HEADER_VALUE) {
			return new Response('Sorry, you have supplied an invalid key.', {
				status: 403,
			});
		}

		// Initial pre-flight Logpush Request to confirm the integration check
		const buf = await request.arrayBuffer();
		const compressed = new Uint8Array(buf);
		const enc = new TextDecoder("utf-8");
		if (enc.decode(compressed).trim() === '{"content":"test","filename":"test.txt"}') {
			const json = '{"content":"test","filename":"test.txt"}';
			return await fetch(`https://cloud.axiom.co/api/v1/datasets/${env.AXIOM_DATASET_NAME}/ingest`, {
				method: "POST",
				body: json,
				headers: {
					"Content-Type": "application/x-ndjson",
					Authorization: `Bearer ${env.AXIOM_API_TOKEN}`
				}
			});
		}

		// Decompress gzipped logpush body to ndjson
		const blob = new Blob([buf])
		const ds = new DecompressionStream('gzip');
		const decompressedStream = blob.stream().pipeThrough(ds);
		const buffer = await new Response(decompressedStream).arrayBuffer();
		const decompressed = new Uint8Array(buffer)
		const ndjson = enc.decode(decompressed)

		// Ingest to Axiom
		return await fetch(`https://cloud.axiom.co/api/v1/datasets/${env.AXIOM_DATASET_NAME}/ingest`, {
			method: "POST",
			body: ndjson,
			headers: {
				"Content-Type": "application/x-ndjson",
				Authorization: `Bearer ${env.AXIOM_API_TOKEN}`
			}
		});
	},
};
```

# Workers Secrets 設定 & デプロイ

Axiom の設定情報（ `AXIOM_DATASET_NAME` 、 `AXIOM_API_TOKEN` ）を Secret に設定します。

```shell
wrangler secret put AXIOM_DATASET_NAME
wrangler secret put AXIOM_API_TOKEN
```

その後、Workers をデプロイします。

```shell
wrangler publish
```

これで Logpush を待ち受けて、Axiom に Ingest する準備ができました。

# 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": "axiom",                                   
  "logpull_options": "fields='$FIELDS'",
  "destination_conf": "https://logpush-axiom.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"
```

# 確認

Axiom にバッチリ連携できました。結構検索もしやすく、動作も速い。

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

# 感想

Cloudflare の Logpush が無料プランでも使えるようになる期待を込めて、今後活用の場面が来ることを楽しみに待ちます。

この方法を使えば、ログの連携先はどこでも大丈夫そうなので、改めて Workers の柔軟性について理解を深めた次第でした。

また、Workers ログも有償プランで Logpush できるようになったので、個人開発にも活用できるかと思います。

> [Workers visibility: announcing Logpush for Worker’s Trace Events](https://blog.cloudflare.com/logpush-for-workers/)
> [Logpush · Cloudflare Workers docs](https://developers.cloudflare.com/workers/platform/logpush/)
> This product is available on the Workers Paid plan.

Workers 何でもできるなぁー