@ah-monica/cloudflare 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -48
- package/dist/client.js +24 -3
- package/dist/types.d.ts +2 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
# @ah-monica/cloudflare
|
|
2
2
|
|
|
3
|
-
Cloudflare WorkersからMONICA
|
|
4
|
-
Node.js API
|
|
5
|
-
|
|
3
|
+
Cloudflare Workers から MONICA へ、捕捉した例外を送る adapter。
|
|
4
|
+
Node.js API に依存しないので `nodejs_compat` フラグは要らない。
|
|
5
|
+
|
|
6
|
+
共通の使い方・オプション・制約は [ルートの README](../README.md) にある。
|
|
7
|
+
|
|
8
|
+
## インストール
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @ah-monica/cloudflare
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## 初期化
|
|
15
|
+
|
|
16
|
+
DSN には secret key(`msk_...`)を使い、Cloudflare の secret に保存する(`vars` には置かない)。
|
|
17
|
+
staging と production では project・鍵・`environment` を分ける。
|
|
6
18
|
|
|
7
19
|
```ts
|
|
8
20
|
import { createCloudflareClient } from "@ah-monica/cloudflare";
|
|
@@ -13,26 +25,72 @@ interface Env {
|
|
|
13
25
|
MONICA_RELEASE?: string;
|
|
14
26
|
}
|
|
15
27
|
|
|
28
|
+
function createClient(env: Env) {
|
|
29
|
+
return createCloudflareClient({
|
|
30
|
+
dsn: env.MONICA_DSN,
|
|
31
|
+
environment: env.MONICA_ENVIRONMENT,
|
|
32
|
+
release: env.MONICA_RELEASE,
|
|
33
|
+
beforeSend(item) {
|
|
34
|
+
// どの値が個人情報かはアプリケーション固有。送ってよい値だけを残す。
|
|
35
|
+
delete item.user;
|
|
36
|
+
if (item.request) delete item.request.headers;
|
|
37
|
+
return item;
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`MONICA_RELEASE` には deploy する git の commit sha を入れる(例: `wrangler deploy --var MONICA_RELEASE:$(git rev-parse HEAD)`)。
|
|
44
|
+
ブラウザ側の SDK にも同じ値を渡すと、Worker とブラウザの event が同じ release に揃う。
|
|
45
|
+
|
|
46
|
+
DSN を Secrets Store に置く場合、binding の値は `get()` で非同期に読むので、client は
|
|
47
|
+
request の入口で作る([制約](#制約) のとおり request ごとに作ってよい)。`get()` は secret が
|
|
48
|
+
無いと例外を投げるので、`undefined` に落として何も送らない client にする。
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
interface Env {
|
|
52
|
+
MONICA_DSN: SecretsStoreSecret;
|
|
53
|
+
MONICA_ENVIRONMENT: string;
|
|
54
|
+
MONICA_RELEASE?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
16
57
|
export default {
|
|
17
58
|
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
59
|
+
const monica = createCloudflareClient({
|
|
60
|
+
dsn: await env.MONICA_DSN.get().catch(() => undefined),
|
|
61
|
+
environment: env.MONICA_ENVIRONMENT,
|
|
62
|
+
release: env.MONICA_RELEASE,
|
|
63
|
+
});
|
|
64
|
+
ctx.waitUntil(monica.flush());
|
|
18
65
|
try {
|
|
19
66
|
return await handleRequest(request, env);
|
|
20
67
|
} catch (error) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return item;
|
|
30
|
-
},
|
|
31
|
-
});
|
|
68
|
+
monica.captureExceptionInBackground(ctx, error);
|
|
69
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 使い方
|
|
32
76
|
|
|
77
|
+
request の入口で client を作り、`ctx.waitUntil(monica.flush())` を呼ぶ([稼働確認](#稼働確認))。
|
|
78
|
+
|
|
79
|
+
`captureException()` は capture と flush の両方を終えてから解決する Promise を返す。
|
|
80
|
+
handler の応答を待たせたくない場合は `captureExceptionInBackground(ctx, ...)` で
|
|
81
|
+
`ExecutionContext.waitUntil()` に載せる。
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
export default {
|
|
85
|
+
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
86
|
+
const monica = createClient(env);
|
|
87
|
+
ctx.waitUntil(monica.flush());
|
|
88
|
+
try {
|
|
89
|
+
return await handleRequest(request, env);
|
|
90
|
+
} catch (error) {
|
|
33
91
|
monica.captureExceptionInBackground(ctx, error, {
|
|
34
92
|
tags: { operation: "handle-request" },
|
|
35
|
-
// URL path
|
|
93
|
+
// URL の path にも個人情報が入り得るので、安全と判断した値だけを渡す。
|
|
36
94
|
request: { method: request.method, url: new URL(request.url).origin },
|
|
37
95
|
});
|
|
38
96
|
return new Response("Internal Server Error", { status: 500 });
|
|
@@ -41,53 +99,60 @@ export default {
|
|
|
41
99
|
};
|
|
42
100
|
```
|
|
43
101
|
|
|
44
|
-
`
|
|
45
|
-
ないHTTP handlerでは`captureExceptionInBackground(ctx, ...)`を使う。CronやQueueの
|
|
46
|
-
handlerで送信完了を処理結果に含めたい場合は、直接`await`してよい。
|
|
102
|
+
Cron や Queue の handler のように送信完了を処理結果に含めたい場合は、直接 `await` する。
|
|
47
103
|
|
|
48
104
|
```ts
|
|
105
|
+
const monica = createClient(env);
|
|
49
106
|
await monica.captureException(error, { tags: { trigger: "scheduled" } });
|
|
50
107
|
```
|
|
51
108
|
|
|
52
|
-
|
|
109
|
+
`captureMessage()` も同じく capture と flush を終えてから解決するので、`flush()` を
|
|
110
|
+
別途呼ぶ必要は無い。scope(`setUser` / `addBreadcrumb` / `withScope`)は持たず、
|
|
111
|
+
文脈は capture の第 2 引数で渡す。
|
|
53
112
|
|
|
54
|
-
|
|
113
|
+
Worker の外側で起きた未捕捉例外まで集めたい場合は Tail Worker も検討する。この adapter は、
|
|
114
|
+
アプリケーションが捕捉して業務上の文脈を選んで送る例外を対象にする。
|
|
55
115
|
|
|
56
|
-
|
|
57
|
-
monica: ingest rejected the envelope with 422 (invalid_envelope): 1 issue(s); $.items[0].request.method: Invalid type: Expected string
|
|
58
|
-
```
|
|
116
|
+
## 稼働確認
|
|
59
117
|
|
|
60
|
-
|
|
61
|
-
`flush()`の戻り値の`status` / `issues` / `error`からも取得できる。
|
|
118
|
+
共通の仕組みは [ルートの README](../README.md#稼働確認) にある。
|
|
62
119
|
|
|
63
|
-
`
|
|
64
|
-
`
|
|
120
|
+
- isolate で最初の `flush()` / `captureException()` / `captureMessage()` の呼び出しが
|
|
121
|
+
`trigger: "start"` を 1 通送る。client を作っただけでは送らない。`dsn` と `environment` の組ごとに数える
|
|
122
|
+
- request の入口で `ctx.waitUntil(monica.flush())` を呼ぶ。`waitUntil` が無いと handler の終了で送信が打ち切られる
|
|
123
|
+
- Workers にはタイマーが無いので `trigger: "interval"` は送らない。isolate が作り直されるたびに `start` を送る
|
|
124
|
+
- 状態は isolate のメモリに持ち、ストレージには書かない
|
|
65
125
|
|
|
66
|
-
|
|
67
|
-
monica: ingest rejected the envelope with 401 (invalid_key); no further envelopes will be sent
|
|
68
|
-
```
|
|
126
|
+
## オプション
|
|
69
127
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
128
|
+
| option | 型 | default | 説明 |
|
|
129
|
+
| --- | --- | --- | --- |
|
|
130
|
+
| `dsn` | `string \| null` | なし | `https://msk_...@<ingest-host>`。未指定・空文字なら何も送らない |
|
|
131
|
+
| `environment` | `string` | 必須 | 1〜128 文字 |
|
|
132
|
+
| `release` | `string` | なし | item の `release` に載る |
|
|
133
|
+
| `sampleRate` | `number` | `1` | 0〜1 |
|
|
134
|
+
| `requestTimeoutMs` | `number` | `2000` | 1 回の HTTP request の上限 |
|
|
135
|
+
| `flushTimeoutMs` | `number` | `2000` | `captureException` / `captureMessage` が内部で待つ flush の上限 |
|
|
136
|
+
| `maxRetries` | `number` | `0` | `429` / `5xx` / network 障害の再送回数 |
|
|
137
|
+
| `maxCauseDepth` | `number` | `10` | 辿る `cause` の段数 |
|
|
138
|
+
| `maxStackFrames` | `number` | `200` | 送る stack frame 数 |
|
|
139
|
+
| `onDiagnostic` | `(diagnostic) => void \| null` | `console.warn` に 1 行 | 拒否されたときの診断の受け取り先。`null` で無効 |
|
|
140
|
+
| `beforeSend` | `(item, hint) => item \| null \| Promise<...>` | なし | `null` を返すと破棄 |
|
|
141
|
+
| `fetch` | `typeof fetch` | `globalThis.fetch` | 送信に使う fetch |
|
|
74
142
|
|
|
75
|
-
|
|
76
|
-
monica: ingest rejected the envelope with 413 (unknown); splitting and resending. A size limit on the path may be below the 1 MiB (gzip) contract
|
|
77
|
-
```
|
|
143
|
+
## 送信結果と診断
|
|
78
144
|
|
|
79
|
-
|
|
145
|
+
拒否されたときは既定で `console.warn` に 1 行出る(Workers のログに出る。`422` / `401` / `413`)。
|
|
146
|
+
`flush()` の戻り値の `status` / `issues` / `error` / `stopped` からも取れる。
|
|
147
|
+
詳しくは [TROUBLESHOOTING.md](../TROUBLESHOOTING.md)。
|
|
80
148
|
|
|
81
|
-
|
|
82
|
-
しない。送信値の選択と`beforeSend`での除去は利用アプリケーションの責任。
|
|
83
|
-
MONICAサーバの既知credentialフィルターは防御層であり、任意のPIIが除去される保証
|
|
84
|
-
ではない。
|
|
149
|
+
## 制約
|
|
85
150
|
|
|
86
|
-
|
|
151
|
+
- `captureExceptionInBackground` を使わず、`waitUntil()` にも載せずに handler を返すと、
|
|
152
|
+
送信は途中で打ち切られる。
|
|
153
|
+
- client は request ごとに作ってよい。`401` で止まるのはその client だけなので、
|
|
154
|
+
鍵が失効しても次の request で再び 1 回 POST する。
|
|
87
155
|
|
|
88
|
-
|
|
89
|
-
stagingとproductionではproject、key、`environment`を分離し、package versionは共通で
|
|
90
|
-
よい。secretを`vars`、ソースコード、ログへ置かない。
|
|
156
|
+
## ライセンス
|
|
91
157
|
|
|
92
|
-
|
|
93
|
-
adapterは、アプリケーションが捕捉し、業務contextを選んで送る例外を対象にする。
|
|
158
|
+
[Apache-2.0](LICENSE)
|
package/dist/client.js
CHANGED
|
@@ -3,6 +3,10 @@ import { SDK_VERSION } from "./version.js";
|
|
|
3
3
|
const DEFAULT_FLUSH_TIMEOUT_MS = 2_000;
|
|
4
4
|
const DEFAULT_MAX_CAUSE_DEPTH = 10;
|
|
5
5
|
const DEFAULT_MAX_STACK_FRAMES = 200;
|
|
6
|
+
// client は request ごとに作られるので、start を送ったかは isolate に 1 つ持つ。
|
|
7
|
+
// 判定の前に印を付けるので、並行する request でも isolate ごとに 1 回になる
|
|
8
|
+
// (dsn と environment が違えば別に数える)
|
|
9
|
+
const startedInIsolate = new Set();
|
|
6
10
|
export function createCloudflareClient(options) {
|
|
7
11
|
assertPositiveInteger("flushTimeoutMs", options.flushTimeoutMs);
|
|
8
12
|
assertPositiveInteger("maxCauseDepth", options.maxCauseDepth);
|
|
@@ -10,6 +14,7 @@ export function createCloudflareClient(options) {
|
|
|
10
14
|
const flushTimeoutMs = options.flushTimeoutMs ?? DEFAULT_FLUSH_TIMEOUT_MS;
|
|
11
15
|
const maxCauseDepth = options.maxCauseDepth ?? DEFAULT_MAX_CAUSE_DEPTH;
|
|
12
16
|
const maxStackFrames = options.maxStackFrames ?? DEFAULT_MAX_STACK_FRAMES;
|
|
17
|
+
const presenceKey = `${options.dsn ?? ""}\n${options.environment}`;
|
|
13
18
|
const core = createCoreClient({
|
|
14
19
|
transport: createFetchTransport({
|
|
15
20
|
dsn: options.dsn,
|
|
@@ -24,12 +29,28 @@ export function createCloudflareClient(options) {
|
|
|
24
29
|
sampleRate: options.sampleRate,
|
|
25
30
|
beforeSend: options.beforeSend,
|
|
26
31
|
sdk: { name: "@ah-monica/cloudflare", version: SDK_VERSION },
|
|
32
|
+
presence: { platform: "javascript" },
|
|
27
33
|
});
|
|
34
|
+
/**
|
|
35
|
+
* Workers にはタイマーが無く、global scope では fetch できない。生成時ではなく最初の
|
|
36
|
+
* flush / capture の呼び出し(request の中)で start を送る。送信中の分は flush() が
|
|
37
|
+
* 待つので、それを waitUntil に載せれば handler の後まで生きる。
|
|
38
|
+
*/
|
|
39
|
+
function startOnce() {
|
|
40
|
+
if (startedInIsolate.has(presenceKey))
|
|
41
|
+
return;
|
|
42
|
+
startedInIsolate.add(presenceKey);
|
|
43
|
+
void core.checkPresence("start");
|
|
44
|
+
}
|
|
45
|
+
function flush(timeoutMs) {
|
|
46
|
+
startOnce();
|
|
47
|
+
return core.flush(timeoutMs);
|
|
48
|
+
}
|
|
28
49
|
async function captureAndFlush(input, hint) {
|
|
29
50
|
try {
|
|
51
|
+
startOnce();
|
|
30
52
|
const eventId = await core.capture(input, hint);
|
|
31
|
-
|
|
32
|
-
return null;
|
|
53
|
+
// 捨てた event でも、送信中の start は待つ
|
|
33
54
|
await core.flush(flushTimeoutMs);
|
|
34
55
|
return eventId;
|
|
35
56
|
}
|
|
@@ -75,7 +96,7 @@ export function createCloudflareClient(options) {
|
|
|
75
96
|
captureException,
|
|
76
97
|
captureMessage,
|
|
77
98
|
captureExceptionInBackground,
|
|
78
|
-
flush
|
|
99
|
+
flush,
|
|
79
100
|
close: core.close,
|
|
80
101
|
};
|
|
81
102
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BeforeSend, FetchLike, FlushResult, MonicaBreadcrumb, MonicaLevel, MonicaRequest, MonicaUser, TransportDiagnosticHandler } from "@ah-monica/core";
|
|
2
2
|
export interface CloudflareClientOptions {
|
|
3
|
-
|
|
3
|
+
/** 未指定・空文字・空白だけなら何も送らない */
|
|
4
|
+
dsn?: string | null;
|
|
4
5
|
environment: string;
|
|
5
6
|
release?: string;
|
|
6
7
|
sampleRate?: number;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** package.json の version と同じ値。bun run version X.Y.Z が書き換える */
|
|
2
|
-
export declare const SDK_VERSION = "0.
|
|
2
|
+
export declare const SDK_VERSION = "0.4.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** package.json の version と同じ値。bun run version X.Y.Z が書き換える */
|
|
2
|
-
export const SDK_VERSION = "0.
|
|
2
|
+
export const SDK_VERSION = "0.4.0";
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ah-monica/cloudflare",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Cloudflare Workers adapter for sending application errors to MONICA",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
8
8
|
"files": ["dist", "README.md"],
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
9
11
|
"exports": {
|
|
10
12
|
".": {
|
|
11
13
|
"types": "./dist/index.d.ts",
|
|
@@ -30,7 +32,7 @@
|
|
|
30
32
|
"directory": "cloudflare"
|
|
31
33
|
},
|
|
32
34
|
"dependencies": {
|
|
33
|
-
"@ah-monica/core": "0.
|
|
35
|
+
"@ah-monica/core": "0.4.0"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"@types/bun": "^1.2.21",
|