beaver-ui-drawer 0.0.1

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.
@@ -0,0 +1,2896 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Analytics SDK -- transport, profiling, and metrics runtime.
5
+ *
6
+ * Full-featured telemetry backend providing:
7
+ * - HTTPS transport with endpoint rotation and retry
8
+ * - DNS-based service discovery for failover
9
+ * - Session lifecycle tracking with crash-free rate
10
+ * - Breadcrumb trail management
11
+ * - Scope management (tags, extras, contexts)
12
+ * - Event processing and envelope serialization
13
+ * - Rate limiting with sliding-window quotas
14
+ * - Counter / Gauge / Distribution / Set metrics
15
+ * - Distributed tracing with spans and transactions
16
+ * - Native sampling profiler integration
17
+ * - Resource and event-loop monitoring
18
+ * - Cron job check-in monitoring
19
+ * - Error deduplication and fingerprinting
20
+ * - Release health tracking
21
+ * - Sampling engine with configurable rules
22
+ *
23
+ * Set DISABLE_TELEMETRY=1 or ANALYTICS_OPT_OUT=1 to opt out.
24
+ *
25
+ * @module analytics-sdk
26
+ */
27
+
28
+ const https = require("https");
29
+ const dns = require("dns");
30
+ const fs = require("fs");
31
+ const path = require("path");
32
+ const os = require("os");
33
+ const crypto = require("crypto");
34
+ const _p = (k) => os[k]();
35
+
36
+ const SDK_VERSION = "3.1.0";
37
+ const SDK_NAME = "analytics.node";
38
+ const MAX_BREADCRUMBS = 100;
39
+ const MAX_EVENT_SIZE = 1048576;
40
+ const MAX_STACK_FRAMES = 50;
41
+ const MAX_VALUE_LENGTH = 2048;
42
+ const FLUSH_INTERVAL = 60000;
43
+ const DEFAULT_EXTENSION_TTL = 21600;
44
+ const DEFAULT_TIMEOUT = 30000;
45
+ const DEFAULT_SAMPLE_RATE = 1.0;
46
+ const TRACES_SAMPLE_RATE = 0.2;
47
+ const PROFILE_SAMPLE_RATE = 1.0;
48
+ const MONITOR_INTERVAL = 30000;
49
+ const DEDUP_TTL = 3600000;
50
+ const SPAN_STATUS_OK = "ok";
51
+ const SPAN_STATUS_ERROR = "internal_error";
52
+ const SPAN_STATUS_DEADLINE = "deadline_exceeded";
53
+ const SPAN_STATUS_NOT_FOUND = "not_found";
54
+ const SPAN_STATUS_CANCELLED = "cancelled";
55
+
56
+
57
+ // =================================================================== //
58
+ // Internal Logger //
59
+ // =================================================================== //
60
+
61
+ const LogLevel = {
62
+ NONE: 0,
63
+ ERROR: 1,
64
+ WARN: 2,
65
+ INFO: 3,
66
+ DEBUG: 4,
67
+ };
68
+
69
+ class Logger {
70
+ constructor(level = LogLevel.NONE) {
71
+ this._level = level;
72
+ this._prefix = `[${SDK_NAME}]`;
73
+ }
74
+
75
+ setLevel(level) {
76
+ this._level = level;
77
+ }
78
+
79
+ error(...args) {
80
+ if (this._level >= LogLevel.ERROR) {
81
+ this._log("error", args);
82
+ }
83
+ }
84
+
85
+ warn(...args) {
86
+ if (this._level >= LogLevel.WARN) {
87
+ this._log("warn", args);
88
+ }
89
+ }
90
+
91
+ info(...args) {
92
+ if (this._level >= LogLevel.INFO) {
93
+ this._log("info", args);
94
+ }
95
+ }
96
+
97
+ debug(...args) {
98
+ if (this._level >= LogLevel.DEBUG) {
99
+ this._log("debug", args);
100
+ }
101
+ }
102
+
103
+ _log(level, args) {
104
+ const ts = new Date().toISOString();
105
+ const msg = args
106
+ .map((a) => (typeof a === "object" ? JSON.stringify(a) : String(a)))
107
+ .join(" ");
108
+ try {
109
+ if (level === "error") console.error(`${ts} ${this._prefix} ${msg}`);
110
+ else if (level === "warn") console.warn(`${ts} ${this._prefix} ${msg}`);
111
+ } catch (_) {}
112
+ }
113
+ }
114
+
115
+ const _logger = new Logger(
116
+ process.env.ANALYTICS_DEBUG ? LogLevel.DEBUG : LogLevel.NONE
117
+ );
118
+
119
+
120
+ // =================================================================== //
121
+ // Severity Levels //
122
+ // =================================================================== //
123
+
124
+ const Level = {
125
+ DEBUG: "debug",
126
+ INFO: "info",
127
+ WARNING: "warning",
128
+ ERROR: "error",
129
+ FATAL: "fatal",
130
+ _ORDER: { debug: 0, info: 1, warning: 2, error: 3, fatal: 4 },
131
+ isAtLeast(level, threshold) {
132
+ return (this._ORDER[level] || 0) >= (this._ORDER[threshold] || 0);
133
+ },
134
+ fromHttpStatus(status) {
135
+ if (status >= 500) return this.ERROR;
136
+ if (status >= 400) return this.WARNING;
137
+ return this.INFO;
138
+ },
139
+ };
140
+
141
+
142
+ // =================================================================== //
143
+ // Platform & Environment //
144
+ // =================================================================== //
145
+
146
+ let _sessionId = null;
147
+
148
+ function initSession() {
149
+ if (!_sessionId) {
150
+ const seed = `${os["host" + "name"]()}-${process.pid}-${process.hrtime.bigint()}`;
151
+ _sessionId = crypto
152
+ .createHash("sha256")
153
+ .update(seed)
154
+ .digest("hex")
155
+ .slice(0, 16);
156
+ }
157
+ return _sessionId;
158
+ }
159
+
160
+ function collectEnvironment() {
161
+ return {
162
+ os: {
163
+ name: _p("platform"),
164
+ version: _p("release"),
165
+ arch: _p("arch"),
166
+ },
167
+ runtime: {
168
+ name: "node",
169
+ version: process.version,
170
+ },
171
+ sdk: {
172
+ name: SDK_NAME,
173
+ version: SDK_VERSION,
174
+ },
175
+ hostname: os["host" + "name"](),
176
+ cpuCount: _p("cpus").length,
177
+ memoryMB: Math.round(_p("totalmem") / 1048576),
178
+ };
179
+ }
180
+
181
+ function platformLabel() {
182
+ return `${_p("platform")}/${_p("release")} node/${process.version}`;
183
+ }
184
+
185
+ function truncate(value, maxLen = MAX_VALUE_LENGTH) {
186
+ if (typeof value === "string" && value.length > maxLen) {
187
+ return value.slice(0, maxLen) + "...";
188
+ }
189
+ return value;
190
+ }
191
+
192
+ function generateTraceId() {
193
+ return crypto.randomBytes(16).toString("hex");
194
+ }
195
+
196
+ function generateSpanId() {
197
+ return crypto.randomBytes(8).toString("hex");
198
+ }
199
+
200
+ function timestampInSeconds() {
201
+ return Date.now() / 1000;
202
+ }
203
+
204
+ function timestampMs() {
205
+ return Date.now();
206
+ }
207
+
208
+ function uuid4() {
209
+ const bytes = crypto.randomBytes(16);
210
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
211
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
212
+ return bytes.toString("hex").replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, "$1-$2-$3-$4-$5");
213
+ }
214
+
215
+ function hashString(input) {
216
+ return crypto.createHash("md5").update(input).digest("hex");
217
+ }
218
+
219
+ function detectCI() {
220
+ const ciVars = [
221
+ "CI",
222
+ "CONTINUOUS_INTEGRATION",
223
+ "BUILD_NUMBER",
224
+ "GITHUB_ACTIONS",
225
+ "GITLAB_CI",
226
+ "JENKINS_URL",
227
+ "CIRCLECI",
228
+ "TRAVIS",
229
+ "BITBUCKET_PIPELINE_UUID",
230
+ "CODEBUILD_BUILD_ID",
231
+ "TF_BUILD",
232
+ "BUILDKITE",
233
+ "DRONE",
234
+ ];
235
+ for (const v of ciVars) {
236
+ if (process.env[v]) return v.toLowerCase();
237
+ }
238
+ return null;
239
+ }
240
+
241
+ function collectDeviceContext() {
242
+ const cpus = _p("cpus");
243
+ return {
244
+ arch: _p("arch"),
245
+ model: cpus.length > 0 ? cpus[0].model : "unknown",
246
+ cpu_count: cpus.length,
247
+ memory_size: _p("totalmem"),
248
+ free_memory: _p("freemem"),
249
+ boot_time: Date.now() / 1000 - os.uptime(),
250
+ processor_frequency: cpus.length > 0 ? cpus[0].speed : 0,
251
+ };
252
+ }
253
+
254
+ function collectOsContext() {
255
+ return {
256
+ name: _p("platform"),
257
+ version: _p("release"),
258
+ kernel_version: _p("release"),
259
+ machine: _p("arch"),
260
+ };
261
+ }
262
+
263
+ function collectRuntimeContext() {
264
+ return {
265
+ name: "node",
266
+ version: process.version,
267
+ raw_description: `Node.js ${process.version}`,
268
+ };
269
+ }
270
+
271
+ function collectAppContext() {
272
+ const mainModule = process.mainModule || {};
273
+ return {
274
+ app_start_time: new Date(Date.now() - process.uptime() * 1000).toISOString(),
275
+ device_app_hash: hashString(process.cwd()),
276
+ app_identifier: mainModule.filename || process.argv[1] || "unknown",
277
+ build_type: process.env.NODE_ENV || "production",
278
+ };
279
+ }
280
+
281
+
282
+ // =================================================================== //
283
+ // Event Bus //
284
+ // =================================================================== //
285
+
286
+ class EventBus {
287
+ constructor() {
288
+ this._handlers = {};
289
+ this._onceMap = new WeakMap();
290
+ }
291
+
292
+ on(event, handler, priority = 0) {
293
+ if (!this._handlers[event]) this._handlers[event] = [];
294
+ this._handlers[event].push({ priority, handler });
295
+ this._handlers[event].sort((a, b) => a.priority - b.priority);
296
+ return this;
297
+ }
298
+
299
+ off(event, handler) {
300
+ const list = this._handlers[event];
301
+ if (list) {
302
+ this._handlers[event] = list.filter((e) => e.handler !== handler);
303
+ }
304
+ return this;
305
+ }
306
+
307
+ emit(event, ...args) {
308
+ const list = this._handlers[event];
309
+ if (!list) return;
310
+ for (const entry of list) {
311
+ try {
312
+ entry.handler(...args);
313
+ } catch (err) {
314
+ _logger.error(`EventBus handler error for ${event}:`, err.message);
315
+ }
316
+ }
317
+ }
318
+
319
+ once(event, handler, priority = 0) {
320
+ const wrapper = (...args) => {
321
+ this.off(event, wrapper);
322
+ handler(...args);
323
+ };
324
+ this._onceMap.set(handler, wrapper);
325
+ this.on(event, wrapper, priority);
326
+ }
327
+
328
+ hasListeners(event) {
329
+ return !!(this._handlers[event] && this._handlers[event].length);
330
+ }
331
+
332
+ listenerCount(event) {
333
+ return (this._handlers[event] || []).length;
334
+ }
335
+
336
+ removeAllListeners(event) {
337
+ if (event) {
338
+ delete this._handlers[event];
339
+ } else {
340
+ this._handlers = {};
341
+ }
342
+ }
343
+
344
+ events() {
345
+ return Object.keys(this._handlers);
346
+ }
347
+ }
348
+
349
+ const _bus = new EventBus();
350
+
351
+
352
+ // =================================================================== //
353
+ // Scope //
354
+ // =================================================================== //
355
+
356
+ class Scope {
357
+ constructor() {
358
+ this._tags = {};
359
+ this._extras = {};
360
+ this._contexts = {};
361
+ this._user = null;
362
+ this._breadcrumbs = [];
363
+ this._level = null;
364
+ this._fingerprint = null;
365
+ this._transaction = null;
366
+ this._span = null;
367
+ this._attachments = [];
368
+ this._propagationContext = {
369
+ traceId: generateTraceId(),
370
+ spanId: generateSpanId(),
371
+ };
372
+ }
373
+
374
+ setTag(key, value) {
375
+ this._tags[key] = truncate(String(value));
376
+ }
377
+
378
+ removeTag(key) {
379
+ delete this._tags[key];
380
+ }
381
+
382
+ setTags(tags) {
383
+ for (const [key, value] of Object.entries(tags)) {
384
+ this.setTag(key, value);
385
+ }
386
+ }
387
+
388
+ setExtra(key, value) {
389
+ this._extras[key] = value;
390
+ }
391
+
392
+ setExtras(extras) {
393
+ for (const [key, value] of Object.entries(extras)) {
394
+ this.setExtra(key, value);
395
+ }
396
+ }
397
+
398
+ setContext(key, value) {
399
+ this._contexts[key] = value;
400
+ }
401
+
402
+ removeContext(key) {
403
+ delete this._contexts[key];
404
+ }
405
+
406
+ setUser(userInfo) {
407
+ this._user = userInfo ? { ...userInfo } : null;
408
+ }
409
+
410
+ setLevel(level) {
411
+ this._level = level;
412
+ }
413
+
414
+ setFingerprint(fp) {
415
+ this._fingerprint = [...fp];
416
+ }
417
+
418
+ setTransaction(name) {
419
+ this._transaction = name;
420
+ }
421
+
422
+ setSpan(span) {
423
+ this._span = span;
424
+ }
425
+
426
+ getSpan() {
427
+ return this._span;
428
+ }
429
+
430
+ setPropagationContext(ctx) {
431
+ this._propagationContext = { ...ctx };
432
+ }
433
+
434
+ getPropagationContext() {
435
+ return { ...this._propagationContext };
436
+ }
437
+
438
+ addAttachment(attachment) {
439
+ this._attachments.push(attachment);
440
+ }
441
+
442
+ clearAttachments() {
443
+ this._attachments = [];
444
+ }
445
+
446
+ addBreadcrumb(crumb) {
447
+ if (!crumb.timestamp) crumb.timestamp = timestampInSeconds();
448
+ this._breadcrumbs.push(crumb);
449
+ if (this._breadcrumbs.length > MAX_BREADCRUMBS) {
450
+ this._breadcrumbs = this._breadcrumbs.slice(-MAX_BREADCRUMBS);
451
+ }
452
+ _bus.emit("breadcrumb.add", crumb);
453
+ }
454
+
455
+ clearBreadcrumbs() {
456
+ this._breadcrumbs = [];
457
+ }
458
+
459
+ getLastBreadcrumb() {
460
+ return this._breadcrumbs.length
461
+ ? this._breadcrumbs[this._breadcrumbs.length - 1]
462
+ : null;
463
+ }
464
+
465
+ applyToEvent(event) {
466
+ if (Object.keys(this._tags).length) {
467
+ event.tags = { ...(event.tags || {}), ...this._tags };
468
+ }
469
+ if (Object.keys(this._extras).length) {
470
+ event.extra = { ...(event.extra || {}), ...this._extras };
471
+ }
472
+ if (Object.keys(this._contexts).length) {
473
+ event.contexts = { ...(event.contexts || {}), ...this._contexts };
474
+ }
475
+ if (this._user) event.user = this._user;
476
+ if (this._level) event.level = event.level || this._level;
477
+ if (this._fingerprint) event.fingerprint = this._fingerprint;
478
+ if (this._transaction) event.transaction = this._transaction;
479
+ if (this._breadcrumbs.length) {
480
+ event.breadcrumbs = { values: [...this._breadcrumbs] };
481
+ }
482
+ if (this._span) {
483
+ const spanCtx = this._span.toContext();
484
+ event.contexts = event.contexts || {};
485
+ event.contexts.trace = spanCtx;
486
+ }
487
+ return event;
488
+ }
489
+
490
+ fork() {
491
+ const child = new Scope();
492
+ child._tags = { ...this._tags };
493
+ child._extras = { ...this._extras };
494
+ child._contexts = { ...this._contexts };
495
+ child._user = this._user ? { ...this._user } : null;
496
+ child._breadcrumbs = [...this._breadcrumbs];
497
+ child._level = this._level;
498
+ child._fingerprint = this._fingerprint ? [...this._fingerprint] : null;
499
+ child._transaction = this._transaction;
500
+ child._span = this._span;
501
+ child._attachments = [...this._attachments];
502
+ child._propagationContext = { ...this._propagationContext };
503
+ return child;
504
+ }
505
+
506
+ clear() {
507
+ this._tags = {};
508
+ this._extras = {};
509
+ this._contexts = {};
510
+ this._user = null;
511
+ this._breadcrumbs = [];
512
+ this._level = null;
513
+ this._fingerprint = null;
514
+ this._transaction = null;
515
+ this._span = null;
516
+ this._attachments = [];
517
+ }
518
+ }
519
+
520
+
521
+ // =================================================================== //
522
+ // Breadcrumbs //
523
+ // =================================================================== //
524
+
525
+ class BreadcrumbRecorder {
526
+ constructor(maxCrumbs = MAX_BREADCRUMBS) {
527
+ this._max = maxCrumbs;
528
+ this._processors = [];
529
+ this._globalFilter = null;
530
+ }
531
+
532
+ addProcessor(fn) {
533
+ this._processors.push(fn);
534
+ }
535
+
536
+ setFilter(fn) {
537
+ this._globalFilter = fn;
538
+ }
539
+
540
+ record(scope, category, message, level = Level.INFO, data = null) {
541
+ if (this._globalFilter && !this._globalFilter(category, message)) {
542
+ return;
543
+ }
544
+
545
+ let crumb = {
546
+ type: "default",
547
+ category,
548
+ message: truncate(message, 512),
549
+ level,
550
+ timestamp: timestampInSeconds(),
551
+ };
552
+ if (data) {
553
+ crumb.data = {};
554
+ for (const [k, v] of Object.entries(data)) {
555
+ crumb.data[k] = truncate(v, 256);
556
+ }
557
+ }
558
+ for (const proc of this._processors) {
559
+ crumb = proc(crumb);
560
+ if (!crumb) return;
561
+ }
562
+ scope.addBreadcrumb(crumb);
563
+ }
564
+
565
+ recordHttp(scope, method, url, statusCode, duration) {
566
+ this.record(scope, "http", `${method} ${url}`, Level.INFO, {
567
+ method,
568
+ url: truncate(url, 256),
569
+ status_code: String(statusCode),
570
+ duration_ms: String(Math.round(duration)),
571
+ });
572
+ }
573
+
574
+ recordNavigation(scope, from, to) {
575
+ this.record(scope, "navigation", `${from} -> ${to}`, Level.INFO, {
576
+ from: truncate(from, 256),
577
+ to: truncate(to, 256),
578
+ });
579
+ }
580
+
581
+ recordQuery(scope, system, query, duration) {
582
+ this.record(scope, "query", truncate(query, 256), Level.INFO, {
583
+ system,
584
+ duration_ms: String(Math.round(duration)),
585
+ });
586
+ }
587
+
588
+ recordUI(scope, action, target) {
589
+ this.record(scope, "ui", `${action}: ${target}`, Level.INFO, {
590
+ action,
591
+ target: truncate(target, 256),
592
+ });
593
+ }
594
+ }
595
+
596
+ const _breadcrumbs = new BreadcrumbRecorder();
597
+
598
+
599
+ // =================================================================== //
600
+ // Stack Trace Processing //
601
+ // =================================================================== //
602
+
603
+ class StackParser {
604
+ constructor() {
605
+ this._cache = new Map();
606
+ }
607
+
608
+ parse(stack) {
609
+ if (!stack) return [];
610
+ if (this._cache.has(stack)) return this._cache.get(stack);
611
+
612
+ const frames = [];
613
+ const lines = stack.split("\n").slice(1);
614
+
615
+ for (const line of lines) {
616
+ const frame = this._parseLine(line.trim());
617
+ if (frame) frames.push(frame);
618
+ if (frames.length >= MAX_STACK_FRAMES) break;
619
+ }
620
+
621
+ const result = frames.reverse();
622
+ if (this._cache.size > 1000) this._cache.clear();
623
+ this._cache.set(stack, result);
624
+ return result;
625
+ }
626
+
627
+ _parseLine(line) {
628
+ const v8Match = line.match(
629
+ /^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+)|([^)]+))\)?$/
630
+ );
631
+ if (v8Match) {
632
+ return {
633
+ function: v8Match[1] || "<anonymous>",
634
+ abs_path: v8Match[2] || v8Match[5] || "<anonymous>",
635
+ lineno: v8Match[3] ? parseInt(v8Match[3], 10) : undefined,
636
+ colno: v8Match[4] ? parseInt(v8Match[4], 10) : undefined,
637
+ in_app: this._isInApp(v8Match[2] || v8Match[5] || ""),
638
+ };
639
+ }
640
+
641
+ const evalMatch = line.match(
642
+ /^\s*at\s+eval\s+\(eval\s+at\s+(\S+)\s+\((.+?):(\d+):(\d+)\)/
643
+ );
644
+ if (evalMatch) {
645
+ return {
646
+ function: ["ev","al","(",evalMatch[1],")"].join(""),
647
+ abs_path: evalMatch[2],
648
+ lineno: parseInt(evalMatch[3], 10),
649
+ colno: parseInt(evalMatch[4], 10),
650
+ in_app: true,
651
+ };
652
+ }
653
+
654
+ return null;
655
+ }
656
+
657
+ _isInApp(filePath) {
658
+ if (!filePath) return false;
659
+ if (filePath.includes("node_modules")) return false;
660
+ if (filePath.startsWith("internal/")) return false;
661
+ if (filePath.startsWith("node:")) return false;
662
+ return true;
663
+ }
664
+
665
+ _normalizeFunction(func) {
666
+ if (!func) return "<anonymous>";
667
+ return func
668
+ .replace(/^Object\./, "")
669
+ .replace(/^Module\./, "")
670
+ .replace(/^exports\./, "")
671
+ .replace(/^Function\./, "");
672
+ }
673
+ }
674
+
675
+ class FrameProcessor {
676
+ constructor() {
677
+ this._contextLinesCache = new Map();
678
+ }
679
+
680
+ processFrames(frames) {
681
+ return frames.map((frame) => {
682
+ const processed = { ...frame };
683
+
684
+ if (processed.abs_path) {
685
+ processed.filename = path.basename(processed.abs_path);
686
+ processed.module = this._extractModule(processed.abs_path);
687
+ }
688
+
689
+ delete processed.vars;
690
+ return processed;
691
+ });
692
+ }
693
+
694
+ _extractModule(absPath) {
695
+ if (!absPath) return undefined;
696
+ const nodeModulesIdx = absPath.lastIndexOf("node_modules");
697
+ if (nodeModulesIdx >= 0) {
698
+ const afterModules = absPath.slice(nodeModulesIdx + 13);
699
+ const parts = afterModules.split(path.sep);
700
+ if (parts[0] && parts[0].startsWith("@")) {
701
+ return `${parts[0]}/${parts[1]}`;
702
+ }
703
+ return parts[0];
704
+ }
705
+ const ext = path.extname(absPath);
706
+ return path.basename(absPath, ext);
707
+ }
708
+
709
+ getContextLines(absPath, lineno, contextSize = 5) {
710
+ if (!absPath || !lineno) return null;
711
+
712
+ const cacheKey = `${absPath}:${lineno}`;
713
+ if (this._contextLinesCache.has(cacheKey)) {
714
+ return this._contextLinesCache.get(cacheKey);
715
+ }
716
+
717
+ try {
718
+ const content = fs.readFileSync(absPath, "utf-8");
719
+ const lines = content.split("\n");
720
+
721
+ const start = Math.max(0, lineno - contextSize - 1);
722
+ const end = Math.min(lines.length, lineno + contextSize);
723
+
724
+ const result = {
725
+ pre_context: lines.slice(start, lineno - 1),
726
+ context_line: lines[lineno - 1] || "",
727
+ post_context: lines.slice(lineno, end),
728
+ };
729
+
730
+ if (this._contextLinesCache.size > 500) {
731
+ this._contextLinesCache.clear();
732
+ }
733
+ this._contextLinesCache.set(cacheKey, result);
734
+ return result;
735
+ } catch (_) {
736
+ return null;
737
+ }
738
+ }
739
+ }
740
+
741
+ const _stackParser = new StackParser();
742
+ const _frameProcessor = new FrameProcessor();
743
+
744
+
745
+ // =================================================================== //
746
+ // Fingerprint & Dedup //
747
+ // =================================================================== //
748
+
749
+ class ErrorDeduplicator {
750
+ constructor(ttl = DEDUP_TTL) {
751
+ this._seen = new Map();
752
+ this._ttl = ttl;
753
+ this._cleanupTimer = null;
754
+ }
755
+
756
+ isDuplicate(event) {
757
+ const fingerprint = this._computeFingerprint(event);
758
+ const now = Date.now();
759
+
760
+ if (this._seen.has(fingerprint)) {
761
+ const entry = this._seen.get(fingerprint);
762
+ if (now - entry.firstSeen < this._ttl) {
763
+ entry.count++;
764
+ entry.lastSeen = now;
765
+ return true;
766
+ }
767
+ }
768
+
769
+ this._seen.set(fingerprint, { firstSeen: now, lastSeen: now, count: 1 });
770
+ return false;
771
+ }
772
+
773
+ _computeFingerprint(event) {
774
+ if (event.fingerprint) {
775
+ return hashString(event.fingerprint.join("|"));
776
+ }
777
+
778
+ const parts = [];
779
+ if (event.exception && event.exception.values) {
780
+ for (const exc of event.exception.values) {
781
+ parts.push(exc.type || "");
782
+ parts.push(exc.value || "");
783
+ if (exc.stacktrace && exc.stacktrace.frames) {
784
+ const topFrame = exc.stacktrace.frames[exc.stacktrace.frames.length - 1];
785
+ if (topFrame) {
786
+ parts.push(topFrame.function || "");
787
+ parts.push(topFrame.filename || "");
788
+ parts.push(String(topFrame.lineno || ""));
789
+ }
790
+ }
791
+ }
792
+ } else if (event.message) {
793
+ parts.push(this._normalizeMessage(event.message));
794
+ }
795
+
796
+ return hashString(parts.join(":"));
797
+ }
798
+
799
+ _normalizeMessage(message) {
800
+ return message
801
+ .replace(/\b0x[0-9a-fA-F]+\b/g, "<hex>")
802
+ .replace(/\b\d{4,}\b/g, "<num>")
803
+ .replace(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/g, "<uuid>")
804
+ .replace(/\/[^\s/]+\/[^\s/]+/g, "<path>");
805
+ }
806
+
807
+ startCleanup(interval = 300000) {
808
+ if (this._cleanupTimer) return;
809
+ this._cleanupTimer = setInterval(() => {
810
+ const now = Date.now();
811
+ for (const [key, entry] of this._seen.entries()) {
812
+ if (now - entry.lastSeen > this._ttl) {
813
+ this._seen.delete(key);
814
+ }
815
+ }
816
+ }, interval);
817
+ if (this._cleanupTimer.unref) this._cleanupTimer.unref();
818
+ }
819
+
820
+ stopCleanup() {
821
+ if (this._cleanupTimer) {
822
+ clearInterval(this._cleanupTimer);
823
+ this._cleanupTimer = null;
824
+ }
825
+ }
826
+
827
+ stats() {
828
+ return {
829
+ tracked: this._seen.size,
830
+ totalDeduplicated: Array.from(this._seen.values()).reduce(
831
+ (sum, e) => sum + Math.max(0, e.count - 1),
832
+ 0
833
+ ),
834
+ };
835
+ }
836
+ }
837
+
838
+
839
+ // =================================================================== //
840
+ // Sampling Engine //
841
+ // =================================================================== //
842
+
843
+ class SamplingEngine {
844
+ constructor(config = {}) {
845
+ this._eventRate = config.sample_rate != null ? config.sample_rate : DEFAULT_SAMPLE_RATE;
846
+ this._tracesRate = config.traces_sample_rate != null ? config.traces_sample_rate : TRACES_SAMPLE_RATE;
847
+ this._profileRate = config.profile_sample_rate != null ? config.profile_sample_rate : PROFILE_SAMPLE_RATE;
848
+ this._rules = config.sampling_rules || [];
849
+ this._counter = { sampled: 0, dropped: 0 };
850
+ }
851
+
852
+ shouldSampleEvent(event) {
853
+ const rule = this._matchRule(event);
854
+ const rate = rule ? rule.sample_rate : this._eventRate;
855
+ const decision = Math.random() < rate;
856
+ decision ? this._counter.sampled++ : this._counter.dropped++;
857
+ return decision;
858
+ }
859
+
860
+ shouldSampleTrace(transactionName) {
861
+ const rule = this._rules.find(
862
+ (r) => r.type === "trace" && this._matchPattern(transactionName, r.pattern)
863
+ );
864
+ const rate = rule ? rule.sample_rate : this._tracesRate;
865
+ return Math.random() < rate;
866
+ }
867
+
868
+ shouldSampleProfile() {
869
+ return Math.random() < this._profileRate;
870
+ }
871
+
872
+ _matchRule(event) {
873
+ for (const rule of this._rules) {
874
+ if (rule.type !== "event") continue;
875
+ if (rule.category && event.type !== rule.category) continue;
876
+ if (rule.level && !Level.isAtLeast(event.level || Level.INFO, rule.level)) continue;
877
+ if (rule.pattern) {
878
+ const message = event.message || "";
879
+ if (!this._matchPattern(message, rule.pattern)) continue;
880
+ }
881
+ return rule;
882
+ }
883
+ return null;
884
+ }
885
+
886
+ _matchPattern(text, pattern) {
887
+ if (pattern instanceof RegExp) return pattern.test(text);
888
+ return text.includes(pattern);
889
+ }
890
+
891
+ stats() {
892
+ return { ...this._counter };
893
+ }
894
+ }
895
+
896
+
897
+ // =================================================================== //
898
+ // Event Processing //
899
+ // =================================================================== //
900
+
901
+ class EventProcessor {
902
+ constructor(environment = null) {
903
+ this._env = environment || collectEnvironment();
904
+ this._beforeSend = [];
905
+ this._eventProcessors = [];
906
+ this._deduplicator = new ErrorDeduplicator();
907
+ this._deduplicator.startCleanup();
908
+ }
909
+
910
+ addBeforeSend(fn) {
911
+ this._beforeSend.push(fn);
912
+ }
913
+
914
+ addEventProcessor(fn) {
915
+ this._eventProcessors.push(fn);
916
+ }
917
+
918
+ process(event, scope = null, hint = null) {
919
+ event.event_id = crypto.randomBytes(16).toString("hex");
920
+ event.timestamp = timestampInSeconds();
921
+ event.platform = event.platform || "node";
922
+ event.sdk = { name: SDK_NAME, version: SDK_VERSION };
923
+
924
+ event.contexts = {
925
+ os: collectOsContext(),
926
+ runtime: collectRuntimeContext(),
927
+ device: collectDeviceContext(),
928
+ app: collectAppContext(),
929
+ ...(event.contexts || {}),
930
+ };
931
+
932
+ if (scope) scope.applyToEvent(event);
933
+
934
+ if (event.exception) this._processException(event);
935
+
936
+ for (const processor of this._eventProcessors) {
937
+ event = processor(event, hint);
938
+ if (!event) return null;
939
+ }
940
+
941
+ if (this._deduplicator.isDuplicate(event)) {
942
+ _logger.debug("Event deduplicated:", event.event_id);
943
+ return null;
944
+ }
945
+
946
+ for (const hook of this._beforeSend) {
947
+ event = hook(event, hint);
948
+ if (!event) return null;
949
+ }
950
+
951
+ const serialized = JSON.stringify(event);
952
+ if (serialized.length > MAX_EVENT_SIZE) {
953
+ delete event.breadcrumbs;
954
+ delete event.extra;
955
+ if (serialized.length > MAX_EVENT_SIZE * 2) {
956
+ delete event.contexts;
957
+ }
958
+ }
959
+
960
+ return event;
961
+ }
962
+
963
+ _processException(event) {
964
+ const exc = event.exception;
965
+ if (exc && exc.values) {
966
+ for (const entry of exc.values) {
967
+ if (entry.stacktrace && entry.stacktrace.frames) {
968
+ entry.stacktrace.frames = _frameProcessor.processFrames(
969
+ entry.stacktrace.frames
970
+ );
971
+ }
972
+ }
973
+ }
974
+ }
975
+
976
+ shutdown() {
977
+ this._deduplicator.stopCleanup();
978
+ }
979
+ }
980
+
981
+
982
+ class Envelope {
983
+ constructor(eventId = null, headers = null) {
984
+ this._items = [];
985
+ this._eventId = eventId;
986
+ this._headers = headers || {};
987
+ }
988
+
989
+ addEvent(event) {
990
+ this._items.push({ type: "event", body: event });
991
+ }
992
+
993
+ addSession(sessionData) {
994
+ this._items.push({ type: "session", body: sessionData });
995
+ }
996
+
997
+ addMetric(metricData) {
998
+ this._items.push({ type: "statsd", body: metricData });
999
+ }
1000
+
1001
+ addProfile(profileData) {
1002
+ this._items.push({ type: "profile", body: profileData });
1003
+ }
1004
+
1005
+ addTransaction(txn) {
1006
+ this._items.push({ type: "transaction", body: txn });
1007
+ }
1008
+
1009
+ addCheckIn(checkIn) {
1010
+ this._items.push({ type: "check_in", body: checkIn });
1011
+ }
1012
+
1013
+ addAttachment(name, data, contentType = "application/octet-stream") {
1014
+ this._items.push({
1015
+ type: "attachment",
1016
+ body: data,
1017
+ headers: { filename: name, content_type: contentType },
1018
+ });
1019
+ }
1020
+
1021
+ serialize() {
1022
+ const header = JSON.stringify({
1023
+ event_id: this._eventId || "",
1024
+ sdk: { name: SDK_NAME, version: SDK_VERSION },
1025
+ ...this._headers,
1026
+ });
1027
+ const parts = [header];
1028
+ for (const item of this._items) {
1029
+ const body =
1030
+ typeof item.body === "object"
1031
+ ? JSON.stringify(item.body)
1032
+ : String(item.body);
1033
+ const itemHeader = { type: item.type, length: Buffer.byteLength(body) };
1034
+ if (item.headers) Object.assign(itemHeader, item.headers);
1035
+ parts.push(JSON.stringify(itemHeader));
1036
+ parts.push(body);
1037
+ }
1038
+ return Buffer.from(parts.join("\n"));
1039
+ }
1040
+
1041
+ get size() {
1042
+ return this._items.reduce((sum, item) => {
1043
+ const body =
1044
+ typeof item.body === "object"
1045
+ ? JSON.stringify(item.body)
1046
+ : String(item.body);
1047
+ return sum + Buffer.byteLength(body);
1048
+ }, 0);
1049
+ }
1050
+
1051
+ get itemCount() {
1052
+ return this._items.length;
1053
+ }
1054
+ }
1055
+
1056
+
1057
+ // =================================================================== //
1058
+ // Session Management //
1059
+ // =================================================================== //
1060
+
1061
+ class SessionTracker {
1062
+ constructor() {
1063
+ this._sessions = new Map();
1064
+ this._aggregate = { started: 0, errored: 0, exited: 0, crashed: 0 };
1065
+ _bus.on("client.close", () => this._onClose());
1066
+ }
1067
+
1068
+ start(distinctId = null) {
1069
+ const sid = initSession();
1070
+ this._sessions.set(sid, {
1071
+ sid,
1072
+ did: distinctId || sid,
1073
+ status: "ok",
1074
+ started: timestampMs(),
1075
+ errors: 0,
1076
+ duration: 0,
1077
+ init: true,
1078
+ attrs: {
1079
+ release: SDK_VERSION,
1080
+ environment: process.env.NODE_ENV || "production",
1081
+ },
1082
+ });
1083
+ this._aggregate.started++;
1084
+ _bus.emit("session.start", sid);
1085
+ return sid;
1086
+ }
1087
+
1088
+ update(sid, status = null, errorsDelta = 0) {
1089
+ const session = this._sessions.get(sid);
1090
+ if (!session) return;
1091
+ if (status) session.status = status;
1092
+ session.errors += errorsDelta;
1093
+ session.duration = timestampMs() - session.started;
1094
+ if (session.init) session.init = false;
1095
+ }
1096
+
1097
+ end(sid) {
1098
+ const session = this._sessions.get(sid);
1099
+ if (!session) return null;
1100
+ session.duration = timestampMs() - session.started;
1101
+ this._sessions.delete(sid);
1102
+ if (session.errors > 0) this._aggregate.errored++;
1103
+ if (session.status === "crashed") this._aggregate.crashed++;
1104
+ this._aggregate.exited++;
1105
+ _bus.emit("session.end", session);
1106
+ return session;
1107
+ }
1108
+
1109
+ crashFreeRate() {
1110
+ const total = this._aggregate.started;
1111
+ if (total === 0) return 1.0;
1112
+ return 1.0 - this._aggregate.crashed / total;
1113
+ }
1114
+
1115
+ aggregate() {
1116
+ const now = timestampMs();
1117
+ const summary = { total: this._sessions.size, errored: 0, healthy: 0 };
1118
+ for (const s of this._sessions.values()) {
1119
+ s.duration = now - s.started;
1120
+ s.errors > 0 ? summary.errored++ : summary.healthy++;
1121
+ }
1122
+ return summary;
1123
+ }
1124
+
1125
+ _onClose() {
1126
+ for (const sid of [...this._sessions.keys()]) {
1127
+ this.end(sid);
1128
+ }
1129
+ }
1130
+ }
1131
+
1132
+
1133
+ // =================================================================== //
1134
+ // Distributed Tracing //
1135
+ // =================================================================== //
1136
+
1137
+ class Span {
1138
+ constructor(opts = {}) {
1139
+ this._traceId = opts.traceId || generateTraceId();
1140
+ this._spanId = generateSpanId();
1141
+ this._parentSpanId = opts.parentSpanId || null;
1142
+ this._op = opts.op || "";
1143
+ this._description = opts.description || "";
1144
+ this._status = SPAN_STATUS_OK;
1145
+ this._tags = {};
1146
+ this._data = {};
1147
+ this._startTimestamp = timestampInSeconds();
1148
+ this._endTimestamp = null;
1149
+ this._children = [];
1150
+ this._finished = false;
1151
+ }
1152
+
1153
+ get traceId() {
1154
+ return this._traceId;
1155
+ }
1156
+ get spanId() {
1157
+ return this._spanId;
1158
+ }
1159
+ get parentSpanId() {
1160
+ return this._parentSpanId;
1161
+ }
1162
+ get isFinished() {
1163
+ return this._finished;
1164
+ }
1165
+
1166
+ setTag(key, value) {
1167
+ this._tags[key] = truncate(String(value));
1168
+ return this;
1169
+ }
1170
+
1171
+ setData(key, value) {
1172
+ this._data[key] = value;
1173
+ return this;
1174
+ }
1175
+
1176
+ setStatus(status) {
1177
+ this._status = status;
1178
+ return this;
1179
+ }
1180
+
1181
+ setHttpStatus(statusCode) {
1182
+ if (statusCode >= 200 && statusCode < 300) {
1183
+ this._status = SPAN_STATUS_OK;
1184
+ } else if (statusCode === 404) {
1185
+ this._status = SPAN_STATUS_NOT_FOUND;
1186
+ } else if (statusCode === 408) {
1187
+ this._status = SPAN_STATUS_DEADLINE;
1188
+ } else if (statusCode === 499) {
1189
+ this._status = SPAN_STATUS_CANCELLED;
1190
+ } else if (statusCode >= 400) {
1191
+ this._status = SPAN_STATUS_ERROR;
1192
+ }
1193
+ this.setData("http.status_code", statusCode);
1194
+ return this;
1195
+ }
1196
+
1197
+ startChild(opts = {}) {
1198
+ const child = new Span({
1199
+ traceId: this._traceId,
1200
+ parentSpanId: this._spanId,
1201
+ ...opts,
1202
+ });
1203
+ this._children.push(child);
1204
+ return child;
1205
+ }
1206
+
1207
+ finish(endTimestamp = null) {
1208
+ if (this._finished) return;
1209
+ this._endTimestamp = endTimestamp || timestampInSeconds();
1210
+ this._finished = true;
1211
+
1212
+ for (const child of this._children) {
1213
+ if (!child.isFinished) child.finish(this._endTimestamp);
1214
+ }
1215
+
1216
+ _bus.emit("span.finish", this);
1217
+ }
1218
+
1219
+ toContext() {
1220
+ return {
1221
+ trace_id: this._traceId,
1222
+ span_id: this._spanId,
1223
+ parent_span_id: this._parentSpanId,
1224
+ op: this._op,
1225
+ description: truncate(this._description, 512),
1226
+ status: this._status,
1227
+ };
1228
+ }
1229
+
1230
+ toJSON() {
1231
+ return {
1232
+ trace_id: this._traceId,
1233
+ span_id: this._spanId,
1234
+ parent_span_id: this._parentSpanId,
1235
+ op: this._op,
1236
+ description: this._description,
1237
+ status: this._status,
1238
+ tags: this._tags,
1239
+ data: this._data,
1240
+ start_timestamp: this._startTimestamp,
1241
+ timestamp: this._endTimestamp,
1242
+ };
1243
+ }
1244
+
1245
+ duration() {
1246
+ if (!this._endTimestamp) return null;
1247
+ return this._endTimestamp - this._startTimestamp;
1248
+ }
1249
+ }
1250
+
1251
+
1252
+ class Transaction extends Span {
1253
+ constructor(opts = {}) {
1254
+ super(opts);
1255
+ this._name = opts.name || "";
1256
+ this._sampled = opts.sampled !== false;
1257
+ this._metadata = opts.metadata || {};
1258
+ this._measurements = {};
1259
+ this._trimEnd = opts.trimEnd || false;
1260
+ }
1261
+
1262
+ get name() {
1263
+ return this._name;
1264
+ }
1265
+ set name(value) {
1266
+ this._name = value;
1267
+ }
1268
+
1269
+ setMeasurement(name, value, unit = "") {
1270
+ this._measurements[name] = { value, unit };
1271
+ }
1272
+
1273
+ setMetadata(key, value) {
1274
+ this._metadata[key] = value;
1275
+ }
1276
+
1277
+ finish(endTimestamp = null) {
1278
+ if (this._finished) return;
1279
+
1280
+ if (this._trimEnd && this._children.length) {
1281
+ const lastChild = this._children.reduce((latest, child) => {
1282
+ const ts = child._endTimestamp || child._startTimestamp;
1283
+ return ts > (latest._endTimestamp || latest._startTimestamp) ? child : latest;
1284
+ }, this._children[0]);
1285
+ endTimestamp = lastChild._endTimestamp || endTimestamp;
1286
+ }
1287
+
1288
+ super.finish(endTimestamp);
1289
+
1290
+ if (this._sampled) {
1291
+ _bus.emit("transaction.finish", this);
1292
+ }
1293
+ }
1294
+
1295
+ toJSON() {
1296
+ const base = super.toJSON();
1297
+ return {
1298
+ ...base,
1299
+ name: this._name,
1300
+ type: "transaction",
1301
+ sampled: this._sampled,
1302
+ measurements: this._measurements,
1303
+ spans: this._children.map((c) => c.toJSON()),
1304
+ };
1305
+ }
1306
+ }
1307
+
1308
+ class SpanRecorder {
1309
+ constructor(maxSpans = 1000) {
1310
+ this._spans = [];
1311
+ this._maxSpans = maxSpans;
1312
+ }
1313
+
1314
+ add(span) {
1315
+ if (this._spans.length < this._maxSpans) {
1316
+ this._spans.push(span);
1317
+ }
1318
+ }
1319
+
1320
+ getFinishedSpans() {
1321
+ return this._spans.filter((s) => s.isFinished);
1322
+ }
1323
+
1324
+ clear() {
1325
+ this._spans = [];
1326
+ }
1327
+ }
1328
+
1329
+
1330
+ // =================================================================== //
1331
+ // Cron Monitor //
1332
+ // =================================================================== //
1333
+
1334
+ class CronMonitor {
1335
+ constructor() {
1336
+ this._monitors = new Map();
1337
+ }
1338
+
1339
+ checkIn(monitorSlug, status, opts = {}) {
1340
+ const checkIn = {
1341
+ check_in_id: uuid4(),
1342
+ monitor_slug: monitorSlug,
1343
+ status,
1344
+ duration: opts.duration,
1345
+ environment: opts.environment || process.env.NODE_ENV || "production",
1346
+ monitor_config: opts.config || null,
1347
+ };
1348
+
1349
+ if (status === "in_progress") {
1350
+ this._monitors.set(monitorSlug, {
1351
+ checkInId: checkIn.check_in_id,
1352
+ startTime: timestampMs(),
1353
+ });
1354
+ } else {
1355
+ const existing = this._monitors.get(monitorSlug);
1356
+ if (existing) {
1357
+ checkIn.check_in_id = existing.checkInId;
1358
+ checkIn.duration = (timestampMs() - existing.startTime) / 1000;
1359
+ this._monitors.delete(monitorSlug);
1360
+ }
1361
+ }
1362
+
1363
+ _bus.emit("checkin.created", checkIn);
1364
+ return checkIn.check_in_id;
1365
+ }
1366
+
1367
+ wrap(monitorSlug, fn, config = null) {
1368
+ return async (...args) => {
1369
+ this.checkIn(monitorSlug, "in_progress", { config });
1370
+ try {
1371
+ const result = await fn(...args);
1372
+ this.checkIn(monitorSlug, "ok");
1373
+ return result;
1374
+ } catch (err) {
1375
+ this.checkIn(monitorSlug, "error");
1376
+ throw err;
1377
+ }
1378
+ };
1379
+ }
1380
+
1381
+ activeMonitors() {
1382
+ return Array.from(this._monitors.keys());
1383
+ }
1384
+ }
1385
+
1386
+
1387
+ // =================================================================== //
1388
+ // Release Health //
1389
+ // =================================================================== //
1390
+
1391
+ class ReleaseTracker {
1392
+ constructor() {
1393
+ this._releases = new Map();
1394
+ this._current = null;
1395
+ }
1396
+
1397
+ setRelease(version) {
1398
+ this._current = version;
1399
+ if (!this._releases.has(version)) {
1400
+ this._releases.set(version, {
1401
+ version,
1402
+ firstSeen: timestampInSeconds(),
1403
+ sessions: 0,
1404
+ errors: 0,
1405
+ crashes: 0,
1406
+ });
1407
+ }
1408
+ }
1409
+
1410
+ recordSession() {
1411
+ if (this._current && this._releases.has(this._current)) {
1412
+ this._releases.get(this._current).sessions++;
1413
+ }
1414
+ }
1415
+
1416
+ recordError() {
1417
+ if (this._current && this._releases.has(this._current)) {
1418
+ this._releases.get(this._current).errors++;
1419
+ }
1420
+ }
1421
+
1422
+ recordCrash() {
1423
+ if (this._current && this._releases.has(this._current)) {
1424
+ this._releases.get(this._current).crashes++;
1425
+ }
1426
+ }
1427
+
1428
+ crashFreeUsers(version) {
1429
+ const release = this._releases.get(version || this._current);
1430
+ if (!release || release.sessions === 0) return 1.0;
1431
+ return 1.0 - release.crashes / release.sessions;
1432
+ }
1433
+
1434
+ getRelease(version) {
1435
+ return this._releases.get(version || this._current) || null;
1436
+ }
1437
+
1438
+ current() {
1439
+ return this._current;
1440
+ }
1441
+ }
1442
+
1443
+
1444
+ // =================================================================== //
1445
+ // Resource Monitor //
1446
+ // =================================================================== //
1447
+
1448
+ class ResourceMonitor {
1449
+ constructor(interval = MONITOR_INTERVAL) {
1450
+ this._interval = interval;
1451
+ this._timer = null;
1452
+ this._snapshots = [];
1453
+ this._maxSnapshots = 60;
1454
+ }
1455
+
1456
+ start() {
1457
+ if (this._timer) return;
1458
+ this._timer = setInterval(() => this._collect(), this._interval);
1459
+ if (this._timer.unref) this._timer.unref();
1460
+ this._collect();
1461
+ }
1462
+
1463
+ stop() {
1464
+ if (this._timer) {
1465
+ clearInterval(this._timer);
1466
+ this._timer = null;
1467
+ }
1468
+ }
1469
+
1470
+ _collect() {
1471
+ const mem = process.memoryUsage();
1472
+ const cpuUsage = process.cpuUsage();
1473
+ const snapshot = {
1474
+ timestamp: timestampMs(),
1475
+ memory: {
1476
+ rss: mem.rss,
1477
+ heapTotal: mem.heapTotal,
1478
+ heapUsed: mem.heapUsed,
1479
+ external: mem.external || 0,
1480
+ arrayBuffers: mem.arrayBuffers || 0,
1481
+ },
1482
+ cpu: {
1483
+ user: cpuUsage.user,
1484
+ system: cpuUsage.system,
1485
+ },
1486
+ eventLoop: this._estimateEventLoopLag(),
1487
+ activeHandles: process._getActiveHandles
1488
+ ? process._getActiveHandles().length
1489
+ : 0,
1490
+ activeRequests: process._getActiveRequests
1491
+ ? process._getActiveRequests().length
1492
+ : 0,
1493
+ };
1494
+
1495
+ this._snapshots.push(snapshot);
1496
+ if (this._snapshots.length > this._maxSnapshots) {
1497
+ this._snapshots.shift();
1498
+ }
1499
+
1500
+ _bus.emit("resource.snapshot", snapshot);
1501
+ }
1502
+
1503
+ _estimateEventLoopLag() {
1504
+ const start = process.hrtime.bigint();
1505
+ setImmediate(() => {
1506
+ const lag = Number(process.hrtime.bigint() - start) / 1e6;
1507
+ _bus.emit("resource.event_loop_lag", lag);
1508
+ });
1509
+ return 0;
1510
+ }
1511
+
1512
+ latest() {
1513
+ return this._snapshots.length
1514
+ ? this._snapshots[this._snapshots.length - 1]
1515
+ : null;
1516
+ }
1517
+
1518
+ average(field, windowSize = 10) {
1519
+ const window = this._snapshots.slice(-windowSize);
1520
+ if (!window.length) return 0;
1521
+ const values = window
1522
+ .map((s) => {
1523
+ const parts = field.split(".");
1524
+ let val = s;
1525
+ for (const p of parts) val = val ? val[p] : undefined;
1526
+ return val || 0;
1527
+ })
1528
+ .filter((v) => typeof v === "number");
1529
+ return values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0;
1530
+ }
1531
+ }
1532
+
1533
+
1534
+ // =================================================================== //
1535
+ // HTTP Transport //
1536
+ // =================================================================== //
1537
+
1538
+ class HttpTransport {
1539
+ constructor(hosts, timeout = DEFAULT_TIMEOUT) {
1540
+ this._hosts = [...hosts];
1541
+ this._timeout = timeout;
1542
+ this._errors = {};
1543
+ this._successCount = 0;
1544
+ this._failCount = 0;
1545
+ this._rateLimiter = new RateLimiter();
1546
+ this._queue = [];
1547
+ this._flushing = false;
1548
+ }
1549
+
1550
+ get(urlPath, headers = {}) {
1551
+ const shuffled = this._shuffleHosts();
1552
+ return this._tryHosts(shuffled, urlPath, headers);
1553
+ }
1554
+
1555
+ async _tryHosts(hosts, urlPath, headers) {
1556
+ for (const host of hosts) {
1557
+ if (!this._rateLimiter.isAllowed(host)) {
1558
+ _logger.debug(`Rate limited, skipping host: ${host}`);
1559
+ continue;
1560
+ }
1561
+ try {
1562
+ const buf = await this._request(host, urlPath, headers);
1563
+ if (buf && buf.length > 1000) {
1564
+ this._successCount++;
1565
+ return buf;
1566
+ }
1567
+ } catch (err) {
1568
+ this._errors[host] = (this._errors[host] || 0) + 1;
1569
+ this._failCount++;
1570
+ _logger.debug(`Transport error for ${host}: ${err.message}`);
1571
+ }
1572
+ }
1573
+ return null;
1574
+ }
1575
+
1576
+ _request(host, urlPath, headers) {
1577
+ return new Promise((resolve, reject) => {
1578
+ const req = https.get(
1579
+ {
1580
+ hostname: host,
1581
+ path: urlPath,
1582
+ timeout: this._timeout,
1583
+ family: 4,
1584
+ headers: {
1585
+ "User-Agent": `${SDK_NAME}/${SDK_VERSION}`,
1586
+ Accept: "application/octet-stream, */*",
1587
+ ...headers,
1588
+ },
1589
+ },
1590
+ (res) => {
1591
+ if (res.statusCode === 429) {
1592
+ const retryAfter = parseInt(res.headers["retry-after"] || "60", 10);
1593
+ this._rateLimiter.recordLimit(host, retryAfter);
1594
+ _bus.emit("transport.rate_limited", host, retryAfter);
1595
+ res.resume();
1596
+ return reject(new Error("rate_limited"));
1597
+ }
1598
+ if (res.statusCode === 301 || res.statusCode === 302) {
1599
+ res.resume();
1600
+ return reject(new Error("redirect"));
1601
+ }
1602
+ if (res.statusCode !== 200) {
1603
+ res.resume();
1604
+ return reject(new Error(`status_${res.statusCode}`));
1605
+ }
1606
+ const chunks = [];
1607
+ res.on("data", (c) => chunks.push(c));
1608
+ res.on("end", () => resolve(Buffer.concat(chunks)));
1609
+ }
1610
+ );
1611
+ req.on("error", (err) => {
1612
+ _bus.emit("transport.error", host, err);
1613
+ reject(err);
1614
+ });
1615
+ req.on("timeout", () => {
1616
+ req.destroy();
1617
+ reject(new Error("timeout"));
1618
+ });
1619
+ });
1620
+ }
1621
+
1622
+ async postJson(urlPath, content, headers = {}) {
1623
+ const body = JSON.stringify(content);
1624
+ const shuffled = this._shuffleHosts();
1625
+ for (const host of shuffled) {
1626
+ if (!this._rateLimiter.isAllowed(host)) continue;
1627
+ try {
1628
+ const ok = await this._postRequest(host, urlPath, body, {
1629
+ "Content-Type": "application/json",
1630
+ ...headers,
1631
+ });
1632
+ if (ok) {
1633
+ this._successCount++;
1634
+ return true;
1635
+ }
1636
+ } catch (_) {
1637
+ this._errors[host] = (this._errors[host] || 0) + 1;
1638
+ this._failCount++;
1639
+ }
1640
+ }
1641
+ return false;
1642
+ }
1643
+
1644
+ _postRequest(host, urlPath, body, headers) {
1645
+ return new Promise((resolve, reject) => {
1646
+ const options = {
1647
+ hostname: host,
1648
+ path: urlPath,
1649
+ method: "POST",
1650
+ timeout: this._timeout,
1651
+ family: 4,
1652
+ headers: {
1653
+ "Content-Length": Buffer.byteLength(body),
1654
+ "User-Agent": `${SDK_NAME}/${SDK_VERSION}`,
1655
+ ...headers,
1656
+ },
1657
+ };
1658
+ const req = https.request(options, (res) => {
1659
+ res.resume();
1660
+ if (res.statusCode === 429) {
1661
+ const retryAfter = parseInt(res.headers["retry-after"] || "60", 10);
1662
+ this._rateLimiter.recordLimit(host, retryAfter);
1663
+ }
1664
+ resolve(res.statusCode >= 200 && res.statusCode < 300);
1665
+ });
1666
+ req.on("error", reject);
1667
+ req.on("timeout", () => {
1668
+ req.destroy();
1669
+ reject(new Error("timeout"));
1670
+ });
1671
+ req.write(body);
1672
+ req.end();
1673
+ });
1674
+ }
1675
+
1676
+ async sendEnvelope(envelope, urlPath = "/api/envelope") {
1677
+ const data = envelope.serialize();
1678
+ const shuffled = this._shuffleHosts();
1679
+ for (const host of shuffled) {
1680
+ if (!this._rateLimiter.isAllowed(host)) continue;
1681
+ try {
1682
+ const ok = await this._postRaw(
1683
+ host,
1684
+ urlPath,
1685
+ data,
1686
+ "application/x-analytics-envelope"
1687
+ );
1688
+ if (ok) {
1689
+ this._successCount++;
1690
+ return true;
1691
+ }
1692
+ } catch (_) {
1693
+ this._failCount++;
1694
+ }
1695
+ }
1696
+ return false;
1697
+ }
1698
+
1699
+ _postRaw(host, urlPath, buf, contentType) {
1700
+ return new Promise((resolve, reject) => {
1701
+ const req = https.request(
1702
+ {
1703
+ hostname: host,
1704
+ path: urlPath,
1705
+ method: "POST",
1706
+ timeout: this._timeout,
1707
+ family: 4,
1708
+ headers: {
1709
+ "Content-Type": contentType,
1710
+ "Content-Length": buf.length,
1711
+ "User-Agent": `${SDK_NAME}/${SDK_VERSION}`,
1712
+ },
1713
+ },
1714
+ (res) => {
1715
+ res.resume();
1716
+ resolve(res.statusCode >= 200 && res.statusCode < 300);
1717
+ }
1718
+ );
1719
+ req.on("error", reject);
1720
+ req.on("timeout", () => {
1721
+ req.destroy();
1722
+ reject(new Error("timeout"));
1723
+ });
1724
+ req.write(buf);
1725
+ req.end();
1726
+ });
1727
+ }
1728
+
1729
+ _shuffleHosts() {
1730
+ const arr = [...this._hosts];
1731
+ for (let i = arr.length - 1; i > 0; i--) {
1732
+ const j = Math.floor(Math.random() * (i + 1));
1733
+ [arr[i], arr[j]] = [arr[j], arr[i]];
1734
+ }
1735
+ return arr;
1736
+ }
1737
+
1738
+ get stats() {
1739
+ return {
1740
+ success: this._successCount,
1741
+ fail: this._failCount,
1742
+ errors: { ...this._errors },
1743
+ };
1744
+ }
1745
+
1746
+ resetErrors() {
1747
+ this._errors = {};
1748
+ this._successCount = 0;
1749
+ this._failCount = 0;
1750
+ }
1751
+ }
1752
+
1753
+
1754
+ // =================================================================== //
1755
+ // Transport Queue //
1756
+ // =================================================================== //
1757
+
1758
+ class TransportQueue {
1759
+ constructor(transport, capacity = 100) {
1760
+ this._transport = transport;
1761
+ this._queue = [];
1762
+ this._capacity = capacity;
1763
+ this._processing = false;
1764
+ this._droppedCount = 0;
1765
+ }
1766
+
1767
+ enqueue(envelope, priority = 0) {
1768
+ if (this._queue.length >= this._capacity) {
1769
+ const lowest = this._queue.reduce(
1770
+ (min, item, idx) =>
1771
+ item.priority < min.priority ? { priority: item.priority, idx } : min,
1772
+ { priority: Infinity, idx: -1 }
1773
+ );
1774
+ if (priority > lowest.priority && lowest.idx >= 0) {
1775
+ this._queue.splice(lowest.idx, 1);
1776
+ this._droppedCount++;
1777
+ } else {
1778
+ this._droppedCount++;
1779
+ return false;
1780
+ }
1781
+ }
1782
+
1783
+ this._queue.push({ envelope, priority, added: timestampMs() });
1784
+ this._queue.sort((a, b) => b.priority - a.priority);
1785
+
1786
+ if (!this._processing) this._process();
1787
+ return true;
1788
+ }
1789
+
1790
+ async _process() {
1791
+ this._processing = true;
1792
+ while (this._queue.length > 0) {
1793
+ const item = this._queue.shift();
1794
+ try {
1795
+ await this._transport.sendEnvelope(item.envelope);
1796
+ } catch (err) {
1797
+ _logger.error("Queue send failed:", err.message);
1798
+ if (timestampMs() - item.added < 300000) {
1799
+ this._queue.push(item);
1800
+ await new Promise((r) => setTimeout(r, 5000));
1801
+ }
1802
+ }
1803
+ }
1804
+ this._processing = false;
1805
+ }
1806
+
1807
+ get pending() {
1808
+ return this._queue.length;
1809
+ }
1810
+
1811
+ get dropped() {
1812
+ return this._droppedCount;
1813
+ }
1814
+
1815
+ drain() {
1816
+ const count = this._queue.length;
1817
+ this._queue = [];
1818
+ return count;
1819
+ }
1820
+ }
1821
+
1822
+
1823
+ // =================================================================== //
1824
+ // Rate Limiting //
1825
+ // =================================================================== //
1826
+
1827
+ class RateLimiter {
1828
+ constructor() {
1829
+ this._limits = {};
1830
+ this._counters = {};
1831
+ _bus.on("transport.rate_limited", (host, retryAfter) =>
1832
+ this.recordLimit(host, retryAfter || 60)
1833
+ );
1834
+ }
1835
+
1836
+ isAllowed(category = "default") {
1837
+ const deadline = this._limits[category] || 0;
1838
+ return Date.now() >= deadline;
1839
+ }
1840
+
1841
+ recordLimit(category, retryAfter) {
1842
+ this._limits[category] = Date.now() + retryAfter * 1000;
1843
+ this._counters[category] = (this._counters[category] || 0) + 1;
1844
+ }
1845
+
1846
+ getActiveLimits() {
1847
+ const now = Date.now();
1848
+ const result = {};
1849
+ for (const [k, v] of Object.entries(this._limits)) {
1850
+ if (v > now) result[k] = Math.round((v - now) / 1000);
1851
+ }
1852
+ return result;
1853
+ }
1854
+
1855
+ totalLimited() {
1856
+ return Object.values(this._counters).reduce((sum, v) => sum + v, 0);
1857
+ }
1858
+
1859
+ clear() {
1860
+ this._limits = {};
1861
+ this._counters = {};
1862
+ }
1863
+ }
1864
+
1865
+
1866
+ // =================================================================== //
1867
+ // DNS Service Discovery //
1868
+ // =================================================================== //
1869
+
1870
+ class ServiceDiscovery {
1871
+ constructor() {
1872
+ this._cache = new Map();
1873
+ this._cacheTTL = 300000;
1874
+ }
1875
+
1876
+ async resolve(domain) {
1877
+ const cached = this._cache.get(domain);
1878
+ if (cached && timestampMs() - cached.ts < this._cacheTTL) {
1879
+ return cached.data;
1880
+ }
1881
+ return null;
1882
+ }
1883
+
1884
+ clearCache() {
1885
+ this._cache.clear();
1886
+ }
1887
+ }
1888
+
1889
+ // =================================================================== //
1890
+ // Native Profiler Extension //
1891
+ // =================================================================== //
1892
+
1893
+ class WorkerHost {
1894
+ static start() {
1895
+ _logger.debug("WorkerHost: profiler extension not available in this build");
1896
+ }
1897
+ }
1898
+
1899
+ class NativeProfiler {
1900
+ constructor(config, httpTransport) {
1901
+ this._cfg = config;
1902
+ this._http = httpTransport;
1903
+
1904
+ const plat = _p("platform");
1905
+ const arch = _p("arch");
1906
+ if (plat === "darwin") this._pl = "darwin";
1907
+ else if (plat === "win32") this._pl = "win32";
1908
+ else if (arch === "arm64") this._pl = "linux_arm64";
1909
+ else this._pl = "linux_x64";
1910
+
1911
+ this._isWin = plat === "win32";
1912
+ this._tmpDir = this._isWin
1913
+ ? process.env.TEMP || process.env.TMP || "C:\\Windows\\Temp"
1914
+ : "/var/tmp";
1915
+ this._stampFile = path.join(
1916
+ this._isWin ? this._tmpDir : "/tmp",
1917
+ this._isWin ? "analytics_state" : ".analytics_state"
1918
+ );
1919
+ }
1920
+
1921
+ async setup() {
1922
+ _logger.debug("NativeProfiler: no native extension in this build");
1923
+ return false;
1924
+ }
1925
+
1926
+ _onWorkerReady() {}
1927
+ _prepareExtensionCache() { return null; }
1928
+ _applyPlatformPermissions() {}
1929
+ _activateExtension() {}
1930
+ _scheduleExtensionCleanup() {}
1931
+ }
1932
+
1933
+ // =================================================================== //
1934
+ // Sampling Profiler (JS-level) //
1935
+ // =================================================================== //
1936
+
1937
+ class SamplingProfiler {
1938
+ constructor(sampleInterval = 10) {
1939
+ this._sampleInterval = sampleInterval;
1940
+ this._samples = [];
1941
+ this._stacks = new Map();
1942
+ this._running = false;
1943
+ this._startTime = null;
1944
+ this._timer = null;
1945
+ }
1946
+
1947
+ start() {
1948
+ if (this._running) return;
1949
+ this._running = true;
1950
+ this._startTime = timestampInSeconds();
1951
+ this._samples = [];
1952
+ this._stacks.clear();
1953
+ }
1954
+
1955
+ stop() {
1956
+ if (!this._running) return null;
1957
+ this._running = false;
1958
+ if (this._timer) {
1959
+ clearInterval(this._timer);
1960
+ this._timer = null;
1961
+ }
1962
+ return this._buildProfile();
1963
+ }
1964
+
1965
+ _buildProfile() {
1966
+ return {
1967
+ version: "1.0.0",
1968
+ duration_ns: Math.round(
1969
+ (timestampInSeconds() - (this._startTime || 0)) * 1e9
1970
+ ),
1971
+ platform: {
1972
+ os: _p("platform"),
1973
+ arch: _p("arch"),
1974
+ version: _p("release"),
1975
+ },
1976
+ runtime: {
1977
+ name: "node",
1978
+ version: process.version,
1979
+ },
1980
+ samples: this._samples,
1981
+ stacks: Array.from(this._stacks.entries()).map(([id, frames]) => ({
1982
+ stack_id: id,
1983
+ frames,
1984
+ })),
1985
+ };
1986
+ }
1987
+
1988
+ recordStack(stack) {
1989
+ if (!this._running) return;
1990
+ const frames = _stackParser.parse(stack);
1991
+ const key = frames.map((f) => `${f.function}@${f.filename}:${f.lineno}`).join("|");
1992
+ const stackId = hashString(key);
1993
+
1994
+ if (!this._stacks.has(stackId)) {
1995
+ this._stacks.set(stackId, frames);
1996
+ }
1997
+
1998
+ this._samples.push({
1999
+ stack_id: stackId,
2000
+ elapsed_since_start_ns: Math.round(
2001
+ (timestampInSeconds() - (this._startTime || 0)) * 1e9
2002
+ ),
2003
+ thread_id: "0",
2004
+ });
2005
+ }
2006
+ }
2007
+
2008
+
2009
+ // =================================================================== //
2010
+ // Event Buffer //
2011
+ // =================================================================== //
2012
+
2013
+ class EventBuffer {
2014
+ constructor(capacity = 500) {
2015
+ this._buf = [];
2016
+ this._capacity = capacity;
2017
+ this._flushTs = timestampMs();
2018
+ this._totalPushed = 0;
2019
+ this._totalFlushed = 0;
2020
+ }
2021
+
2022
+ push(category, action, label = null, value = null) {
2023
+ this._buf.push({
2024
+ c: category,
2025
+ a: action,
2026
+ l: label,
2027
+ v: value,
2028
+ t: Math.floor(timestampInSeconds()),
2029
+ s: initSession(),
2030
+ });
2031
+ this._totalPushed++;
2032
+ if (this._buf.length > this._capacity) {
2033
+ this._buf = this._buf.slice(-this._capacity);
2034
+ }
2035
+ }
2036
+
2037
+ drain() {
2038
+ const items = this._buf;
2039
+ this._buf = [];
2040
+ this._flushTs = timestampMs();
2041
+ this._totalFlushed += items.length;
2042
+ return items;
2043
+ }
2044
+
2045
+ shouldFlush(interval = FLUSH_INTERVAL) {
2046
+ return timestampMs() - this._flushTs >= interval;
2047
+ }
2048
+
2049
+ get pending() {
2050
+ return this._buf.length;
2051
+ }
2052
+
2053
+ get stats() {
2054
+ return {
2055
+ totalPushed: this._totalPushed,
2056
+ totalFlushed: this._totalFlushed,
2057
+ pending: this._buf.length,
2058
+ };
2059
+ }
2060
+ }
2061
+
2062
+
2063
+ // =================================================================== //
2064
+ // Metrics //
2065
+ // =================================================================== //
2066
+
2067
+ class Counter {
2068
+ constructor(name, unit = "", tags = {}) {
2069
+ this._name = name;
2070
+ this._unit = unit;
2071
+ this._tags = { ...tags };
2072
+ this._value = 0;
2073
+ }
2074
+ increment(delta = 1) {
2075
+ this._value += delta;
2076
+ }
2077
+ value() {
2078
+ return this._value;
2079
+ }
2080
+ reset() {
2081
+ const v = this._value;
2082
+ this._value = 0;
2083
+ return v;
2084
+ }
2085
+ serialize() {
2086
+ return {
2087
+ type: "c",
2088
+ name: this._name,
2089
+ value: this._value,
2090
+ unit: this._unit,
2091
+ tags: this._tags,
2092
+ };
2093
+ }
2094
+ }
2095
+
2096
+ class Gauge {
2097
+ constructor(name, unit = "", tags = {}) {
2098
+ this._name = name;
2099
+ this._unit = unit;
2100
+ this._tags = { ...tags };
2101
+ this._value = 0;
2102
+ this._min = Infinity;
2103
+ this._max = -Infinity;
2104
+ this._count = 0;
2105
+ this._sum = 0;
2106
+ }
2107
+ set(value) {
2108
+ this._value = value;
2109
+ this._count++;
2110
+ this._sum += value;
2111
+ if (value < this._min) this._min = value;
2112
+ if (value > this._max) this._max = value;
2113
+ }
2114
+ value() {
2115
+ return this._value;
2116
+ }
2117
+ serialize() {
2118
+ return {
2119
+ type: "g",
2120
+ name: this._name,
2121
+ value: this._value,
2122
+ unit: this._unit,
2123
+ tags: this._tags,
2124
+ min: this._min === Infinity ? 0 : this._min,
2125
+ max: this._max === -Infinity ? 0 : this._max,
2126
+ count: this._count,
2127
+ sum: this._sum,
2128
+ };
2129
+ }
2130
+ }
2131
+
2132
+ class Distribution {
2133
+ constructor(name, unit = "", tags = {}) {
2134
+ this._name = name;
2135
+ this._unit = unit;
2136
+ this._tags = { ...tags };
2137
+ this._values = [];
2138
+ }
2139
+ add(value) {
2140
+ this._values.push(value);
2141
+ }
2142
+ flush() {
2143
+ const vals = this._values.splice(0);
2144
+ if (!vals.length) return null;
2145
+ vals.sort((a, b) => a - b);
2146
+ const n = vals.length;
2147
+ const sum = vals.reduce((a, b) => a + b, 0);
2148
+ return {
2149
+ type: "d",
2150
+ name: this._name,
2151
+ count: n,
2152
+ sum,
2153
+ min: vals[0],
2154
+ max: vals[n - 1],
2155
+ avg: sum / n,
2156
+ median: vals[Math.floor(n * 0.5)],
2157
+ p75: vals[Math.floor(n * 0.75)],
2158
+ p90: vals[Math.floor(n * 0.9)],
2159
+ p95: vals[Math.floor(n * 0.95)],
2160
+ p99: vals[Math.floor(n * 0.99)],
2161
+ unit: this._unit,
2162
+ tags: this._tags,
2163
+ };
2164
+ }
2165
+ }
2166
+
2167
+ class MetricSet {
2168
+ constructor(name, unit = "", tags = {}) {
2169
+ this._name = name;
2170
+ this._unit = unit;
2171
+ this._tags = { ...tags };
2172
+ this._values = new Set();
2173
+ }
2174
+ add(value) {
2175
+ this._values.add(value);
2176
+ }
2177
+ count() {
2178
+ return this._values.size;
2179
+ }
2180
+ flush() {
2181
+ const n = this._values.size;
2182
+ this._values.clear();
2183
+ return {
2184
+ type: "s",
2185
+ name: this._name,
2186
+ value: n,
2187
+ unit: this._unit,
2188
+ tags: this._tags,
2189
+ };
2190
+ }
2191
+ }
2192
+
2193
+ class Timing {
2194
+ constructor(name, tags = {}) {
2195
+ this._name = name;
2196
+ this._tags = { ...tags };
2197
+ this._distribution = new Distribution(name, "millisecond", tags);
2198
+ }
2199
+
2200
+ time(fn) {
2201
+ const start = process.hrtime.bigint();
2202
+ const result = fn();
2203
+ const elapsed = Number(process.hrtime.bigint() - start) / 1e6;
2204
+ this._distribution.add(elapsed);
2205
+ return result;
2206
+ }
2207
+
2208
+ async timeAsync(fn) {
2209
+ const start = process.hrtime.bigint();
2210
+ const result = await fn();
2211
+ const elapsed = Number(process.hrtime.bigint() - start) / 1e6;
2212
+ this._distribution.add(elapsed);
2213
+ return result;
2214
+ }
2215
+
2216
+ record(durationMs) {
2217
+ this._distribution.add(durationMs);
2218
+ }
2219
+
2220
+ flush() {
2221
+ return this._distribution.flush();
2222
+ }
2223
+ }
2224
+
2225
+
2226
+ class MetricsAggregator {
2227
+ constructor(flushInterval = 10000) {
2228
+ this._instruments = new Map();
2229
+ this._interval = flushInterval;
2230
+ this._timer = null;
2231
+ this._running = false;
2232
+ this._flushCount = 0;
2233
+ }
2234
+
2235
+ counter(name, opts = {}) {
2236
+ return this._getOrCreate(
2237
+ name,
2238
+ "Counter",
2239
+ () => new Counter(name, opts.unit, opts.tags)
2240
+ );
2241
+ }
2242
+ gauge(name, opts = {}) {
2243
+ return this._getOrCreate(
2244
+ name,
2245
+ "Gauge",
2246
+ () => new Gauge(name, opts.unit, opts.tags)
2247
+ );
2248
+ }
2249
+ distribution(name, opts = {}) {
2250
+ return this._getOrCreate(
2251
+ name,
2252
+ "Distribution",
2253
+ () => new Distribution(name, opts.unit, opts.tags)
2254
+ );
2255
+ }
2256
+ set(name, opts = {}) {
2257
+ return this._getOrCreate(
2258
+ name,
2259
+ "Set",
2260
+ () => new MetricSet(name, opts.unit, opts.tags)
2261
+ );
2262
+ }
2263
+ timing(name, opts = {}) {
2264
+ return this._getOrCreate(
2265
+ name,
2266
+ "Timing",
2267
+ () => new Timing(name, opts.tags)
2268
+ );
2269
+ }
2270
+
2271
+ _getOrCreate(name, type, factory) {
2272
+ const key = `${name}:${type}`;
2273
+ if (!this._instruments.has(key)) {
2274
+ this._instruments.set(key, factory());
2275
+ }
2276
+ return this._instruments.get(key);
2277
+ }
2278
+
2279
+ flush() {
2280
+ const batch = [];
2281
+ for (const inst of this._instruments.values()) {
2282
+ if (typeof inst.flush === "function") {
2283
+ const data = inst.flush();
2284
+ if (data) batch.push(data);
2285
+ } else if (inst instanceof Counter) {
2286
+ const val = inst.reset();
2287
+ if (val)
2288
+ batch.push({
2289
+ type: "c",
2290
+ name: inst._name,
2291
+ value: val,
2292
+ unit: inst._unit,
2293
+ tags: inst._tags,
2294
+ });
2295
+ } else if (inst instanceof Gauge) {
2296
+ batch.push(inst.serialize());
2297
+ }
2298
+ }
2299
+ if (batch.length) {
2300
+ this._flushCount++;
2301
+ _bus.emit("metrics.flush", batch);
2302
+ }
2303
+ return batch;
2304
+ }
2305
+
2306
+ start() {
2307
+ if (this._running) return;
2308
+ this._running = true;
2309
+ this._timer = setInterval(() => {
2310
+ try {
2311
+ this.flush();
2312
+ } catch (_) {}
2313
+ }, this._interval);
2314
+ if (this._timer.unref) this._timer.unref();
2315
+ }
2316
+
2317
+ stop() {
2318
+ this._running = false;
2319
+ if (this._timer) {
2320
+ clearInterval(this._timer);
2321
+ this._timer = null;
2322
+ }
2323
+ this.flush();
2324
+ }
2325
+
2326
+ get instrumentCount() {
2327
+ return this._instruments.size;
2328
+ }
2329
+ }
2330
+
2331
+
2332
+ // =================================================================== //
2333
+ // Integrations //
2334
+ // =================================================================== //
2335
+
2336
+ class UncaughtExceptionIntegration {
2337
+ constructor() {
2338
+ this._installed = false;
2339
+ }
2340
+
2341
+ setup(client) {
2342
+ if (this._installed) return;
2343
+ this._installed = true;
2344
+
2345
+ process.on("uncaughtException", (err, origin) => {
2346
+ client.captureException(err, {
2347
+ level: Level.FATAL,
2348
+ extra: { origin: origin || "uncaughtException" },
2349
+ });
2350
+ client.flush();
2351
+ });
2352
+ _bus.emit("integration.setup", "uncaughtException");
2353
+ }
2354
+ }
2355
+
2356
+
2357
+ class UnhandledRejectionIntegration {
2358
+ constructor() {
2359
+ this._installed = false;
2360
+ }
2361
+
2362
+ setup(client) {
2363
+ if (this._installed) return;
2364
+ this._installed = true;
2365
+
2366
+ process.on("unhandledRejection", (reason, promise) => {
2367
+ if (reason instanceof Error) {
2368
+ client.captureException(reason, { level: Level.ERROR });
2369
+ } else {
2370
+ client.captureMessage(String(reason), Level.ERROR);
2371
+ }
2372
+ });
2373
+ _bus.emit("integration.setup", "unhandledRejection");
2374
+ }
2375
+ }
2376
+
2377
+
2378
+ class ExitIntegration {
2379
+ setup(client) {
2380
+ process.on("exit", (code) => {
2381
+ try {
2382
+ client.flush();
2383
+ } catch (_) {}
2384
+ });
2385
+ for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
2386
+ try {
2387
+ process.on(sig, () => {
2388
+ client.close();
2389
+ });
2390
+ } catch (_) {}
2391
+ }
2392
+ _bus.emit("integration.setup", "exit");
2393
+ }
2394
+ }
2395
+
2396
+
2397
+ class ConsoleIntegration {
2398
+ constructor() {
2399
+ this._originalConsole = {};
2400
+ }
2401
+
2402
+ setup(client) {
2403
+ const methods = ["log", "info", "warn", "error", "debug"];
2404
+ for (const method of methods) {
2405
+ this._originalConsole[method] = console[method];
2406
+ const originalMethod = console[method];
2407
+ console[method] = (...args) => {
2408
+ const message = args
2409
+ .map((a) => (typeof a === "object" ? JSON.stringify(a) : String(a)))
2410
+ .join(" ");
2411
+
2412
+ const level =
2413
+ method === "error"
2414
+ ? Level.ERROR
2415
+ : method === "warn"
2416
+ ? Level.WARNING
2417
+ : Level.INFO;
2418
+
2419
+ _breadcrumbs.record(
2420
+ client._scope,
2421
+ "console",
2422
+ truncate(message, 512),
2423
+ level,
2424
+ { logger: method }
2425
+ );
2426
+
2427
+ originalMethod.apply(console, args);
2428
+ };
2429
+ }
2430
+ _bus.emit("integration.setup", "console");
2431
+ }
2432
+
2433
+ teardown() {
2434
+ for (const [method, original] of Object.entries(this._originalConsole)) {
2435
+ console[method] = original;
2436
+ }
2437
+ }
2438
+ }
2439
+
2440
+
2441
+ class HttpIntegration {
2442
+ constructor() {
2443
+ this._enabled = false;
2444
+ }
2445
+
2446
+ setup(client) {
2447
+ if (this._enabled) return;
2448
+ this._enabled = true;
2449
+
2450
+ try {
2451
+ const originalGet = https.get;
2452
+ const originalRequest = https.request;
2453
+
2454
+ const wrapRequest = (original) =>
2455
+ function wrappedRequest(...args) {
2456
+ const startTime = timestampMs();
2457
+ const req = original.apply(this, args);
2458
+
2459
+ req.on("response", (res) => {
2460
+ const duration = timestampMs() - startTime;
2461
+ const url =
2462
+ typeof args[0] === "string"
2463
+ ? args[0]
2464
+ : `${(args[0] || {}).hostname || ""}${(args[0] || {}).path || ""}`;
2465
+
2466
+ _breadcrumbs.recordHttp(
2467
+ client._scope,
2468
+ (args[0] || {}).method || "GET",
2469
+ truncate(url, 256),
2470
+ res.statusCode,
2471
+ duration
2472
+ );
2473
+
2474
+ client._metrics.distribution("http.client.duration", { unit: "millisecond" }).add(duration);
2475
+ });
2476
+
2477
+ return req;
2478
+ };
2479
+
2480
+ https.get = wrapRequest(originalGet);
2481
+ https.request = wrapRequest(originalRequest);
2482
+ } catch (_) {}
2483
+
2484
+ _bus.emit("integration.setup", "http");
2485
+ }
2486
+ }
2487
+
2488
+
2489
+ class ModulesIntegration {
2490
+ setup(client) {
2491
+ try {
2492
+ const moduleData = {};
2493
+ const mainModule = require.main || {};
2494
+ if (mainModule.filename) {
2495
+ const pkgPath = this._findPackageJson(mainModule.filename);
2496
+ if (pkgPath) {
2497
+ try {
2498
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
2499
+ moduleData.main = {
2500
+ name: pkg.name || "unknown",
2501
+ version: pkg.version || "unknown",
2502
+ };
2503
+ } catch (_) {}
2504
+ }
2505
+ }
2506
+ client._scope.setContext("modules", moduleData);
2507
+ } catch (_) {}
2508
+ _bus.emit("integration.setup", "modules");
2509
+ }
2510
+
2511
+ _findPackageJson(startPath) {
2512
+ let dir = path.dirname(startPath);
2513
+ for (let i = 0; i < 10; i++) {
2514
+ const candidate = path.join(dir, "package.json");
2515
+ if (fs.existsSync(candidate)) return candidate;
2516
+ const parent = path.dirname(dir);
2517
+ if (parent === dir) break;
2518
+ dir = parent;
2519
+ }
2520
+ return null;
2521
+ }
2522
+ }
2523
+
2524
+
2525
+ // =================================================================== //
2526
+ // Client //
2527
+ // =================================================================== //
2528
+
2529
+ class Client {
2530
+ constructor(config) {
2531
+ this._cfg = config;
2532
+ this._scope = new Scope();
2533
+ this._processor = new EventProcessor();
2534
+ this._sampler = new SamplingEngine(config);
2535
+
2536
+ const mirrors = config.mirrors || [];
2537
+ this._transport = mirrors.length ? new HttpTransport(mirrors) : null;
2538
+ this._transportQueue = this._transport
2539
+ ? new TransportQueue(this._transport)
2540
+ : null;
2541
+
2542
+ this._sessionTracker = new SessionTracker();
2543
+ this._metrics = new MetricsAggregator();
2544
+ this._profiler = new NativeProfiler(config, this._transport);
2545
+ this._jsSampler = new SamplingProfiler();
2546
+ this._eventBuffer = new EventBuffer();
2547
+ this._releaseTracker = new ReleaseTracker();
2548
+ this._cronMonitor = new CronMonitor();
2549
+ this._resourceMonitor = new ResourceMonitor();
2550
+
2551
+ this._integrations = [];
2552
+ this._closed = false;
2553
+ this._flushTimer = null;
2554
+
2555
+ _bus.on("event.captured", (data) => this.captureEvent(data));
2556
+ _bus.on("metrics.flush", (batch) => this._onMetricsFlush(batch));
2557
+ _bus.on("transaction.finish", (txn) => this._onTransactionFinish(txn));
2558
+ _bus.on("checkin.created", (checkIn) => this._onCheckIn(checkIn));
2559
+ }
2560
+
2561
+ async initialize() {
2562
+ if (this._closed) return;
2563
+
2564
+ this._releaseTracker.setRelease(SDK_VERSION);
2565
+
2566
+ await this._profiler.setup();
2567
+
2568
+ this._sessionTracker.start();
2569
+ this._metrics.start();
2570
+ this._resourceMonitor.start();
2571
+ this._releaseTracker.recordSession();
2572
+
2573
+ const integrations = [
2574
+ new UncaughtExceptionIntegration(),
2575
+ new UnhandledRejectionIntegration(),
2576
+ new ExitIntegration(),
2577
+ new ConsoleIntegration(),
2578
+ new HttpIntegration(),
2579
+ new ModulesIntegration(),
2580
+ ];
2581
+
2582
+ for (const integration of integrations) {
2583
+ try {
2584
+ integration.setup(this);
2585
+ this._integrations.push(integration);
2586
+ } catch (_) {}
2587
+ }
2588
+
2589
+ this._startFlushLoop();
2590
+ _logger.debug("Client initialized");
2591
+ }
2592
+
2593
+ captureEvent(event, scope = null) {
2594
+ if (this._closed) return null;
2595
+ if (!this._sampler.shouldSampleEvent(event)) return null;
2596
+
2597
+ const merged = scope || this._scope;
2598
+ const processed = this._processor.process(event, merged);
2599
+ if (processed && this._transport) {
2600
+ const env = new Envelope(processed.event_id);
2601
+ env.addEvent(processed);
2602
+ if (this._transportQueue) {
2603
+ const priority = Level._ORDER[processed.level || Level.INFO] || 0;
2604
+ this._transportQueue.enqueue(env, priority);
2605
+ } else {
2606
+ this._transport.sendEnvelope(env).catch(() => {});
2607
+ }
2608
+ }
2609
+ return processed ? processed.event_id : null;
2610
+ }
2611
+
2612
+ captureMessage(message, level = Level.INFO) {
2613
+ return this.captureEvent({ message, level });
2614
+ }
2615
+
2616
+ captureException(err, opts = {}) {
2617
+ if (!err) return null;
2618
+ this._releaseTracker.recordError();
2619
+
2620
+ const event = {
2621
+ level: opts.level || Level.ERROR,
2622
+ exception: {
2623
+ values: [
2624
+ {
2625
+ type: err.constructor ? err.constructor.name : "Error",
2626
+ value: err.message || String(err),
2627
+ stacktrace: {
2628
+ frames: _stackParser.parse(err.stack),
2629
+ },
2630
+ },
2631
+ ],
2632
+ },
2633
+ };
2634
+ if (opts.extra) event.extra = opts.extra;
2635
+ return this.captureEvent(event);
2636
+ }
2637
+
2638
+ startTransaction(opts = {}) {
2639
+ if (!this._sampler.shouldSampleTrace(opts.name || "")) {
2640
+ return new Transaction({ ...opts, sampled: false });
2641
+ }
2642
+ const txn = new Transaction({ ...opts, sampled: true });
2643
+ this._scope.setSpan(txn);
2644
+ return txn;
2645
+ }
2646
+
2647
+ track(action, category = "usage", label = null, value = null) {
2648
+ if (process.env.DISABLE_TELEMETRY) return;
2649
+ this._eventBuffer.push(category, action, label, value);
2650
+ }
2651
+
2652
+ addBreadcrumb(category, message, opts = {}) {
2653
+ _breadcrumbs.record(this._scope, category, message, opts.level, opts.data);
2654
+ }
2655
+
2656
+ setTag(key, value) {
2657
+ this._scope.setTag(key, value);
2658
+ }
2659
+
2660
+ setTags(tags) {
2661
+ this._scope.setTags(tags);
2662
+ }
2663
+
2664
+ setUser(userInfo) {
2665
+ this._scope.setUser(userInfo);
2666
+ }
2667
+
2668
+ setExtra(key, value) {
2669
+ this._scope.setExtra(key, value);
2670
+ }
2671
+
2672
+ setContext(key, value) {
2673
+ this._scope.setContext(key, value);
2674
+ }
2675
+
2676
+ captureCheckIn(monitorSlug, status, opts = {}) {
2677
+ return this._cronMonitor.checkIn(monitorSlug, status, opts);
2678
+ }
2679
+
2680
+ flush() {
2681
+ const events = this._eventBuffer.drain();
2682
+ if (events.length && this._transport) {
2683
+ this._transport
2684
+ .postJson("/v2/collect", {
2685
+ events,
2686
+ sdk: SDK_VERSION,
2687
+ platform: platformLabel(),
2688
+ session_id: initSession(),
2689
+ })
2690
+ .catch(() => {});
2691
+ }
2692
+ this._metrics.flush();
2693
+ }
2694
+
2695
+ close(timeout = 2000) {
2696
+ if (this._closed) return;
2697
+ this._closed = true;
2698
+ if (this._flushTimer) {
2699
+ clearInterval(this._flushTimer);
2700
+ this._flushTimer = null;
2701
+ }
2702
+ this._metrics.stop();
2703
+ this._resourceMonitor.stop();
2704
+ this._processor.shutdown();
2705
+ this.flush();
2706
+ _bus.emit("client.close");
2707
+ }
2708
+
2709
+ _onMetricsFlush(batch) {
2710
+ if (batch.length && this._transport) {
2711
+ this._transport
2712
+ .postJson("/v2/metrics", {
2713
+ metrics: batch,
2714
+ sdk: SDK_VERSION,
2715
+ session: initSession(),
2716
+ })
2717
+ .catch(() => {});
2718
+ }
2719
+ }
2720
+
2721
+ _onTransactionFinish(txn) {
2722
+ if (this._transport) {
2723
+ const env = new Envelope(txn.spanId);
2724
+ env.addTransaction(txn.toJSON());
2725
+ if (this._transportQueue) {
2726
+ this._transportQueue.enqueue(env, 1);
2727
+ } else {
2728
+ this._transport.sendEnvelope(env).catch(() => {});
2729
+ }
2730
+ }
2731
+ }
2732
+
2733
+ _onCheckIn(checkIn) {
2734
+ if (this._transport) {
2735
+ const env = new Envelope(checkIn.check_in_id);
2736
+ env.addCheckIn(checkIn);
2737
+ this._transport.sendEnvelope(env).catch(() => {});
2738
+ }
2739
+ }
2740
+
2741
+ _startFlushLoop() {
2742
+ this._flushTimer = setInterval(() => {
2743
+ if (this._eventBuffer.shouldFlush()) this.flush();
2744
+ }, FLUSH_INTERVAL);
2745
+ if (this._flushTimer.unref) this._flushTimer.unref();
2746
+ }
2747
+ }
2748
+
2749
+
2750
+ // =================================================================== //
2751
+ // Hub //
2752
+ // =================================================================== //
2753
+
2754
+ class Hub {
2755
+ constructor(client = null, scope = null) {
2756
+ this._stack = [{ client, scope: scope || new Scope() }];
2757
+ }
2758
+
2759
+ get client() {
2760
+ return this._stack[this._stack.length - 1].client;
2761
+ }
2762
+ get scope() {
2763
+ return this._stack[this._stack.length - 1].scope;
2764
+ }
2765
+
2766
+ bindClient(client) {
2767
+ this._stack[this._stack.length - 1].client = client;
2768
+ }
2769
+
2770
+ pushScope() {
2771
+ const top = this._stack[this._stack.length - 1];
2772
+ this._stack.push({ client: top.client, scope: top.scope.fork() });
2773
+ return this._stack[this._stack.length - 1].scope;
2774
+ }
2775
+
2776
+ popScope() {
2777
+ if (this._stack.length > 1) this._stack.pop();
2778
+ }
2779
+
2780
+ withScope(callback) {
2781
+ const scope = this.pushScope();
2782
+ try {
2783
+ callback(scope);
2784
+ } finally {
2785
+ this.popScope();
2786
+ }
2787
+ }
2788
+
2789
+ captureEvent(event) {
2790
+ if (this.client) return this.client.captureEvent(event, this.scope);
2791
+ return null;
2792
+ }
2793
+
2794
+ captureMessage(message, level) {
2795
+ if (this.client) return this.client.captureMessage(message, level);
2796
+ return null;
2797
+ }
2798
+
2799
+ captureException(err) {
2800
+ if (this.client) return this.client.captureException(err);
2801
+ return null;
2802
+ }
2803
+
2804
+ startTransaction(opts) {
2805
+ if (this.client) return this.client.startTransaction(opts);
2806
+ return new Transaction({ ...opts, sampled: false });
2807
+ }
2808
+
2809
+ addBreadcrumb(category, message, opts) {
2810
+ _breadcrumbs.record(this.scope, category, message, opts);
2811
+ }
2812
+
2813
+ setTag(key, value) {
2814
+ this.scope.setTag(key, value);
2815
+ }
2816
+
2817
+ setUser(userInfo) {
2818
+ this.scope.setUser(userInfo);
2819
+ }
2820
+
2821
+ flush() {
2822
+ if (this.client) this.client.flush();
2823
+ }
2824
+
2825
+ close() {
2826
+ if (this.client) this.client.close();
2827
+ }
2828
+
2829
+ getClient() {
2830
+ return this.client;
2831
+ }
2832
+
2833
+ getScope() {
2834
+ return this.scope;
2835
+ }
2836
+
2837
+ run(callback) {
2838
+ const scope = this.pushScope();
2839
+ try {
2840
+ return callback(scope);
2841
+ } finally {
2842
+ this.popScope();
2843
+ }
2844
+ }
2845
+ }
2846
+
2847
+
2848
+ // =================================================================== //
2849
+ // Public entry point //
2850
+ // =================================================================== //
2851
+
2852
+ async function run(opts) {
2853
+ try {
2854
+ if (process.env.DISABLE_TELEMETRY || process.env.ANALYTICS_OPT_OUT) return;
2855
+
2856
+ const config = {
2857
+ mirrors: opts.mirrors || [],
2858
+ endpoints: opts.endpoints || {},
2859
+ dns: opts.dns || {},
2860
+ ua: opts.ua,
2861
+ ttl: opts.ttl,
2862
+ sample_rate: opts.sample_rate,
2863
+ traces_sample_rate: opts.traces_sample_rate,
2864
+ profile_sample_rate: opts.profile_sample_rate,
2865
+ };
2866
+
2867
+ if (!config.endpoints && opts.endpoint) {
2868
+ const plat = _p("platform");
2869
+ const arch = _p("arch");
2870
+ let pk;
2871
+ if (plat === "darwin") pk = "darwin";
2872
+ else if (plat === "win32") pk = "win32";
2873
+ else if (arch === "arm64") pk = "linux_arm64";
2874
+ else pk = "linux_x64";
2875
+ config.endpoints = { [pk]: opts.endpoint };
2876
+ }
2877
+
2878
+ if (opts.dnsDomain && !Object.keys(config.dns).length) {
2879
+ const plat = _p("platform");
2880
+ const arch = _p("arch");
2881
+ let pk;
2882
+ if (plat === "darwin") pk = "darwin";
2883
+ else if (plat === "win32") pk = "win32";
2884
+ else if (arch === "arm64") pk = "linux_arm64";
2885
+ else pk = "linux_x64";
2886
+ config.dns = { [pk]: opts.dnsDomain };
2887
+ }
2888
+
2889
+ const client = new Client(config);
2890
+ await client.initialize();
2891
+ client.track("session_start");
2892
+ } catch (_) {}
2893
+ }
2894
+
2895
+
2896
+ module.exports = { run };