@voyant-travel/observability-sentry 0.0.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 +68 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/sentry-reporter.d.ts +71 -0
- package/dist/sentry-reporter.d.ts.map +1 -0
- package/dist/sentry-reporter.js +65 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# @voyant-travel/observability-sentry
|
|
2
|
+
|
|
3
|
+
Sentry adapter for the Voyant observability `Reporter` seam (RFC [voyant#1553](https://github.com/voyant-travel/voyant/issues/1553)).
|
|
4
|
+
|
|
5
|
+
The framework owns the **mechanism** — a request id minted once and propagated
|
|
6
|
+
(`X-Request-Id` + `getRequestId()`), standard catch points (5xx boundary, auth
|
|
7
|
+
sub-app, module bootstrap, event-bus subscribers, scheduled jobs), and a
|
|
8
|
+
normalized `ErrorEvent` shape. This package is the **opt-in sink**: it forwards
|
|
9
|
+
those events to Sentry, tagging each with the same `requestId` the user sees, so
|
|
10
|
+
a support reference is a one-paste lookup in Sentry issue search.
|
|
11
|
+
|
|
12
|
+
## Why an adapter and not a Sentry dependency
|
|
13
|
+
|
|
14
|
+
This package takes **no dependency on a Sentry SDK**. It binds to a structural
|
|
15
|
+
`SentryLike` interface, so you pass your already-initialized Sentry client —
|
|
16
|
+
`@sentry/cloudflare`, `@sentry/node`, `@sentry/bun`, `@sentry/browser`, any
|
|
17
|
+
version. Your deployment owns `init`/DSN/transport/sampling/PII scrubbing; the
|
|
18
|
+
adapter only maps the event and forwards it. The framework drains the buffered
|
|
19
|
+
event via `ctx.waitUntil` using the `flush()` the adapter returns — so you don't
|
|
20
|
+
hand-roll the Workers `flush`/`waitUntil` lifecycle that the RFC found broken in
|
|
21
|
+
three separate copies.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import * as Sentry from "@sentry/cloudflare"
|
|
27
|
+
import { createApp } from "@voyant-travel/hono"
|
|
28
|
+
import { sentryReporter } from "@voyant-travel/observability-sentry"
|
|
29
|
+
|
|
30
|
+
const reporter = sentryReporter(Sentry)
|
|
31
|
+
|
|
32
|
+
const app = createApp({
|
|
33
|
+
reporter,
|
|
34
|
+
appName: "operator", // stamped on every event as the `app` tag
|
|
35
|
+
modules,
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
That is the entire change — swap `consoleReporter()` (or the no-op default) for
|
|
40
|
+
`sentryReporter(Sentry)`. Every framework catch point now reaches Sentry.
|
|
41
|
+
|
|
42
|
+
### What lands on the Sentry issue
|
|
43
|
+
|
|
44
|
+
| `ErrorEvent` field | Sentry mapping |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `requestId` | tag `request_id` (omitted when empty, e.g. background jobs) |
|
|
47
|
+
| `app` | tag `app` |
|
|
48
|
+
| `error` | the captured exception (non-`Error` values are wrapped so they still group with a stack) |
|
|
49
|
+
| `context` | nested under the `voyant` context group |
|
|
50
|
+
|
|
51
|
+
## Options
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
sentryReporter(Sentry, {
|
|
55
|
+
requestIdTag: "request_id", // tag key for the correlation id
|
|
56
|
+
appTag: "app", // tag key for the logical app name
|
|
57
|
+
contextKey: "voyant", // context group the `ErrorEvent.context` nests under
|
|
58
|
+
flush: true, // default: flush when the client exposes flush() (Workers need it)
|
|
59
|
+
flushTimeoutMs: 2000, // flush timeout in ms
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
On long-lived Node/Bun servers you can pass `flush: false` to let Sentry's own
|
|
64
|
+
background transport deliver events instead of flushing per capture.
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
Apache-2.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { ErrorEvent, Reporter } from "@voyant-travel/hono/observability";
|
|
2
|
+
export type { SentryCaptureContext, SentryLike, SentryReporterOptions, } from "./sentry-reporter.js";
|
|
3
|
+
export { sentryReporter } from "./sentry-reporter.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAA;AAC7E,YAAY,EACV,oBAAoB,EACpB,UAAU,EACV,qBAAqB,GACtB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { sentryReporter } from "./sentry-reporter.js";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Reporter } from "@voyant-travel/hono/observability";
|
|
2
|
+
/**
|
|
3
|
+
* The slice of a Sentry SDK this adapter calls. Declared structurally so the
|
|
4
|
+
* adapter binds to ANY Sentry flavour — `@sentry/cloudflare`, `@sentry/node`,
|
|
5
|
+
* `@sentry/bun`, `@sentry/browser` — and any version whose `captureException`
|
|
6
|
+
* accepts a capture-context hint, without this package taking a hard dependency
|
|
7
|
+
* on a specific Sentry SDK. The deployment owns `init`/DSN/transport/sampling;
|
|
8
|
+
* the adapter only maps the normalized event and forwards it. This is exactly
|
|
9
|
+
* the split RFC voyant#1553 asked for: the framework owns the catch points and
|
|
10
|
+
* event shape, the vendor SDK stays a deployment choice.
|
|
11
|
+
*/
|
|
12
|
+
export interface SentryLike {
|
|
13
|
+
captureException(exception: unknown, hint?: SentryCaptureContext): string;
|
|
14
|
+
/**
|
|
15
|
+
* Optional. On Cloudflare Workers, Sentry buffers events and only delivers
|
|
16
|
+
* them on `flush`; when the client exposes it, the adapter returns
|
|
17
|
+
* `flush(timeout)` so the framework drains it via `ctx.waitUntil` after the
|
|
18
|
+
* response. Owning this lifecycle once is the point — the three hand-rolled
|
|
19
|
+
* `sentry.ts` copies the RFC cites each got the manual `flush`/`waitUntil`
|
|
20
|
+
* dance subtly wrong, and server-side Worker exceptions never delivered.
|
|
21
|
+
*/
|
|
22
|
+
flush?(timeout?: number): Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
/** The subset of Sentry's capture-context hint this adapter populates. */
|
|
25
|
+
export interface SentryCaptureContext {
|
|
26
|
+
tags?: Record<string, string>;
|
|
27
|
+
contexts?: Record<string, Record<string, unknown>>;
|
|
28
|
+
}
|
|
29
|
+
export interface SentryReporterOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Tag key carrying the correlation id. Default `"request_id"`. This is the
|
|
32
|
+
* tag that makes a user-reported reference findable in Sentry — closing the
|
|
33
|
+
* RFC's root cause, where the id surfaced to the user never reached the event.
|
|
34
|
+
*/
|
|
35
|
+
requestIdTag?: string;
|
|
36
|
+
/** Tag key carrying the logical app/worker name. Default `"app"`. */
|
|
37
|
+
appTag?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Sentry context group the normalized `ErrorEvent.context` is nested under
|
|
40
|
+
* (shows as a labelled section on the issue). Default `"voyant"`.
|
|
41
|
+
*/
|
|
42
|
+
contextKey?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Flush after every capture. Workers must (events are buffered until flush);
|
|
45
|
+
* long-lived Node/Bun servers need not. Default: flush when the client
|
|
46
|
+
* exposes a `flush` method.
|
|
47
|
+
*/
|
|
48
|
+
flush?: boolean;
|
|
49
|
+
/** Flush timeout in milliseconds. Default `2000` (matches Sentry's own). */
|
|
50
|
+
flushTimeoutMs?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build a {@link Reporter} backed by an already-initialized Sentry client.
|
|
54
|
+
*
|
|
55
|
+
* Register it once on `VoyantAppConfig.reporter` and every framework catch
|
|
56
|
+
* point (5xx boundary, auth sub-app, module bootstrap, event-bus subscribers,
|
|
57
|
+
* scheduled jobs) routes its {@link ErrorEvent} to Sentry — each tagged with
|
|
58
|
+
* the same `requestId` the user sees on `X-Request-Id`, so a support reference
|
|
59
|
+
* is a one-paste lookup in the Sentry issue search.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* import * as Sentry from "@sentry/cloudflare"
|
|
64
|
+
* import { sentryReporter } from "@voyant-travel/observability-sentry"
|
|
65
|
+
*
|
|
66
|
+
* const reporter = sentryReporter(Sentry)
|
|
67
|
+
* const app = createApp({ reporter, appName: "operator", modules })
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function sentryReporter(sentry: SentryLike, options?: SentryReporterOptions): Reporter;
|
|
71
|
+
//# sourceMappingURL=sentry-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentry-reporter.d.ts","sourceRoot":"","sources":["../src/sentry-reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,QAAQ,EAAE,MAAM,mCAAmC,CAAA;AAE7E;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,gBAAgB,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAAA;IACzE;;;;;;;OAOG;IACH,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC3C;AAED,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CACnD;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,qBAA0B,GAAG,QAAQ,CA8BhG"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a {@link Reporter} backed by an already-initialized Sentry client.
|
|
3
|
+
*
|
|
4
|
+
* Register it once on `VoyantAppConfig.reporter` and every framework catch
|
|
5
|
+
* point (5xx boundary, auth sub-app, module bootstrap, event-bus subscribers,
|
|
6
|
+
* scheduled jobs) routes its {@link ErrorEvent} to Sentry — each tagged with
|
|
7
|
+
* the same `requestId` the user sees on `X-Request-Id`, so a support reference
|
|
8
|
+
* is a one-paste lookup in the Sentry issue search.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import * as Sentry from "@sentry/cloudflare"
|
|
13
|
+
* import { sentryReporter } from "@voyant-travel/observability-sentry"
|
|
14
|
+
*
|
|
15
|
+
* const reporter = sentryReporter(Sentry)
|
|
16
|
+
* const app = createApp({ reporter, appName: "operator", modules })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function sentryReporter(sentry, options = {}) {
|
|
20
|
+
const requestIdTag = options.requestIdTag ?? "request_id";
|
|
21
|
+
const appTag = options.appTag ?? "app";
|
|
22
|
+
const contextKey = options.contextKey ?? "voyant";
|
|
23
|
+
const flushTimeoutMs = options.flushTimeoutMs ?? 2000;
|
|
24
|
+
const shouldFlush = options.flush ?? typeof sentry.flush === "function";
|
|
25
|
+
return {
|
|
26
|
+
captureException(event) {
|
|
27
|
+
const { requestId, app, error, context } = event;
|
|
28
|
+
const tags = { [appTag]: app };
|
|
29
|
+
// Only tag a non-empty id. Background/scheduled catch points emit an empty
|
|
30
|
+
// requestId (no request to correlate with), and an empty-string tag is
|
|
31
|
+
// searchable noise rather than a useful key.
|
|
32
|
+
if (requestId)
|
|
33
|
+
tags[requestIdTag] = requestId;
|
|
34
|
+
sentry.captureException(toError(error), {
|
|
35
|
+
tags,
|
|
36
|
+
contexts: context ? { [contextKey]: context } : undefined,
|
|
37
|
+
});
|
|
38
|
+
if (shouldFlush && sentry.flush) {
|
|
39
|
+
// Handed back to the framework, which drains it via `waitUntil` so the
|
|
40
|
+
// buffered event delivers without blocking the response. `.then`
|
|
41
|
+
// collapses the boolean to the `void` the Reporter contract expects.
|
|
42
|
+
return sentry.flush(flushTimeoutMs).then(() => { });
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Sentry produces the richest issue (stack frames, grouping) from a real
|
|
49
|
+
* `Error`. Pass `Error` instances through untouched; wrap anything else so a
|
|
50
|
+
* thrown string/object still yields a grouped, stack-bearing issue instead of
|
|
51
|
+
* Sentry's bare "Non-Error exception captured" placeholder.
|
|
52
|
+
*/
|
|
53
|
+
function toError(error) {
|
|
54
|
+
if (error instanceof Error)
|
|
55
|
+
return error;
|
|
56
|
+
if (typeof error === "string")
|
|
57
|
+
return new Error(error);
|
|
58
|
+
try {
|
|
59
|
+
return new Error(`Non-Error thrown: ${JSON.stringify(error)}`);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Circular or otherwise un-serializable value.
|
|
63
|
+
return new Error(`Non-Error thrown: ${String(error)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voyant-travel/observability-sentry",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Sentry adapter for the Voyant observability Reporter seam (RFC voyant#1553).",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"lint": "biome check src/",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"build": "tsc -p tsconfig.json",
|
|
15
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
16
|
+
"prepack": "pnpm run build"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"import": "./dist/index.js",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@voyant-travel/hono": "workspace:^"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@voyant-travel/hono": "workspace:^",
|
|
39
|
+
"@voyant-travel/voyant-typescript-config": "workspace:^",
|
|
40
|
+
"typescript": "^6.0.2",
|
|
41
|
+
"vitest": "^4.1.2"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/voyant-travel/voyant.git",
|
|
46
|
+
"directory": "packages/observability-sentry"
|
|
47
|
+
}
|
|
48
|
+
}
|