@bigbertha/sdk 0.6.1 → 0.8.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 +52 -1
- package/dist/browser/index.d.ts +5 -0
- package/dist/browser/index.js +29 -11
- package/dist/browser/spans.d.ts +14 -0
- package/dist/browser/spans.js +115 -0
- package/dist/core/client.js +2 -1
- package/dist/core/otel.d.ts +3 -0
- package/dist/core/otel.js +18 -0
- package/dist/core/otlp.d.ts +13 -0
- package/dist/core/otlp.js +17 -0
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/edge/index.d.ts +2 -0
- package/dist/edge/index.js +1 -0
- package/dist/next/index.d.ts +2 -1
- package/dist/next/index.js +1 -0
- package/dist/node/index.d.ts +2 -0
- package/dist/node/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
Error reporting for bertha. Browser, React, Node, edge and Next.js, with no dependencies
|
|
4
4
|
and source maps matched by debug id rather than by file path.
|
|
5
5
|
|
|
6
|
-
- About
|
|
6
|
+
- About 6.9 kB gzipped in the browser (`npm run size` enforces the budget).
|
|
7
7
|
- Batches events, backs off when bertha says so, and sends what's queued as the page or
|
|
8
8
|
process goes away.
|
|
9
9
|
- Scrubs emails, bearer tokens and secret-looking query values by default.
|
|
10
10
|
- Each event says how it surfaced (an uncaught error, a rejection, a boundary, a call),
|
|
11
11
|
where (the page or request) and in what state, and links the page's requests to the
|
|
12
12
|
server errors they caused by W3C `traceparent`.
|
|
13
|
+
- Works with OpenTelemetry: `otlp()` points an OTel exporter at bertha, an error inside
|
|
14
|
+
an active span is recorded on it, and the browser sends its page's requests as spans
|
|
15
|
+
once the page has an error. See [Traces](#traces).
|
|
13
16
|
- The build plugins give each bundle a debug id, upload its map and delete the `.map`
|
|
14
17
|
files, so maps never ship to browsers.
|
|
15
18
|
|
|
@@ -110,6 +113,11 @@ export default defineConfig({ plugins: [bertha()] })
|
|
|
110
113
|
init({ key, url, tracePropagationTargets: ["https://api.example.com"] })
|
|
111
114
|
```
|
|
112
115
|
|
|
116
|
+
- **The page's requests as spans:** the page itself, its document load and each fetch
|
|
117
|
+
and XHR with its method, scrubbed URL and status. They wait in memory (the last 200)
|
|
118
|
+
and go to bertha only once the page has had an error, then for as long as it stays
|
|
119
|
+
open, so a page that never errors sends none. `spans: false` turns it off.
|
|
120
|
+
|
|
113
121
|
Frames in browser extensions and in scripts Safari hides (`webkit-masked-url://`) are
|
|
114
122
|
sent as not the app's code, so bertha can say when an error probably isn't yours.
|
|
115
123
|
|
|
@@ -184,6 +192,48 @@ For bundled Node code, give the output debug ids and upload its maps after the b
|
|
|
184
192
|
npx bertha sourcemaps upload dist
|
|
185
193
|
```
|
|
186
194
|
|
|
195
|
+
## Traces
|
|
196
|
+
|
|
197
|
+
bertha takes OpenTelemetry traces over OTLP/HTTP, so an error's page shows what the
|
|
198
|
+
whole request did across your services: where the time went, what failed first, and
|
|
199
|
+
calls into a service that never answered. It keeps spans for an hour and keeps a
|
|
200
|
+
trace for good once an error event names it, so only traces with an error stay.
|
|
201
|
+
|
|
202
|
+
The SDK doesn't record server spans itself. Use OpenTelemetry's own instrumentation and
|
|
203
|
+
point its exporter at bertha with `otlp()`, which gives the URL and the key's header
|
|
204
|
+
from `BERTHA_KEY` and `BERTHA_URL` (or `key` and `url`):
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
// Next.js: instrumentation.ts (npm install @vercel/otel)
|
|
208
|
+
import { OTLPHttpProtoTraceExporter, registerOTel } from "@vercel/otel"
|
|
209
|
+
import { otlp, register as bertha } from "@bigbertha/sdk/next"
|
|
210
|
+
export { onRequestError } from "@bigbertha/sdk/next"
|
|
211
|
+
|
|
212
|
+
export function register() {
|
|
213
|
+
registerOTel({ serviceName: "shop", traceExporter: new OTLPHttpProtoTraceExporter(otlp()) })
|
|
214
|
+
return bertha()
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
// Node: before anything else loads
|
|
220
|
+
// (npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-proto)
|
|
221
|
+
import { NodeSDK } from "@opentelemetry/sdk-node"
|
|
222
|
+
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"
|
|
223
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto"
|
|
224
|
+
import { init, otlp } from "@bigbertha/sdk/node"
|
|
225
|
+
|
|
226
|
+
new NodeSDK({ traceExporter: new OTLPTraceExporter(otlp()), instrumentations: [getNodeAutoInstrumentations()] }).start()
|
|
227
|
+
init()
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Any other OTLP exporter works too: send to `<url>/ingest/v1/traces` with
|
|
231
|
+
`Authorization: Bearer <key>`, as protobuf or JSON.
|
|
232
|
+
|
|
233
|
+
With OpenTelemetry running, every event names the span that was active when it was
|
|
234
|
+
captured, so bertha places the error on that span. Without it, events still carry the
|
|
235
|
+
trace from an incoming `traceparent` and land on the request's outermost span.
|
|
236
|
+
|
|
187
237
|
## Edge
|
|
188
238
|
|
|
189
239
|
```ts
|
|
@@ -219,6 +269,7 @@ await flush()
|
|
|
219
269
|
| `versions` | | More versions on every event, as `{ name: version }`. |
|
|
220
270
|
| `sendIp` | `true` | Browser: store the visitor's IP. |
|
|
221
271
|
| `tracePropagationTargets` | same origin only | Browser: other origins whose requests get a `traceparent`. |
|
|
272
|
+
| `spans` | `true` | Browser: record the page's requests as spans and send them once the page has an error. |
|
|
222
273
|
| `breadcrumbs` | all on | Browser: `fetch`, `xhr`, `navigation`, `click`, `console`. |
|
|
223
274
|
| `debug` | `false` | Log what the SDK does. |
|
|
224
275
|
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ export interface BrowserOptions extends Options {
|
|
|
13
13
|
* must allow the header in CORS.
|
|
14
14
|
*/
|
|
15
15
|
tracePropagationTargets?: (string | RegExp)[];
|
|
16
|
+
/**
|
|
17
|
+
* Record the page's requests as spans, and send them to the trace once the page has
|
|
18
|
+
* had an error, so its waterfall shows what the browser did. On by default.
|
|
19
|
+
*/
|
|
20
|
+
spans?: boolean;
|
|
16
21
|
}
|
|
17
22
|
export declare function init(options: BrowserOptions): Client | undefined;
|
|
18
23
|
/** What the page looked like when it failed. Called for every event. */
|
package/dist/browser/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { scrubUrl } from "../core/scrub.js";
|
|
|
6
6
|
import { randomHex, traceparent } from "../core/trace.js";
|
|
7
7
|
import { fetchTransport, streamGzip } from "../core/transport.js";
|
|
8
8
|
import { VERSION } from "../core/version.js";
|
|
9
|
+
import { spans } from "./spans.js";
|
|
9
10
|
import { userAgent } from "./user-agent.js";
|
|
10
11
|
export { addBreadcrumb, captureException, captureMessage, flush, getClient, setTag, setUser } from "../core/hub.js";
|
|
11
12
|
let installed = false;
|
|
@@ -19,14 +20,19 @@ export function init(options) {
|
|
|
19
20
|
return undefined;
|
|
20
21
|
}
|
|
21
22
|
const nativeFetch = window.fetch.bind(window);
|
|
23
|
+
const beacon = (url, body) => !!navigator.sendBeacon?.(url, new Blob([body], { type: "text/plain" }));
|
|
24
|
+
const recorder = options.spans === false ? undefined : spans({ url: options.url, key: options.key, traceId, fetch: nativeFetch, beacon });
|
|
22
25
|
const client = new Client(options, fetchTransport({
|
|
23
26
|
url: options.url,
|
|
24
27
|
key: options.key,
|
|
25
28
|
keyInQuery: true,
|
|
26
29
|
gzip: streamGzip,
|
|
27
30
|
fetch: nativeFetch,
|
|
28
|
-
beacon
|
|
29
|
-
}), { name: "bertha.browser", version: VERSION }, { ...userAgent(navigator.userAgent), userAgent: navigator.userAgent }, undefined, (event) =>
|
|
31
|
+
beacon,
|
|
32
|
+
}), { name: "bertha.browser", version: VERSION }, { ...userAgent(navigator.userAgent), userAgent: navigator.userAgent }, undefined, (event) => {
|
|
33
|
+
enrich(event, options.sendIp !== false);
|
|
34
|
+
recorder?.errored();
|
|
35
|
+
});
|
|
30
36
|
setClient(client);
|
|
31
37
|
if (installed)
|
|
32
38
|
return client;
|
|
@@ -41,7 +47,11 @@ export function init(options) {
|
|
|
41
47
|
});
|
|
42
48
|
});
|
|
43
49
|
window.addEventListener("unhandledrejection", (e) => client.captureException(e.reason, { mechanism: { handler: "onunhandledrejection", handled: false } }));
|
|
44
|
-
|
|
50
|
+
const final = () => {
|
|
51
|
+
void client.flush(true);
|
|
52
|
+
recorder?.flush(true);
|
|
53
|
+
};
|
|
54
|
+
addEventListener("pagehide", final);
|
|
45
55
|
const on = { fetch: true, xhr: true, navigation: true, click: true, console: true, ...options.breadcrumbs };
|
|
46
56
|
const base = options.url.replace(/\/+$/, "");
|
|
47
57
|
const crumb = (category, message, level, timestamp) => client.addBreadcrumb({ category, message, ...(level ? { level } : {}), ...(timestamp ? { timestamp } : {}) });
|
|
@@ -50,13 +60,14 @@ export function init(options) {
|
|
|
50
60
|
if (on.navigation)
|
|
51
61
|
crumb("navigation", hidden ? "Page hidden" : "Page visible");
|
|
52
62
|
if (hidden)
|
|
53
|
-
|
|
63
|
+
final();
|
|
54
64
|
});
|
|
55
65
|
const propagate = propagateTo(options.tracePropagationTargets ?? []);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
const quiet = () => undefined;
|
|
67
|
+
if (on.fetch || recorder)
|
|
68
|
+
instrumentFetch(base, on.fetch ? crumb : quiet, propagate, recorder);
|
|
69
|
+
if (on.xhr || recorder)
|
|
70
|
+
instrumentXhr(base, on.xhr ? crumb : quiet, propagate, recorder);
|
|
60
71
|
if (on.navigation)
|
|
61
72
|
instrumentHistory(crumb);
|
|
62
73
|
if (on.click)
|
|
@@ -122,7 +133,7 @@ function requestLine(method, url, status, start, span) {
|
|
|
122
133
|
const ms = Math.round(performance.now() - start);
|
|
123
134
|
return `${method.toUpperCase()} ${scrubUrl(url)} ${status} in ${ms}ms${span ? `, span ${span}` : ""}`;
|
|
124
135
|
}
|
|
125
|
-
function instrumentFetch(base, crumb, propagate) {
|
|
136
|
+
function instrumentFetch(base, crumb, propagate, recorder) {
|
|
126
137
|
const original = window.fetch;
|
|
127
138
|
window.fetch = async function (input, init) {
|
|
128
139
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
@@ -137,18 +148,21 @@ function instrumentFetch(base, crumb, propagate) {
|
|
|
137
148
|
init = { ...init, headers };
|
|
138
149
|
}
|
|
139
150
|
const start = performance.now();
|
|
151
|
+
const done = recorder?.request(method, url, span ?? randomHex(16));
|
|
140
152
|
try {
|
|
141
153
|
const res = await original.call(window, input, init);
|
|
142
154
|
crumb("fetch", requestLine(method, url, res.status, start, span), res.status >= 400 ? "error" : undefined);
|
|
155
|
+
done?.(res.status);
|
|
143
156
|
return res;
|
|
144
157
|
}
|
|
145
158
|
catch (e) {
|
|
146
159
|
crumb("fetch", requestLine(method, url, "failed", start, span), "error");
|
|
160
|
+
done?.(0, e);
|
|
147
161
|
throw e;
|
|
148
162
|
}
|
|
149
163
|
};
|
|
150
164
|
}
|
|
151
|
-
function instrumentXhr(base, crumb, propagate) {
|
|
165
|
+
function instrumentXhr(base, crumb, propagate, recorder) {
|
|
152
166
|
const proto = XMLHttpRequest.prototype;
|
|
153
167
|
const open = proto.open;
|
|
154
168
|
const send = proto.send;
|
|
@@ -171,7 +185,11 @@ function instrumentXhr(base, crumb, propagate) {
|
|
|
171
185
|
}
|
|
172
186
|
}
|
|
173
187
|
const start = performance.now();
|
|
174
|
-
|
|
188
|
+
const done = recorder?.request(m[0], m[1], span ?? randomHex(16));
|
|
189
|
+
this.addEventListener("loadend", () => {
|
|
190
|
+
crumb("fetch", requestLine(m[0], m[1], this.status || "failed", start, span), !this.status || this.status >= 400 ? "error" : undefined);
|
|
191
|
+
done?.(this.status, this.status ? undefined : "The request failed: no response");
|
|
192
|
+
});
|
|
175
193
|
}
|
|
176
194
|
return send.call(this, body);
|
|
177
195
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Spans {
|
|
2
|
+
/** Starts a request span; call what it returns with the status or the error. */
|
|
3
|
+
request(method: string, url: string, spanId: string): (status: number, error?: unknown) => void;
|
|
4
|
+
/** The page has an event: send what it has, and keep sending. */
|
|
5
|
+
errored(): void;
|
|
6
|
+
flush(final: boolean): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function spans(o: {
|
|
9
|
+
url: string;
|
|
10
|
+
key: string;
|
|
11
|
+
traceId: string;
|
|
12
|
+
fetch: typeof fetch;
|
|
13
|
+
beacon: (url: string, body: string) => boolean;
|
|
14
|
+
}): Spans;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// The page's own requests as OpenTelemetry spans, so a trace shows what the browser did
|
|
2
|
+
// around the server's spans. They wait in memory and are sent only once the page has
|
|
3
|
+
// had an error: bertha keeps a trace only when an error names it.
|
|
4
|
+
import { scrubUrl } from "../core/scrub.js";
|
|
5
|
+
import { randomHex } from "../core/trace.js";
|
|
6
|
+
import { endpoint } from "../core/transport.js";
|
|
7
|
+
import { VERSION } from "../core/version.js";
|
|
8
|
+
/** Spans a page holds before its first error, and sends in all. */
|
|
9
|
+
const HELD = 200;
|
|
10
|
+
const SENT = 1000;
|
|
11
|
+
/** Time for the requests in flight when the error happened to finish. */
|
|
12
|
+
const SETTLE_MS = 3000;
|
|
13
|
+
export function spans(o) {
|
|
14
|
+
const target = `${endpoint(o.url, "traces")}?key=${encodeURIComponent(o.key)}`;
|
|
15
|
+
const origin = performance.timeOrigin || Date.now() - performance.now();
|
|
16
|
+
const now = () => origin + performance.now();
|
|
17
|
+
const page = { spanId: randomHex(16), parentSpanId: "", name: `page ${location.pathname}`, kind: 1, start: origin, end: origin, attributes: { "url.full": scrubUrl(location.href) } };
|
|
18
|
+
let held = [];
|
|
19
|
+
let sent = 0;
|
|
20
|
+
let loaded = false;
|
|
21
|
+
let armed = false;
|
|
22
|
+
let timer;
|
|
23
|
+
const later = () => (timer ??= setTimeout(() => flush(false), SETTLE_MS));
|
|
24
|
+
function flush(final) {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
timer = undefined;
|
|
27
|
+
if (!armed)
|
|
28
|
+
return;
|
|
29
|
+
const batch = held.splice(0, SENT - sent);
|
|
30
|
+
const nav = performance.getEntriesByType?.("navigation")[0];
|
|
31
|
+
const end = nav && (nav.loadEventEnd || nav.domContentLoadedEventEnd);
|
|
32
|
+
if (!loaded && nav && end) {
|
|
33
|
+
loaded = true;
|
|
34
|
+
batch.push({ spanId: randomHex(16), parentSpanId: page.spanId, name: "document load", kind: 1, start: origin + nav.startTime, end: origin + end, attributes: { "navigation.type": nav.type } });
|
|
35
|
+
}
|
|
36
|
+
if (!batch.length && !final)
|
|
37
|
+
return;
|
|
38
|
+
sent += batch.length;
|
|
39
|
+
// The page span grows with the page; bertha keeps its longest copy.
|
|
40
|
+
page.end = now();
|
|
41
|
+
batch.unshift(page);
|
|
42
|
+
const body = JSON.stringify(otlpJson(o.traceId, batch));
|
|
43
|
+
if (final && o.beacon(target, body))
|
|
44
|
+
return;
|
|
45
|
+
o.fetch(target, { method: "POST", headers: { "content-type": "text/plain" }, body, keepalive: body.length < 60_000, credentials: "omit" }).catch(() => undefined);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
request(method, url, spanId) {
|
|
49
|
+
const start = now();
|
|
50
|
+
let abs;
|
|
51
|
+
try {
|
|
52
|
+
abs = new URL(url, location.href);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Recorded as the app wrote it.
|
|
56
|
+
}
|
|
57
|
+
const where = !abs ? url : abs.origin === location.origin ? abs.pathname : abs.host + abs.pathname;
|
|
58
|
+
return (status, error) => {
|
|
59
|
+
if (sent + held.length >= SENT)
|
|
60
|
+
return;
|
|
61
|
+
const span = {
|
|
62
|
+
spanId,
|
|
63
|
+
parentSpanId: page.spanId,
|
|
64
|
+
name: `${method.toUpperCase()} ${where}`,
|
|
65
|
+
kind: 3,
|
|
66
|
+
start,
|
|
67
|
+
end: now(),
|
|
68
|
+
attributes: { "http.request.method": method.toUpperCase(), "url.full": scrubUrl(abs?.href ?? url), ...(status ? { "http.response.status_code": status } : {}) },
|
|
69
|
+
error: error !== undefined ? (error instanceof Error ? error.message : String(error)) : status >= 400 ? `HTTP ${status}` : undefined,
|
|
70
|
+
};
|
|
71
|
+
held.push(span);
|
|
72
|
+
if (!armed && held.length > HELD)
|
|
73
|
+
held = held.slice(-HELD);
|
|
74
|
+
if (armed)
|
|
75
|
+
later();
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
errored() {
|
|
79
|
+
armed = true;
|
|
80
|
+
later();
|
|
81
|
+
},
|
|
82
|
+
flush,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Epoch milliseconds as OTLP's nanoseconds, in a string: they're past 2^53. */
|
|
86
|
+
function nanos(ms) {
|
|
87
|
+
return `${Math.round(ms * 1000)}000`;
|
|
88
|
+
}
|
|
89
|
+
function otlpJson(traceId, batch) {
|
|
90
|
+
// bertha keeps every attribute as text.
|
|
91
|
+
const attr = (key, v) => ({ key, value: { stringValue: String(v) } });
|
|
92
|
+
return {
|
|
93
|
+
resourceSpans: [
|
|
94
|
+
{
|
|
95
|
+
resource: { attributes: [attr("telemetry.sdk.language", "webjs"), attr("telemetry.sdk.name", "@bigbertha/sdk"), attr("telemetry.sdk.version", VERSION)] },
|
|
96
|
+
scopeSpans: [
|
|
97
|
+
{
|
|
98
|
+
scope: { name: "@bigbertha/sdk", version: VERSION },
|
|
99
|
+
spans: batch.map((s) => ({
|
|
100
|
+
traceId,
|
|
101
|
+
spanId: s.spanId,
|
|
102
|
+
parentSpanId: s.parentSpanId,
|
|
103
|
+
name: s.name,
|
|
104
|
+
kind: s.kind,
|
|
105
|
+
startTimeUnixNano: nanos(s.start),
|
|
106
|
+
endTimeUnixNano: nanos(s.end),
|
|
107
|
+
attributes: Object.entries(s.attributes).map(([k, v]) => attr(k, v)),
|
|
108
|
+
status: s.error ? { code: 2, message: s.error } : {},
|
|
109
|
+
})),
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
}
|
package/dist/core/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// One client per app: builds events, scrubs them, batches them and sends them,
|
|
2
2
|
// backing off when bertha asks it to.
|
|
3
3
|
import { builtRelease, debugIdsFor } from "./debug-ids.js";
|
|
4
|
+
import { activeSpan } from "./otel.js";
|
|
4
5
|
import { scrubText, scrubUrl } from "./scrub.js";
|
|
5
6
|
import { parseStack } from "./stack.js";
|
|
6
7
|
import { versions } from "./versions.js";
|
|
@@ -118,7 +119,7 @@ export class Client {
|
|
|
118
119
|
tags: { ...this.tags, ...ctx.tags },
|
|
119
120
|
fingerprint: ctx.fingerprint,
|
|
120
121
|
request: request && ctx.status !== undefined ? { ...request, status: ctx.status } : request,
|
|
121
|
-
trace: ctx.trace ?? fromReq.trace,
|
|
122
|
+
trace: ctx.trace ?? activeSpan() ?? fromReq.trace,
|
|
122
123
|
};
|
|
123
124
|
try {
|
|
124
125
|
this.enrich?.(event);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The active OpenTelemetry span, when the app runs OpenTelemetry, read off the API's
|
|
2
|
+
// global so the SDK doesn't depend on @opentelemetry/api.
|
|
3
|
+
const API = Symbol.for("opentelemetry.js.api.1");
|
|
4
|
+
const SPAN = Symbol.for("OpenTelemetry Context Key SPAN");
|
|
5
|
+
const INVALID = /^0*$/;
|
|
6
|
+
/** The span the code is running in, so an error lands on it in the trace. */
|
|
7
|
+
export function activeSpan() {
|
|
8
|
+
try {
|
|
9
|
+
const api = globalThis[API];
|
|
10
|
+
const c = api?.context?.active?.().getValue?.(SPAN)?.spanContext?.();
|
|
11
|
+
if (!c?.traceId || !c.spanId || INVALID.test(c.traceId) || INVALID.test(c.spanId))
|
|
12
|
+
return undefined;
|
|
13
|
+
return { traceId: c.traceId, spanId: c.spanId };
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface OtlpOptions {
|
|
2
|
+
url?: string;
|
|
3
|
+
key?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Settings for an OTLP/HTTP trace exporter (protobuf or JSON) that sends to bertha:
|
|
7
|
+
* new OTLPTraceExporter(otlp())
|
|
8
|
+
* Reads BERTHA_URL and BERTHA_KEY unless given.
|
|
9
|
+
*/
|
|
10
|
+
export declare function otlp(options?: OtlpOptions): {
|
|
11
|
+
url?: string;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Where an OpenTelemetry exporter sends spans to bertha.
|
|
2
|
+
import { firstEnv } from "./env.js";
|
|
3
|
+
import { endpoint } from "./transport.js";
|
|
4
|
+
/**
|
|
5
|
+
* Settings for an OTLP/HTTP trace exporter (protobuf or JSON) that sends to bertha:
|
|
6
|
+
* new OTLPTraceExporter(otlp())
|
|
7
|
+
* Reads BERTHA_URL and BERTHA_KEY unless given.
|
|
8
|
+
*/
|
|
9
|
+
export function otlp(options = {}) {
|
|
10
|
+
const key = options.key ?? firstEnv("BERTHA_KEY", "NEXT_PUBLIC_BERTHA_KEY");
|
|
11
|
+
const url = options.url ?? firstEnv("BERTHA_URL", "NEXT_PUBLIC_BERTHA_URL");
|
|
12
|
+
if (!key || !url) {
|
|
13
|
+
console.warn("[bertha] Set BERTHA_KEY and BERTHA_URL (or pass key and url). No spans will reach bertha.");
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
return { url: endpoint(url, "traces"), headers: { authorization: `Bearer ${key}` } };
|
|
17
|
+
}
|
package/dist/core/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.8.0";
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
1
|
+
export const VERSION = "0.8.0";
|
package/dist/edge/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Client } from "../core/client.ts";
|
|
2
2
|
import type { Options } from "../core/types.ts";
|
|
3
3
|
export { addBreadcrumb, captureException, captureMessage, flush, getClient, setTag, setUser } from "../core/hub.ts";
|
|
4
|
+
export { otlp } from "../core/otlp.ts";
|
|
5
|
+
export type { OtlpOptions } from "../core/otlp.ts";
|
|
4
6
|
export type { CaptureContext, IngestEvent, Options } from "../core/types.ts";
|
|
5
7
|
export declare function init(options?: Partial<Options>): Client | undefined;
|
package/dist/edge/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { fromAnyRequest } from "../core/request.js";
|
|
|
7
7
|
import { fetchTransport, streamGzip } from "../core/transport.js";
|
|
8
8
|
import { VERSION } from "../core/version.js";
|
|
9
9
|
export { addBreadcrumb, captureException, captureMessage, flush, getClient, setTag, setUser } from "../core/hub.js";
|
|
10
|
+
export { otlp } from "../core/otlp.js";
|
|
10
11
|
export function init(options = {}) {
|
|
11
12
|
const key = options.key ?? firstEnv("BERTHA_KEY", "NEXT_PUBLIC_BERTHA_KEY");
|
|
12
13
|
const url = options.url ?? firstEnv("BERTHA_URL", "NEXT_PUBLIC_BERTHA_URL");
|
package/dist/next/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { otlp } from "../core/otlp.ts";
|
|
2
|
+
export type { OtlpOptions } from "../core/otlp.ts";
|
|
1
3
|
export declare function register(): Promise<void>;
|
|
2
4
|
interface NextRequestInfo {
|
|
3
5
|
path: string;
|
|
@@ -13,4 +15,3 @@ interface NextErrorContext {
|
|
|
13
15
|
}
|
|
14
16
|
/** Next calls this for errors in server components, route handlers, actions and middleware. */
|
|
15
17
|
export declare function onRequestError(error: unknown, request: NextRequestInfo, context: NextErrorContext): Promise<void>;
|
|
16
|
-
export {};
|
package/dist/next/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// export { register, onRequestError } from "@bigbertha/sdk/next"
|
|
3
3
|
import { captureException, flush } from "../core/hub.js";
|
|
4
4
|
import { fromRequest } from "../core/request.js";
|
|
5
|
+
export { otlp } from "../core/otlp.js";
|
|
5
6
|
export async function register() {
|
|
6
7
|
if (process.env.NEXT_RUNTIME === "nodejs")
|
|
7
8
|
(await import("../node/index.js")).init();
|
package/dist/node/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Client } from "../core/client.ts";
|
|
2
2
|
import type { Options } from "../core/types.ts";
|
|
3
3
|
export { addBreadcrumb, captureException, captureMessage, flush, getClient, setTag, setUser } from "../core/hub.ts";
|
|
4
|
+
export { otlp } from "../core/otlp.ts";
|
|
5
|
+
export type { OtlpOptions } from "../core/otlp.ts";
|
|
4
6
|
export type { CaptureContext, IngestEvent, Options } from "../core/types.ts";
|
|
5
7
|
export interface NodeOptions extends Partial<Options> {
|
|
6
8
|
/** Report uncaught exceptions and unhandled rejections, then exit as Node would. On by default. */
|
package/dist/node/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { endpoint, fetchTransport } from "../core/transport.js";
|
|
|
12
12
|
import { VERSION } from "../core/version.js";
|
|
13
13
|
import { addContext } from "./context.js";
|
|
14
14
|
export { addBreadcrumb, captureException, captureMessage, flush, getClient, setTag, setUser } from "../core/hub.js";
|
|
15
|
+
export { otlp } from "../core/otlp.js";
|
|
15
16
|
export const HEARTBEAT_MS = 20 * 60 * 1000;
|
|
16
17
|
/** V8 keeps 10 frames, which a framework's own calls can use up before reaching yours. */
|
|
17
18
|
export const STACK_FRAMES = 50;
|