このブログにSentryを導入した。そういえば忘れていた
公式SDKの @sentry/astro をベースに、Cloudflare Worker環境に合わせた設定をメモしておく
環境
- astro: ^5.13.5
- @sentry/astro: ^10.36.0
- @sentry/cloudflare: ^10.36.0
クライアントとエッジ(api)の両方をカバーするように設定している
インストール
npm install @sentry/astro @sentry/cloudflare設定
今回は astro.config.ts でのインテグレーション追加に加えて、クライアント側とサーバー(Worker)側で個別に初期化ファイルを用意する構成にした。
astro.config.ts
import sentry from "@sentry/astro";
export default defineConfig({
// ...
integrations: [
// ...
sentry({
sourcemaps: {
filesToDeleteAfterUpload: ["./dist/**/*.map"],
},
}),
],
});クライアントサイド (sentry.client.config.ts)
クライアント側の初期化設定。astro.config.ts に書くこともできるが、deplicagted な warning が出たので sentry のコンフィグ側に書いた
>[@sentry/astro] You passed in additional options (project, org, authToken, telemetry) to the Sentry integration.
>This is deprecated and will stop working in a future version. Instead, configure the Sentry SDK in your `sentry.client.config.(js|ts)` or `sentry.server.config.(js|ts)` files.
import * as Sentry from "@sentry/astro";
Sentry.init({
dsn: import.meta.env.PUBLIC_SENTRY_DSN,
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
]
});
// 匿名IDでのユーザー紐付け
if (typeof window !== "undefined") {
// localStorageにUUIDを保存して setUser する
}サーバーサイド (functions/_middleware.js)
Cloudflare Worker上で動かしているので、ミドルウェアで @sentry/cloudflare のプラグインを通す。これでWorker側のエラーも補足できるようになる
import * as Sentry from "@sentry/cloudflare";
export const onRequest = [
Sentry.sentryPagesPlugin((context) => ({
dsn: context.env.PUBLIC_SENTRY_DSN,
tracesSampleRate: 1.0,
})),
async (context) => {
const clientId = context.request.headers.get('cf-connecting-ip') || 'anonymous';
if (context.data?.sentry) {
context.data.sentry.setUser({ id: clientId, ip_address: clientId });
}
return context.next();
},
];環境変数の設定・取得
Sentryを動かすために必要な環境変数は以下。これらを .env やデプロイ環境のシークレットに設定しておく。
| 変数名 | 用途 | 参照タイミング |
|---|---|---|
PUBLIC_SENTRY_DSN | エラーの送信先URL | ビルド時・ランタイム(Client/Worker) |
SENTRY_AUTH_TOKEN | ソースマップのアップロード用トークン | ビルド時 |
SENTRY_ORG | Sentryの組織スラッグ | ビルド時 |
SENTRY_PROJECT | Sentryのプロジェクト名 | ビルド時 |
各々取得方法は以下。sentryの画面から適宜作成・取得する
- SENTRY_ORG / SENTRY_PROJECT
- SENTRY_ORG: SentryのURL(例: https://sentry.io/organizations/my-org/)の my-org の部分
- SENTRY_PROJECT: プロジェクト名(スラッグ)
- PUBLIC_SENTRY_DSN (Client Key) Sentryにログインし、対象の Project を選択 左メニューの [Settings] をクリック。Settingsメニュー下部にある[SDK Setup] セクションの [Client Keys (DSN)] を選択。DSNの項目、https://… で始まるURLをコピーする
- SENTRY_AUTH_TOKEN (Auth Token)
Sentryの組織設定、またはユーザー設定
[Settings] > [Organization] > [Developer Settings] > [Internal Integrations] の下部にある[New Token]から、新しいトークンを作成する
こちらはソースマップのアップロードに用いる。必要な権限は
Release:read&write Project:read&write Organization: read
デプロイ (GitHub Actions)
GitHub Actionsでビルド・デプロイする場合、ビルドステップでこれらの変数を渡す必要がある
- name: Build Astro project
run: npm run build
env:
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
PUBLIC_SENTRY_DSN: ${{ secrets.PUBLIC_SENTRY_DSN }}また、Cloudflare Worker側でも dsn を参照するため、wrangler-action の secrets や env にも PUBLIC_SENTRY_DSN を含めておくとよい
ポイント
導入時の工夫
とりあえず導入するだけだとノイズも多いので、beforeSend で不要なエラーを間引くようにできる
...
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
...
// 特定のエラーを通知しない
beforeSend(event, hint) {
const error = hint.originalException;
if (
error instanceof Error &&
error.message.includes("Invalid origin")
) {
return null;
}
return event;
},
...また、Cloudflare Worker の Middleware を使うことで、Astro インテグレーションから漏れがちなエッジ側のエラーも拾えるようにしたのが今回のこだわりポイント。
IP送信の制限
IPアドレスは立派な個人情報(PII)にあたる。これを収集してしまうとプライバシーポリシーの記述が複雑になったりする恐れがあるし、利用しないデータなので取得は避けた方が無難なため無効にしておく。
Sentryの設定画面(Settings -> Organization -> Security & Privacy)から Prevent Storing of IP Addresses をオンにすることで、IPアドレスを保持せずに運用できる。
IPを保存しない代わりに、Sentry.setUser で発行した匿名ID(Trace ID / Anonymous ID)を紐付けることで、特定のユーザーでエラーが頻発しているか、といった情報も追えるようにしている
コードはこんな感じ
// localStorageを使った匿名IDでのユーザー特定
if (typeof window !== "undefined") {
const getOrCreateAnonymousId = () => {
const STORAGE_KEY = "sentry_user_id";
let id = localStorage.getItem(STORAGE_KEY);
if (!id && typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
id = crypto.randomUUID();
localStorage.setItem(STORAGE_KEY, id);
}
return id;
};
const anonId = getOrCreateAnonymousId();
if (anonId) {
Sentry.setUser({ id: anonId });
}
}ソースマップの管理
astro.config.tsセクションの通り、ビルド後に .map ファイルが残らないよう、filesToDeleteAfterUpload で自動削除されるように設定した。
感想
SDKやドキュメントが充実していたおかげで思っていたよりはスムーズに導入できた