@bigbertha/sdk 0.5.0 → 0.5.2
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 +20 -1
- package/dist/core/client.d.ts +4 -1
- package/dist/core/client.js +9 -4
- package/dist/core/request.d.ts +6 -0
- package/dist/core/request.js +23 -0
- package/dist/core/types.d.ts +8 -0
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/edge/index.js +2 -1
- package/dist/node/index.js +3 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -156,7 +156,26 @@ deep framework call doesn't hide your code. A limit you set yourself is left alo
|
|
|
156
156
|
request: its URL, its headers (never `authorization`, `proxy-authorization`, `cookie`,
|
|
157
157
|
`set-cookie` or `x-api-key`), the client IP from the first `x-forwarded-for` hop or the
|
|
158
158
|
socket, and the trace from an incoming `traceparent`. Next.js's `onRequestError` sends
|
|
159
|
-
the same.
|
|
159
|
+
the same.
|
|
160
|
+
|
|
161
|
+
An app with its own error middleware passes the framework's request as `req` and gets
|
|
162
|
+
the same request, IP and trace:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// Koa
|
|
166
|
+
app.on("error", (err, ctx) =>
|
|
167
|
+
captureException(err, { req: ctx, status: err.status ?? 500, mechanism: { handler: "koa", handled: false } }),
|
|
168
|
+
)
|
|
169
|
+
// Express, after the routes
|
|
170
|
+
app.use((err, req, res, next) => {
|
|
171
|
+
captureException(err, { req, status: 500, mechanism: { handler: "express", handled: false } })
|
|
172
|
+
next(err)
|
|
173
|
+
})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`req` takes Node's `IncomingMessage`, Express's `req`, Koa's `ctx` or a Fetch `Request`
|
|
177
|
+
(the edge entry too). `mechanism` says the error escaped the app's own code, so bertha
|
|
178
|
+
doesn't read it as one the app caught. Server events also name the host (Heroku's `DYNO`, else the hostname) and the
|
|
160
179
|
Node and Next.js versions.
|
|
161
180
|
|
|
162
181
|
For bundled Node code, give the output debug ids and upload its maps after the build:
|
package/dist/core/client.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ type Context = IngestEvent["context"];
|
|
|
7
7
|
export type AddContext = (frames: IngestFrame[]) => void;
|
|
8
8
|
/** Adds what the runtime knows at capture time (the page, its state). Runs before scrubbing. */
|
|
9
9
|
export type Enrich = (event: IngestEvent) => void;
|
|
10
|
+
/** Reads a framework's request passed as `req`. Server entries only, to keep the browser small. */
|
|
11
|
+
export type ReadRequest = (req: unknown) => CaptureContext;
|
|
10
12
|
export declare class Client {
|
|
11
13
|
readonly options: Options;
|
|
12
14
|
private readonly transport;
|
|
@@ -14,6 +16,7 @@ export declare class Client {
|
|
|
14
16
|
private readonly context;
|
|
15
17
|
private readonly addContext;
|
|
16
18
|
private readonly enrich;
|
|
19
|
+
private readonly readRequest;
|
|
17
20
|
private user;
|
|
18
21
|
private tags;
|
|
19
22
|
private crumbs;
|
|
@@ -24,7 +27,7 @@ export declare class Client {
|
|
|
24
27
|
private window;
|
|
25
28
|
private pausedUntil;
|
|
26
29
|
private warned;
|
|
27
|
-
constructor(options: Options, transport: Transport, sdk: IngestSdk, context?: Context, addContext?: AddContext, enrich?: Enrich);
|
|
30
|
+
constructor(options: Options, transport: Transport, sdk: IngestSdk, context?: Context, addContext?: AddContext, enrich?: Enrich, readRequest?: ReadRequest);
|
|
28
31
|
captureException(error: unknown, ctx?: CaptureContext): string | undefined;
|
|
29
32
|
captureMessage(message: string, ctx?: CaptureContext): string | undefined;
|
|
30
33
|
setUser(user: IngestUser | null): void;
|
package/dist/core/client.js
CHANGED
|
@@ -21,6 +21,7 @@ export class Client {
|
|
|
21
21
|
context;
|
|
22
22
|
addContext;
|
|
23
23
|
enrich;
|
|
24
|
+
readRequest;
|
|
24
25
|
user;
|
|
25
26
|
tags;
|
|
26
27
|
crumbs = [];
|
|
@@ -31,13 +32,14 @@ export class Client {
|
|
|
31
32
|
window = { start: 0, count: 0 };
|
|
32
33
|
pausedUntil = 0;
|
|
33
34
|
warned = false;
|
|
34
|
-
constructor(options, transport, sdk, context, addContext, enrich) {
|
|
35
|
+
constructor(options, transport, sdk, context, addContext, enrich, readRequest) {
|
|
35
36
|
this.options = options;
|
|
36
37
|
this.transport = transport;
|
|
37
38
|
this.sdk = sdk;
|
|
38
39
|
this.context = context;
|
|
39
40
|
this.addContext = addContext;
|
|
40
41
|
this.enrich = enrich;
|
|
42
|
+
this.readRequest = readRequest;
|
|
41
43
|
this.tags = { ...options.tags };
|
|
42
44
|
}
|
|
43
45
|
captureException(error, ctx = {}) {
|
|
@@ -101,6 +103,9 @@ export class Client {
|
|
|
101
103
|
if (++this.window.count > PER_MINUTE)
|
|
102
104
|
return undefined;
|
|
103
105
|
const id = uuid();
|
|
106
|
+
const fromReq = (ctx.req !== undefined && this.readRequest?.(ctx.req)) || {};
|
|
107
|
+
const request = ctx.request ?? fromReq.request;
|
|
108
|
+
const user = fromReq.user || ctx.user ? { ...fromReq.user, ...ctx.user } : undefined;
|
|
104
109
|
let event = {
|
|
105
110
|
...base,
|
|
106
111
|
id,
|
|
@@ -108,12 +113,12 @@ export class Client {
|
|
|
108
113
|
release: o.release ?? builtRelease(),
|
|
109
114
|
environment: o.environment,
|
|
110
115
|
breadcrumbs: this.crumbs.slice(),
|
|
111
|
-
user:
|
|
116
|
+
user: user ? { ...this.user, ...user } : this.user,
|
|
112
117
|
context: { ...this.context, versions: { ...versions(), ...o.versions, ...this.context?.versions } },
|
|
113
118
|
tags: { ...this.tags, ...ctx.tags },
|
|
114
119
|
fingerprint: ctx.fingerprint,
|
|
115
|
-
request: ctx.request,
|
|
116
|
-
trace: ctx.trace,
|
|
120
|
+
request: request && ctx.status !== undefined ? { ...request, status: ctx.status } : request,
|
|
121
|
+
trace: ctx.trace ?? fromReq.trace,
|
|
117
122
|
};
|
|
118
123
|
try {
|
|
119
124
|
this.enrich?.(event);
|
package/dist/core/request.d.ts
CHANGED
|
@@ -4,3 +4,9 @@ import type { CaptureContext } from "./types.ts";
|
|
|
4
4
|
* (the first proxy hop, else the socket) and the trace from an incoming `traceparent`.
|
|
5
5
|
*/
|
|
6
6
|
export declare function fromRequest(method: string, url: string, headers: Record<string, unknown>, remoteAddress?: string): CaptureContext;
|
|
7
|
+
/**
|
|
8
|
+
* The same from a framework's own request: Node's `IncomingMessage`, Express's `req`,
|
|
9
|
+
* Koa's `ctx` or `ctx.req`, or a Fetch `Request` (edge, Hono's `c.req.raw`). Anything
|
|
10
|
+
* else gives nothing.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fromAnyRequest(req: unknown): CaptureContext;
|
package/dist/core/request.js
CHANGED
|
@@ -29,3 +29,26 @@ export function fromRequest(method, url, headers, remoteAddress) {
|
|
|
29
29
|
ctx.trace = trace;
|
|
30
30
|
return ctx;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The same from a framework's own request: Node's `IncomingMessage`, Express's `req`,
|
|
34
|
+
* Koa's `ctx` or `ctx.req`, or a Fetch `Request` (edge, Hono's `c.req.raw`). Anything
|
|
35
|
+
* else gives nothing.
|
|
36
|
+
*/
|
|
37
|
+
export function fromAnyRequest(req) {
|
|
38
|
+
const r = req;
|
|
39
|
+
if (!r || typeof r !== "object" || typeof r.method !== "string" || !r.headers || typeof r.headers !== "object")
|
|
40
|
+
return {};
|
|
41
|
+
let headers;
|
|
42
|
+
if (typeof r.headers.forEach === "function") {
|
|
43
|
+
headers = {};
|
|
44
|
+
r.headers.forEach((v, k) => (headers[k] = v));
|
|
45
|
+
}
|
|
46
|
+
else
|
|
47
|
+
headers = r.headers;
|
|
48
|
+
const path = typeof r.originalUrl === "string" ? r.originalUrl : typeof r.url === "string" ? r.url : "/";
|
|
49
|
+
const first = (v) => (typeof v === "string" ? v.split(",")[0]?.trim() : undefined);
|
|
50
|
+
const host = first(headers.host) ?? first(headers.Host);
|
|
51
|
+
const proto = first(headers["x-forwarded-proto"]) ?? "http";
|
|
52
|
+
const url = host && path.startsWith("/") ? `${proto}://${host}${path}` : path;
|
|
53
|
+
return fromRequest(r.method, url, headers, r.socket?.remoteAddress);
|
|
54
|
+
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -27,6 +27,14 @@ export interface CaptureContext {
|
|
|
27
27
|
tags?: Record<string, string>;
|
|
28
28
|
fingerprint?: string[];
|
|
29
29
|
request?: IngestEvent["request"];
|
|
30
|
+
/**
|
|
31
|
+
* The framework's request: Node's `IncomingMessage`, Express's `req`, Koa's `ctx`, or a
|
|
32
|
+
* Fetch `Request`. Its method, URL, headers (never credentials), the client IP and an
|
|
33
|
+
* incoming `traceparent` are sent. `request`, `user` and `trace` win over what it gives.
|
|
34
|
+
*/
|
|
35
|
+
req?: unknown;
|
|
36
|
+
/** The response status, sent with the request. */
|
|
37
|
+
status?: number;
|
|
30
38
|
/** Merged over the user from `setUser`. */
|
|
31
39
|
user?: IngestUser;
|
|
32
40
|
/** How the error reached the SDK. Defaults to `captureException`, handled. */
|
package/dist/core/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.5.
|
|
1
|
+
export declare const VERSION = "0.5.2";
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.5.
|
|
1
|
+
export const VERSION = "0.5.2";
|
package/dist/edge/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Client } from "../core/client.js";
|
|
4
4
|
import { firstEnv } from "../core/env.js";
|
|
5
5
|
import { setClient } from "../core/hub.js";
|
|
6
|
+
import { fromAnyRequest } from "../core/request.js";
|
|
6
7
|
import { fetchTransport, streamGzip } from "../core/transport.js";
|
|
7
8
|
import { VERSION } from "../core/version.js";
|
|
8
9
|
export { addBreadcrumb, captureException, captureMessage, flush, getClient, setTag, setUser } from "../core/hub.js";
|
|
@@ -19,7 +20,7 @@ export function init(options = {}) {
|
|
|
19
20
|
url,
|
|
20
21
|
release: options.release ?? firstEnv("BERTHA_RELEASE"),
|
|
21
22
|
environment: options.environment ?? firstEnv("BERTHA_ENVIRONMENT", "VERCEL_ENV", "NODE_ENV"),
|
|
22
|
-
}, fetchTransport({ url, key, gzip: streamGzip }), { name: "bertha.edge", version: VERSION }, { runtime: "Edge" });
|
|
23
|
+
}, fetchTransport({ url, key, gzip: streamGzip }), { name: "bertha.edge", version: VERSION }, { runtime: "Edge" }, undefined, undefined, fromAnyRequest);
|
|
23
24
|
setClient(client);
|
|
24
25
|
return client;
|
|
25
26
|
}
|
package/dist/node/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { gzipSync } from "node:zlib";
|
|
|
7
7
|
import { Client } from "../core/client.js";
|
|
8
8
|
import { envCommit, firstEnv } from "../core/env.js";
|
|
9
9
|
import { getClient, setClient } from "../core/hub.js";
|
|
10
|
-
import {
|
|
10
|
+
import { fromAnyRequest } from "../core/request.js";
|
|
11
11
|
import { endpoint, fetchTransport } from "../core/transport.js";
|
|
12
12
|
import { VERSION } from "../core/version.js";
|
|
13
13
|
import { addContext } from "./context.js";
|
|
@@ -39,7 +39,7 @@ export function init(options = {}) {
|
|
|
39
39
|
os: `${platform()} ${arch()}`,
|
|
40
40
|
host: process.env.DYNO ?? safe(hostname),
|
|
41
41
|
versions: installedVersions(),
|
|
42
|
-
}, options.contextLines === false ? undefined : addContext);
|
|
42
|
+
}, options.contextLines === false ? undefined : addContext, undefined, fromAnyRequest);
|
|
43
43
|
setClient(client);
|
|
44
44
|
// Only V8's default: a limit the app set itself was a choice.
|
|
45
45
|
if (Error.stackTraceLimit === V8_DEFAULT_FRAMES)
|
|
@@ -106,7 +106,7 @@ export async function sendHeartbeat(o) {
|
|
|
106
106
|
export function wrapHandler(handler) {
|
|
107
107
|
return function (...args) {
|
|
108
108
|
const report = (error) => {
|
|
109
|
-
getClient()?.captureException(error,
|
|
109
|
+
getClient()?.captureException(error, { req: args[0], mechanism: { handler: "wrapHandler", handled: false } });
|
|
110
110
|
throw error;
|
|
111
111
|
};
|
|
112
112
|
try {
|
|
@@ -118,16 +118,6 @@ export function wrapHandler(handler) {
|
|
|
118
118
|
}
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
|
-
function requestContext(req) {
|
|
122
|
-
const r = req;
|
|
123
|
-
if (!r || typeof r.method !== "string")
|
|
124
|
-
return {};
|
|
125
|
-
const path = r.originalUrl ?? r.url ?? "/";
|
|
126
|
-
const host = typeof r.headers?.host === "string" ? r.headers.host : undefined;
|
|
127
|
-
const proto = typeof r.headers?.["x-forwarded-proto"] === "string" ? r.headers["x-forwarded-proto"] : "http";
|
|
128
|
-
const url = host && path.startsWith("/") ? `${proto}://${host}${path}` : path;
|
|
129
|
-
return fromRequest(r.method, url, r.headers ?? {}, r.socket?.remoteAddress);
|
|
130
|
-
}
|
|
131
121
|
/** Node's version, and Next.js's when the app has it installed. */
|
|
132
122
|
function installedVersions() {
|
|
133
123
|
const out = { node: process.versions.node };
|