@hasna/logs 0.3.25 → 0.3.27
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 +33 -10
- package/dashboard/dist/assets/index-C0wZYq1m.js +53 -0
- package/dashboard/dist/assets/index-DGNrK5qb.css +1 -0
- package/dashboard/dist/index.html +14 -0
- package/dist/cli/index.js +8511 -177
- package/dist/count-bmj4r2zb.js +10 -0
- package/dist/{diagnose-e0w5rwbc.js → diagnose-3q5cy9ra.js} +2 -2
- package/dist/{export-c3eqjste.js → export-cngdb9fh.js} +1 -1
- package/dist/{http-zm3ph78w.js → http-r0xc3d2s.js} +79 -8
- package/dist/index-931pbyn5.js +141 -0
- package/dist/index-b5c72f1p.js +7 -0
- package/dist/{index-p1vgwwsz.js → index-bnr19y0h.js} +596 -37
- package/dist/{index-7w7v7hnr.js → index-by1pdzbr.js} +14 -5
- package/dist/{index-3dr7d80h.js → index-e1930v9b.js} +12 -8
- package/dist/{index-eh9bkbpa.js → index-e72k53yq.js} +10 -2
- package/dist/{index-edn08m6f.js → index-gcd14q2f.js} +9 -6
- package/dist/index-hq6kzaah.js +26 -0
- package/dist/index-j34f36wy.js +5672 -0
- package/dist/index-p4dbdzx4.js +1849 -0
- package/dist/{index-5qznfyah.js → index-q27bgpr1.js} +1086 -1646
- package/dist/index-t3x838zw.js +2583 -0
- package/dist/{index-ww5ggfv3.js → index-zkb3z95a.js} +12 -9
- package/dist/index.js +2982 -22
- package/dist/{jobs-ypmmc2ma.js → jobs-hsgyhfvm.js} +2 -1
- package/dist/mcp/index.js +1473 -4286
- package/dist/{query-7jwj05er.js → query-c5a43zx3.js} +3 -2
- package/dist/server/index.js +2944 -417
- package/dist/storage.js +50 -0
- package/package.json +27 -8
- package/biome.json +0 -13
- package/bun.lock +0 -376
- package/dashboard/README.md +0 -73
- package/dashboard/bun.lock +0 -526
- package/dashboard/eslint.config.js +0 -23
- package/dashboard/index.html +0 -13
- package/dashboard/package.json +0 -32
- package/dashboard/src/App.css +0 -184
- package/dashboard/src/App.tsx +0 -49
- package/dashboard/src/api.ts +0 -33
- package/dashboard/src/assets/hero.png +0 -0
- package/dashboard/src/assets/react.svg +0 -1
- package/dashboard/src/assets/vite.svg +0 -1
- package/dashboard/src/index.css +0 -111
- package/dashboard/src/main.tsx +0 -10
- package/dashboard/src/pages/Alerts.tsx +0 -69
- package/dashboard/src/pages/Issues.tsx +0 -50
- package/dashboard/src/pages/Perf.tsx +0 -75
- package/dashboard/src/pages/Projects.tsx +0 -67
- package/dashboard/src/pages/Summary.tsx +0 -67
- package/dashboard/src/pages/Tail.tsx +0 -65
- package/dashboard/tsconfig.app.json +0 -28
- package/dashboard/tsconfig.json +0 -7
- package/dashboard/tsconfig.node.json +0 -26
- package/dashboard/vite.config.ts +0 -14
- package/dist/count-x3n7qg3c.js +0 -9
- package/dist/index-5cj74qka.js +0 -10803
- package/dist/index-997bkzr2.js +0 -15
- package/dist/index-kezb178p.js +0 -1241
- package/dist/index-pen6t0yc.js +0 -10794
- package/sdk/package.json +0 -27
- package/sdk/src/index.ts +0 -143
- package/sdk/src/types.ts +0 -56
- package/src/cli/entrypoints.test.ts +0 -63
- package/src/cli/index.ts +0 -471
- package/src/db/index.test.ts +0 -33
- package/src/db/index.ts +0 -189
- package/src/db/migrations/001_alert_rules.ts +0 -21
- package/src/db/migrations/002_issues.ts +0 -21
- package/src/db/migrations/003_retention.ts +0 -15
- package/src/db/migrations/004_page_auth.ts +0 -13
- package/src/db/pg-migrations.ts +0 -167
- package/src/index.ts +0 -1
- package/src/lib/alerts.test.ts +0 -67
- package/src/lib/alerts.ts +0 -117
- package/src/lib/browser-script.test.ts +0 -35
- package/src/lib/browser-script.ts +0 -31
- package/src/lib/compare.test.ts +0 -52
- package/src/lib/compare.ts +0 -85
- package/src/lib/count.test.ts +0 -44
- package/src/lib/count.ts +0 -55
- package/src/lib/diagnose.test.ts +0 -55
- package/src/lib/diagnose.ts +0 -91
- package/src/lib/export.test.ts +0 -66
- package/src/lib/export.ts +0 -65
- package/src/lib/github.ts +0 -38
- package/src/lib/health.test.ts +0 -48
- package/src/lib/health.ts +0 -51
- package/src/lib/ingest.test.ts +0 -57
- package/src/lib/ingest.ts +0 -78
- package/src/lib/issues.test.ts +0 -79
- package/src/lib/issues.ts +0 -70
- package/src/lib/jobs.test.ts +0 -69
- package/src/lib/jobs.ts +0 -63
- package/src/lib/lighthouse.ts +0 -65
- package/src/lib/package-meta.test.ts +0 -43
- package/src/lib/package-meta.ts +0 -80
- package/src/lib/page-auth.test.ts +0 -54
- package/src/lib/page-auth.ts +0 -48
- package/src/lib/parse-time.test.ts +0 -37
- package/src/lib/parse-time.ts +0 -14
- package/src/lib/perf.test.ts +0 -45
- package/src/lib/perf.ts +0 -46
- package/src/lib/projects.test.ts +0 -73
- package/src/lib/projects.ts +0 -69
- package/src/lib/query.test.ts +0 -104
- package/src/lib/query.ts +0 -84
- package/src/lib/retention.test.ts +0 -42
- package/src/lib/retention.ts +0 -62
- package/src/lib/rotate.test.ts +0 -37
- package/src/lib/rotate.ts +0 -27
- package/src/lib/scanner.ts +0 -131
- package/src/lib/scheduler.ts +0 -63
- package/src/lib/session-context.ts +0 -28
- package/src/lib/summarize.test.ts +0 -38
- package/src/lib/summarize.ts +0 -23
- package/src/mcp/http.test.ts +0 -92
- package/src/mcp/http.ts +0 -135
- package/src/mcp/index.test.ts +0 -27
- package/src/mcp/index.ts +0 -444
- package/src/server/index.ts +0 -61
- package/src/server/routes/alerts.ts +0 -32
- package/src/server/routes/issues.ts +0 -43
- package/src/server/routes/jobs.ts +0 -32
- package/src/server/routes/logs.ts +0 -113
- package/src/server/routes/perf.ts +0 -23
- package/src/server/routes/projects.ts +0 -67
- package/src/server/routes/stream.ts +0 -43
- package/src/server/server.test.ts +0 -194
- package/src/types/index.ts +0 -119
- package/tsconfig.json +0 -22
- /package/dashboard/{public → dist}/favicon.svg +0 -0
- /package/dashboard/{public → dist}/icons.svg +0 -0
package/dist/index.js
CHANGED
|
@@ -2,34 +2,172 @@
|
|
|
2
2
|
import"./index-re3ntm60.js";
|
|
3
3
|
|
|
4
4
|
// sdk/src/index.ts
|
|
5
|
+
var expressCaptureSymbol = Symbol.for("@hasna/logs.express.capture");
|
|
5
6
|
var DEFAULT_URL = "http://localhost:3460";
|
|
6
7
|
|
|
7
8
|
class LogsClient {
|
|
8
9
|
url;
|
|
9
10
|
projectId;
|
|
11
|
+
source;
|
|
12
|
+
environment;
|
|
13
|
+
releaseId;
|
|
14
|
+
appId;
|
|
15
|
+
machineId;
|
|
16
|
+
repoId;
|
|
17
|
+
processId;
|
|
18
|
+
runId;
|
|
19
|
+
sessionId;
|
|
10
20
|
headers;
|
|
21
|
+
writeHeaders;
|
|
22
|
+
hasBrowserToken;
|
|
11
23
|
constructor(opts = {}) {
|
|
12
24
|
this.url = (opts.url ?? DEFAULT_URL).replace(/\/$/, "");
|
|
13
25
|
this.projectId = opts.projectId;
|
|
26
|
+
this.source = opts.source ?? "sdk";
|
|
27
|
+
this.environment = opts.environment;
|
|
28
|
+
this.releaseId = opts.releaseId;
|
|
29
|
+
this.appId = opts.appId;
|
|
30
|
+
this.machineId = opts.machineId;
|
|
31
|
+
this.repoId = opts.repoId;
|
|
32
|
+
this.processId = opts.processId;
|
|
33
|
+
this.runId = opts.runId;
|
|
34
|
+
this.sessionId = opts.sessionId;
|
|
35
|
+
this.hasBrowserToken = Boolean(opts.browserToken);
|
|
14
36
|
this.headers = { "Content-Type": "application/json" };
|
|
15
37
|
if (opts.apiKey)
|
|
16
38
|
this.headers["Authorization"] = `Bearer ${opts.apiKey}`;
|
|
39
|
+
this.writeHeaders = { ...this.headers };
|
|
40
|
+
if (opts.browserToken)
|
|
41
|
+
this.writeHeaders["X-Logs-Browser-Token"] = opts.browserToken;
|
|
17
42
|
}
|
|
18
43
|
async push(entry) {
|
|
19
44
|
const res = await fetch(`${this.url}/api/logs`, {
|
|
20
45
|
method: "POST",
|
|
21
|
-
headers: this.
|
|
46
|
+
headers: this.writeHeaders,
|
|
22
47
|
body: JSON.stringify({ project_id: this.projectId, ...entry })
|
|
23
48
|
});
|
|
24
|
-
return res
|
|
49
|
+
return readJson(res);
|
|
25
50
|
}
|
|
26
51
|
async pushBatch(entries) {
|
|
27
52
|
const res = await fetch(`${this.url}/api/logs`, {
|
|
28
53
|
method: "POST",
|
|
29
|
-
headers: this.
|
|
54
|
+
headers: this.writeHeaders,
|
|
30
55
|
body: JSON.stringify(entries.map((e) => ({ project_id: this.projectId, ...e })))
|
|
31
56
|
});
|
|
32
|
-
return res
|
|
57
|
+
return readJson(res);
|
|
58
|
+
}
|
|
59
|
+
async pushEvent(event) {
|
|
60
|
+
const res = await fetch(`${this.url}/api/events`, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: this.writeHeaders,
|
|
63
|
+
body: JSON.stringify(this.withDefaultEventContext(event))
|
|
64
|
+
});
|
|
65
|
+
return readJson(res);
|
|
66
|
+
}
|
|
67
|
+
async pushEvents(events) {
|
|
68
|
+
const res = await fetch(`${this.url}/api/events`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: this.writeHeaders,
|
|
71
|
+
body: JSON.stringify(events.map((event) => this.withDefaultEventContext(event)))
|
|
72
|
+
});
|
|
73
|
+
return readJson(res);
|
|
74
|
+
}
|
|
75
|
+
async pushStructuredLog(record, opts = {}) {
|
|
76
|
+
const url = `${this.url}/api/logs/structured${structuredLogQuery(this.withDefaultStructuredLogContext(opts))}`;
|
|
77
|
+
const res = await fetch(url, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: this.headers,
|
|
80
|
+
body: JSON.stringify(record)
|
|
81
|
+
});
|
|
82
|
+
return readJson(res);
|
|
83
|
+
}
|
|
84
|
+
async pushStructuredLogs(records, opts = {}) {
|
|
85
|
+
if (records.length === 0)
|
|
86
|
+
return { inserted: 0, events: [] };
|
|
87
|
+
const context = this.withDefaultStructuredLogContext(opts);
|
|
88
|
+
const url = `${this.url}/api/logs/structured${structuredLogQuery(context)}`;
|
|
89
|
+
const body = structuredLogBatchBody(records, context);
|
|
90
|
+
const res = await fetch(url, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: this.headers,
|
|
93
|
+
body: JSON.stringify(body)
|
|
94
|
+
});
|
|
95
|
+
return readJson(res);
|
|
96
|
+
}
|
|
97
|
+
async captureException(error, opts = {}) {
|
|
98
|
+
const normalized = normalizeError(error);
|
|
99
|
+
return this.pushEvent({
|
|
100
|
+
type: "exception",
|
|
101
|
+
severity: opts.severity ?? "error",
|
|
102
|
+
message: opts.message ?? normalized.message,
|
|
103
|
+
trace_id: opts.trace_id,
|
|
104
|
+
span_id: opts.span_id,
|
|
105
|
+
parent_span_id: opts.parent_span_id,
|
|
106
|
+
body: {
|
|
107
|
+
exception: {
|
|
108
|
+
type: normalized.type,
|
|
109
|
+
value: normalized.message,
|
|
110
|
+
stack_trace: normalized.stack,
|
|
111
|
+
handled: opts.handled ?? false,
|
|
112
|
+
mechanism: opts.mechanism ?? "sdk.captureException"
|
|
113
|
+
},
|
|
114
|
+
...opts.body
|
|
115
|
+
},
|
|
116
|
+
attributes: {
|
|
117
|
+
exception_type: normalized.type,
|
|
118
|
+
stack_trace: normalized.stack,
|
|
119
|
+
handled: opts.handled ?? false,
|
|
120
|
+
mechanism: opts.mechanism ?? "sdk.captureException",
|
|
121
|
+
...opts.attributes
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async captureMetric(name, value, opts = {}) {
|
|
126
|
+
return this.pushEvent({
|
|
127
|
+
type: "metric",
|
|
128
|
+
severity: "info",
|
|
129
|
+
message: name,
|
|
130
|
+
trace_id: opts.trace_id,
|
|
131
|
+
span_id: opts.span_id,
|
|
132
|
+
body: {
|
|
133
|
+
name,
|
|
134
|
+
value,
|
|
135
|
+
kind: opts.kind ?? "gauge",
|
|
136
|
+
unit: opts.unit
|
|
137
|
+
},
|
|
138
|
+
attributes: {
|
|
139
|
+
name,
|
|
140
|
+
value,
|
|
141
|
+
metric_kind: opts.kind ?? "gauge",
|
|
142
|
+
unit: opts.unit,
|
|
143
|
+
...opts.attributes
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
async captureSpan(span) {
|
|
148
|
+
return this.pushEvent({
|
|
149
|
+
type: "span",
|
|
150
|
+
severity: span.status === "error" ? "error" : "info",
|
|
151
|
+
message: span.name,
|
|
152
|
+
trace_id: span.trace_id,
|
|
153
|
+
span_id: span.span_id,
|
|
154
|
+
parent_span_id: span.parent_span_id,
|
|
155
|
+
body: {
|
|
156
|
+
name: span.name,
|
|
157
|
+
operation: span.operation,
|
|
158
|
+
status: span.status,
|
|
159
|
+
duration_ms: span.duration_ms
|
|
160
|
+
},
|
|
161
|
+
attributes: {
|
|
162
|
+
name: span.name,
|
|
163
|
+
operation: span.operation,
|
|
164
|
+
status: span.status,
|
|
165
|
+
started_at: span.started_at,
|
|
166
|
+
ended_at: span.ended_at,
|
|
167
|
+
duration_ms: span.duration_ms,
|
|
168
|
+
...span.attributes
|
|
169
|
+
}
|
|
170
|
+
});
|
|
33
171
|
}
|
|
34
172
|
async search(query = {}) {
|
|
35
173
|
const params = new URLSearchParams;
|
|
@@ -54,7 +192,7 @@ class LogsClient {
|
|
|
54
192
|
if (query.fields)
|
|
55
193
|
params.set("fields", query.fields.join(","));
|
|
56
194
|
const res = await fetch(`${this.url}/api/logs?${params}`, { headers: this.headers });
|
|
57
|
-
return res
|
|
195
|
+
return readJson(res);
|
|
58
196
|
}
|
|
59
197
|
async tail(projectId, n = 50) {
|
|
60
198
|
const params = new URLSearchParams({ n: String(n) });
|
|
@@ -62,7 +200,7 @@ class LogsClient {
|
|
|
62
200
|
if (pid)
|
|
63
201
|
params.set("project_id", pid);
|
|
64
202
|
const res = await fetch(`${this.url}/api/logs/tail?${params}`, { headers: this.headers });
|
|
65
|
-
return res
|
|
203
|
+
return readJson(res);
|
|
66
204
|
}
|
|
67
205
|
async summary(projectId, since) {
|
|
68
206
|
const params = new URLSearchParams;
|
|
@@ -72,11 +210,11 @@ class LogsClient {
|
|
|
72
210
|
if (since)
|
|
73
211
|
params.set("since", since);
|
|
74
212
|
const res = await fetch(`${this.url}/api/logs/summary?${params}`, { headers: this.headers });
|
|
75
|
-
return res
|
|
213
|
+
return readJson(res);
|
|
76
214
|
}
|
|
77
215
|
async context(traceId) {
|
|
78
216
|
const res = await fetch(`${this.url}/api/logs/${traceId}/context`, { headers: this.headers });
|
|
79
|
-
return res
|
|
217
|
+
return readJson(res);
|
|
80
218
|
}
|
|
81
219
|
async registerProject(name, githubRepo, baseUrl) {
|
|
82
220
|
const res = await fetch(`${this.url}/api/projects`, {
|
|
@@ -84,7 +222,7 @@ class LogsClient {
|
|
|
84
222
|
headers: this.headers,
|
|
85
223
|
body: JSON.stringify({ name, github_repo: githubRepo, base_url: baseUrl })
|
|
86
224
|
});
|
|
87
|
-
return res
|
|
225
|
+
return readJson(res);
|
|
88
226
|
}
|
|
89
227
|
async registerPage(projectId, url, path, name) {
|
|
90
228
|
const res = await fetch(`${this.url}/api/projects/${projectId}/pages`, {
|
|
@@ -92,7 +230,7 @@ class LogsClient {
|
|
|
92
230
|
headers: this.headers,
|
|
93
231
|
body: JSON.stringify({ url, path, name })
|
|
94
232
|
});
|
|
95
|
-
return res
|
|
233
|
+
return readJson(res);
|
|
96
234
|
}
|
|
97
235
|
async createScanJob(projectId, schedule, pageId) {
|
|
98
236
|
const res = await fetch(`${this.url}/api/jobs`, {
|
|
@@ -100,14 +238,14 @@ class LogsClient {
|
|
|
100
238
|
headers: this.headers,
|
|
101
239
|
body: JSON.stringify({ project_id: projectId, schedule, page_id: pageId })
|
|
102
240
|
});
|
|
103
|
-
return res
|
|
241
|
+
return readJson(res);
|
|
104
242
|
}
|
|
105
243
|
async perfSnapshot(projectId, pageId) {
|
|
106
244
|
const params = new URLSearchParams({ project_id: projectId });
|
|
107
245
|
if (pageId)
|
|
108
246
|
params.set("page_id", pageId);
|
|
109
247
|
const res = await fetch(`${this.url}/api/perf?${params}`, { headers: this.headers });
|
|
110
|
-
return res
|
|
248
|
+
return readJson(res);
|
|
111
249
|
}
|
|
112
250
|
async perfTrend(projectId, pageId, since, limit) {
|
|
113
251
|
const params = new URLSearchParams({ project_id: projectId });
|
|
@@ -118,39 +256,2861 @@ class LogsClient {
|
|
|
118
256
|
if (limit)
|
|
119
257
|
params.set("limit", String(limit));
|
|
120
258
|
const res = await fetch(`${this.url}/api/perf/trend?${params}`, { headers: this.headers });
|
|
121
|
-
return res
|
|
259
|
+
return readJson(res);
|
|
260
|
+
}
|
|
261
|
+
withDefaultEventContext(event) {
|
|
262
|
+
const identityContext = this.hasBrowserToken ? {} : {
|
|
263
|
+
project_id: this.projectId,
|
|
264
|
+
machine_id: this.machineId,
|
|
265
|
+
repo_id: this.repoId,
|
|
266
|
+
process_id: this.processId,
|
|
267
|
+
run_id: this.runId
|
|
268
|
+
};
|
|
269
|
+
const withContext = {
|
|
270
|
+
source: this.source,
|
|
271
|
+
environment: this.environment,
|
|
272
|
+
release_id: this.releaseId,
|
|
273
|
+
app_id: this.appId,
|
|
274
|
+
session_id: this.sessionId,
|
|
275
|
+
...identityContext,
|
|
276
|
+
...event,
|
|
277
|
+
attributes: {
|
|
278
|
+
sdk_name: "@hasna/logs-sdk",
|
|
279
|
+
sdk_runtime: runtimeName(),
|
|
280
|
+
...event.attributes ?? {}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
return this.hasBrowserToken ? stripBrowserUniversalEventIdentity(withContext) : withContext;
|
|
122
284
|
}
|
|
285
|
+
withDefaultStructuredLogContext(opts) {
|
|
286
|
+
return {
|
|
287
|
+
projectId: this.projectId,
|
|
288
|
+
environment: this.environment,
|
|
289
|
+
releaseId: this.releaseId,
|
|
290
|
+
appId: this.appId,
|
|
291
|
+
machineId: this.machineId,
|
|
292
|
+
repoId: this.repoId,
|
|
293
|
+
processId: this.processId,
|
|
294
|
+
runId: this.runId,
|
|
295
|
+
sessionId: this.sessionId,
|
|
296
|
+
...opts
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function createPinoOpenLogsTransport(opts = {}) {
|
|
301
|
+
const queue = createStructuredLogQueue({ ...opts, format: opts.format ?? "pino" });
|
|
302
|
+
const decoder = new TextDecoder;
|
|
303
|
+
let pending = "";
|
|
304
|
+
let stopped = false;
|
|
305
|
+
const parseLine = (line) => {
|
|
306
|
+
const trimmed = line.trim();
|
|
307
|
+
if (!trimmed)
|
|
308
|
+
return;
|
|
309
|
+
try {
|
|
310
|
+
queue.enqueue(JSON.parse(trimmed));
|
|
311
|
+
return;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
const normalized = toError(error);
|
|
314
|
+
opts.onError?.(normalized);
|
|
315
|
+
return normalized;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
const drain = (text) => {
|
|
319
|
+
pending += text;
|
|
320
|
+
while (true) {
|
|
321
|
+
const newline = pending.indexOf(`
|
|
322
|
+
`);
|
|
323
|
+
if (newline < 0)
|
|
324
|
+
return;
|
|
325
|
+
const line = pending.slice(0, newline).replace(/\r$/, "");
|
|
326
|
+
pending = pending.slice(newline + 1);
|
|
327
|
+
const error = parseLine(line);
|
|
328
|
+
if (error)
|
|
329
|
+
return error;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
const transport = {
|
|
333
|
+
write(chunk, encoding, callback) {
|
|
334
|
+
if (stopped) {
|
|
335
|
+
const error2 = new Error("open-logs Pino transport is stopped");
|
|
336
|
+
callbackFromArgs(encoding, callback)?.(error2);
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
const done = callbackFromArgs(encoding, callback);
|
|
340
|
+
const error = drain(chunkText(chunk, decoder));
|
|
341
|
+
if (error) {
|
|
342
|
+
done?.(error);
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
if (opts.waitForTelemetry && queue.shouldFlush()) {
|
|
346
|
+
queue.flush().then(() => done?.(), (err) => done?.(toError(err)));
|
|
347
|
+
} else {
|
|
348
|
+
if (queue.shouldFlush())
|
|
349
|
+
queue.flush().catch(opts.onError ?? noop);
|
|
350
|
+
done?.();
|
|
351
|
+
}
|
|
352
|
+
return true;
|
|
353
|
+
},
|
|
354
|
+
end(chunk, encoding, callback) {
|
|
355
|
+
let done;
|
|
356
|
+
if (typeof chunk === "function") {
|
|
357
|
+
done = chunk;
|
|
358
|
+
} else if (typeof encoding === "function") {
|
|
359
|
+
done = encoding;
|
|
360
|
+
} else {
|
|
361
|
+
done = callback;
|
|
362
|
+
}
|
|
363
|
+
if (chunk !== undefined && typeof chunk !== "function") {
|
|
364
|
+
const error = drain(chunkText(chunk, decoder));
|
|
365
|
+
if (error)
|
|
366
|
+
opts.onError?.(error);
|
|
367
|
+
}
|
|
368
|
+
const tail = decoder.decode();
|
|
369
|
+
if (tail)
|
|
370
|
+
pending += tail;
|
|
371
|
+
if (pending.trim()) {
|
|
372
|
+
const error = parseLine(pending);
|
|
373
|
+
if (error)
|
|
374
|
+
opts.onError?.(error);
|
|
375
|
+
}
|
|
376
|
+
pending = "";
|
|
377
|
+
stopped = true;
|
|
378
|
+
queue.flush().catch(opts.onError ?? noop).finally(() => {
|
|
379
|
+
queue.stop();
|
|
380
|
+
done?.();
|
|
381
|
+
});
|
|
382
|
+
},
|
|
383
|
+
flush: queue.flush,
|
|
384
|
+
stats: queue.stats,
|
|
385
|
+
stop() {
|
|
386
|
+
stopped = true;
|
|
387
|
+
queue.flush().catch(opts.onError ?? noop).finally(() => {
|
|
388
|
+
queue.stop();
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
return transport;
|
|
393
|
+
}
|
|
394
|
+
function createWinstonOpenLogsTransport(opts = {}) {
|
|
395
|
+
const queue = createStructuredLogQueue({ ...opts, format: opts.format ?? "winston" });
|
|
396
|
+
const listeners = new Map;
|
|
397
|
+
let closed = false;
|
|
398
|
+
const finish = () => {
|
|
399
|
+
if (closed)
|
|
400
|
+
return;
|
|
401
|
+
closed = true;
|
|
402
|
+
queue.stop();
|
|
403
|
+
transport.writable = false;
|
|
404
|
+
transport.emit("finish");
|
|
405
|
+
transport.emit("close");
|
|
406
|
+
};
|
|
407
|
+
const reportError = (error) => {
|
|
408
|
+
opts.onError?.(error);
|
|
409
|
+
transport.emit("error", error);
|
|
410
|
+
};
|
|
411
|
+
const transport = {
|
|
412
|
+
name: "open-logs",
|
|
413
|
+
level: opts.level,
|
|
414
|
+
silent: false,
|
|
415
|
+
writable: true,
|
|
416
|
+
_writableState: { objectMode: true },
|
|
417
|
+
pipe(destination) {
|
|
418
|
+
return destination ?? transport;
|
|
419
|
+
},
|
|
420
|
+
write(info, encoding, callback) {
|
|
421
|
+
const done = callbackFromUnknownArgs(encoding, callback);
|
|
422
|
+
transport.log(info, done);
|
|
423
|
+
return transport.writable;
|
|
424
|
+
},
|
|
425
|
+
end(info, encoding, callback) {
|
|
426
|
+
const done = callbackFromUnknownArgs(encoding, callback) ?? (typeof info === "function" ? info : undefined);
|
|
427
|
+
if (info && typeof info === "object")
|
|
428
|
+
transport.write(info);
|
|
429
|
+
queue.flush().catch(reportError).finally(() => {
|
|
430
|
+
finish();
|
|
431
|
+
done?.();
|
|
432
|
+
});
|
|
433
|
+
},
|
|
434
|
+
log(info, callback, ...legacyArgs) {
|
|
435
|
+
const normalized = normalizeWinstonLogArguments(info, callback, legacyArgs);
|
|
436
|
+
const done = normalized.callback;
|
|
437
|
+
if (transport.silent) {
|
|
438
|
+
done?.();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
queue.enqueue(normalized.info);
|
|
442
|
+
queueMicrotask(() => transport.emit("logged", normalized.info));
|
|
443
|
+
if (opts.waitForTelemetry && queue.shouldFlush()) {
|
|
444
|
+
queue.flush().then(() => done?.(), (error) => {
|
|
445
|
+
reportError(error);
|
|
446
|
+
done?.();
|
|
447
|
+
});
|
|
448
|
+
} else {
|
|
449
|
+
if (queue.shouldFlush())
|
|
450
|
+
queue.flush().catch((error) => {
|
|
451
|
+
reportError(error);
|
|
452
|
+
});
|
|
453
|
+
done?.();
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
flush: queue.flush,
|
|
457
|
+
stats: queue.stats,
|
|
458
|
+
close() {
|
|
459
|
+
queue.flush().catch(reportError).finally(finish);
|
|
460
|
+
},
|
|
461
|
+
stop() {
|
|
462
|
+
transport.close();
|
|
463
|
+
},
|
|
464
|
+
on(event, listener) {
|
|
465
|
+
let eventListeners = listeners.get(event);
|
|
466
|
+
if (!eventListeners) {
|
|
467
|
+
eventListeners = new Set;
|
|
468
|
+
listeners.set(event, eventListeners);
|
|
469
|
+
}
|
|
470
|
+
eventListeners.add(listener);
|
|
471
|
+
return transport;
|
|
472
|
+
},
|
|
473
|
+
once(event, listener) {
|
|
474
|
+
const onceListener = (...args) => {
|
|
475
|
+
transport.removeListener(event, onceListener);
|
|
476
|
+
listener(...args);
|
|
477
|
+
};
|
|
478
|
+
return transport.on(event, onceListener);
|
|
479
|
+
},
|
|
480
|
+
off(event, listener) {
|
|
481
|
+
return transport.removeListener(event, listener);
|
|
482
|
+
},
|
|
483
|
+
removeListener(event, listener) {
|
|
484
|
+
listeners.get(event)?.delete(listener);
|
|
485
|
+
return transport;
|
|
486
|
+
},
|
|
487
|
+
emit(event, ...args) {
|
|
488
|
+
const eventListeners = listeners.get(event);
|
|
489
|
+
if (!eventListeners?.size)
|
|
490
|
+
return false;
|
|
491
|
+
for (const listener of [...eventListeners])
|
|
492
|
+
listener(...args);
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
return transport;
|
|
497
|
+
}
|
|
498
|
+
async function captureHttpRequest(request, run, opts) {
|
|
499
|
+
const source = opts.source ?? opts.framework ?? runtimeName();
|
|
500
|
+
const client = opts.client ?? new LogsClient({ ...opts, source });
|
|
501
|
+
const startedAt = Date.now();
|
|
502
|
+
const startedIso = new Date(startedAt).toISOString();
|
|
503
|
+
const { traceId, parentSpanId } = traceContextFromRequest(request);
|
|
504
|
+
const spanId = randomHex(16);
|
|
505
|
+
const method = request.method || "GET";
|
|
506
|
+
const url = safeUrlParts(request.url);
|
|
507
|
+
const operation = opts.operation ?? "http.server";
|
|
508
|
+
try {
|
|
509
|
+
const result = await run();
|
|
510
|
+
const response = responseLike(result);
|
|
511
|
+
const durationMs = Date.now() - startedAt;
|
|
512
|
+
const statusCode = response?.status;
|
|
513
|
+
const route = routeForRequest(request, opts.route);
|
|
514
|
+
const name = `${method} ${route}`;
|
|
515
|
+
const event = httpServerSpanEvent({
|
|
516
|
+
source,
|
|
517
|
+
request,
|
|
518
|
+
response,
|
|
519
|
+
traceId,
|
|
520
|
+
spanId,
|
|
521
|
+
parentSpanId,
|
|
522
|
+
method,
|
|
523
|
+
route,
|
|
524
|
+
url,
|
|
525
|
+
operation,
|
|
526
|
+
name,
|
|
527
|
+
statusCode,
|
|
528
|
+
durationMs,
|
|
529
|
+
startedIso,
|
|
530
|
+
requestHeaderNames: opts.requestHeaderNames,
|
|
531
|
+
responseHeaderNames: opts.responseHeaderNames,
|
|
532
|
+
framework: opts.framework
|
|
533
|
+
});
|
|
534
|
+
await sendRequestTelemetry(client, [event], opts.waitForTelemetry);
|
|
535
|
+
return result;
|
|
536
|
+
} catch (error) {
|
|
537
|
+
const durationMs = Date.now() - startedAt;
|
|
538
|
+
const normalized = normalizeError(error);
|
|
539
|
+
const route = routeForRequest(request, opts.route);
|
|
540
|
+
const name = `${method} ${route}`;
|
|
541
|
+
const span = httpServerSpanEvent({
|
|
542
|
+
source,
|
|
543
|
+
request,
|
|
544
|
+
traceId,
|
|
545
|
+
spanId,
|
|
546
|
+
parentSpanId,
|
|
547
|
+
method,
|
|
548
|
+
route,
|
|
549
|
+
url,
|
|
550
|
+
operation,
|
|
551
|
+
name,
|
|
552
|
+
statusCode: 500,
|
|
553
|
+
durationMs,
|
|
554
|
+
startedIso,
|
|
555
|
+
requestHeaderNames: opts.requestHeaderNames,
|
|
556
|
+
framework: opts.framework,
|
|
557
|
+
errorType: normalized.type
|
|
558
|
+
});
|
|
559
|
+
const exception = {
|
|
560
|
+
type: "exception",
|
|
561
|
+
source,
|
|
562
|
+
severity: "error",
|
|
563
|
+
trace_id: traceId,
|
|
564
|
+
span_id: spanId,
|
|
565
|
+
parent_span_id: parentSpanId,
|
|
566
|
+
message: normalized.message,
|
|
567
|
+
body: {
|
|
568
|
+
exception: {
|
|
569
|
+
type: normalized.type,
|
|
570
|
+
value: normalized.message,
|
|
571
|
+
stack_trace: normalized.stack,
|
|
572
|
+
handled: false,
|
|
573
|
+
mechanism: `${opts.framework ?? "fetch"}.request`
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
attributes: {
|
|
577
|
+
exception_type: normalized.type,
|
|
578
|
+
stack_trace: normalized.stack,
|
|
579
|
+
handled: false,
|
|
580
|
+
mechanism: `${opts.framework ?? "fetch"}.request`,
|
|
581
|
+
operation,
|
|
582
|
+
method,
|
|
583
|
+
route,
|
|
584
|
+
url_scheme: url.scheme,
|
|
585
|
+
url_host: url.host,
|
|
586
|
+
url_path: url.path,
|
|
587
|
+
query_present: url.queryPresent,
|
|
588
|
+
framework: opts.framework,
|
|
589
|
+
duration_ms: durationMs
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
await sendRequestTelemetry(client, [span, exception], opts.waitForTelemetry);
|
|
593
|
+
throw error;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
function instrumentFetchHandler(handler, opts) {
|
|
597
|
+
return (request, ...args) => captureHttpRequest(request, () => handler(request, ...args), opts);
|
|
598
|
+
}
|
|
599
|
+
function createHonoTelemetryMiddleware(opts) {
|
|
600
|
+
return async (c, next) => {
|
|
601
|
+
const route = opts.route ?? (() => c.req.routePath ?? c.req.path);
|
|
602
|
+
await captureHttpRequest(c.req.raw, async () => {
|
|
603
|
+
await next();
|
|
604
|
+
return c.res ?? new Response(null, { status: 204 });
|
|
605
|
+
}, { ...opts, route, framework: opts.framework ?? "hono" });
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function captureNodeHttpRequest(request, response, opts) {
|
|
609
|
+
const startedAt = Date.now();
|
|
610
|
+
const startedIso = new Date(startedAt).toISOString();
|
|
611
|
+
let pending;
|
|
612
|
+
let completed = false;
|
|
613
|
+
let stopped = false;
|
|
614
|
+
const errorMonitor = nodeErrorMonitorEvent();
|
|
615
|
+
const listenerCleanups = [];
|
|
616
|
+
const finish = (error) => {
|
|
617
|
+
if (completed || stopped)
|
|
618
|
+
return;
|
|
619
|
+
completed = true;
|
|
620
|
+
cleanupNodeListeners(listenerCleanups);
|
|
621
|
+
pending = emitNodeHttpRequestTelemetry({
|
|
622
|
+
request,
|
|
623
|
+
response,
|
|
624
|
+
opts,
|
|
625
|
+
startedAt,
|
|
626
|
+
startedIso,
|
|
627
|
+
error
|
|
628
|
+
}).catch(() => {});
|
|
629
|
+
};
|
|
630
|
+
const onFinish = () => finish();
|
|
631
|
+
const onClose = () => {
|
|
632
|
+
if (!completed)
|
|
633
|
+
finish(new ResponseClosedError);
|
|
634
|
+
};
|
|
635
|
+
const onResponseError = (error) => finish(error);
|
|
636
|
+
const onRequestError = (error) => finish(error);
|
|
637
|
+
const onRequestAborted = () => finish(new ResponseClosedError);
|
|
638
|
+
listenerCleanups.push(addNodeListener(response, "finish", onFinish));
|
|
639
|
+
listenerCleanups.push(addNodeListener(response, "close", onClose));
|
|
640
|
+
if (errorMonitor)
|
|
641
|
+
listenerCleanups.push(addNodeListener(response, errorMonitor, onResponseError));
|
|
642
|
+
if (errorMonitor)
|
|
643
|
+
listenerCleanups.push(addNodeListener(request, errorMonitor, onRequestError));
|
|
644
|
+
listenerCleanups.push(addNodeListener(request, "aborted", onRequestAborted));
|
|
645
|
+
return {
|
|
646
|
+
finish,
|
|
647
|
+
async flush() {
|
|
648
|
+
await pending;
|
|
649
|
+
},
|
|
650
|
+
stop() {
|
|
651
|
+
stopped = true;
|
|
652
|
+
cleanupNodeListeners(listenerCleanups);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function createExpressTelemetryMiddleware(opts) {
|
|
657
|
+
return (request, response, next) => {
|
|
658
|
+
const capture = captureNodeHttpRequest(request, response, { ...opts, framework: opts.framework ?? "express" });
|
|
659
|
+
setExpressCapture(request, capture);
|
|
660
|
+
try {
|
|
661
|
+
next();
|
|
662
|
+
} catch (error) {
|
|
663
|
+
capture.finish(error);
|
|
664
|
+
clearExpressCapture(request);
|
|
665
|
+
throw error;
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
function createExpressErrorTelemetryMiddleware(opts) {
|
|
670
|
+
return (error, request, response, next) => {
|
|
671
|
+
const capture = getExpressCapture(request) ?? captureNodeHttpRequest(request, response, { ...opts, framework: opts.framework ?? "express" });
|
|
672
|
+
capture.finish(error);
|
|
673
|
+
clearExpressCapture(request);
|
|
674
|
+
next(error);
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function createFastifyTelemetryHooks(opts) {
|
|
678
|
+
const captures = new WeakMap;
|
|
679
|
+
const onRequest = (request, reply, done) => {
|
|
680
|
+
captures.set(request, captureNodeHttpRequest(fastifyRequestLike(request), fastifyResponseLike(reply), { ...opts, framework: opts.framework ?? "fastify", route: opts.route ?? fastifyRoute }));
|
|
681
|
+
done?.();
|
|
682
|
+
};
|
|
683
|
+
const onResponse = (request, _reply, done) => {
|
|
684
|
+
const capture = captures.get(request);
|
|
685
|
+
captures.delete(request);
|
|
686
|
+
capture?.finish();
|
|
687
|
+
done?.();
|
|
688
|
+
};
|
|
689
|
+
const onError = (request, _reply, error, done) => {
|
|
690
|
+
const capture = captures.get(request);
|
|
691
|
+
captures.delete(request);
|
|
692
|
+
capture?.finish(error);
|
|
693
|
+
done?.();
|
|
694
|
+
};
|
|
695
|
+
return { onRequest, onResponse, onError };
|
|
123
696
|
}
|
|
124
697
|
function initLogs(opts) {
|
|
125
|
-
|
|
698
|
+
const browser = globalThis;
|
|
699
|
+
if (!browser.window || !browser.location)
|
|
126
700
|
return;
|
|
127
701
|
const serverUrl = (opts.url ?? DEFAULT_URL).replace(/\/$/, "");
|
|
128
|
-
const client = new LogsClient({ url: serverUrl, projectId: opts.projectId });
|
|
702
|
+
const client = new LogsClient({ url: serverUrl, projectId: opts.projectId, browserToken: opts.browserToken, apiKey: opts.apiKey });
|
|
129
703
|
const q = [];
|
|
130
704
|
const flush = () => {
|
|
131
705
|
if (q.length)
|
|
132
706
|
client.pushBatch(q.splice(0)).catch(() => {});
|
|
133
707
|
};
|
|
708
|
+
const currentHref = () => browser.location?.href ?? "";
|
|
134
709
|
setInterval(flush, 2000);
|
|
135
710
|
const _ce = console.error.bind(console);
|
|
136
711
|
console.error = (...args) => {
|
|
137
712
|
_ce(...args);
|
|
138
|
-
q.push({ level: "error", message: args.map(String).join(" "), source: "script", url:
|
|
713
|
+
q.push({ level: "error", message: args.map(String).join(" "), source: "script", url: currentHref() });
|
|
139
714
|
};
|
|
140
715
|
const _cw = console.warn.bind(console);
|
|
141
716
|
console.warn = (...args) => {
|
|
142
717
|
_cw(...args);
|
|
143
|
-
q.push({ level: "warn", message: args.map(String).join(" "), source: "script", url:
|
|
718
|
+
q.push({ level: "warn", message: args.map(String).join(" "), source: "script", url: currentHref() });
|
|
144
719
|
};
|
|
145
|
-
window.addEventListener("error", (e) => {
|
|
146
|
-
q.push({ level: "error", message: e.message, stack_trace: e.error?.stack, source: "script", url:
|
|
720
|
+
browser.window.addEventListener("error", (e) => {
|
|
721
|
+
q.push({ level: "error", message: e.message ?? "Browser error", stack_trace: e.error?.stack, source: "script", url: currentHref() });
|
|
722
|
+
});
|
|
723
|
+
browser.window.addEventListener("unhandledrejection", (e) => {
|
|
724
|
+
const reason = typeof e.reason === "string" ? { message: e.reason } : e.reason;
|
|
725
|
+
q.push({ level: "error", message: `Unhandled: ${reason?.message ?? "promise rejection"}`, stack_trace: reason?.stack, source: "script", url: currentHref() });
|
|
147
726
|
});
|
|
148
|
-
window.addEventListener("
|
|
149
|
-
|
|
727
|
+
browser.window.addEventListener("beforeunload", flush);
|
|
728
|
+
}
|
|
729
|
+
function initUniversalLogs(opts) {
|
|
730
|
+
const browser = globalThis;
|
|
731
|
+
if (browser.window && browser.location)
|
|
732
|
+
return initBrowserUniversalLogs(opts);
|
|
733
|
+
return initNodeLogs(opts);
|
|
734
|
+
}
|
|
735
|
+
function initNodeLogs(opts) {
|
|
736
|
+
const runtime = globalThis;
|
|
737
|
+
const processLike = runtime.process;
|
|
738
|
+
if (!processLike || !runtime.fetch)
|
|
739
|
+
return;
|
|
740
|
+
const source = opts.source ?? runtimeName();
|
|
741
|
+
const processId = opts.processId ?? defaultProcessId(processLike);
|
|
742
|
+
const client = new LogsClient({ ...opts, source, processId });
|
|
743
|
+
const q = [];
|
|
744
|
+
const maxBatchSize = opts.maxBatchSize ?? 20;
|
|
745
|
+
const collectorUrl = (opts.url ?? DEFAULT_URL).replace(/\/$/, "");
|
|
746
|
+
let stopped = false;
|
|
747
|
+
const enqueue = (event) => {
|
|
748
|
+
if (stopped)
|
|
749
|
+
return;
|
|
750
|
+
q.push({
|
|
751
|
+
event_time: new Date().toISOString(),
|
|
752
|
+
source,
|
|
753
|
+
process_id: processId,
|
|
754
|
+
...event,
|
|
755
|
+
attributes: {
|
|
756
|
+
pid: processLike.pid,
|
|
757
|
+
runtime: runtimeName(),
|
|
758
|
+
...event.attributes ?? {}
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
if (q.length >= maxBatchSize)
|
|
762
|
+
flush();
|
|
763
|
+
};
|
|
764
|
+
const flush = async () => {
|
|
765
|
+
if (!q.length)
|
|
766
|
+
return;
|
|
767
|
+
const batch = q.splice(0);
|
|
768
|
+
await client.pushEvents(batch).catch(() => {});
|
|
769
|
+
};
|
|
770
|
+
const interval = setInterval(() => {
|
|
771
|
+
flush();
|
|
772
|
+
}, opts.flushIntervalMs ?? 2000);
|
|
773
|
+
const restores = [() => clearInterval(interval)];
|
|
774
|
+
const removeProcessListener = (event, listener) => {
|
|
775
|
+
if (typeof processLike.off === "function")
|
|
776
|
+
processLike.off(event, listener);
|
|
777
|
+
else if (typeof processLike.removeListener === "function")
|
|
778
|
+
processLike.removeListener(event, listener);
|
|
779
|
+
};
|
|
780
|
+
if (opts.captureProcess !== false) {
|
|
781
|
+
enqueue({
|
|
782
|
+
type: "process",
|
|
783
|
+
severity: "info",
|
|
784
|
+
source_event_id: `${processId}:start`,
|
|
785
|
+
message: "Process started",
|
|
786
|
+
attributes: {
|
|
787
|
+
phase: "start",
|
|
788
|
+
argv: processLike.argv,
|
|
789
|
+
cwd: safeCall(processLike.cwd),
|
|
790
|
+
exec_path: processLike.execPath,
|
|
791
|
+
node_version: processLike.versions?.node ?? processLike.version,
|
|
792
|
+
bun_version: processLike.versions?.bun
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
const beforeExit = (code) => {
|
|
796
|
+
enqueue({
|
|
797
|
+
type: "process",
|
|
798
|
+
severity: code === 0 ? "info" : "error",
|
|
799
|
+
source_event_id: `${processId}:beforeExit:${String(code)}`,
|
|
800
|
+
message: `Process beforeExit ${String(code)}`,
|
|
801
|
+
attributes: { phase: "beforeExit", exit_code: code }
|
|
802
|
+
});
|
|
803
|
+
flush();
|
|
804
|
+
};
|
|
805
|
+
processLike.on?.("beforeExit", beforeExit);
|
|
806
|
+
restores.push(() => removeProcessListener("beforeExit", beforeExit));
|
|
807
|
+
}
|
|
808
|
+
if (opts.captureConsole !== false) {
|
|
809
|
+
const consoleRecord = console;
|
|
810
|
+
const consoleMethods = [
|
|
811
|
+
["debug", "debug"],
|
|
812
|
+
["log", "info"],
|
|
813
|
+
["info", "info"],
|
|
814
|
+
["warn", "warn"],
|
|
815
|
+
["error", "error"]
|
|
816
|
+
];
|
|
817
|
+
for (const [method, severity] of consoleMethods) {
|
|
818
|
+
const original = consoleRecord[method];
|
|
819
|
+
if (typeof original !== "function")
|
|
820
|
+
continue;
|
|
821
|
+
const bound = original.bind(console);
|
|
822
|
+
consoleRecord[method] = (...args) => {
|
|
823
|
+
bound(...args);
|
|
824
|
+
enqueue({
|
|
825
|
+
type: "log",
|
|
826
|
+
severity,
|
|
827
|
+
message: formatArgs(args),
|
|
828
|
+
attributes: {
|
|
829
|
+
console_method: method
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
};
|
|
833
|
+
restores.push(() => {
|
|
834
|
+
consoleRecord[method] = original;
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (opts.captureExceptions !== false) {
|
|
839
|
+
const uncaughtExceptionMonitor = (error, origin) => {
|
|
840
|
+
const normalized = normalizeError(error);
|
|
841
|
+
enqueue({
|
|
842
|
+
type: "exception",
|
|
843
|
+
severity: "fatal",
|
|
844
|
+
message: normalized.message,
|
|
845
|
+
body: {
|
|
846
|
+
exception: {
|
|
847
|
+
type: normalized.type,
|
|
848
|
+
value: normalized.message,
|
|
849
|
+
stack_trace: normalized.stack,
|
|
850
|
+
handled: false,
|
|
851
|
+
mechanism: "process.uncaughtExceptionMonitor"
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
attributes: {
|
|
855
|
+
exception_type: normalized.type,
|
|
856
|
+
stack_trace: normalized.stack,
|
|
857
|
+
handled: false,
|
|
858
|
+
mechanism: "process.uncaughtExceptionMonitor",
|
|
859
|
+
origin
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
flush();
|
|
863
|
+
};
|
|
864
|
+
processLike.on?.("uncaughtExceptionMonitor", uncaughtExceptionMonitor);
|
|
865
|
+
restores.push(() => removeProcessListener("uncaughtExceptionMonitor", uncaughtExceptionMonitor));
|
|
866
|
+
}
|
|
867
|
+
if (opts.captureRejections === true) {
|
|
868
|
+
const unhandledRejection = (reason) => {
|
|
869
|
+
const normalized = normalizeError(reason);
|
|
870
|
+
enqueue({
|
|
871
|
+
type: "exception",
|
|
872
|
+
severity: "error",
|
|
873
|
+
message: `Unhandled rejection: ${normalized.message}`,
|
|
874
|
+
body: {
|
|
875
|
+
exception: {
|
|
876
|
+
type: normalized.type,
|
|
877
|
+
value: normalized.message,
|
|
878
|
+
stack_trace: normalized.stack,
|
|
879
|
+
handled: false,
|
|
880
|
+
mechanism: "process.unhandledRejection"
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
attributes: {
|
|
884
|
+
exception_type: normalized.type,
|
|
885
|
+
stack_trace: normalized.stack,
|
|
886
|
+
handled: false,
|
|
887
|
+
mechanism: "process.unhandledRejection"
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
flush();
|
|
891
|
+
};
|
|
892
|
+
processLike.on?.("unhandledRejection", unhandledRejection);
|
|
893
|
+
restores.push(() => removeProcessListener("unhandledRejection", unhandledRejection));
|
|
894
|
+
}
|
|
895
|
+
if (opts.captureFetch !== false) {
|
|
896
|
+
const originalFetch = runtime.fetch;
|
|
897
|
+
const boundFetch = originalFetch.bind(globalThis);
|
|
898
|
+
runtime.fetch = async (input, init) => {
|
|
899
|
+
const requestUrl = requestUrlString(input);
|
|
900
|
+
const method = requestMethod(input, init);
|
|
901
|
+
const collectorRequest = isCollectorRequest(requestUrl, collectorUrl);
|
|
902
|
+
let fetchInput = input;
|
|
903
|
+
let fetchInit = init;
|
|
904
|
+
const existingTraceparent = traceparentInfoFromFetchInput(input, init);
|
|
905
|
+
let traceContext = existingTraceparent.context;
|
|
906
|
+
let traceparentInjected = false;
|
|
907
|
+
if (!existingTraceparent.present && !collectorRequest && shouldPropagateTrace(opts, requestUrl)) {
|
|
908
|
+
traceContext = { traceId: randomHex(32), spanId: randomHex(16) };
|
|
909
|
+
const tracedFetch = fetchInputWithTraceparent(input, init, traceparentFromContext(traceContext));
|
|
910
|
+
fetchInput = tracedFetch.input;
|
|
911
|
+
fetchInit = tracedFetch.init;
|
|
912
|
+
traceparentInjected = true;
|
|
913
|
+
}
|
|
914
|
+
const startedAt = Date.now();
|
|
915
|
+
try {
|
|
916
|
+
const response = await boundFetch(fetchInput, fetchInit);
|
|
917
|
+
if (!collectorRequest) {
|
|
918
|
+
enqueue({
|
|
919
|
+
type: "span",
|
|
920
|
+
severity: response.ok ? "info" : "error",
|
|
921
|
+
trace_id: traceContext?.traceId,
|
|
922
|
+
span_id: traceContext?.spanId ?? randomId("span"),
|
|
923
|
+
message: `${method} ${requestUrl}`,
|
|
924
|
+
body: {
|
|
925
|
+
name: `${method} ${requestUrl}`,
|
|
926
|
+
operation: "http.client",
|
|
927
|
+
status: response.ok ? "ok" : "error",
|
|
928
|
+
duration_ms: Date.now() - startedAt
|
|
929
|
+
},
|
|
930
|
+
attributes: {
|
|
931
|
+
name: `${method} ${requestUrl}`,
|
|
932
|
+
operation: "http.client",
|
|
933
|
+
method,
|
|
934
|
+
url: requestUrl,
|
|
935
|
+
status_code: response.status,
|
|
936
|
+
ok: response.ok,
|
|
937
|
+
duration_ms: Date.now() - startedAt,
|
|
938
|
+
traceparent_propagated: traceparentInjected || undefined,
|
|
939
|
+
traceparent_existing: existingTraceparent.present || undefined
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
return response;
|
|
944
|
+
} catch (error) {
|
|
945
|
+
if (!collectorRequest) {
|
|
946
|
+
const normalized = normalizeError(error);
|
|
947
|
+
enqueue({
|
|
948
|
+
type: "network",
|
|
949
|
+
severity: "error",
|
|
950
|
+
trace_id: traceContext?.traceId,
|
|
951
|
+
span_id: traceContext?.spanId ?? randomId("span"),
|
|
952
|
+
message: `${method} ${requestUrl} failed: ${normalized.message}`,
|
|
953
|
+
body: {
|
|
954
|
+
error: {
|
|
955
|
+
type: normalized.type,
|
|
956
|
+
value: normalized.message,
|
|
957
|
+
stack_trace: normalized.stack
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
attributes: {
|
|
961
|
+
operation: "http.client",
|
|
962
|
+
method,
|
|
963
|
+
url: requestUrl,
|
|
964
|
+
duration_ms: Date.now() - startedAt,
|
|
965
|
+
error_type: normalized.type,
|
|
966
|
+
stack_trace: normalized.stack,
|
|
967
|
+
traceparent_propagated: traceparentInjected || undefined,
|
|
968
|
+
traceparent_existing: existingTraceparent.present || undefined
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
throw error;
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
restores.push(() => {
|
|
976
|
+
runtime.fetch = originalFetch;
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
return {
|
|
980
|
+
flush,
|
|
981
|
+
stop() {
|
|
982
|
+
stopped = true;
|
|
983
|
+
while (restores.length)
|
|
984
|
+
restores.pop()?.();
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
function initBrowserUniversalLogs(opts) {
|
|
989
|
+
const browser = globalThis;
|
|
990
|
+
const runtime = globalThis;
|
|
991
|
+
if (!browser.window || !browser.location)
|
|
992
|
+
return;
|
|
993
|
+
const serverUrl = browserRequestUrlString(opts.url ?? DEFAULT_URL, browser.location.href).replace(/\/$/, "");
|
|
994
|
+
const client = new LogsClient({ ...opts, url: serverUrl, source: opts.source ?? "browser" });
|
|
995
|
+
const maxBatchSize = opts.maxBatchSize ?? 10;
|
|
996
|
+
const maxQueueSize = Math.max(1, opts.maxQueueSize ?? 1000);
|
|
997
|
+
const spool = createBrowserUniversalSpool(opts);
|
|
998
|
+
const loadedSpool = spool.load();
|
|
999
|
+
const q = loadedSpool.events.slice(-maxQueueSize).map((event) => ({
|
|
1000
|
+
event,
|
|
1001
|
+
spoolEvent: event
|
|
1002
|
+
}));
|
|
1003
|
+
let inFlightBatch;
|
|
1004
|
+
let flushPromise;
|
|
1005
|
+
let stopped = false;
|
|
1006
|
+
const currentHref = () => browser.location?.href ?? "";
|
|
1007
|
+
const allSpoolItems = () => [...inFlightBatch ?? [], ...q];
|
|
1008
|
+
const persistSpool = () => persistBrowserUniversalSpool(spool, allSpoolItems());
|
|
1009
|
+
const enforceQueueLimit = () => {
|
|
1010
|
+
const inFlightCount = inFlightBatch?.length ?? 0;
|
|
1011
|
+
while (q.length + inFlightCount > maxQueueSize)
|
|
1012
|
+
q.shift();
|
|
1013
|
+
};
|
|
1014
|
+
if (loadedSpool.hadStoredRecord)
|
|
1015
|
+
persistSpool();
|
|
1016
|
+
const enqueue = (event) => {
|
|
1017
|
+
if (stopped)
|
|
1018
|
+
return;
|
|
1019
|
+
const queued = {
|
|
1020
|
+
event_time: new Date().toISOString(),
|
|
1021
|
+
source: "browser",
|
|
1022
|
+
...event,
|
|
1023
|
+
attributes: {
|
|
1024
|
+
url: currentHref(),
|
|
1025
|
+
...event.attributes ?? {}
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
q.push({
|
|
1029
|
+
event: queued,
|
|
1030
|
+
spoolEvent: redactBrowserUniversalEvent(queued)
|
|
1031
|
+
});
|
|
1032
|
+
enforceQueueLimit();
|
|
1033
|
+
persistSpool();
|
|
1034
|
+
if (q.length >= maxBatchSize)
|
|
1035
|
+
flush();
|
|
1036
|
+
};
|
|
1037
|
+
const flush = () => {
|
|
1038
|
+
if (flushPromise)
|
|
1039
|
+
return flushPromise;
|
|
1040
|
+
if (!q.length)
|
|
1041
|
+
return Promise.resolve();
|
|
1042
|
+
const batch = q.splice(0, maxBatchSize);
|
|
1043
|
+
inFlightBatch = batch;
|
|
1044
|
+
persistSpool();
|
|
1045
|
+
flushPromise = (async () => {
|
|
1046
|
+
try {
|
|
1047
|
+
await client.pushEvents(batch.map((item) => item.event));
|
|
1048
|
+
} catch {
|
|
1049
|
+
if (inFlightBatch === batch)
|
|
1050
|
+
inFlightBatch = undefined;
|
|
1051
|
+
q.unshift(...batch.map((item) => ({
|
|
1052
|
+
event: item.spoolEvent,
|
|
1053
|
+
spoolEvent: item.spoolEvent
|
|
1054
|
+
})));
|
|
1055
|
+
enforceQueueLimit();
|
|
1056
|
+
} finally {
|
|
1057
|
+
if (inFlightBatch === batch)
|
|
1058
|
+
inFlightBatch = undefined;
|
|
1059
|
+
flushPromise = undefined;
|
|
1060
|
+
persistSpool();
|
|
1061
|
+
if (!stopped && q.length >= maxBatchSize)
|
|
1062
|
+
flush();
|
|
1063
|
+
}
|
|
1064
|
+
})();
|
|
1065
|
+
return flushPromise;
|
|
1066
|
+
};
|
|
1067
|
+
const interval = setInterval(() => {
|
|
1068
|
+
flush();
|
|
1069
|
+
}, opts.flushIntervalMs ?? 2000);
|
|
1070
|
+
const restores = [() => clearInterval(interval)];
|
|
1071
|
+
if (opts.captureConsole !== false) {
|
|
1072
|
+
const consoleRecord = console;
|
|
1073
|
+
const consoleMethods = [
|
|
1074
|
+
["debug", "debug"],
|
|
1075
|
+
["log", "info"],
|
|
1076
|
+
["info", "info"],
|
|
1077
|
+
["warn", "warn"],
|
|
1078
|
+
["error", "error"]
|
|
1079
|
+
];
|
|
1080
|
+
for (const [method, severity] of consoleMethods) {
|
|
1081
|
+
const original = consoleRecord[method];
|
|
1082
|
+
if (typeof original !== "function")
|
|
1083
|
+
continue;
|
|
1084
|
+
const bound = original.bind(console);
|
|
1085
|
+
consoleRecord[method] = (...args) => {
|
|
1086
|
+
bound(...args);
|
|
1087
|
+
enqueue({
|
|
1088
|
+
type: "log",
|
|
1089
|
+
severity,
|
|
1090
|
+
message: formatArgs(args),
|
|
1091
|
+
attributes: {
|
|
1092
|
+
console_method: method
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
};
|
|
1096
|
+
restores.push(() => {
|
|
1097
|
+
consoleRecord[method] = original;
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (opts.captureExceptions !== false) {
|
|
1102
|
+
const browserErrorListener = (e) => {
|
|
1103
|
+
enqueue({
|
|
1104
|
+
type: "exception",
|
|
1105
|
+
severity: "error",
|
|
1106
|
+
message: e.message ?? "Browser error",
|
|
1107
|
+
body: {
|
|
1108
|
+
exception: {
|
|
1109
|
+
value: e.message ?? "Browser error",
|
|
1110
|
+
stack_trace: e.error?.stack,
|
|
1111
|
+
handled: false,
|
|
1112
|
+
mechanism: "browser.onerror"
|
|
1113
|
+
}
|
|
1114
|
+
},
|
|
1115
|
+
attributes: {
|
|
1116
|
+
stack_trace: e.error?.stack,
|
|
1117
|
+
mechanism: "browser.onerror"
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
};
|
|
1121
|
+
browser.window.addEventListener("error", browserErrorListener);
|
|
1122
|
+
restores.push(() => {
|
|
1123
|
+
browser.window?.removeEventListener?.("error", browserErrorListener);
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
if (opts.captureRejections !== false) {
|
|
1127
|
+
const browserRejectionListener = (e) => {
|
|
1128
|
+
const reason = typeof e.reason === "string" ? { message: e.reason } : e.reason;
|
|
1129
|
+
enqueue({
|
|
1130
|
+
type: "exception",
|
|
1131
|
+
severity: "error",
|
|
1132
|
+
message: `Unhandled: ${reason?.message ?? "promise rejection"}`,
|
|
1133
|
+
body: {
|
|
1134
|
+
exception: {
|
|
1135
|
+
value: reason?.message ?? "promise rejection",
|
|
1136
|
+
stack_trace: reason?.stack,
|
|
1137
|
+
handled: false,
|
|
1138
|
+
mechanism: "browser.unhandledrejection"
|
|
1139
|
+
}
|
|
1140
|
+
},
|
|
1141
|
+
attributes: {
|
|
1142
|
+
stack_trace: reason?.stack,
|
|
1143
|
+
mechanism: "browser.unhandledrejection"
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
};
|
|
1147
|
+
browser.window.addEventListener("unhandledrejection", browserRejectionListener);
|
|
1148
|
+
restores.push(() => {
|
|
1149
|
+
browser.window?.removeEventListener?.("unhandledrejection", browserRejectionListener);
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
if (opts.captureNavigation) {
|
|
1153
|
+
let lastHref = currentHref();
|
|
1154
|
+
const enqueueNavigation = (navigationType, fromUrl, toUrl = currentHref()) => {
|
|
1155
|
+
enqueue({
|
|
1156
|
+
type: "span",
|
|
1157
|
+
severity: "info",
|
|
1158
|
+
span_id: randomId("span"),
|
|
1159
|
+
message: `NAVIGATION ${toUrl}`,
|
|
1160
|
+
body: {
|
|
1161
|
+
name: `NAVIGATION ${toUrl}`,
|
|
1162
|
+
operation: "browser.navigation",
|
|
1163
|
+
status: "ok"
|
|
1164
|
+
},
|
|
1165
|
+
attributes: {
|
|
1166
|
+
name: `NAVIGATION ${toUrl}`,
|
|
1167
|
+
operation: "browser.navigation",
|
|
1168
|
+
navigation_type: navigationType,
|
|
1169
|
+
from_url: fromUrl,
|
|
1170
|
+
to_url: toUrl
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
};
|
|
1174
|
+
enqueueNavigation("page_load", undefined, lastHref);
|
|
1175
|
+
const emitRouteChange = (navigationType) => {
|
|
1176
|
+
const previousHref = lastHref;
|
|
1177
|
+
const nextHref = currentHref();
|
|
1178
|
+
if (nextHref === previousHref)
|
|
1179
|
+
return;
|
|
1180
|
+
lastHref = nextHref;
|
|
1181
|
+
enqueueNavigation(navigationType, previousHref, nextHref);
|
|
1182
|
+
};
|
|
1183
|
+
const popstateListener = () => emitRouteChange("popstate");
|
|
1184
|
+
const hashchangeListener = () => emitRouteChange("hashchange");
|
|
1185
|
+
browser.window.addEventListener("popstate", popstateListener);
|
|
1186
|
+
browser.window.addEventListener("hashchange", hashchangeListener);
|
|
1187
|
+
restores.push(() => {
|
|
1188
|
+
browser.window?.removeEventListener?.("popstate", popstateListener);
|
|
1189
|
+
browser.window?.removeEventListener?.("hashchange", hashchangeListener);
|
|
1190
|
+
});
|
|
1191
|
+
const originalPushState = browser.history?.pushState;
|
|
1192
|
+
if (typeof originalPushState === "function") {
|
|
1193
|
+
const wrappedPushState = function pushState(state, title, url) {
|
|
1194
|
+
originalPushState.call(this, state, title, url);
|
|
1195
|
+
emitRouteChange("pushState");
|
|
1196
|
+
};
|
|
1197
|
+
browser.history.pushState = wrappedPushState;
|
|
1198
|
+
restores.push(() => {
|
|
1199
|
+
if (browser.history?.pushState === wrappedPushState)
|
|
1200
|
+
browser.history.pushState = originalPushState;
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
const originalReplaceState = browser.history?.replaceState;
|
|
1204
|
+
if (typeof originalReplaceState === "function") {
|
|
1205
|
+
const wrappedReplaceState = function replaceState(state, title, url) {
|
|
1206
|
+
originalReplaceState.call(this, state, title, url);
|
|
1207
|
+
emitRouteChange("replaceState");
|
|
1208
|
+
};
|
|
1209
|
+
browser.history.replaceState = wrappedReplaceState;
|
|
1210
|
+
restores.push(() => {
|
|
1211
|
+
if (browser.history?.replaceState === wrappedReplaceState)
|
|
1212
|
+
browser.history.replaceState = originalReplaceState;
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (opts.captureResourceTiming) {
|
|
1217
|
+
let resourceTimingEvents = 0;
|
|
1218
|
+
const configuredResourceLimit = opts.maxResourceTimingEvents;
|
|
1219
|
+
const maxResourceTimingEvents = Number.isFinite(configuredResourceLimit) ? Math.max(1, Math.floor(configuredResourceLimit)) : 100;
|
|
1220
|
+
const seenResourceTimings = new Set;
|
|
1221
|
+
const enqueueResourceTiming = (entry) => {
|
|
1222
|
+
if (resourceTimingEvents >= maxResourceTimingEvents)
|
|
1223
|
+
return;
|
|
1224
|
+
const resourceUrl = browserPerformanceEntryUrl(entry, currentHref());
|
|
1225
|
+
if (!resourceUrl || isCollectorRequest(resourceUrl, serverUrl))
|
|
1226
|
+
return;
|
|
1227
|
+
const durationMs = roundedNumber(entry.duration);
|
|
1228
|
+
const initiatorType = typeof entry.initiatorType === "string" ? entry.initiatorType : "resource";
|
|
1229
|
+
const resourceKey = browserPerformanceEntryKey(entry, resourceUrl, initiatorType);
|
|
1230
|
+
if (seenResourceTimings.has(resourceKey))
|
|
1231
|
+
return;
|
|
1232
|
+
seenResourceTimings.add(resourceKey);
|
|
1233
|
+
resourceTimingEvents += 1;
|
|
1234
|
+
enqueue({
|
|
1235
|
+
type: "span",
|
|
1236
|
+
severity: "info",
|
|
1237
|
+
span_id: randomId("span"),
|
|
1238
|
+
message: `RESOURCE ${resourceUrl}`,
|
|
1239
|
+
body: {
|
|
1240
|
+
name: `RESOURCE ${resourceUrl}`,
|
|
1241
|
+
operation: "browser.resource",
|
|
1242
|
+
status: "ok",
|
|
1243
|
+
duration_ms: durationMs
|
|
1244
|
+
},
|
|
1245
|
+
attributes: {
|
|
1246
|
+
name: `RESOURCE ${resourceUrl}`,
|
|
1247
|
+
operation: "browser.resource",
|
|
1248
|
+
url: resourceUrl,
|
|
1249
|
+
initiator_type: initiatorType,
|
|
1250
|
+
start_time_ms: roundedNumber(entry.startTime),
|
|
1251
|
+
duration_ms: durationMs,
|
|
1252
|
+
transfer_size: roundedNumber(entry.transferSize),
|
|
1253
|
+
encoded_body_size: roundedNumber(entry.encodedBodySize),
|
|
1254
|
+
decoded_body_size: roundedNumber(entry.decodedBodySize),
|
|
1255
|
+
response_status: roundedNumber(entry.responseStatus)
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
};
|
|
1259
|
+
for (const entry of browser.performance?.getEntriesByType?.("resource") ?? []) {
|
|
1260
|
+
enqueueResourceTiming(entry);
|
|
1261
|
+
}
|
|
1262
|
+
if (runtime.PerformanceObserver) {
|
|
1263
|
+
try {
|
|
1264
|
+
const observer = new runtime.PerformanceObserver((list) => {
|
|
1265
|
+
for (const entry of list.getEntries())
|
|
1266
|
+
enqueueResourceTiming(entry);
|
|
1267
|
+
});
|
|
1268
|
+
let observing = false;
|
|
1269
|
+
try {
|
|
1270
|
+
observer.observe({ type: "resource", buffered: true });
|
|
1271
|
+
observing = true;
|
|
1272
|
+
} catch {
|
|
1273
|
+
try {
|
|
1274
|
+
observer.observe({ entryTypes: ["resource"] });
|
|
1275
|
+
observing = true;
|
|
1276
|
+
} catch {}
|
|
1277
|
+
}
|
|
1278
|
+
if (observing)
|
|
1279
|
+
restores.push(() => observer.disconnect());
|
|
1280
|
+
} catch {}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
if (opts.captureWebVitals) {
|
|
1284
|
+
let webVitalEvents = 0;
|
|
1285
|
+
let cumulativeLayoutShift = 0;
|
|
1286
|
+
let currentInp = 0;
|
|
1287
|
+
const configuredWebVitalLimit = opts.maxWebVitalEvents;
|
|
1288
|
+
const maxWebVitalEvents = Number.isFinite(configuredWebVitalLimit) ? Math.max(1, Math.floor(configuredWebVitalLimit)) : 50;
|
|
1289
|
+
const seenWebVitalEntries = new Set;
|
|
1290
|
+
const webVitalObserverDisconnects = [];
|
|
1291
|
+
let webVitalObservationStopped = false;
|
|
1292
|
+
const stopWebVitalObservation = () => {
|
|
1293
|
+
if (webVitalObservationStopped)
|
|
1294
|
+
return;
|
|
1295
|
+
webVitalObservationStopped = true;
|
|
1296
|
+
seenWebVitalEntries.clear();
|
|
1297
|
+
while (webVitalObserverDisconnects.length)
|
|
1298
|
+
webVitalObserverDisconnects.pop()?.();
|
|
1299
|
+
};
|
|
1300
|
+
const enqueueWebVital = (metricName, value, entry, extraAttributes = {}) => {
|
|
1301
|
+
if (webVitalObservationStopped || webVitalEvents >= maxWebVitalEvents) {
|
|
1302
|
+
stopWebVitalObservation();
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const normalizedValue = normalizedWebVitalValue(metricName, value);
|
|
1306
|
+
if (normalizedValue === undefined)
|
|
1307
|
+
return;
|
|
1308
|
+
webVitalEvents += 1;
|
|
1309
|
+
const unit = metricName === "cls" ? "score" : "ms";
|
|
1310
|
+
const name = `browser.web_vital.${metricName}`;
|
|
1311
|
+
enqueue({
|
|
1312
|
+
type: "metric",
|
|
1313
|
+
severity: "info",
|
|
1314
|
+
message: `WEB_VITAL ${metricName} ${normalizedValue}`,
|
|
1315
|
+
body: {
|
|
1316
|
+
name,
|
|
1317
|
+
value: normalizedValue,
|
|
1318
|
+
kind: "gauge",
|
|
1319
|
+
unit
|
|
1320
|
+
},
|
|
1321
|
+
attributes: {
|
|
1322
|
+
name,
|
|
1323
|
+
operation: "browser.web_vital",
|
|
1324
|
+
web_vital: metricName,
|
|
1325
|
+
metric_kind: "gauge",
|
|
1326
|
+
value: normalizedValue,
|
|
1327
|
+
unit,
|
|
1328
|
+
rating: webVitalRating(metricName, normalizedValue),
|
|
1329
|
+
entry_type: entry.entryType,
|
|
1330
|
+
entry_name: entry.name,
|
|
1331
|
+
start_time_ms: roundedNumber(entry.startTime),
|
|
1332
|
+
duration_ms: roundedNumber(entry.duration),
|
|
1333
|
+
interaction_id: roundedNumber(entry.interactionId),
|
|
1334
|
+
...extraAttributes
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
if (webVitalEvents >= maxWebVitalEvents)
|
|
1338
|
+
stopWebVitalObservation();
|
|
1339
|
+
};
|
|
1340
|
+
const processWebVitalEntry = (entry) => {
|
|
1341
|
+
if (webVitalObservationStopped || webVitalEvents >= maxWebVitalEvents) {
|
|
1342
|
+
stopWebVitalObservation();
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
const entryType = entry.entryType;
|
|
1346
|
+
if (entryType === "paint" && entry.name === "first-contentful-paint") {
|
|
1347
|
+
if (normalizedWebVitalValue("fcp", entry.startTime) === undefined)
|
|
1348
|
+
return;
|
|
1349
|
+
const key = browserWebVitalEntryKey("fcp", entry);
|
|
1350
|
+
if (seenWebVitalEntries.has(key))
|
|
1351
|
+
return;
|
|
1352
|
+
seenWebVitalEntries.add(key);
|
|
1353
|
+
enqueueWebVital("fcp", entry.startTime, entry);
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
if (entryType === "largest-contentful-paint") {
|
|
1357
|
+
const value = entry.renderTime ?? entry.loadTime ?? entry.startTime;
|
|
1358
|
+
if (normalizedWebVitalValue("lcp", value) === undefined)
|
|
1359
|
+
return;
|
|
1360
|
+
const key = browserWebVitalEntryKey("lcp", entry);
|
|
1361
|
+
if (seenWebVitalEntries.has(key))
|
|
1362
|
+
return;
|
|
1363
|
+
seenWebVitalEntries.add(key);
|
|
1364
|
+
enqueueWebVital("lcp", value, entry);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
if (entryType === "layout-shift") {
|
|
1368
|
+
if (entry.hadRecentInput)
|
|
1369
|
+
return;
|
|
1370
|
+
const delta = normalizedWebVitalValue("cls", entry.value);
|
|
1371
|
+
if (delta === undefined)
|
|
1372
|
+
return;
|
|
1373
|
+
const key = browserWebVitalEntryKey("cls", entry);
|
|
1374
|
+
if (seenWebVitalEntries.has(key))
|
|
1375
|
+
return;
|
|
1376
|
+
seenWebVitalEntries.add(key);
|
|
1377
|
+
cumulativeLayoutShift = normalizedWebVitalValue("cls", cumulativeLayoutShift + delta) ?? cumulativeLayoutShift;
|
|
1378
|
+
enqueueWebVital("cls", cumulativeLayoutShift, entry, { delta });
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
if (entryType === "first-input") {
|
|
1382
|
+
const value = typeof entry.processingStart === "number" && typeof entry.startTime === "number" ? entry.processingStart - entry.startTime : entry.duration;
|
|
1383
|
+
if (normalizedWebVitalValue("fid", value) === undefined)
|
|
1384
|
+
return;
|
|
1385
|
+
const key = browserWebVitalEntryKey("fid", entry);
|
|
1386
|
+
if (seenWebVitalEntries.has(key))
|
|
1387
|
+
return;
|
|
1388
|
+
seenWebVitalEntries.add(key);
|
|
1389
|
+
enqueueWebVital("fid", value, entry);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
if (entryType === "event") {
|
|
1393
|
+
const value = typeof entry.duration === "number" ? entry.duration : undefined;
|
|
1394
|
+
const normalizedValue = normalizedWebVitalValue("inp", value);
|
|
1395
|
+
if (normalizedValue === undefined || normalizedValue <= currentInp)
|
|
1396
|
+
return;
|
|
1397
|
+
const key = browserWebVitalEntryKey("inp", entry);
|
|
1398
|
+
if (seenWebVitalEntries.has(key))
|
|
1399
|
+
return;
|
|
1400
|
+
seenWebVitalEntries.add(key);
|
|
1401
|
+
currentInp = normalizedValue;
|
|
1402
|
+
enqueueWebVital("inp", normalizedValue, entry);
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
const processExistingWebVitalEntries = (entryType) => {
|
|
1406
|
+
for (const entry of browser.performance?.getEntriesByType?.(entryType) ?? []) {
|
|
1407
|
+
if (webVitalObservationStopped)
|
|
1408
|
+
break;
|
|
1409
|
+
processWebVitalEntry(entry);
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
for (const entryType of ["paint", "largest-contentful-paint", "layout-shift", "first-input", "event"]) {
|
|
1413
|
+
if (webVitalObservationStopped)
|
|
1414
|
+
break;
|
|
1415
|
+
processExistingWebVitalEntries(entryType);
|
|
1416
|
+
}
|
|
1417
|
+
const observeWebVitalEntries = (entryType, observeOptions) => {
|
|
1418
|
+
if (webVitalObservationStopped || !runtime.PerformanceObserver)
|
|
1419
|
+
return;
|
|
1420
|
+
try {
|
|
1421
|
+
const observer = new runtime.PerformanceObserver((list) => {
|
|
1422
|
+
for (const entry of list.getEntries()) {
|
|
1423
|
+
if (webVitalObservationStopped || webVitalEvents >= maxWebVitalEvents) {
|
|
1424
|
+
stopWebVitalObservation();
|
|
1425
|
+
break;
|
|
1426
|
+
}
|
|
1427
|
+
processWebVitalEntry(entry);
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
let observing = false;
|
|
1431
|
+
try {
|
|
1432
|
+
observer.observe(observeOptions);
|
|
1433
|
+
observing = true;
|
|
1434
|
+
} catch {
|
|
1435
|
+
try {
|
|
1436
|
+
observer.observe({ entryTypes: [entryType] });
|
|
1437
|
+
observing = true;
|
|
1438
|
+
} catch {}
|
|
1439
|
+
}
|
|
1440
|
+
if (observing) {
|
|
1441
|
+
let disconnected = false;
|
|
1442
|
+
const disconnectObserver = () => {
|
|
1443
|
+
if (disconnected)
|
|
1444
|
+
return;
|
|
1445
|
+
disconnected = true;
|
|
1446
|
+
observer.disconnect();
|
|
1447
|
+
};
|
|
1448
|
+
webVitalObserverDisconnects.push(disconnectObserver);
|
|
1449
|
+
restores.push(disconnectObserver);
|
|
1450
|
+
}
|
|
1451
|
+
} catch {}
|
|
1452
|
+
};
|
|
1453
|
+
observeWebVitalEntries("paint", { type: "paint", buffered: true });
|
|
1454
|
+
observeWebVitalEntries("largest-contentful-paint", { type: "largest-contentful-paint", buffered: true });
|
|
1455
|
+
observeWebVitalEntries("layout-shift", { type: "layout-shift", buffered: true });
|
|
1456
|
+
observeWebVitalEntries("first-input", { type: "first-input", buffered: true });
|
|
1457
|
+
observeWebVitalEntries("event", { type: "event", buffered: true, durationThreshold: 16 });
|
|
1458
|
+
}
|
|
1459
|
+
if (opts.captureFetch !== false && runtime.fetch) {
|
|
1460
|
+
const originalFetch = runtime.fetch;
|
|
1461
|
+
const boundFetch = originalFetch.bind(globalThis);
|
|
1462
|
+
runtime.fetch = async (input, init) => {
|
|
1463
|
+
const requestUrl = browserRequestUrlString(input, browser.location?.href);
|
|
1464
|
+
const method = requestMethod(input, init);
|
|
1465
|
+
const collectorRequest = isCollectorRequest(requestUrl, serverUrl);
|
|
1466
|
+
const traceparentSuppressed = browserTraceparentSuppressionReason(requestUrl, browserRequestMode(input, init));
|
|
1467
|
+
let fetchInput = input;
|
|
1468
|
+
let fetchInit = init;
|
|
1469
|
+
const existingTraceparent = traceparentSuppressed ? { present: false } : traceparentInfoFromFetchInput(input, init);
|
|
1470
|
+
let traceContext = existingTraceparent.context;
|
|
1471
|
+
let traceparentInjected = false;
|
|
1472
|
+
if (!traceparentSuppressed && !existingTraceparent.present && !collectorRequest && shouldPropagateTrace(opts, requestUrl, browser.location?.href)) {
|
|
1473
|
+
traceContext = { traceId: randomHex(32), spanId: randomHex(16) };
|
|
1474
|
+
const tracedFetch = fetchInputWithTraceparent(input, init, traceparentFromContext(traceContext));
|
|
1475
|
+
fetchInput = tracedFetch.input;
|
|
1476
|
+
fetchInit = tracedFetch.init;
|
|
1477
|
+
traceparentInjected = true;
|
|
1478
|
+
}
|
|
1479
|
+
const startedAt = Date.now();
|
|
1480
|
+
try {
|
|
1481
|
+
const response = await boundFetch(fetchInput, fetchInit);
|
|
1482
|
+
if (!collectorRequest) {
|
|
1483
|
+
enqueue({
|
|
1484
|
+
type: "span",
|
|
1485
|
+
severity: response.ok ? "info" : "error",
|
|
1486
|
+
trace_id: traceContext?.traceId,
|
|
1487
|
+
span_id: traceContext?.spanId ?? randomId("span"),
|
|
1488
|
+
message: `${method} ${requestUrl}`,
|
|
1489
|
+
body: {
|
|
1490
|
+
name: `${method} ${requestUrl}`,
|
|
1491
|
+
operation: "http.client",
|
|
1492
|
+
status: response.ok ? "ok" : "error",
|
|
1493
|
+
duration_ms: Date.now() - startedAt
|
|
1494
|
+
},
|
|
1495
|
+
attributes: {
|
|
1496
|
+
name: `${method} ${requestUrl}`,
|
|
1497
|
+
operation: "http.client",
|
|
1498
|
+
method,
|
|
1499
|
+
url: requestUrl,
|
|
1500
|
+
status_code: response.status,
|
|
1501
|
+
ok: response.ok,
|
|
1502
|
+
duration_ms: Date.now() - startedAt,
|
|
1503
|
+
traceparent_propagated: traceparentInjected || undefined,
|
|
1504
|
+
traceparent_existing: existingTraceparent.present || undefined,
|
|
1505
|
+
traceparent_suppressed: traceparentSuppressed
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
return response;
|
|
1510
|
+
} catch (error) {
|
|
1511
|
+
if (!collectorRequest) {
|
|
1512
|
+
const normalized = normalizeError(error);
|
|
1513
|
+
enqueue({
|
|
1514
|
+
type: "network",
|
|
1515
|
+
severity: "error",
|
|
1516
|
+
trace_id: traceContext?.traceId,
|
|
1517
|
+
span_id: traceContext?.spanId ?? randomId("span"),
|
|
1518
|
+
message: `${method} ${requestUrl} failed: ${normalized.message}`,
|
|
1519
|
+
body: {
|
|
1520
|
+
error: {
|
|
1521
|
+
type: normalized.type,
|
|
1522
|
+
value: normalized.message,
|
|
1523
|
+
stack_trace: normalized.stack
|
|
1524
|
+
}
|
|
1525
|
+
},
|
|
1526
|
+
attributes: {
|
|
1527
|
+
operation: "http.client",
|
|
1528
|
+
method,
|
|
1529
|
+
url: requestUrl,
|
|
1530
|
+
duration_ms: Date.now() - startedAt,
|
|
1531
|
+
error_type: normalized.type,
|
|
1532
|
+
stack_trace: normalized.stack,
|
|
1533
|
+
traceparent_propagated: traceparentInjected || undefined,
|
|
1534
|
+
traceparent_existing: existingTraceparent.present || undefined,
|
|
1535
|
+
traceparent_suppressed: traceparentSuppressed
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
throw error;
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
restores.push(() => {
|
|
1543
|
+
runtime.fetch = originalFetch;
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
const beforeUnloadListener = () => {
|
|
1547
|
+
flush();
|
|
1548
|
+
};
|
|
1549
|
+
browser.window.addEventListener("beforeunload", beforeUnloadListener);
|
|
1550
|
+
restores.push(() => {
|
|
1551
|
+
browser.window?.removeEventListener?.("beforeunload", beforeUnloadListener);
|
|
1552
|
+
});
|
|
1553
|
+
return {
|
|
1554
|
+
flush,
|
|
1555
|
+
stop() {
|
|
1556
|
+
stopped = true;
|
|
1557
|
+
while (restores.length)
|
|
1558
|
+
restores.pop()?.();
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
function createBrowserUniversalSpool(opts) {
|
|
1563
|
+
const runtime = globalThis;
|
|
1564
|
+
const storage = runtime.localStorage;
|
|
1565
|
+
const key = browserUniversalSpoolKey(opts);
|
|
1566
|
+
if (!storage || !key) {
|
|
1567
|
+
return {
|
|
1568
|
+
enabled: false,
|
|
1569
|
+
load: () => ({ hadStoredRecord: false, events: [] }),
|
|
1570
|
+
save: () => {}
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
return {
|
|
1574
|
+
enabled: true,
|
|
1575
|
+
load() {
|
|
1576
|
+
let raw;
|
|
1577
|
+
try {
|
|
1578
|
+
raw = storage.getItem(key);
|
|
1579
|
+
} catch {
|
|
1580
|
+
return { hadStoredRecord: false, events: [] };
|
|
1581
|
+
}
|
|
1582
|
+
if (!raw)
|
|
1583
|
+
return { hadStoredRecord: false, events: [] };
|
|
1584
|
+
try {
|
|
1585
|
+
const parsed = JSON.parse(raw);
|
|
1586
|
+
if (!isObjectRecord(parsed) || parsed.version !== 1 || !Array.isArray(parsed.events)) {
|
|
1587
|
+
return { hadStoredRecord: true, events: [] };
|
|
1588
|
+
}
|
|
1589
|
+
const events = parsed.events.filter(isValidBrowserUniversalSpoolEvent).map((event) => redactBrowserUniversalEvent(event)).filter(isValidBrowserUniversalSpoolEvent);
|
|
1590
|
+
return { hadStoredRecord: true, events };
|
|
1591
|
+
} catch {
|
|
1592
|
+
return { hadStoredRecord: true, events: [] };
|
|
1593
|
+
}
|
|
1594
|
+
},
|
|
1595
|
+
save(events) {
|
|
1596
|
+
try {
|
|
1597
|
+
const validEvents = events.filter(isValidBrowserUniversalSpoolEvent).map(redactBrowserUniversalEvent).filter(isValidBrowserUniversalSpoolEvent);
|
|
1598
|
+
if (!validEvents.length) {
|
|
1599
|
+
storage.removeItem(key);
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
storage.setItem(key, JSON.stringify({
|
|
1603
|
+
version: 1,
|
|
1604
|
+
events: validEvents
|
|
1605
|
+
}));
|
|
1606
|
+
} catch {}
|
|
1607
|
+
}
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
function browserUniversalSpoolKey(opts) {
|
|
1611
|
+
if (!opts.browserSpool && !opts.browserSpoolKey)
|
|
1612
|
+
return;
|
|
1613
|
+
if (opts.browserSpoolKey)
|
|
1614
|
+
return opts.browserSpoolKey;
|
|
1615
|
+
return [
|
|
1616
|
+
"open-logs",
|
|
1617
|
+
"browser-universal-spool",
|
|
1618
|
+
sanitizeSpoolPathPart(opts.projectId),
|
|
1619
|
+
sanitizeSpoolPathPart(opts.sessionId ?? "default")
|
|
1620
|
+
].join(":");
|
|
1621
|
+
}
|
|
1622
|
+
function persistBrowserUniversalSpool(spool, q) {
|
|
1623
|
+
if (!spool.enabled)
|
|
1624
|
+
return;
|
|
1625
|
+
spool.save(q.map((item) => item.spoolEvent));
|
|
1626
|
+
}
|
|
1627
|
+
function redactBrowserUniversalEvent(event) {
|
|
1628
|
+
const redacted = redactSdkValue(event);
|
|
1629
|
+
return isObjectRecord(redacted) ? redacted : { type: "log", message: String(redacted) };
|
|
1630
|
+
}
|
|
1631
|
+
function stripBrowserUniversalEventIdentity(event) {
|
|
1632
|
+
const stripped = { ...event };
|
|
1633
|
+
for (const key of SDK_BROWSER_FORBIDDEN_IDENTITY_FIELDS)
|
|
1634
|
+
delete stripped[key];
|
|
1635
|
+
if (isObjectRecord(stripped.attributes)) {
|
|
1636
|
+
stripped.attributes = stripBrowserIdentityFields(stripped.attributes);
|
|
1637
|
+
}
|
|
1638
|
+
if (isObjectRecord(stripped.metadata)) {
|
|
1639
|
+
stripped.metadata = stripBrowserIdentityFields(stripped.metadata);
|
|
1640
|
+
}
|
|
1641
|
+
return stripped;
|
|
1642
|
+
}
|
|
1643
|
+
function stripBrowserIdentityFields(value) {
|
|
1644
|
+
const stripped = { ...value };
|
|
1645
|
+
for (const key of SDK_BROWSER_FORBIDDEN_IDENTITY_FIELDS)
|
|
1646
|
+
delete stripped[key];
|
|
1647
|
+
return stripped;
|
|
1648
|
+
}
|
|
1649
|
+
function isValidBrowserUniversalSpoolEvent(event) {
|
|
1650
|
+
if (!isObjectRecord(event))
|
|
1651
|
+
return false;
|
|
1652
|
+
for (const key of Object.keys(event)) {
|
|
1653
|
+
if (!SDK_BROWSER_UNIVERSAL_EVENT_FIELDS.has(key))
|
|
1654
|
+
return false;
|
|
1655
|
+
}
|
|
1656
|
+
if (!SDK_BROWSER_UNIVERSAL_EVENT_TYPES.has(event.type))
|
|
1657
|
+
return false;
|
|
1658
|
+
if (event.source !== undefined && event.source !== "browser")
|
|
1659
|
+
return false;
|
|
1660
|
+
if (event.schema_version !== undefined && (!Number.isInteger(event.schema_version) || Number(event.schema_version) < 1)) {
|
|
1661
|
+
return false;
|
|
1662
|
+
}
|
|
1663
|
+
const severity = event.severity ?? event.level;
|
|
1664
|
+
if (severity !== undefined && severity !== null) {
|
|
1665
|
+
if (typeof severity !== "string" || !SDK_BROWSER_SEVERITIES.has(severity))
|
|
1666
|
+
return false;
|
|
1667
|
+
}
|
|
1668
|
+
if (event.privacy !== undefined && event.privacy !== null) {
|
|
1669
|
+
if (typeof event.privacy !== "string" || !SDK_BROWSER_PRIVACY_CLASSES.has(event.privacy))
|
|
1670
|
+
return false;
|
|
1671
|
+
}
|
|
1672
|
+
for (const key of SDK_BROWSER_STRING_EVENT_FIELDS) {
|
|
1673
|
+
const item = event[key];
|
|
1674
|
+
if (item !== undefined && item !== null && typeof item !== "string")
|
|
1675
|
+
return false;
|
|
1676
|
+
}
|
|
1677
|
+
for (const key of SDK_BROWSER_TIMESTAMP_EVENT_FIELDS) {
|
|
1678
|
+
const item = event[key];
|
|
1679
|
+
if (typeof item === "string" && item.length > 0 && Number.isNaN(new Date(item).getTime())) {
|
|
1680
|
+
return false;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
for (const key of SDK_BROWSER_OBJECT_EVENT_FIELDS) {
|
|
1684
|
+
const item = event[key];
|
|
1685
|
+
if (item !== undefined && !isObjectRecord(item))
|
|
1686
|
+
return false;
|
|
1687
|
+
}
|
|
1688
|
+
if (hasBrowserIdentityFields(event.attributes) || hasBrowserIdentityFields(event.metadata))
|
|
1689
|
+
return false;
|
|
1690
|
+
for (const key of SDK_BROWSER_FORBIDDEN_IDENTITY_FIELDS) {
|
|
1691
|
+
if (event[key] !== undefined && event[key] !== null)
|
|
1692
|
+
return false;
|
|
1693
|
+
}
|
|
1694
|
+
return true;
|
|
1695
|
+
}
|
|
1696
|
+
function hasBrowserIdentityFields(value) {
|
|
1697
|
+
if (!isObjectRecord(value))
|
|
1698
|
+
return false;
|
|
1699
|
+
return SDK_BROWSER_FORBIDDEN_IDENTITY_FIELDS.some((key) => value[key] !== undefined && value[key] !== null);
|
|
1700
|
+
}
|
|
1701
|
+
var SDK_BROWSER_UNIVERSAL_EVENT_FIELDS = new Set([
|
|
1702
|
+
"schema_version",
|
|
1703
|
+
"event_id",
|
|
1704
|
+
"id",
|
|
1705
|
+
"source_event_id",
|
|
1706
|
+
"event_time",
|
|
1707
|
+
"timestamp",
|
|
1708
|
+
"type",
|
|
1709
|
+
"source",
|
|
1710
|
+
"severity",
|
|
1711
|
+
"level",
|
|
1712
|
+
"privacy",
|
|
1713
|
+
"project_id",
|
|
1714
|
+
"page_id",
|
|
1715
|
+
"machine_id",
|
|
1716
|
+
"repo_id",
|
|
1717
|
+
"app_id",
|
|
1718
|
+
"process_id",
|
|
1719
|
+
"run_id",
|
|
1720
|
+
"trace_id",
|
|
1721
|
+
"span_id",
|
|
1722
|
+
"parent_span_id",
|
|
1723
|
+
"session_id",
|
|
1724
|
+
"release_id",
|
|
1725
|
+
"environment",
|
|
1726
|
+
"artifact_id",
|
|
1727
|
+
"message",
|
|
1728
|
+
"body",
|
|
1729
|
+
"attributes",
|
|
1730
|
+
"metadata"
|
|
1731
|
+
]);
|
|
1732
|
+
var SDK_BROWSER_UNIVERSAL_EVENT_TYPES = new Set([
|
|
1733
|
+
"log",
|
|
1734
|
+
"exception",
|
|
1735
|
+
"span",
|
|
1736
|
+
"metric",
|
|
1737
|
+
"network",
|
|
1738
|
+
"replay",
|
|
1739
|
+
"session"
|
|
1740
|
+
]);
|
|
1741
|
+
var SDK_BROWSER_SEVERITIES = new Set(["debug", "info", "warn", "error", "fatal"]);
|
|
1742
|
+
var SDK_BROWSER_PRIVACY_CLASSES = new Set(["public", "internal", "sensitive", "secret", "pii"]);
|
|
1743
|
+
var SDK_BROWSER_STRING_EVENT_FIELDS = [
|
|
1744
|
+
"event_id",
|
|
1745
|
+
"id",
|
|
1746
|
+
"source_event_id",
|
|
1747
|
+
"event_time",
|
|
1748
|
+
"timestamp",
|
|
1749
|
+
"source",
|
|
1750
|
+
"severity",
|
|
1751
|
+
"level",
|
|
1752
|
+
"privacy",
|
|
1753
|
+
"project_id",
|
|
1754
|
+
"page_id",
|
|
1755
|
+
"machine_id",
|
|
1756
|
+
"repo_id",
|
|
1757
|
+
"app_id",
|
|
1758
|
+
"process_id",
|
|
1759
|
+
"run_id",
|
|
1760
|
+
"trace_id",
|
|
1761
|
+
"span_id",
|
|
1762
|
+
"parent_span_id",
|
|
1763
|
+
"session_id",
|
|
1764
|
+
"release_id",
|
|
1765
|
+
"environment",
|
|
1766
|
+
"artifact_id",
|
|
1767
|
+
"message"
|
|
1768
|
+
];
|
|
1769
|
+
var SDK_BROWSER_TIMESTAMP_EVENT_FIELDS = ["event_time", "timestamp"];
|
|
1770
|
+
var SDK_BROWSER_OBJECT_EVENT_FIELDS = ["body", "attributes", "metadata"];
|
|
1771
|
+
var SDK_BROWSER_FORBIDDEN_IDENTITY_FIELDS = [
|
|
1772
|
+
"project_id",
|
|
1773
|
+
"page_id",
|
|
1774
|
+
"machine_id",
|
|
1775
|
+
"repo_id",
|
|
1776
|
+
"app_id",
|
|
1777
|
+
"process_id",
|
|
1778
|
+
"run_id",
|
|
1779
|
+
"artifact_id",
|
|
1780
|
+
"build_id",
|
|
1781
|
+
"agent_id"
|
|
1782
|
+
];
|
|
1783
|
+
function normalizeError(error) {
|
|
1784
|
+
if (error instanceof Error) {
|
|
1785
|
+
return { type: error.name || "Error", message: error.message || String(error), stack: error.stack };
|
|
1786
|
+
}
|
|
1787
|
+
if (typeof error === "object" && error !== null) {
|
|
1788
|
+
const record = error;
|
|
1789
|
+
return {
|
|
1790
|
+
type: typeof record.name === "string" ? record.name : "Error",
|
|
1791
|
+
message: typeof record.message === "string" ? record.message : safeStringify(error),
|
|
1792
|
+
stack: typeof record.stack === "string" ? record.stack : undefined
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
return { type: "Error", message: String(error) };
|
|
1796
|
+
}
|
|
1797
|
+
function formatArgs(args) {
|
|
1798
|
+
return args.map((arg) => {
|
|
1799
|
+
if (typeof arg === "string")
|
|
1800
|
+
return arg;
|
|
1801
|
+
if (arg instanceof Error)
|
|
1802
|
+
return `${arg.name}: ${arg.message}`;
|
|
1803
|
+
if (typeof arg === "object" && arg !== null)
|
|
1804
|
+
return safeStringify(arg);
|
|
1805
|
+
return String(arg);
|
|
1806
|
+
}).join(" ");
|
|
1807
|
+
}
|
|
1808
|
+
function requestUrlString(input) {
|
|
1809
|
+
if (typeof input === "string")
|
|
1810
|
+
return input;
|
|
1811
|
+
if (input instanceof URL)
|
|
1812
|
+
return input.toString();
|
|
1813
|
+
if (typeof Request !== "undefined" && input instanceof Request)
|
|
1814
|
+
return input.url;
|
|
1815
|
+
return String(input);
|
|
1816
|
+
}
|
|
1817
|
+
function browserRequestUrlString(input, baseHref) {
|
|
1818
|
+
const value = requestUrlString(input);
|
|
1819
|
+
if (!baseHref)
|
|
1820
|
+
return value;
|
|
1821
|
+
try {
|
|
1822
|
+
return new URL(value, baseHref).toString();
|
|
1823
|
+
} catch {
|
|
1824
|
+
return value;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
function browserRequestMode(input, init) {
|
|
1828
|
+
if (init?.mode)
|
|
1829
|
+
return init.mode;
|
|
1830
|
+
if (typeof Request !== "undefined" && input instanceof Request)
|
|
1831
|
+
return input.mode;
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
function browserTraceparentSuppressionReason(requestUrl, requestMode) {
|
|
1835
|
+
if (requestMode === "no-cors")
|
|
1836
|
+
return "no-cors";
|
|
1837
|
+
try {
|
|
1838
|
+
const protocol = new URL(requestUrl).protocol;
|
|
1839
|
+
if (protocol !== "http:" && protocol !== "https:")
|
|
1840
|
+
return "non-http";
|
|
1841
|
+
} catch {
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
function browserPerformanceEntryUrl(entry, baseHref) {
|
|
1847
|
+
if (typeof entry.name !== "string" || !entry.name)
|
|
1848
|
+
return;
|
|
1849
|
+
try {
|
|
1850
|
+
return baseHref ? new URL(entry.name, baseHref).toString() : entry.name;
|
|
1851
|
+
} catch {
|
|
1852
|
+
return entry.name;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
function browserPerformanceEntryKey(entry, url, initiatorType) {
|
|
1856
|
+
return [
|
|
1857
|
+
url,
|
|
1858
|
+
initiatorType,
|
|
1859
|
+
roundedNumber(entry.startTime) ?? "",
|
|
1860
|
+
roundedNumber(entry.duration) ?? "",
|
|
1861
|
+
roundedNumber(entry.transferSize) ?? "",
|
|
1862
|
+
roundedNumber(entry.responseStatus) ?? ""
|
|
1863
|
+
].join("|");
|
|
1864
|
+
}
|
|
1865
|
+
function browserWebVitalEntryKey(metricName, entry) {
|
|
1866
|
+
return [
|
|
1867
|
+
metricName,
|
|
1868
|
+
entry.entryType ?? "",
|
|
1869
|
+
entry.name ?? "",
|
|
1870
|
+
roundedNumber(entry.startTime) ?? "",
|
|
1871
|
+
roundedNumber(entry.duration) ?? "",
|
|
1872
|
+
normalizedWebVitalValue(metricName, entry.value) ?? "",
|
|
1873
|
+
roundedNumber(entry.processingStart) ?? "",
|
|
1874
|
+
roundedNumber(entry.interactionId) ?? "",
|
|
1875
|
+
roundedNumber(entry.renderTime) ?? "",
|
|
1876
|
+
roundedNumber(entry.loadTime) ?? ""
|
|
1877
|
+
].join("|");
|
|
1878
|
+
}
|
|
1879
|
+
function normalizedWebVitalValue(metricName, value) {
|
|
1880
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
1881
|
+
return;
|
|
1882
|
+
return metricName === "cls" ? Math.round(value * 1000) / 1000 : Math.round(value);
|
|
1883
|
+
}
|
|
1884
|
+
function webVitalRating(metricName, value) {
|
|
1885
|
+
const thresholds = WEB_VITAL_THRESHOLDS[metricName];
|
|
1886
|
+
if (!thresholds)
|
|
1887
|
+
return;
|
|
1888
|
+
if (value <= thresholds.good)
|
|
1889
|
+
return "good";
|
|
1890
|
+
if (value <= thresholds.needsImprovement)
|
|
1891
|
+
return "needs_improvement";
|
|
1892
|
+
return "poor";
|
|
1893
|
+
}
|
|
1894
|
+
var WEB_VITAL_THRESHOLDS = {
|
|
1895
|
+
fcp: { good: 1800, needsImprovement: 3000 },
|
|
1896
|
+
lcp: { good: 2500, needsImprovement: 4000 },
|
|
1897
|
+
cls: { good: 0.1, needsImprovement: 0.25 },
|
|
1898
|
+
fid: { good: 100, needsImprovement: 300 },
|
|
1899
|
+
inp: { good: 200, needsImprovement: 500 }
|
|
1900
|
+
};
|
|
1901
|
+
function roundedNumber(value) {
|
|
1902
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.round(value) : undefined;
|
|
1903
|
+
}
|
|
1904
|
+
function requestMethod(input, init) {
|
|
1905
|
+
if (init?.method)
|
|
1906
|
+
return init.method.toUpperCase();
|
|
1907
|
+
if (typeof Request !== "undefined" && input instanceof Request && input.method)
|
|
1908
|
+
return input.method.toUpperCase();
|
|
1909
|
+
return "GET";
|
|
1910
|
+
}
|
|
1911
|
+
function traceparentInfoFromFetchInput(input, init) {
|
|
1912
|
+
if (init?.headers !== undefined) {
|
|
1913
|
+
const header = fetchHeaderValue(init.headers, "traceparent");
|
|
1914
|
+
return {
|
|
1915
|
+
present: header.present,
|
|
1916
|
+
value: header.value,
|
|
1917
|
+
context: parseTraceparent(header.value)
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
if (typeof Request !== "undefined" && input instanceof Request) {
|
|
1921
|
+
const value = input.headers.get("traceparent") ?? undefined;
|
|
1922
|
+
return {
|
|
1923
|
+
present: value !== undefined,
|
|
1924
|
+
value,
|
|
1925
|
+
context: parseTraceparent(value)
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
return { present: false };
|
|
1929
|
+
}
|
|
1930
|
+
function fetchInputWithTraceparent(input, init, traceparent) {
|
|
1931
|
+
const headers = new Headers(init?.headers ?? (typeof Request !== "undefined" && input instanceof Request ? input.headers : undefined));
|
|
1932
|
+
headers.set("traceparent", traceparent);
|
|
1933
|
+
return { input, init: { ...init ?? {}, headers } };
|
|
1934
|
+
}
|
|
1935
|
+
function fetchHeaderValue(headers, name) {
|
|
1936
|
+
if (!headers)
|
|
1937
|
+
return { present: false };
|
|
1938
|
+
if (typeof Headers !== "undefined") {
|
|
1939
|
+
const value = new Headers(headers).get(name) ?? undefined;
|
|
1940
|
+
return { present: value !== undefined, value };
|
|
1941
|
+
}
|
|
1942
|
+
const normalizedName = name.toLowerCase();
|
|
1943
|
+
if (Array.isArray(headers)) {
|
|
1944
|
+
const pairs = headers.filter((entry) => entry[0]?.toLowerCase() === normalizedName);
|
|
1945
|
+
return pairs.length ? { present: true, value: pairs.map((entry) => entry[1]).join(", ") } : { present: false };
|
|
1946
|
+
}
|
|
1947
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1948
|
+
if (key.toLowerCase() === normalizedName)
|
|
1949
|
+
return { present: true, value };
|
|
1950
|
+
}
|
|
1951
|
+
return { present: false };
|
|
1952
|
+
}
|
|
1953
|
+
function shouldPropagateTrace(opts, requestUrl, baseHref) {
|
|
1954
|
+
if (opts.propagateTrace !== true)
|
|
1955
|
+
return false;
|
|
1956
|
+
const targets = opts.tracePropagationTargets ?? [];
|
|
1957
|
+
if (targets.length > 0) {
|
|
1958
|
+
return targets.some((target) => tracePropagationTargetMatches(target, requestUrl));
|
|
1959
|
+
}
|
|
1960
|
+
if (baseHref)
|
|
1961
|
+
return sameOrigin(requestUrl, baseHref);
|
|
1962
|
+
return true;
|
|
1963
|
+
}
|
|
1964
|
+
function tracePropagationTargetMatches(target, requestUrl) {
|
|
1965
|
+
if (typeof target === "string") {
|
|
1966
|
+
try {
|
|
1967
|
+
const parsedTarget = new URL(target);
|
|
1968
|
+
const parsedRequest = new URL(requestUrl);
|
|
1969
|
+
if (parsedRequest.origin !== parsedTarget.origin)
|
|
1970
|
+
return false;
|
|
1971
|
+
return `${parsedRequest.pathname}${parsedRequest.search}`.startsWith(`${parsedTarget.pathname}${parsedTarget.search}`);
|
|
1972
|
+
} catch {
|
|
1973
|
+
return requestUrl.startsWith(target);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
target.lastIndex = 0;
|
|
1977
|
+
return target.test(requestUrl);
|
|
1978
|
+
}
|
|
1979
|
+
function sameOrigin(requestUrl, baseHref) {
|
|
1980
|
+
try {
|
|
1981
|
+
return new URL(requestUrl, baseHref).origin === new URL(baseHref).origin;
|
|
1982
|
+
} catch {
|
|
1983
|
+
return false;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
function traceparentFromContext(context) {
|
|
1987
|
+
return `00-${context.traceId}-${context.spanId}-01`;
|
|
1988
|
+
}
|
|
1989
|
+
function routeForRequest(request, route) {
|
|
1990
|
+
if (typeof route === "function")
|
|
1991
|
+
return route(request) ?? safeUrlParts(request.url).path;
|
|
1992
|
+
if (route)
|
|
1993
|
+
return route;
|
|
1994
|
+
return safeUrlParts(request.url).path;
|
|
1995
|
+
}
|
|
1996
|
+
async function emitNodeHttpRequestTelemetry(input) {
|
|
1997
|
+
const source = input.opts.source ?? input.opts.framework ?? runtimeName();
|
|
1998
|
+
const client = input.opts.client ?? new LogsClient({ ...input.opts, source });
|
|
1999
|
+
const request = nodeRequestToRequest(input.request);
|
|
2000
|
+
const { traceId, parentSpanId } = traceContextFromRequest(request);
|
|
2001
|
+
const spanId = randomHex(16);
|
|
2002
|
+
const method = request.method || "GET";
|
|
2003
|
+
const url = safeUrlParts(request.url);
|
|
2004
|
+
const operation = input.opts.operation ?? "http.server";
|
|
2005
|
+
const durationMs = Date.now() - input.startedAt;
|
|
2006
|
+
const route = nodeRouteForRequest(input.request, input.opts.route);
|
|
2007
|
+
const name = `${method} ${route}`;
|
|
2008
|
+
const statusCode = nodeResponseStatus(input.response, input.error);
|
|
2009
|
+
const normalized = input.error ? normalizeError(input.error) : undefined;
|
|
2010
|
+
const response = nodeResponseToResponse(input.response, statusCode, input.opts.responseHeaderNames);
|
|
2011
|
+
const span = httpServerSpanEvent({
|
|
2012
|
+
source,
|
|
2013
|
+
request,
|
|
2014
|
+
response,
|
|
2015
|
+
traceId,
|
|
2016
|
+
spanId,
|
|
2017
|
+
parentSpanId,
|
|
2018
|
+
method,
|
|
2019
|
+
route,
|
|
2020
|
+
url,
|
|
2021
|
+
operation,
|
|
2022
|
+
name,
|
|
2023
|
+
statusCode,
|
|
2024
|
+
durationMs,
|
|
2025
|
+
startedIso: input.startedIso,
|
|
2026
|
+
requestHeaderNames: input.opts.requestHeaderNames,
|
|
2027
|
+
responseHeaderNames: input.opts.responseHeaderNames,
|
|
2028
|
+
framework: input.opts.framework,
|
|
2029
|
+
errorType: normalized?.type
|
|
2030
|
+
});
|
|
2031
|
+
if (!input.error || input.error instanceof ResponseClosedError) {
|
|
2032
|
+
await sendRequestTelemetry(client, [span], input.opts.waitForTelemetry);
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const exception = {
|
|
2036
|
+
type: "exception",
|
|
2037
|
+
source,
|
|
2038
|
+
severity: "error",
|
|
2039
|
+
trace_id: traceId,
|
|
2040
|
+
span_id: spanId,
|
|
2041
|
+
parent_span_id: parentSpanId,
|
|
2042
|
+
message: normalized?.message ?? "Request failed",
|
|
2043
|
+
body: {
|
|
2044
|
+
exception: {
|
|
2045
|
+
type: normalized?.type,
|
|
2046
|
+
value: normalized?.message,
|
|
2047
|
+
stack_trace: normalized?.stack,
|
|
2048
|
+
handled: false,
|
|
2049
|
+
mechanism: `${input.opts.framework ?? "node-http"}.request`
|
|
2050
|
+
}
|
|
2051
|
+
},
|
|
2052
|
+
attributes: {
|
|
2053
|
+
exception_type: normalized?.type,
|
|
2054
|
+
stack_trace: normalized?.stack,
|
|
2055
|
+
handled: false,
|
|
2056
|
+
mechanism: `${input.opts.framework ?? "node-http"}.request`,
|
|
2057
|
+
operation,
|
|
2058
|
+
method,
|
|
2059
|
+
route,
|
|
2060
|
+
url_scheme: url.scheme,
|
|
2061
|
+
url_host: url.host,
|
|
2062
|
+
url_path: url.path,
|
|
2063
|
+
query_present: url.queryPresent,
|
|
2064
|
+
framework: input.opts.framework,
|
|
2065
|
+
duration_ms: durationMs
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
await sendRequestTelemetry(client, [span, exception], input.opts.waitForTelemetry);
|
|
2069
|
+
}
|
|
2070
|
+
function nodeRequestToRequest(request) {
|
|
2071
|
+
return new Request(nodeRequestUrl(request), {
|
|
2072
|
+
method: nodeRequestMethod(request),
|
|
2073
|
+
headers: nodeHeaders(request)
|
|
150
2074
|
});
|
|
151
|
-
|
|
2075
|
+
}
|
|
2076
|
+
function nodeRequestUrl(request) {
|
|
2077
|
+
const input = request.originalUrl ?? request.url ?? request.path ?? "/";
|
|
2078
|
+
if (/^https?:\/\//i.test(input))
|
|
2079
|
+
return safeAbsoluteUrl(input) ?? "http://localhost/";
|
|
2080
|
+
const path = input.startsWith("/") ? input : `/${input}`;
|
|
2081
|
+
const host = safeNodeHost(firstHeaderValue(nodeRequestHeader(request, "host")) ?? request.hostname) ?? "localhost";
|
|
2082
|
+
const scheme = safeNodeScheme(request.protocol ?? firstHeaderValue(nodeRequestHeader(request, "x-forwarded-proto")));
|
|
2083
|
+
return safeAbsoluteUrl(`${scheme}://${host}${path}`) ?? `http://localhost${path}`;
|
|
2084
|
+
}
|
|
2085
|
+
function safeAbsoluteUrl(input) {
|
|
2086
|
+
try {
|
|
2087
|
+
return new URL(input).toString();
|
|
2088
|
+
} catch {
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
function safeNodeHost(input) {
|
|
2093
|
+
const host = input?.trim();
|
|
2094
|
+
if (!host || /[\s/\\?#@]/.test(host))
|
|
2095
|
+
return;
|
|
2096
|
+
try {
|
|
2097
|
+
const parsed = new URL(`http://${host}/`);
|
|
2098
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash)
|
|
2099
|
+
return;
|
|
2100
|
+
if (parsed.host.toLowerCase() !== host.toLowerCase())
|
|
2101
|
+
return;
|
|
2102
|
+
return host;
|
|
2103
|
+
} catch {
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
function safeNodeScheme(input) {
|
|
2108
|
+
const scheme = input?.split(",")[0]?.trim().replace(/:$/, "").toLowerCase();
|
|
2109
|
+
return scheme === "https" ? "https" : "http";
|
|
2110
|
+
}
|
|
2111
|
+
function nodeRequestMethod(request) {
|
|
2112
|
+
const method = request.method?.toUpperCase() ?? "GET";
|
|
2113
|
+
return /^[A-Z][A-Z0-9-]*$/.test(method) ? method : "GET";
|
|
2114
|
+
}
|
|
2115
|
+
function nodeHeaders(request) {
|
|
2116
|
+
const headers = new Headers;
|
|
2117
|
+
if (request.headers instanceof Headers) {
|
|
2118
|
+
request.headers.forEach((value, name) => headers.set(name, value));
|
|
2119
|
+
return headers;
|
|
2120
|
+
}
|
|
2121
|
+
for (const [name, value] of Object.entries(request.headers ?? {})) {
|
|
2122
|
+
setHeaderValue(headers, name, value);
|
|
2123
|
+
}
|
|
2124
|
+
for (const name of ["host", "traceparent", "tracestate", "x-forwarded-proto"]) {
|
|
2125
|
+
const value = firstHeaderValue(nodeRequestHeader(request, name));
|
|
2126
|
+
if (value && !headers.has(name))
|
|
2127
|
+
headers.set(name, value);
|
|
2128
|
+
}
|
|
2129
|
+
return headers;
|
|
2130
|
+
}
|
|
2131
|
+
function setHeaderValue(headers, name, value) {
|
|
2132
|
+
if (value === undefined)
|
|
2133
|
+
return;
|
|
2134
|
+
if (Array.isArray(value)) {
|
|
2135
|
+
headers.set(name, value.map(String).join(", "));
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
headers.set(name, String(value));
|
|
2139
|
+
}
|
|
2140
|
+
function nodeRequestHeader(request, name) {
|
|
2141
|
+
const getter = request.header ?? request.get;
|
|
2142
|
+
const direct = getter?.call(request, name);
|
|
2143
|
+
if (direct !== undefined)
|
|
2144
|
+
return direct;
|
|
2145
|
+
const headers = request.headers;
|
|
2146
|
+
if (!headers)
|
|
2147
|
+
return;
|
|
2148
|
+
if (headers instanceof Headers)
|
|
2149
|
+
return headers.get(name) ?? undefined;
|
|
2150
|
+
const lowerName = name.toLowerCase();
|
|
2151
|
+
for (const [headerName, value] of Object.entries(headers)) {
|
|
2152
|
+
if (headerName.toLowerCase() === lowerName)
|
|
2153
|
+
return value;
|
|
2154
|
+
}
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
function nodeRouteForRequest(request, route) {
|
|
2158
|
+
if (typeof route === "function")
|
|
2159
|
+
return route(request) ?? nodeRouteFallback(request);
|
|
2160
|
+
if (route)
|
|
2161
|
+
return route;
|
|
2162
|
+
return nodeRouteFallback(request);
|
|
2163
|
+
}
|
|
2164
|
+
function nodeRouteFallback(request) {
|
|
2165
|
+
const routePath = routePathValue(request.route);
|
|
2166
|
+
if (routePath)
|
|
2167
|
+
return joinRoutePath(request.baseUrl, routePath);
|
|
2168
|
+
if (request.routerPath)
|
|
2169
|
+
return request.routerPath;
|
|
2170
|
+
if (request.routeOptions?.url)
|
|
2171
|
+
return request.routeOptions.url;
|
|
2172
|
+
if (request.path)
|
|
2173
|
+
return request.path;
|
|
2174
|
+
return safeUrlParts(nodeRequestUrl(request)).path;
|
|
2175
|
+
}
|
|
2176
|
+
function routePathValue(route) {
|
|
2177
|
+
if (!route)
|
|
2178
|
+
return;
|
|
2179
|
+
if (typeof route === "string")
|
|
2180
|
+
return route;
|
|
2181
|
+
const path = route.path;
|
|
2182
|
+
if (!path)
|
|
2183
|
+
return;
|
|
2184
|
+
if (Array.isArray(path))
|
|
2185
|
+
return path.map((part) => String(part)).join("|");
|
|
2186
|
+
return String(path);
|
|
2187
|
+
}
|
|
2188
|
+
function joinRoutePath(baseUrl, routePath) {
|
|
2189
|
+
if (!baseUrl)
|
|
2190
|
+
return routePath;
|
|
2191
|
+
if (routePath === "/")
|
|
2192
|
+
return baseUrl || "/";
|
|
2193
|
+
return `${baseUrl.replace(/\/$/, "")}/${routePath.replace(/^\//, "")}`;
|
|
2194
|
+
}
|
|
2195
|
+
function nodeResponseStatus(response, error) {
|
|
2196
|
+
if (error instanceof ResponseClosedError)
|
|
2197
|
+
return 499;
|
|
2198
|
+
const status = response.statusCode ?? response.status;
|
|
2199
|
+
if (typeof status === "number" && status >= 100 && status <= 599) {
|
|
2200
|
+
if (error && status < 400)
|
|
2201
|
+
return 500;
|
|
2202
|
+
return status;
|
|
2203
|
+
}
|
|
2204
|
+
return error ? 500 : 200;
|
|
2205
|
+
}
|
|
2206
|
+
function nodeResponseToResponse(response, status, headerNames) {
|
|
2207
|
+
const headers = new Headers;
|
|
2208
|
+
for (const name of headerNames ?? []) {
|
|
2209
|
+
const value = firstHeaderValue(nodeResponseHeader(response, name));
|
|
2210
|
+
if (value !== undefined)
|
|
2211
|
+
headers.set(name, value);
|
|
2212
|
+
}
|
|
2213
|
+
return new Response(null, { status: status >= 200 ? status : 200, headers });
|
|
2214
|
+
}
|
|
2215
|
+
function nodeResponseHeader(response, name) {
|
|
2216
|
+
const direct = response.getHeader?.call(response, name);
|
|
2217
|
+
if (direct !== undefined)
|
|
2218
|
+
return direct;
|
|
2219
|
+
const headers = response.headers;
|
|
2220
|
+
if (!headers)
|
|
2221
|
+
return;
|
|
2222
|
+
if (headers instanceof Headers)
|
|
2223
|
+
return headers.get(name) ?? undefined;
|
|
2224
|
+
const lowerName = name.toLowerCase();
|
|
2225
|
+
for (const [headerName, value] of Object.entries(headers)) {
|
|
2226
|
+
if (headerName.toLowerCase() === lowerName)
|
|
2227
|
+
return value;
|
|
2228
|
+
}
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
function firstHeaderValue(value) {
|
|
2232
|
+
if (value === undefined)
|
|
2233
|
+
return;
|
|
2234
|
+
if (Array.isArray(value))
|
|
2235
|
+
return value.length ? String(value[0]) : undefined;
|
|
2236
|
+
return String(value);
|
|
2237
|
+
}
|
|
2238
|
+
function addNodeListener(target, event, listener) {
|
|
2239
|
+
if (typeof target.on === "function") {
|
|
2240
|
+
target.on(event, listener);
|
|
2241
|
+
return () => removeNodeListener(target, event, listener);
|
|
2242
|
+
}
|
|
2243
|
+
if (typeof target.once === "function") {
|
|
2244
|
+
const onceListener = (...args) => {
|
|
2245
|
+
removeNodeListener(target, event, onceListener);
|
|
2246
|
+
listener(...args);
|
|
2247
|
+
};
|
|
2248
|
+
target.once(event, onceListener);
|
|
2249
|
+
return () => removeNodeListener(target, event, onceListener);
|
|
2250
|
+
}
|
|
2251
|
+
return () => {};
|
|
2252
|
+
}
|
|
2253
|
+
function cleanupNodeListeners(cleanups) {
|
|
2254
|
+
while (cleanups.length)
|
|
2255
|
+
cleanups.pop()?.();
|
|
2256
|
+
}
|
|
2257
|
+
function removeNodeListener(target, event, listener) {
|
|
2258
|
+
if (typeof target.off === "function") {
|
|
2259
|
+
target.off(event, listener);
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
target.removeListener?.(event, listener);
|
|
2263
|
+
}
|
|
2264
|
+
function nodeErrorMonitorEvent() {
|
|
2265
|
+
try {
|
|
2266
|
+
const runtime = globalThis;
|
|
2267
|
+
const errorMonitor = runtime.process?.getBuiltinModule?.("node:events")?.errorMonitor;
|
|
2268
|
+
return typeof errorMonitor === "symbol" ? errorMonitor : undefined;
|
|
2269
|
+
} catch {
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
function setExpressCapture(request, capture) {
|
|
2274
|
+
if (request && typeof request === "object") {
|
|
2275
|
+
request[expressCaptureSymbol] = capture;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
function getExpressCapture(request) {
|
|
2279
|
+
return request[expressCaptureSymbol];
|
|
2280
|
+
}
|
|
2281
|
+
function clearExpressCapture(request) {
|
|
2282
|
+
if (request && typeof request === "object") {
|
|
2283
|
+
delete request[expressCaptureSymbol];
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
function fastifyRequestLike(request) {
|
|
2287
|
+
if (!request.raw)
|
|
2288
|
+
return request;
|
|
2289
|
+
return {
|
|
2290
|
+
...request.raw,
|
|
2291
|
+
method: request.method ?? request.raw.method,
|
|
2292
|
+
url: request.url ?? request.raw.url,
|
|
2293
|
+
headers: request.headers ?? request.raw.headers,
|
|
2294
|
+
header: request.header?.bind(request) ?? request.raw.header?.bind(request.raw),
|
|
2295
|
+
get: request.get?.bind(request) ?? request.raw.get?.bind(request.raw),
|
|
2296
|
+
on: request.raw.on?.bind(request.raw),
|
|
2297
|
+
once: request.raw.once?.bind(request.raw),
|
|
2298
|
+
off: request.raw.off?.bind(request.raw),
|
|
2299
|
+
removeListener: request.raw.removeListener?.bind(request.raw),
|
|
2300
|
+
routeOptions: request.routeOptions ?? request.raw.routeOptions,
|
|
2301
|
+
routerPath: request.routerPath ?? request.raw.routerPath
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
function fastifyResponseLike(reply) {
|
|
2305
|
+
if (!reply.raw)
|
|
2306
|
+
return reply;
|
|
2307
|
+
return {
|
|
2308
|
+
...reply.raw,
|
|
2309
|
+
statusCode: reply.statusCode ?? reply.raw.statusCode,
|
|
2310
|
+
status: reply.status ?? reply.raw.status,
|
|
2311
|
+
headers: reply.headers ?? reply.raw.headers,
|
|
2312
|
+
getHeader: reply.getHeader?.bind(reply) ?? reply.raw.getHeader?.bind(reply.raw),
|
|
2313
|
+
on: reply.raw.on?.bind(reply.raw),
|
|
2314
|
+
once: reply.raw.once?.bind(reply.raw),
|
|
2315
|
+
off: reply.raw.off?.bind(reply.raw),
|
|
2316
|
+
removeListener: reply.raw.removeListener?.bind(reply.raw)
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
function fastifyRoute(request) {
|
|
2320
|
+
return request.routeOptions?.url ?? request.routerPath;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
class ResponseClosedError extends Error {
|
|
2324
|
+
constructor() {
|
|
2325
|
+
super("Response closed before finish");
|
|
2326
|
+
this.name = "ResponseClosedError";
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
function safeUrlParts(input) {
|
|
2330
|
+
try {
|
|
2331
|
+
const url = new URL(input);
|
|
2332
|
+
return {
|
|
2333
|
+
scheme: url.protocol.replace(/:$/, "") || null,
|
|
2334
|
+
host: url.host || null,
|
|
2335
|
+
path: url.pathname || "/",
|
|
2336
|
+
queryPresent: url.search.length > 0
|
|
2337
|
+
};
|
|
2338
|
+
} catch {
|
|
2339
|
+
return { scheme: null, host: null, path: input.split("?")[0] || "/", queryPresent: input.includes("?") };
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
function traceContextFromRequest(request) {
|
|
2343
|
+
const parsed = parseTraceparent(request.headers.get("traceparent"));
|
|
2344
|
+
if (parsed)
|
|
2345
|
+
return { traceId: parsed.traceId, parentSpanId: parsed.spanId };
|
|
2346
|
+
return { traceId: randomHex(32) };
|
|
2347
|
+
}
|
|
2348
|
+
function parseTraceparent(value) {
|
|
2349
|
+
const match = value?.match(/^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/);
|
|
2350
|
+
const version = match?.[1];
|
|
2351
|
+
const traceId = match?.[2];
|
|
2352
|
+
const spanId = match?.[3];
|
|
2353
|
+
if (version === "ff")
|
|
2354
|
+
return;
|
|
2355
|
+
if (!traceId || !spanId)
|
|
2356
|
+
return;
|
|
2357
|
+
if (/^0+$/.test(traceId) || /^0+$/.test(spanId))
|
|
2358
|
+
return;
|
|
2359
|
+
return { traceId, spanId };
|
|
2360
|
+
}
|
|
2361
|
+
function responseLike(value) {
|
|
2362
|
+
if (typeof Response !== "undefined" && value instanceof Response)
|
|
2363
|
+
return value;
|
|
2364
|
+
if (value && typeof value === "object" && "status" in value && typeof value.status === "number") {
|
|
2365
|
+
return value;
|
|
2366
|
+
}
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
function httpServerSpanEvent(input) {
|
|
2370
|
+
const status = input.statusCode ?? 0;
|
|
2371
|
+
const statusText = status >= 500 || input.errorType ? "error" : "ok";
|
|
2372
|
+
return {
|
|
2373
|
+
type: "span",
|
|
2374
|
+
source: input.source,
|
|
2375
|
+
severity: status >= 500 || input.errorType ? "error" : status >= 400 ? "warn" : "info",
|
|
2376
|
+
trace_id: input.traceId,
|
|
2377
|
+
span_id: input.spanId,
|
|
2378
|
+
parent_span_id: input.parentSpanId,
|
|
2379
|
+
message: input.name,
|
|
2380
|
+
body: {
|
|
2381
|
+
name: input.name,
|
|
2382
|
+
operation: input.operation,
|
|
2383
|
+
status: statusText,
|
|
2384
|
+
started_at: input.startedIso,
|
|
2385
|
+
duration_ms: input.durationMs
|
|
2386
|
+
},
|
|
2387
|
+
attributes: {
|
|
2388
|
+
name: input.name,
|
|
2389
|
+
operation: input.operation,
|
|
2390
|
+
method: input.method,
|
|
2391
|
+
route: input.route,
|
|
2392
|
+
framework: input.framework,
|
|
2393
|
+
status: statusText,
|
|
2394
|
+
status_code: input.statusCode,
|
|
2395
|
+
duration_ms: input.durationMs,
|
|
2396
|
+
started_at: input.startedIso,
|
|
2397
|
+
url_scheme: input.url.scheme,
|
|
2398
|
+
url_host: input.url.host,
|
|
2399
|
+
url_path: input.url.path,
|
|
2400
|
+
query_present: input.url.queryPresent,
|
|
2401
|
+
request_headers: pickHeaders(input.request.headers, input.requestHeaderNames),
|
|
2402
|
+
response_headers: input.response ? pickHeaders(input.response.headers, input.responseHeaderNames) : undefined,
|
|
2403
|
+
error_type: input.errorType
|
|
2404
|
+
}
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
function pickHeaders(headers, names) {
|
|
2408
|
+
if (!names?.length)
|
|
2409
|
+
return;
|
|
2410
|
+
const picked = {};
|
|
2411
|
+
for (const name of names) {
|
|
2412
|
+
const value = headers.get(name);
|
|
2413
|
+
if (value !== null)
|
|
2414
|
+
picked[name.toLowerCase()] = value;
|
|
2415
|
+
}
|
|
2416
|
+
return Object.keys(picked).length ? picked : undefined;
|
|
2417
|
+
}
|
|
2418
|
+
function structuredLogQuery(opts) {
|
|
2419
|
+
const params = new URLSearchParams;
|
|
2420
|
+
setParam(params, "format", opts.format);
|
|
2421
|
+
setParam(params, "source", opts.source);
|
|
2422
|
+
setParam(params, "service", opts.service);
|
|
2423
|
+
setParam(params, "project_id", opts.projectId);
|
|
2424
|
+
setParam(params, "page_id", opts.pageId);
|
|
2425
|
+
setParam(params, "machine_id", opts.machineId);
|
|
2426
|
+
setParam(params, "repo_id", opts.repoId);
|
|
2427
|
+
setParam(params, "app_id", opts.appId);
|
|
2428
|
+
setParam(params, "process_id", opts.processId);
|
|
2429
|
+
setParam(params, "run_id", opts.runId);
|
|
2430
|
+
setParam(params, "trace_id", opts.traceId);
|
|
2431
|
+
setParam(params, "span_id", opts.spanId);
|
|
2432
|
+
setParam(params, "parent_span_id", opts.parentSpanId);
|
|
2433
|
+
setParam(params, "session_id", opts.sessionId);
|
|
2434
|
+
setParam(params, "release_id", opts.releaseId);
|
|
2435
|
+
setParam(params, "environment", opts.environment);
|
|
2436
|
+
setParam(params, "agent", opts.agent);
|
|
2437
|
+
setParam(params, "url", opts.logUrl);
|
|
2438
|
+
const query = params.toString();
|
|
2439
|
+
return query ? `?${query}` : "";
|
|
2440
|
+
}
|
|
2441
|
+
function setParam(params, key, value) {
|
|
2442
|
+
if (value !== undefined && value !== "")
|
|
2443
|
+
params.set(key, value);
|
|
2444
|
+
}
|
|
2445
|
+
function structuredLogBatchBody(records, opts) {
|
|
2446
|
+
if (!opts.metadata && !opts.sourceEventPrefix)
|
|
2447
|
+
return records;
|
|
2448
|
+
return {
|
|
2449
|
+
logs: records,
|
|
2450
|
+
metadata: opts.metadata,
|
|
2451
|
+
source_event_prefix: opts.sourceEventPrefix
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
function createStructuredLogQueue(opts) {
|
|
2455
|
+
const client = new LogsClient(opts);
|
|
2456
|
+
const maxBatchSize = Math.max(1, opts.maxBatchSize ?? 20);
|
|
2457
|
+
const maxQueueSize = Math.max(1, opts.maxQueueSize ?? 1e4);
|
|
2458
|
+
const maxRetries = Math.max(0, opts.maxRetries ?? 3);
|
|
2459
|
+
const retryBaseDelayMs = Math.max(0, opts.retryBaseDelayMs ?? 250);
|
|
2460
|
+
const retryMaxDelayMs = Math.max(retryBaseDelayMs, opts.retryMaxDelayMs ?? 5000);
|
|
2461
|
+
const flushIntervalMs = opts.flushIntervalMs ?? 2000;
|
|
2462
|
+
const transportId = randomId(`${opts.format ?? "structured"}_transport`);
|
|
2463
|
+
const transportSendOptions = copyStructuredLogSendOptions(opts);
|
|
2464
|
+
const spooledTransportSendOptions = redactStructuredLogSendOptions(transportSendOptions);
|
|
2465
|
+
const spool = createStructuredLogSpool(opts);
|
|
2466
|
+
let q = [];
|
|
2467
|
+
const stats = {
|
|
2468
|
+
enqueued: 0,
|
|
2469
|
+
sent: 0,
|
|
2470
|
+
dropped: 0,
|
|
2471
|
+
retries: 0,
|
|
2472
|
+
failed_batches: 0,
|
|
2473
|
+
spool_loaded: 0,
|
|
2474
|
+
spool_dropped: 0,
|
|
2475
|
+
spool_errors: 0
|
|
2476
|
+
};
|
|
2477
|
+
let batchNumber = 0;
|
|
2478
|
+
let recordNumber = 0;
|
|
2479
|
+
let stopped = false;
|
|
2480
|
+
let inFlight;
|
|
2481
|
+
let activeBatch;
|
|
2482
|
+
const nextBatchPrefix = (sourceEventPrefix) => {
|
|
2483
|
+
batchNumber += 1;
|
|
2484
|
+
return `${redactSdkString(sourceEventPrefix ?? "sdk-structured-transport")}:${transportId}:${batchNumber}`;
|
|
2485
|
+
};
|
|
2486
|
+
const nextRecordId = () => {
|
|
2487
|
+
recordNumber += 1;
|
|
2488
|
+
return `${transportId}:record:${recordNumber}`;
|
|
2489
|
+
};
|
|
2490
|
+
const persistSpool = () => {
|
|
2491
|
+
if (!spool.enabled)
|
|
2492
|
+
return;
|
|
2493
|
+
try {
|
|
2494
|
+
spool.save(q);
|
|
2495
|
+
} catch (error) {
|
|
2496
|
+
stats.spool_errors += 1;
|
|
2497
|
+
opts.onError?.(error);
|
|
2498
|
+
}
|
|
2499
|
+
};
|
|
2500
|
+
const dropItem = (item, reason, error) => {
|
|
2501
|
+
stats.dropped += 1;
|
|
2502
|
+
if (spool.enabled)
|
|
2503
|
+
stats.spool_dropped += 1;
|
|
2504
|
+
opts.onDrop?.({ reason, record: item.record, attempts: item.attempts, error });
|
|
2505
|
+
};
|
|
2506
|
+
if (spool.enabled) {
|
|
2507
|
+
try {
|
|
2508
|
+
const loaded = spool.load();
|
|
2509
|
+
q = loaded.items;
|
|
2510
|
+
stats.spool_loaded = q.length;
|
|
2511
|
+
stats.spool_errors += loaded.errors;
|
|
2512
|
+
for (const item of loaded.dropped)
|
|
2513
|
+
dropItem(item, "queue_full");
|
|
2514
|
+
if (loaded.errors || loaded.dropped.length)
|
|
2515
|
+
persistSpool();
|
|
2516
|
+
} catch (error) {
|
|
2517
|
+
stats.spool_errors += 1;
|
|
2518
|
+
opts.onError?.(error);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
const makeQueueSpace = (incoming) => {
|
|
2522
|
+
while (q.length >= maxQueueSize) {
|
|
2523
|
+
const dropIndex = q.findIndex((item) => !activeBatch?.includes(item));
|
|
2524
|
+
if (dropIndex < 0) {
|
|
2525
|
+
dropItem({ record: incoming, attempts: 0 }, "queue_full");
|
|
2526
|
+
return false;
|
|
2527
|
+
}
|
|
2528
|
+
const dropped = q.splice(dropIndex, 1)[0];
|
|
2529
|
+
if (dropped)
|
|
2530
|
+
dropItem(dropped, "queue_full");
|
|
2531
|
+
persistSpool();
|
|
2532
|
+
}
|
|
2533
|
+
return true;
|
|
2534
|
+
};
|
|
2535
|
+
const retryDelay = (attempts) => {
|
|
2536
|
+
if (retryBaseDelayMs === 0)
|
|
2537
|
+
return 0;
|
|
2538
|
+
return Math.min(retryMaxDelayMs, retryBaseDelayMs * 2 ** Math.max(0, attempts - 1));
|
|
2539
|
+
};
|
|
2540
|
+
const recordForTransport = (item) => {
|
|
2541
|
+
if (!isObjectRecord(item.record) || hasStructuredProducerId(item.record))
|
|
2542
|
+
return item.record;
|
|
2543
|
+
return { ...item.record, _open_logs_event_id: item.eventId };
|
|
2544
|
+
};
|
|
2545
|
+
const flush = async () => {
|
|
2546
|
+
if (inFlight)
|
|
2547
|
+
return inFlight;
|
|
2548
|
+
inFlight = (async () => {
|
|
2549
|
+
let firstDroppedError;
|
|
2550
|
+
while (q.length) {
|
|
2551
|
+
const batch = nextStructuredLogBatch(q, maxBatchSize);
|
|
2552
|
+
const batchSendOptions = batch[0]?.sendOptions ?? transportSendOptions;
|
|
2553
|
+
const batchPrefix = batch[0]?.batchPrefix ?? nextBatchPrefix(batchSendOptions.sourceEventPrefix);
|
|
2554
|
+
for (const item of batch)
|
|
2555
|
+
item.batchPrefix = batchPrefix;
|
|
2556
|
+
persistSpool();
|
|
2557
|
+
activeBatch = batch;
|
|
2558
|
+
try {
|
|
2559
|
+
await client.pushStructuredLogs(batch.map(recordForTransport), {
|
|
2560
|
+
...batchSendOptions,
|
|
2561
|
+
sourceEventPrefix: batchPrefix
|
|
2562
|
+
});
|
|
2563
|
+
q.splice(0, batch.length);
|
|
2564
|
+
stats.sent += batch.length;
|
|
2565
|
+
persistSpool();
|
|
2566
|
+
} catch (error) {
|
|
2567
|
+
stats.failed_batches += 1;
|
|
2568
|
+
for (const item of batch)
|
|
2569
|
+
item.attempts += 1;
|
|
2570
|
+
const exhausted = batch.filter((item) => item.attempts > maxRetries);
|
|
2571
|
+
if (spool.enabled && exhausted.length) {
|
|
2572
|
+
for (const item of exhausted)
|
|
2573
|
+
item.attempts = 0;
|
|
2574
|
+
opts.onError?.(error);
|
|
2575
|
+
firstDroppedError ??= error;
|
|
2576
|
+
persistSpool();
|
|
2577
|
+
break;
|
|
2578
|
+
}
|
|
2579
|
+
for (const item of exhausted) {
|
|
2580
|
+
const index = q.indexOf(item);
|
|
2581
|
+
if (index >= 0)
|
|
2582
|
+
q.splice(index, 1);
|
|
2583
|
+
dropItem(item, "retries_exhausted", error);
|
|
2584
|
+
}
|
|
2585
|
+
if (exhausted.length)
|
|
2586
|
+
opts.onError?.(error);
|
|
2587
|
+
if (exhausted.length)
|
|
2588
|
+
firstDroppedError ??= error;
|
|
2589
|
+
persistSpool();
|
|
2590
|
+
const retryable = batch.filter((item) => item.attempts <= maxRetries && q.includes(item));
|
|
2591
|
+
if (!retryable.length)
|
|
2592
|
+
continue;
|
|
2593
|
+
const attempts = Math.max(...retryable.map((item) => item.attempts));
|
|
2594
|
+
const nextDelayMs = retryDelay(attempts);
|
|
2595
|
+
stats.retries += 1;
|
|
2596
|
+
opts.onRetry?.({
|
|
2597
|
+
error,
|
|
2598
|
+
attempts,
|
|
2599
|
+
pending: q.length,
|
|
2600
|
+
next_delay_ms: nextDelayMs
|
|
2601
|
+
});
|
|
2602
|
+
if (nextDelayMs > 0)
|
|
2603
|
+
await sleep(nextDelayMs);
|
|
2604
|
+
} finally {
|
|
2605
|
+
activeBatch = undefined;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
if (firstDroppedError)
|
|
2609
|
+
throw toError(firstDroppedError);
|
|
2610
|
+
})().finally(() => {
|
|
2611
|
+
inFlight = undefined;
|
|
2612
|
+
});
|
|
2613
|
+
return inFlight;
|
|
2614
|
+
};
|
|
2615
|
+
const interval = setInterval(() => {
|
|
2616
|
+
if (!q.length || stopped)
|
|
2617
|
+
return;
|
|
2618
|
+
flush().catch(opts.onError ?? noop);
|
|
2619
|
+
}, flushIntervalMs);
|
|
2620
|
+
return {
|
|
2621
|
+
enqueue(record) {
|
|
2622
|
+
if (stopped)
|
|
2623
|
+
return;
|
|
2624
|
+
if (!makeQueueSpace(record))
|
|
2625
|
+
return;
|
|
2626
|
+
q.push({
|
|
2627
|
+
record,
|
|
2628
|
+
spoolRecord: redactStructuredSpoolRecord(record),
|
|
2629
|
+
sendOptions: transportSendOptions,
|
|
2630
|
+
spoolSendOptions: spooledTransportSendOptions,
|
|
2631
|
+
attempts: 0,
|
|
2632
|
+
eventId: nextRecordId(),
|
|
2633
|
+
createdAt: new Date().toISOString()
|
|
2634
|
+
});
|
|
2635
|
+
stats.enqueued += 1;
|
|
2636
|
+
persistSpool();
|
|
2637
|
+
},
|
|
2638
|
+
shouldFlush() {
|
|
2639
|
+
return q.length >= maxBatchSize;
|
|
2640
|
+
},
|
|
2641
|
+
async flush() {
|
|
2642
|
+
await flush();
|
|
2643
|
+
},
|
|
2644
|
+
stats() {
|
|
2645
|
+
return {
|
|
2646
|
+
pending: q.length,
|
|
2647
|
+
in_flight: Boolean(inFlight),
|
|
2648
|
+
enqueued: stats.enqueued,
|
|
2649
|
+
sent: stats.sent,
|
|
2650
|
+
dropped: stats.dropped,
|
|
2651
|
+
retries: stats.retries,
|
|
2652
|
+
failed_batches: stats.failed_batches,
|
|
2653
|
+
max_queue_size: maxQueueSize,
|
|
2654
|
+
spool_enabled: spool.enabled,
|
|
2655
|
+
spool_pending: spool.enabled ? q.length : 0,
|
|
2656
|
+
spool_loaded: stats.spool_loaded,
|
|
2657
|
+
spool_dropped: stats.spool_dropped,
|
|
2658
|
+
spool_errors: stats.spool_errors
|
|
2659
|
+
};
|
|
2660
|
+
},
|
|
2661
|
+
stop() {
|
|
2662
|
+
stopped = true;
|
|
2663
|
+
clearInterval(interval);
|
|
2664
|
+
}
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
function createStructuredLogSpool(opts) {
|
|
2668
|
+
const filePath = structuredLogSpoolPath(opts);
|
|
2669
|
+
const builtins = nodeBuiltins();
|
|
2670
|
+
const spooledTransportSendOptions = redactStructuredLogSendOptions(copyStructuredLogSendOptions(opts));
|
|
2671
|
+
if (!filePath || !builtins) {
|
|
2672
|
+
return {
|
|
2673
|
+
enabled: false,
|
|
2674
|
+
load: () => ({ items: [], dropped: [], errors: 0 }),
|
|
2675
|
+
save: () => {}
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
const { fs, path } = builtins;
|
|
2679
|
+
return {
|
|
2680
|
+
enabled: true,
|
|
2681
|
+
load() {
|
|
2682
|
+
if (!fs.existsSync(filePath))
|
|
2683
|
+
return { items: [], dropped: [], errors: 0 };
|
|
2684
|
+
const contents = fs.readFileSync(filePath, "utf8");
|
|
2685
|
+
const items = [];
|
|
2686
|
+
let errors = 0;
|
|
2687
|
+
for (const line of contents.split(/\n/)) {
|
|
2688
|
+
const trimmed = line.trim();
|
|
2689
|
+
if (!trimmed)
|
|
2690
|
+
continue;
|
|
2691
|
+
let parsed;
|
|
2692
|
+
try {
|
|
2693
|
+
const value = JSON.parse(trimmed);
|
|
2694
|
+
if (!isObjectRecord(value)) {
|
|
2695
|
+
errors += 1;
|
|
2696
|
+
continue;
|
|
2697
|
+
}
|
|
2698
|
+
parsed = value;
|
|
2699
|
+
} catch {
|
|
2700
|
+
errors += 1;
|
|
2701
|
+
continue;
|
|
2702
|
+
}
|
|
2703
|
+
if (parsed.version !== 1 || typeof parsed.event_id !== "string") {
|
|
2704
|
+
errors += 1;
|
|
2705
|
+
continue;
|
|
2706
|
+
}
|
|
2707
|
+
if (!isObjectRecord(parsed.record)) {
|
|
2708
|
+
errors += 1;
|
|
2709
|
+
continue;
|
|
2710
|
+
}
|
|
2711
|
+
const sendOptions = loadStructuredSpoolSendOptions(parsed.send_options, spooledTransportSendOptions);
|
|
2712
|
+
if (!sendOptions) {
|
|
2713
|
+
errors += 1;
|
|
2714
|
+
continue;
|
|
2715
|
+
}
|
|
2716
|
+
const record = redactStructuredSpoolRecord(parsed.record);
|
|
2717
|
+
items.push({
|
|
2718
|
+
record,
|
|
2719
|
+
spoolRecord: record,
|
|
2720
|
+
sendOptions,
|
|
2721
|
+
spoolSendOptions: sendOptions,
|
|
2722
|
+
attempts: typeof parsed.attempts === "number" && parsed.attempts >= 0 ? Math.floor(parsed.attempts) : 0,
|
|
2723
|
+
eventId: parsed.event_id,
|
|
2724
|
+
createdAt: typeof parsed.created_at === "string" ? parsed.created_at : new Date().toISOString(),
|
|
2725
|
+
batchPrefix: typeof parsed.batch_prefix === "string" ? redactStructuredBatchPrefix(parsed.batch_prefix) : undefined
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
const maxItems = Math.max(1, opts.maxQueueSize ?? 1e4);
|
|
2729
|
+
const dropped = items.length > maxItems ? items.slice(0, items.length - maxItems) : [];
|
|
2730
|
+
return {
|
|
2731
|
+
items: items.slice(-maxItems),
|
|
2732
|
+
dropped,
|
|
2733
|
+
errors
|
|
2734
|
+
};
|
|
2735
|
+
},
|
|
2736
|
+
save(items) {
|
|
2737
|
+
if (!items.length) {
|
|
2738
|
+
if (fs.existsSync(filePath))
|
|
2739
|
+
fs.unlinkSync(filePath);
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
2743
|
+
const tmpPath = `${filePath}.${Date.now().toString(36)}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
2744
|
+
const lines = items.map((item) => JSON.stringify({
|
|
2745
|
+
version: 1,
|
|
2746
|
+
record: redactStructuredSpoolRecord(item.spoolRecord),
|
|
2747
|
+
send_options: redactStructuredLogSendOptions(item.spoolSendOptions),
|
|
2748
|
+
event_id: item.eventId,
|
|
2749
|
+
attempts: item.attempts,
|
|
2750
|
+
created_at: item.createdAt,
|
|
2751
|
+
batch_prefix: item.batchPrefix
|
|
2752
|
+
}));
|
|
2753
|
+
fs.writeFileSync(tmpPath, `${lines.join(`
|
|
2754
|
+
`)}
|
|
2755
|
+
`, "utf8");
|
|
2756
|
+
fs.renameSync(tmpPath, filePath);
|
|
2757
|
+
}
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
function nextStructuredLogBatch(queue, maxBatchSize) {
|
|
2761
|
+
const first = queue[0];
|
|
2762
|
+
if (!first)
|
|
2763
|
+
return [];
|
|
2764
|
+
const key = structuredLogSendOptionsKey(first.sendOptions);
|
|
2765
|
+
const batch = [];
|
|
2766
|
+
for (const item of queue.slice(0, maxBatchSize)) {
|
|
2767
|
+
if (batch.length && structuredLogSendOptionsKey(item.sendOptions) !== key)
|
|
2768
|
+
break;
|
|
2769
|
+
batch.push(item);
|
|
2770
|
+
}
|
|
2771
|
+
return batch;
|
|
2772
|
+
}
|
|
2773
|
+
function structuredLogSendOptionsKey(opts) {
|
|
2774
|
+
return safeStringify(opts);
|
|
2775
|
+
}
|
|
2776
|
+
function copyStructuredLogSendOptions(opts) {
|
|
2777
|
+
return {
|
|
2778
|
+
format: opts.format,
|
|
2779
|
+
source: opts.source,
|
|
2780
|
+
service: opts.service,
|
|
2781
|
+
projectId: opts.projectId,
|
|
2782
|
+
pageId: opts.pageId,
|
|
2783
|
+
machineId: opts.machineId,
|
|
2784
|
+
repoId: opts.repoId,
|
|
2785
|
+
appId: opts.appId,
|
|
2786
|
+
processId: opts.processId,
|
|
2787
|
+
runId: opts.runId,
|
|
2788
|
+
traceId: opts.traceId,
|
|
2789
|
+
spanId: opts.spanId,
|
|
2790
|
+
parentSpanId: opts.parentSpanId,
|
|
2791
|
+
sessionId: opts.sessionId,
|
|
2792
|
+
releaseId: opts.releaseId,
|
|
2793
|
+
environment: opts.environment,
|
|
2794
|
+
agent: opts.agent,
|
|
2795
|
+
logUrl: opts.logUrl,
|
|
2796
|
+
metadata: isObjectRecord(opts.metadata) ? { ...opts.metadata } : undefined,
|
|
2797
|
+
sourceEventPrefix: opts.sourceEventPrefix
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
function redactStructuredLogSendOptions(opts) {
|
|
2801
|
+
const redacted = redactSdkValue(copyStructuredLogSendOptions(opts));
|
|
2802
|
+
return isObjectRecord(redacted) ? copyStructuredLogSendOptions(redacted) : {};
|
|
2803
|
+
}
|
|
2804
|
+
function redactStructuredBatchPrefix(value) {
|
|
2805
|
+
const generated = value.match(/^(.*)(:[A-Za-z0-9_-]+_transport_[A-Za-z0-9]+_[A-Za-z0-9]+:\d+)$/);
|
|
2806
|
+
const userPrefix = generated?.[1];
|
|
2807
|
+
const suffix = generated?.[2];
|
|
2808
|
+
if (userPrefix !== undefined && suffix !== undefined) {
|
|
2809
|
+
return `${redactSdkString(userPrefix)}${suffix}`;
|
|
2810
|
+
}
|
|
2811
|
+
return redactSdkString(value);
|
|
2812
|
+
}
|
|
2813
|
+
function loadStructuredSpoolSendOptions(value, fallback) {
|
|
2814
|
+
if (value === undefined)
|
|
2815
|
+
return fallback;
|
|
2816
|
+
if (!isObjectRecord(value))
|
|
2817
|
+
return;
|
|
2818
|
+
if (!isValidStructuredSpoolSendOptions(value))
|
|
2819
|
+
return;
|
|
2820
|
+
return redactStructuredLogSendOptions(value);
|
|
2821
|
+
}
|
|
2822
|
+
function isValidStructuredSpoolSendOptions(value) {
|
|
2823
|
+
if (value.format !== undefined && !SDK_STRUCTURED_LOG_FORMATS.has(value.format)) {
|
|
2824
|
+
return false;
|
|
2825
|
+
}
|
|
2826
|
+
if (value.source !== undefined && !SDK_STRUCTURED_LOG_SOURCES.has(value.source)) {
|
|
2827
|
+
return false;
|
|
2828
|
+
}
|
|
2829
|
+
for (const key of SDK_STRUCTURED_LOG_STRING_OPTION_KEYS) {
|
|
2830
|
+
if (value[key] !== undefined && typeof value[key] !== "string")
|
|
2831
|
+
return false;
|
|
2832
|
+
}
|
|
2833
|
+
return value.metadata === undefined || isObjectRecord(value.metadata);
|
|
2834
|
+
}
|
|
2835
|
+
var SDK_STRUCTURED_LOG_FORMATS = new Set(["auto", "pino", "winston", "json"]);
|
|
2836
|
+
var SDK_STRUCTURED_LOG_SOURCES = new Set([
|
|
2837
|
+
"sdk",
|
|
2838
|
+
"scanner",
|
|
2839
|
+
"node",
|
|
2840
|
+
"bun",
|
|
2841
|
+
"next",
|
|
2842
|
+
"vite",
|
|
2843
|
+
"cli",
|
|
2844
|
+
"build",
|
|
2845
|
+
"test",
|
|
2846
|
+
"mcp",
|
|
2847
|
+
"agent",
|
|
2848
|
+
"otel",
|
|
2849
|
+
"system",
|
|
2850
|
+
"pino",
|
|
2851
|
+
"winston",
|
|
2852
|
+
"structured"
|
|
2853
|
+
]);
|
|
2854
|
+
var SDK_STRUCTURED_LOG_STRING_OPTION_KEYS = [
|
|
2855
|
+
"service",
|
|
2856
|
+
"projectId",
|
|
2857
|
+
"pageId",
|
|
2858
|
+
"machineId",
|
|
2859
|
+
"repoId",
|
|
2860
|
+
"appId",
|
|
2861
|
+
"processId",
|
|
2862
|
+
"runId",
|
|
2863
|
+
"traceId",
|
|
2864
|
+
"spanId",
|
|
2865
|
+
"parentSpanId",
|
|
2866
|
+
"sessionId",
|
|
2867
|
+
"releaseId",
|
|
2868
|
+
"environment",
|
|
2869
|
+
"agent",
|
|
2870
|
+
"logUrl",
|
|
2871
|
+
"sourceEventPrefix"
|
|
2872
|
+
];
|
|
2873
|
+
function structuredLogSpoolPath(opts) {
|
|
2874
|
+
if (opts.spoolFile)
|
|
2875
|
+
return opts.spoolFile;
|
|
2876
|
+
if (!opts.spoolDirectory)
|
|
2877
|
+
return;
|
|
2878
|
+
const builtins = nodeBuiltins();
|
|
2879
|
+
const fileName = [
|
|
2880
|
+
"open-logs",
|
|
2881
|
+
sanitizeSpoolPathPart(opts.projectId ?? "default"),
|
|
2882
|
+
sanitizeSpoolPathPart(opts.format ?? "structured"),
|
|
2883
|
+
"structured-spool.jsonl"
|
|
2884
|
+
].join("-");
|
|
2885
|
+
return builtins?.path.join(opts.spoolDirectory, fileName);
|
|
2886
|
+
}
|
|
2887
|
+
function nodeBuiltins() {
|
|
2888
|
+
const processLike = globalThis.process;
|
|
2889
|
+
const getBuiltinModule = processLike?.getBuiltinModule;
|
|
2890
|
+
if (!getBuiltinModule)
|
|
2891
|
+
return;
|
|
2892
|
+
const fs = getBuiltinModule.call(processLike, "node:fs");
|
|
2893
|
+
const path = getBuiltinModule.call(processLike, "node:path");
|
|
2894
|
+
if (!fs || !path || typeof fs.existsSync !== "function" || typeof fs.mkdirSync !== "function" || typeof fs.readFileSync !== "function" || typeof fs.renameSync !== "function" || typeof fs.unlinkSync !== "function" || typeof fs.writeFileSync !== "function" || typeof path.dirname !== "function" || typeof path.join !== "function") {
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
return { fs, path };
|
|
2898
|
+
}
|
|
2899
|
+
function sanitizeSpoolPathPart(value) {
|
|
2900
|
+
const cleaned = value.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2901
|
+
return cleaned.slice(0, 80) || "default";
|
|
2902
|
+
}
|
|
2903
|
+
function redactStructuredSpoolRecord(value) {
|
|
2904
|
+
return redactSdkValue(value);
|
|
2905
|
+
}
|
|
2906
|
+
function redactSdkValue(value, depth = 0) {
|
|
2907
|
+
if (value === null || value === undefined)
|
|
2908
|
+
return value;
|
|
2909
|
+
if (typeof value === "string")
|
|
2910
|
+
return redactSdkString(value);
|
|
2911
|
+
if (typeof value !== "object" || depth >= 12)
|
|
2912
|
+
return value;
|
|
2913
|
+
if (Array.isArray(value)) {
|
|
2914
|
+
const output2 = [];
|
|
2915
|
+
let previousWasSensitiveFlag = false;
|
|
2916
|
+
for (const item of value) {
|
|
2917
|
+
if (previousWasSensitiveFlag && typeof item === "string") {
|
|
2918
|
+
output2.push("[REDACTED]");
|
|
2919
|
+
previousWasSensitiveFlag = false;
|
|
2920
|
+
continue;
|
|
2921
|
+
}
|
|
2922
|
+
const redacted = redactSdkValue(item, depth + 1);
|
|
2923
|
+
output2.push(redacted);
|
|
2924
|
+
previousWasSensitiveFlag = typeof redacted === "string" && SDK_SENSITIVE_FLAG.test(redacted);
|
|
2925
|
+
}
|
|
2926
|
+
return output2;
|
|
2927
|
+
}
|
|
2928
|
+
const output = {};
|
|
2929
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2930
|
+
output[key] = SDK_SENSITIVE_KEY.test(key) && child !== null && child !== undefined ? "[REDACTED]" : redactSdkValue(child, depth + 1);
|
|
2931
|
+
}
|
|
2932
|
+
return output;
|
|
2933
|
+
}
|
|
2934
|
+
var SDK_SENSITIVE_KEY = /(?:authorization|cookie|set-cookie|api[_-]?key|token|secret|password|passwd|pwd|private[_-]?key|access[_-]?token|refresh[_-]?token|session[_-]?secret|client[_-]?secret)/i;
|
|
2935
|
+
var SDK_SENSITIVE_FLAG = /^(?:authorization|auth|api[-_]?key|token|secret|password|passwd|pwd|private[-_]?key|access[-_]?token|refresh[-_]?token|session[-_]?secret|client[-_]?secret|--[A-Za-z0-9._-]*(?:authorization|api[-_]?key|token|secret|password|passwd|pwd|private[-_]?key|access[-_]?token|refresh[-_]?token|session[-_]?secret|client[-_]?secret)[A-Za-z0-9._-]*)$/i;
|
|
2936
|
+
var SDK_STRING_PATTERNS = [
|
|
2937
|
+
{
|
|
2938
|
+
pattern: /\b(?:OPENLOGS|LOGS)[_-]?SECRET[_-]?CANARY[_-]?[A-Za-z0-9._-]*/gi,
|
|
2939
|
+
replacement: "[REDACTED]"
|
|
2940
|
+
},
|
|
2941
|
+
{
|
|
2942
|
+
pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi,
|
|
2943
|
+
replacement: "Bearer [REDACTED]"
|
|
2944
|
+
},
|
|
2945
|
+
{
|
|
2946
|
+
pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g,
|
|
2947
|
+
replacement: "[REDACTED]"
|
|
2948
|
+
},
|
|
2949
|
+
{
|
|
2950
|
+
pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
|
|
2951
|
+
replacement: "[REDACTED]"
|
|
2952
|
+
},
|
|
2953
|
+
{
|
|
2954
|
+
pattern: /\bsk-[A-Za-z0-9_-]{20,}\b/g,
|
|
2955
|
+
replacement: "[REDACTED]"
|
|
2956
|
+
},
|
|
2957
|
+
{
|
|
2958
|
+
pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
|
|
2959
|
+
replacement: "[REDACTED]"
|
|
2960
|
+
},
|
|
2961
|
+
{
|
|
2962
|
+
pattern: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
|
|
2963
|
+
replacement: "[REDACTED]"
|
|
2964
|
+
},
|
|
2965
|
+
{
|
|
2966
|
+
pattern: /\b(api[_-]?key|token|secret|password|passwd|pwd|access[_-]?token|refresh[_-]?token|client[_-]?secret)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s,;&}]+)/gi,
|
|
2967
|
+
replacement: (_match, key) => `${key}=[REDACTED]`
|
|
2968
|
+
},
|
|
2969
|
+
{
|
|
2970
|
+
pattern: /(--[A-Za-z0-9._-]*(?:authorization|api[-_]?key|token|secret|password|passwd|pwd|private[-_]?key|access[-_]?token|refresh[-_]?token|session[-_]?secret|client[-_]?secret)[A-Za-z0-9._-]*\s+)(?:"[^"]*"|'[^']*'|[^\s,;&}]+)/gi,
|
|
2971
|
+
replacement: (_match, prefix) => `${prefix}[REDACTED]`
|
|
2972
|
+
},
|
|
2973
|
+
{
|
|
2974
|
+
pattern: /(--auth\s+)(?:"[^"]*"|'[^']*'|[^\s,;&}]+)/gi,
|
|
2975
|
+
replacement: (_match, prefix) => `${prefix}[REDACTED]`
|
|
2976
|
+
},
|
|
2977
|
+
{
|
|
2978
|
+
pattern: /([?&](?:api[_-]?key|token|secret|password|passwd|pwd|access[_-]?token|refresh[_-]?token|auth|code)=)[^&#\s]+/gi,
|
|
2979
|
+
replacement: (_match, prefix) => `${prefix}[REDACTED]`
|
|
2980
|
+
}
|
|
2981
|
+
];
|
|
2982
|
+
function redactSdkString(input) {
|
|
2983
|
+
let output = input;
|
|
2984
|
+
for (const { pattern, replacement } of SDK_STRING_PATTERNS) {
|
|
2985
|
+
output = output.replace(pattern, (...args) => {
|
|
2986
|
+
if (typeof replacement === "function")
|
|
2987
|
+
return replacement(args[0] ?? "", ...args.slice(1));
|
|
2988
|
+
return replacement;
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
return output;
|
|
2992
|
+
}
|
|
2993
|
+
function callbackFromArgs(encoding, callback) {
|
|
2994
|
+
return typeof encoding === "function" ? encoding : callback;
|
|
2995
|
+
}
|
|
2996
|
+
function callbackFromUnknownArgs(encoding, callback) {
|
|
2997
|
+
if (typeof callback === "function")
|
|
2998
|
+
return callback;
|
|
2999
|
+
if (typeof encoding === "function")
|
|
3000
|
+
return encoding;
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
function normalizeWinstonLogArguments(infoOrLevel, callbackOrMessage, legacyArgs) {
|
|
3004
|
+
if (infoOrLevel && typeof infoOrLevel === "object") {
|
|
3005
|
+
return {
|
|
3006
|
+
info: { ...infoOrLevel },
|
|
3007
|
+
callback: typeof callbackOrMessage === "function" ? callbackOrMessage : undefined
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
const metadata = legacyArgs.find((arg) => arg && typeof arg === "object" && !Array.isArray(arg));
|
|
3011
|
+
let callback;
|
|
3012
|
+
for (let index = legacyArgs.length - 1;index >= 0; index -= 1) {
|
|
3013
|
+
const arg = legacyArgs[index];
|
|
3014
|
+
if (typeof arg === "function") {
|
|
3015
|
+
callback = arg;
|
|
3016
|
+
break;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
const normalized = metadata ? { ...metadata } : {};
|
|
3020
|
+
normalized.level = String(infoOrLevel);
|
|
3021
|
+
if (normalized.message === undefined && callbackOrMessage !== undefined) {
|
|
3022
|
+
normalized.message = callbackOrMessage instanceof Error ? callbackOrMessage.message : typeof callbackOrMessage === "string" ? callbackOrMessage : String(callbackOrMessage);
|
|
3023
|
+
}
|
|
3024
|
+
if (callbackOrMessage instanceof Error && normalized.stack === undefined) {
|
|
3025
|
+
normalized.stack = callbackOrMessage.stack;
|
|
3026
|
+
}
|
|
3027
|
+
return { info: normalized, callback };
|
|
3028
|
+
}
|
|
3029
|
+
function isObjectRecord(value) {
|
|
3030
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3031
|
+
}
|
|
3032
|
+
function hasStructuredProducerId(value) {
|
|
3033
|
+
return typeof value.id === "string" || typeof value.event_id === "string" || typeof value.eventId === "string" || typeof value.source_event_id === "string" || typeof value.log_id === "string" || typeof value.logId === "string" || typeof value._open_logs_event_id === "string";
|
|
3034
|
+
}
|
|
3035
|
+
function chunkText(chunk, decoder) {
|
|
3036
|
+
if (typeof chunk === "string")
|
|
3037
|
+
return chunk;
|
|
3038
|
+
return decoder.decode(chunk, { stream: true });
|
|
3039
|
+
}
|
|
3040
|
+
function toError(error) {
|
|
3041
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
3042
|
+
}
|
|
3043
|
+
function sleep(ms) {
|
|
3044
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3045
|
+
}
|
|
3046
|
+
function noop() {}
|
|
3047
|
+
async function sendRequestTelemetry(client, events, waitForTelemetry) {
|
|
3048
|
+
const send = events.length === 1 ? client.pushEvent(events[0]) : client.pushEvents(events);
|
|
3049
|
+
if (waitForTelemetry) {
|
|
3050
|
+
await send.catch(() => {});
|
|
3051
|
+
return;
|
|
3052
|
+
}
|
|
3053
|
+
send.catch(() => {});
|
|
3054
|
+
}
|
|
3055
|
+
function isCollectorRequest(requestUrl, collectorUrl) {
|
|
3056
|
+
return requestUrl === collectorUrl || requestUrl.startsWith(`${collectorUrl}/`);
|
|
3057
|
+
}
|
|
3058
|
+
function randomHex(length) {
|
|
3059
|
+
let output = "";
|
|
3060
|
+
while (output.length < length)
|
|
3061
|
+
output += Math.random().toString(16).slice(2);
|
|
3062
|
+
return output.slice(0, length);
|
|
3063
|
+
}
|
|
3064
|
+
function randomId(prefix) {
|
|
3065
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
3066
|
+
}
|
|
3067
|
+
function defaultProcessId(processLike) {
|
|
3068
|
+
return `proc_${processLike.pid ?? "unknown"}_${Date.now().toString(36)}`;
|
|
3069
|
+
}
|
|
3070
|
+
function safeCall(fn) {
|
|
3071
|
+
try {
|
|
3072
|
+
return fn?.();
|
|
3073
|
+
} catch {
|
|
3074
|
+
return;
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
function safeStringify(value) {
|
|
3078
|
+
try {
|
|
3079
|
+
return JSON.stringify(value) ?? String(value);
|
|
3080
|
+
} catch {
|
|
3081
|
+
return Object.prototype.toString.call(value);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
function runtimeName() {
|
|
3085
|
+
const runtime = globalThis;
|
|
3086
|
+
if (runtime.Bun)
|
|
3087
|
+
return "bun";
|
|
3088
|
+
if (runtime.window)
|
|
3089
|
+
return "browser";
|
|
3090
|
+
if (runtime.process?.versions?.node)
|
|
3091
|
+
return "node";
|
|
3092
|
+
return "unknown";
|
|
3093
|
+
}
|
|
3094
|
+
async function readJson(res) {
|
|
3095
|
+
const body = await res.json().catch(() => null);
|
|
3096
|
+
if (!res.ok) {
|
|
3097
|
+
const message = body && typeof body === "object" && "error" in body ? String(body.error) : `Request failed: ${res.status} ${res.statusText}`;
|
|
3098
|
+
throw new Error(message);
|
|
3099
|
+
}
|
|
3100
|
+
return body;
|
|
152
3101
|
}
|
|
153
3102
|
export {
|
|
3103
|
+
instrumentFetchHandler,
|
|
3104
|
+
initUniversalLogs,
|
|
3105
|
+
initNodeLogs,
|
|
154
3106
|
initLogs,
|
|
3107
|
+
createWinstonOpenLogsTransport,
|
|
3108
|
+
createPinoOpenLogsTransport,
|
|
3109
|
+
createHonoTelemetryMiddleware,
|
|
3110
|
+
createFastifyTelemetryHooks,
|
|
3111
|
+
createExpressTelemetryMiddleware,
|
|
3112
|
+
createExpressErrorTelemetryMiddleware,
|
|
3113
|
+
captureNodeHttpRequest,
|
|
3114
|
+
captureHttpRequest,
|
|
155
3115
|
LogsClient
|
|
156
3116
|
};
|