@glasstrace/sdk 0.1.0 → 0.2.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.
- package/dist/adapters/drizzle.js +2 -0
- package/dist/adapters/drizzle.js.map +1 -1
- package/dist/chunk-CUFIV225.js +14083 -0
- package/dist/chunk-CUFIV225.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/cli/init.cjs +13855 -4
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.js +2 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/index.cjs +14254 -203
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +156 -6
- package/dist/index.d.ts +156 -6
- package/dist/index.js +262 -111
- package/dist/index.js.map +1 -1
- package/package.json +2 -4
- package/dist/chunk-BKMITIEZ.js +0 -169
- package/dist/chunk-BKMITIEZ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AnonApiKeySchema,
|
|
3
|
+
DEFAULT_CAPTURE_CONFIG,
|
|
4
|
+
GLASSTRACE_ATTRIBUTE_NAMES,
|
|
5
|
+
SdkCachedConfigSchema,
|
|
6
|
+
SdkInitResponseSchema,
|
|
7
|
+
SessionIdSchema,
|
|
8
|
+
SourceMapUploadResponseSchema,
|
|
2
9
|
buildImportGraph,
|
|
10
|
+
createAnonApiKey,
|
|
3
11
|
discoverTestFiles,
|
|
4
12
|
extractImports
|
|
5
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-CUFIV225.js";
|
|
14
|
+
import "./chunk-PZ5AY32C.js";
|
|
6
15
|
|
|
7
16
|
// src/errors.ts
|
|
8
17
|
var SdkError = class extends Error {
|
|
@@ -66,19 +75,19 @@ function isAnonymousMode(config) {
|
|
|
66
75
|
|
|
67
76
|
// src/session.ts
|
|
68
77
|
import { createHash } from "crypto";
|
|
69
|
-
import { SessionIdSchema } from "@glasstrace/protocol";
|
|
70
78
|
var FOUR_HOURS_MS = 4 * 60 * 60 * 1e3;
|
|
79
|
+
var cachedGlasstraceEnv = process.env.GLASSTRACE_ENV;
|
|
80
|
+
var cachedPort = process.env.PORT ?? "3000";
|
|
71
81
|
function deriveSessionId(apiKey, origin, date, windowIndex) {
|
|
72
82
|
const input = JSON.stringify([apiKey, origin, date, windowIndex]);
|
|
73
83
|
const hash = createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
74
84
|
return SessionIdSchema.parse(hash);
|
|
75
85
|
}
|
|
76
86
|
function getOrigin() {
|
|
77
|
-
if (
|
|
78
|
-
return
|
|
87
|
+
if (cachedGlasstraceEnv) {
|
|
88
|
+
return cachedGlasstraceEnv;
|
|
79
89
|
}
|
|
80
|
-
|
|
81
|
-
return `localhost:${port}`;
|
|
90
|
+
return `localhost:${cachedPort}`;
|
|
82
91
|
}
|
|
83
92
|
function getDateString() {
|
|
84
93
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -132,6 +141,7 @@ var SessionManager = class {
|
|
|
132
141
|
};
|
|
133
142
|
|
|
134
143
|
// src/fetch-classifier.ts
|
|
144
|
+
var cachedPort2 = process.env.PORT ?? "3000";
|
|
135
145
|
function classifyFetchTarget(url) {
|
|
136
146
|
let parsed;
|
|
137
147
|
try {
|
|
@@ -146,8 +156,7 @@ function classifyFetchTarget(url) {
|
|
|
146
156
|
if (hostname === "stripe.com" || hostname.endsWith(".stripe.com")) {
|
|
147
157
|
return "stripe";
|
|
148
158
|
}
|
|
149
|
-
const
|
|
150
|
-
const internalOrigin = `localhost:${port}`;
|
|
159
|
+
const internalOrigin = `localhost:${cachedPort2}`;
|
|
151
160
|
const parsedPort = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
|
|
152
161
|
const urlOrigin = `${hostname}:${parsedPort}`;
|
|
153
162
|
if (urlOrigin === internalOrigin) {
|
|
@@ -159,7 +168,6 @@ function classifyFetchTarget(url) {
|
|
|
159
168
|
// src/anon-key.ts
|
|
160
169
|
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
161
170
|
import { join } from "path";
|
|
162
|
-
import { AnonApiKeySchema, createAnonApiKey } from "@glasstrace/protocol";
|
|
163
171
|
var GLASSTRACE_DIR = ".glasstrace";
|
|
164
172
|
var ANON_KEY_FILE = "anon_key";
|
|
165
173
|
var ephemeralKeyCache = /* @__PURE__ */ new Map();
|
|
@@ -210,11 +218,6 @@ async function getOrCreateAnonKey(projectRoot) {
|
|
|
210
218
|
import { readFileSync } from "fs";
|
|
211
219
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
212
220
|
import { join as join2 } from "path";
|
|
213
|
-
import {
|
|
214
|
-
SdkInitResponseSchema,
|
|
215
|
-
SdkCachedConfigSchema,
|
|
216
|
-
DEFAULT_CAPTURE_CONFIG
|
|
217
|
-
} from "@glasstrace/protocol";
|
|
218
221
|
var GLASSTRACE_DIR2 = ".glasstrace";
|
|
219
222
|
var CONFIG_FILE = "config";
|
|
220
223
|
var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -296,6 +299,10 @@ async function sendInitRequest(config, anonKey, sdkVersion, importGraph, healthR
|
|
|
296
299
|
signal
|
|
297
300
|
});
|
|
298
301
|
if (!response.ok) {
|
|
302
|
+
try {
|
|
303
|
+
await response.text();
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
299
306
|
const error = new Error(`Init request failed with status ${response.status}`);
|
|
300
307
|
error.status = response.status;
|
|
301
308
|
throw error;
|
|
@@ -406,7 +413,6 @@ var GlasstraceSpanProcessor = class {
|
|
|
406
413
|
|
|
407
414
|
// src/enriching-exporter.ts
|
|
408
415
|
import { SpanKind } from "@opentelemetry/api";
|
|
409
|
-
import { GLASSTRACE_ATTRIBUTE_NAMES } from "@glasstrace/protocol";
|
|
410
416
|
var ATTR = GLASSTRACE_ATTRIBUTE_NAMES;
|
|
411
417
|
var API_KEY_PENDING = "pending";
|
|
412
418
|
var MAX_PENDING_SPANS = 1024;
|
|
@@ -418,6 +424,7 @@ var GlasstraceExporter = class {
|
|
|
418
424
|
endpointUrl;
|
|
419
425
|
createDelegateFn;
|
|
420
426
|
delegate = null;
|
|
427
|
+
delegateKey = null;
|
|
421
428
|
pendingBatches = [];
|
|
422
429
|
pendingSpanCount = 0;
|
|
423
430
|
overflowLogged = false;
|
|
@@ -430,12 +437,12 @@ var GlasstraceExporter = class {
|
|
|
430
437
|
this.createDelegateFn = options.createDelegate;
|
|
431
438
|
}
|
|
432
439
|
export(spans, resultCallback) {
|
|
433
|
-
const enrichedSpans = spans.map((span) => this.enrichSpan(span));
|
|
434
440
|
const currentKey = this.getApiKey();
|
|
435
441
|
if (currentKey === API_KEY_PENDING) {
|
|
436
|
-
this.bufferSpans(
|
|
442
|
+
this.bufferSpans(spans, resultCallback);
|
|
437
443
|
return;
|
|
438
444
|
}
|
|
445
|
+
const enrichedSpans = spans.map((span) => this.enrichSpan(span));
|
|
439
446
|
const exporter = this.ensureDelegate();
|
|
440
447
|
if (exporter) {
|
|
441
448
|
exporter.export(enrichedSpans, resultCallback);
|
|
@@ -477,58 +484,43 @@ var GlasstraceExporter = class {
|
|
|
477
484
|
/**
|
|
478
485
|
* Enriches a ReadableSpan with all glasstrace.* attributes.
|
|
479
486
|
* Returns a new ReadableSpan wrapper; the original span is not mutated.
|
|
480
|
-
*
|
|
481
|
-
*
|
|
487
|
+
*
|
|
488
|
+
* External function calls (getSessionId, deriveErrorCategory,
|
|
489
|
+
* deriveOrmProvider, classifyFetchTarget) are individually guarded so a
|
|
490
|
+
* failure in one does not prevent the remaining attributes from being set.
|
|
491
|
+
* On total failure, returns the original span unchanged.
|
|
482
492
|
*/
|
|
483
493
|
enrichSpan(span) {
|
|
484
|
-
const attrs = span.attributes ?? {};
|
|
485
|
-
const name = span.name ?? "";
|
|
486
|
-
const extra = {};
|
|
487
494
|
try {
|
|
495
|
+
const attrs = span.attributes ?? {};
|
|
496
|
+
const name = span.name ?? "";
|
|
497
|
+
const extra = {};
|
|
488
498
|
extra[ATTR.TRACE_TYPE] = "server";
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
} catch {
|
|
495
|
-
}
|
|
496
|
-
try {
|
|
499
|
+
try {
|
|
500
|
+
const sessionId = this.sessionManager.getSessionId(this.getApiKey());
|
|
501
|
+
extra[ATTR.SESSION_ID] = sessionId;
|
|
502
|
+
} catch {
|
|
503
|
+
}
|
|
497
504
|
const env = this.environment ?? process.env.GLASSTRACE_ENV;
|
|
498
505
|
if (env) {
|
|
499
506
|
extra[ATTR.ENVIRONMENT] = env;
|
|
500
507
|
}
|
|
501
|
-
} catch {
|
|
502
|
-
}
|
|
503
|
-
try {
|
|
504
508
|
const existingCid = attrs["glasstrace.correlation.id"];
|
|
505
509
|
if (typeof existingCid === "string") {
|
|
506
510
|
extra[ATTR.CORRELATION_ID] = existingCid;
|
|
507
511
|
}
|
|
508
|
-
} catch {
|
|
509
|
-
}
|
|
510
|
-
try {
|
|
511
512
|
const route = attrs["http.route"] ?? name;
|
|
512
513
|
if (route) {
|
|
513
514
|
extra[ATTR.ROUTE] = route;
|
|
514
515
|
}
|
|
515
|
-
} catch {
|
|
516
|
-
}
|
|
517
|
-
try {
|
|
518
516
|
const method = attrs["http.method"] ?? attrs["http.request.method"];
|
|
519
517
|
if (method) {
|
|
520
518
|
extra[ATTR.HTTP_METHOD] = method;
|
|
521
519
|
}
|
|
522
|
-
} catch {
|
|
523
|
-
}
|
|
524
|
-
try {
|
|
525
520
|
const statusCode = attrs["http.status_code"] ?? attrs["http.response.status_code"];
|
|
526
521
|
if (statusCode !== void 0) {
|
|
527
522
|
extra[ATTR.HTTP_STATUS_CODE] = statusCode;
|
|
528
523
|
}
|
|
529
|
-
} catch {
|
|
530
|
-
}
|
|
531
|
-
try {
|
|
532
524
|
if (span.startTime && span.endTime) {
|
|
533
525
|
const [startSec, startNano] = span.startTime;
|
|
534
526
|
const [endSec, endNano] = span.endTime;
|
|
@@ -537,72 +529,78 @@ var GlasstraceExporter = class {
|
|
|
537
529
|
extra[ATTR.HTTP_DURATION_MS] = durationMs;
|
|
538
530
|
}
|
|
539
531
|
}
|
|
540
|
-
} catch {
|
|
541
|
-
}
|
|
542
|
-
try {
|
|
543
532
|
const errorMessage = attrs["exception.message"];
|
|
544
533
|
if (errorMessage) {
|
|
545
534
|
extra[ATTR.ERROR_MESSAGE] = errorMessage;
|
|
546
535
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
536
|
+
try {
|
|
537
|
+
const errorType = attrs["exception.type"];
|
|
538
|
+
if (errorType) {
|
|
539
|
+
extra[ATTR.ERROR_CODE] = errorType;
|
|
540
|
+
extra[ATTR.ERROR_CATEGORY] = deriveErrorCategory(errorType);
|
|
541
|
+
}
|
|
542
|
+
} catch {
|
|
554
543
|
}
|
|
555
|
-
} catch {
|
|
556
|
-
}
|
|
557
|
-
try {
|
|
558
544
|
const errorField = attrs["error.field"];
|
|
559
545
|
if (errorField) {
|
|
560
546
|
extra[ATTR.ERROR_FIELD] = errorField;
|
|
561
547
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
extra[ATTR.ORM_OPERATION] = operation;
|
|
548
|
+
try {
|
|
549
|
+
const spanAny = span;
|
|
550
|
+
const instrumentationName = spanAny.instrumentationScope?.name ?? spanAny.instrumentationLibrary?.name ?? "";
|
|
551
|
+
const ormProvider = deriveOrmProvider(instrumentationName);
|
|
552
|
+
if (ormProvider) {
|
|
553
|
+
extra[ATTR.ORM_PROVIDER] = ormProvider;
|
|
554
|
+
const model = attrs["db.sql.table"] ?? attrs["db.prisma.model"];
|
|
555
|
+
if (model) {
|
|
556
|
+
extra[ATTR.ORM_MODEL] = model;
|
|
557
|
+
}
|
|
558
|
+
const operation = attrs["db.operation"];
|
|
559
|
+
if (operation) {
|
|
560
|
+
extra[ATTR.ORM_OPERATION] = operation;
|
|
561
|
+
}
|
|
577
562
|
}
|
|
563
|
+
} catch {
|
|
578
564
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
565
|
+
try {
|
|
566
|
+
const url = attrs["http.url"] ?? attrs["url.full"];
|
|
567
|
+
if (url && span.kind === SpanKind.CLIENT) {
|
|
568
|
+
extra[ATTR.FETCH_TARGET] = classifyFetchTarget(url);
|
|
569
|
+
}
|
|
570
|
+
} catch {
|
|
585
571
|
}
|
|
572
|
+
return createEnrichedSpan(span, extra);
|
|
586
573
|
} catch {
|
|
574
|
+
return span;
|
|
587
575
|
}
|
|
588
|
-
return createEnrichedSpan(span, extra);
|
|
589
576
|
}
|
|
590
577
|
/**
|
|
591
578
|
* Lazily creates the delegate OTLP exporter once the API key is resolved.
|
|
579
|
+
* Recreates the delegate if the key has changed (e.g., after key rotation)
|
|
580
|
+
* so the Authorization header stays current.
|
|
592
581
|
*/
|
|
593
582
|
ensureDelegate() {
|
|
594
583
|
if (!this.createDelegateFn) return null;
|
|
595
|
-
if (this.delegate) return this.delegate;
|
|
596
584
|
const currentKey = this.getApiKey();
|
|
597
585
|
if (currentKey === API_KEY_PENDING) return null;
|
|
586
|
+
if (this.delegate && this.delegateKey === currentKey) {
|
|
587
|
+
return this.delegate;
|
|
588
|
+
}
|
|
589
|
+
if (this.delegate) {
|
|
590
|
+
void this.delegate.shutdown?.().catch(() => {
|
|
591
|
+
});
|
|
592
|
+
}
|
|
598
593
|
this.delegate = this.createDelegateFn(this.endpointUrl, {
|
|
599
594
|
Authorization: `Bearer ${currentKey}`
|
|
600
595
|
});
|
|
596
|
+
this.delegateKey = currentKey;
|
|
601
597
|
return this.delegate;
|
|
602
598
|
}
|
|
603
599
|
/**
|
|
604
|
-
* Buffers
|
|
600
|
+
* Buffers raw (unenriched) spans while the API key is pending.
|
|
605
601
|
* Evicts oldest batches if the buffer exceeds MAX_PENDING_SPANS.
|
|
602
|
+
* Re-checks the key after buffering to close the race window where
|
|
603
|
+
* the key resolves between the caller's check and this buffer call.
|
|
606
604
|
*/
|
|
607
605
|
bufferSpans(spans, resultCallback) {
|
|
608
606
|
this.pendingBatches.push({ spans, resultCallback });
|
|
@@ -618,10 +616,14 @@ var GlasstraceExporter = class {
|
|
|
618
616
|
);
|
|
619
617
|
}
|
|
620
618
|
}
|
|
619
|
+
if (this.getApiKey() !== API_KEY_PENDING) {
|
|
620
|
+
this.flushPending();
|
|
621
|
+
}
|
|
621
622
|
}
|
|
622
623
|
/**
|
|
623
624
|
* Flushes all buffered spans through the delegate exporter.
|
|
624
|
-
*
|
|
625
|
+
* Enriches spans at flush time (not buffer time) so that session IDs
|
|
626
|
+
* are computed with the resolved API key instead of the "pending" sentinel.
|
|
625
627
|
*/
|
|
626
628
|
flushPending() {
|
|
627
629
|
if (this.pendingBatches.length === 0) return;
|
|
@@ -638,7 +640,8 @@ var GlasstraceExporter = class {
|
|
|
638
640
|
this.pendingBatches = [];
|
|
639
641
|
this.pendingSpanCount = 0;
|
|
640
642
|
for (const batch of batches) {
|
|
641
|
-
|
|
643
|
+
const enriched = batch.spans.map((span) => this.enrichSpan(span));
|
|
644
|
+
exporter.export(enriched, batch.resultCallback);
|
|
642
645
|
}
|
|
643
646
|
}
|
|
644
647
|
};
|
|
@@ -762,6 +765,7 @@ function createDiscoveryHandler(getAnonKey, getSessionId) {
|
|
|
762
765
|
// src/otel-config.ts
|
|
763
766
|
var _resolvedApiKey = API_KEY_PENDING;
|
|
764
767
|
var _activeExporter = null;
|
|
768
|
+
var _shutdownHandler = null;
|
|
765
769
|
function setResolvedApiKey(key) {
|
|
766
770
|
_resolvedApiKey = key;
|
|
767
771
|
}
|
|
@@ -778,6 +782,33 @@ async function tryImport(moduleId) {
|
|
|
778
782
|
return null;
|
|
779
783
|
}
|
|
780
784
|
}
|
|
785
|
+
function registerShutdownHooks(provider) {
|
|
786
|
+
if (typeof process === "undefined" || typeof process.once !== "function") {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (_shutdownHandler) {
|
|
790
|
+
process.removeListener("SIGTERM", _shutdownHandler);
|
|
791
|
+
process.removeListener("SIGINT", _shutdownHandler);
|
|
792
|
+
}
|
|
793
|
+
let shutdownCalled = false;
|
|
794
|
+
const shutdown = (signal) => {
|
|
795
|
+
if (shutdownCalled) return;
|
|
796
|
+
shutdownCalled = true;
|
|
797
|
+
void provider.shutdown().catch((err) => {
|
|
798
|
+
console.warn(
|
|
799
|
+
`[glasstrace] Error during OTel shutdown: ${err instanceof Error ? err.message : String(err)}`
|
|
800
|
+
);
|
|
801
|
+
}).finally(() => {
|
|
802
|
+
process.removeListener("SIGTERM", _shutdownHandler);
|
|
803
|
+
process.removeListener("SIGINT", _shutdownHandler);
|
|
804
|
+
process.kill(process.pid, signal);
|
|
805
|
+
});
|
|
806
|
+
};
|
|
807
|
+
const handler = (signal) => shutdown(signal);
|
|
808
|
+
_shutdownHandler = handler;
|
|
809
|
+
process.once("SIGTERM", handler);
|
|
810
|
+
process.once("SIGINT", handler);
|
|
811
|
+
}
|
|
781
812
|
async function configureOtel(config, sessionManager) {
|
|
782
813
|
const exporterUrl = `${config.endpoint}/v1/traces`;
|
|
783
814
|
let createOtlpExporter = null;
|
|
@@ -818,7 +849,16 @@ async function configureOtel(config, sessionManager) {
|
|
|
818
849
|
}
|
|
819
850
|
try {
|
|
820
851
|
const otelSdk = await import("@opentelemetry/sdk-trace-base");
|
|
821
|
-
const
|
|
852
|
+
const otelApi3 = await import("@opentelemetry/api");
|
|
853
|
+
const existingProvider = otelApi3.trace.getTracerProvider();
|
|
854
|
+
const probeTracer = existingProvider.getTracer("glasstrace-probe");
|
|
855
|
+
if (probeTracer.constructor.name !== "ProxyTracer") {
|
|
856
|
+
console.warn(
|
|
857
|
+
"[glasstrace] An existing OpenTelemetry TracerProvider is already registered. Glasstrace will not overwrite it. To use Glasstrace alongside another tracing tool, add GlasstraceExporter as an additional span processor on your existing provider."
|
|
858
|
+
);
|
|
859
|
+
_activeExporter = null;
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
822
862
|
if (!createOtlpExporter) {
|
|
823
863
|
const consoleExporter = new otelSdk.ConsoleSpanExporter();
|
|
824
864
|
const consoleGlasstraceExporter = new GlasstraceExporter({
|
|
@@ -837,20 +877,16 @@ async function configureOtel(config, sessionManager) {
|
|
|
837
877
|
const provider2 = new otelSdk.BasicTracerProvider({
|
|
838
878
|
spanProcessors: [processor2]
|
|
839
879
|
});
|
|
840
|
-
|
|
880
|
+
otelApi3.trace.setGlobalTracerProvider(provider2);
|
|
881
|
+
registerShutdownHooks(provider2);
|
|
841
882
|
return;
|
|
842
883
|
}
|
|
843
|
-
const processor = new otelSdk.
|
|
884
|
+
const processor = new otelSdk.BatchSpanProcessor(glasstraceExporter);
|
|
844
885
|
const provider = new otelSdk.BasicTracerProvider({
|
|
845
886
|
spanProcessors: [processor]
|
|
846
887
|
});
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
console.warn(
|
|
850
|
-
"[glasstrace] An existing OpenTelemetry TracerProvider was detected and will be replaced. If you use another tracing tool, configure Glasstrace as an additional exporter instead."
|
|
851
|
-
);
|
|
852
|
-
}
|
|
853
|
-
otelApi.trace.setGlobalTracerProvider(provider);
|
|
888
|
+
otelApi3.trace.setGlobalTracerProvider(provider);
|
|
889
|
+
registerShutdownHooks(provider);
|
|
854
890
|
} catch {
|
|
855
891
|
console.warn(
|
|
856
892
|
"[glasstrace] Neither @vercel/otel nor @opentelemetry/sdk-trace-base available. Tracing disabled."
|
|
@@ -858,7 +894,108 @@ async function configureOtel(config, sessionManager) {
|
|
|
858
894
|
}
|
|
859
895
|
}
|
|
860
896
|
|
|
897
|
+
// src/console-capture.ts
|
|
898
|
+
var isGlasstraceLog = false;
|
|
899
|
+
var originalError = null;
|
|
900
|
+
var originalWarn = null;
|
|
901
|
+
var installed = false;
|
|
902
|
+
var otelApi = null;
|
|
903
|
+
function formatArgs(args) {
|
|
904
|
+
return args.map((arg) => {
|
|
905
|
+
if (typeof arg === "string") return arg;
|
|
906
|
+
if (arg instanceof Error) return arg.stack ?? arg.message;
|
|
907
|
+
try {
|
|
908
|
+
return JSON.stringify(arg);
|
|
909
|
+
} catch {
|
|
910
|
+
return String(arg);
|
|
911
|
+
}
|
|
912
|
+
}).join(" ");
|
|
913
|
+
}
|
|
914
|
+
function isSdkMessage(args) {
|
|
915
|
+
return typeof args[0] === "string" && args[0].startsWith("[glasstrace]");
|
|
916
|
+
}
|
|
917
|
+
async function installConsoleCapture() {
|
|
918
|
+
if (installed) return;
|
|
919
|
+
try {
|
|
920
|
+
otelApi = await import("@opentelemetry/api");
|
|
921
|
+
} catch {
|
|
922
|
+
otelApi = null;
|
|
923
|
+
}
|
|
924
|
+
originalError = console.error;
|
|
925
|
+
originalWarn = console.warn;
|
|
926
|
+
installed = true;
|
|
927
|
+
console.error = (...args) => {
|
|
928
|
+
originalError.apply(console, args);
|
|
929
|
+
if (isGlasstraceLog || isSdkMessage(args)) return;
|
|
930
|
+
if (otelApi) {
|
|
931
|
+
const span = otelApi.trace.getSpan(otelApi.context.active());
|
|
932
|
+
if (span) {
|
|
933
|
+
span.addEvent("console.error", {
|
|
934
|
+
"console.message": formatArgs(args)
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
console.warn = (...args) => {
|
|
940
|
+
originalWarn.apply(console, args);
|
|
941
|
+
if (isGlasstraceLog || isSdkMessage(args)) return;
|
|
942
|
+
if (otelApi) {
|
|
943
|
+
const span = otelApi.trace.getSpan(otelApi.context.active());
|
|
944
|
+
if (span) {
|
|
945
|
+
span.addEvent("console.warn", {
|
|
946
|
+
"console.message": formatArgs(args)
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/capture-error.ts
|
|
954
|
+
var otelApi2 = null;
|
|
955
|
+
var otelLoadAttempted = false;
|
|
956
|
+
var otelLoadPromise = null;
|
|
957
|
+
async function _preloadOtelApi() {
|
|
958
|
+
if (otelLoadAttempted) return;
|
|
959
|
+
otelLoadAttempted = true;
|
|
960
|
+
try {
|
|
961
|
+
otelApi2 = await import("@opentelemetry/api");
|
|
962
|
+
} catch {
|
|
963
|
+
otelApi2 = null;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function captureError(error) {
|
|
967
|
+
if (otelApi2) {
|
|
968
|
+
recordError(otelApi2, error);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
if (!otelLoadAttempted) {
|
|
972
|
+
otelLoadPromise ??= _preloadOtelApi();
|
|
973
|
+
}
|
|
974
|
+
if (otelLoadPromise) {
|
|
975
|
+
void otelLoadPromise.then(() => {
|
|
976
|
+
if (otelApi2) {
|
|
977
|
+
recordError(otelApi2, error);
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
function recordError(api, error) {
|
|
983
|
+
try {
|
|
984
|
+
const span = api.trace.getSpan(api.context.active());
|
|
985
|
+
if (!span) return;
|
|
986
|
+
const attributes = {
|
|
987
|
+
"error.message": String(error)
|
|
988
|
+
};
|
|
989
|
+
if (error instanceof Error) {
|
|
990
|
+
attributes["error.type"] = error.constructor.name;
|
|
991
|
+
}
|
|
992
|
+
span.addEvent("glasstrace.error", attributes);
|
|
993
|
+
} catch {
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
861
997
|
// src/register.ts
|
|
998
|
+
var consoleCaptureInstalled = false;
|
|
862
999
|
var discoveryHandler = null;
|
|
863
1000
|
var isRegistered = false;
|
|
864
1001
|
var registrationGeneration = 0;
|
|
@@ -869,7 +1006,7 @@ function registerGlasstrace(options) {
|
|
|
869
1006
|
}
|
|
870
1007
|
const config = resolveConfig(options);
|
|
871
1008
|
if (config.verbose) {
|
|
872
|
-
console.info("[glasstrace]
|
|
1009
|
+
console.info("[glasstrace] Config resolved.");
|
|
873
1010
|
}
|
|
874
1011
|
if (isProductionDisabled(config)) {
|
|
875
1012
|
console.warn(
|
|
@@ -878,7 +1015,7 @@ function registerGlasstrace(options) {
|
|
|
878
1015
|
return;
|
|
879
1016
|
}
|
|
880
1017
|
if (config.verbose) {
|
|
881
|
-
console.info("[glasstrace]
|
|
1018
|
+
console.info("[glasstrace] Not production-disabled.");
|
|
882
1019
|
}
|
|
883
1020
|
const anonymous = isAnonymousMode(config);
|
|
884
1021
|
let effectiveKey = config.apiKey;
|
|
@@ -887,7 +1024,7 @@ function registerGlasstrace(options) {
|
|
|
887
1024
|
}
|
|
888
1025
|
if (config.verbose) {
|
|
889
1026
|
console.info(
|
|
890
|
-
`[glasstrace]
|
|
1027
|
+
`[glasstrace] Auth mode = ${anonymous ? "anonymous" : "dev-key"}.`
|
|
891
1028
|
);
|
|
892
1029
|
}
|
|
893
1030
|
const cachedInitResponse = loadCachedConfig();
|
|
@@ -896,19 +1033,21 @@ function registerGlasstrace(options) {
|
|
|
896
1033
|
}
|
|
897
1034
|
if (config.verbose) {
|
|
898
1035
|
console.info(
|
|
899
|
-
`[glasstrace]
|
|
1036
|
+
`[glasstrace] Cached config ${cachedInitResponse ? "loaded and applied" : "not found"}.`
|
|
900
1037
|
);
|
|
901
1038
|
}
|
|
902
1039
|
const sessionManager = new SessionManager();
|
|
903
1040
|
if (config.verbose) {
|
|
904
|
-
console.info("[glasstrace]
|
|
1041
|
+
console.info("[glasstrace] SessionManager created.");
|
|
905
1042
|
}
|
|
906
1043
|
isRegistered = true;
|
|
907
1044
|
const currentGeneration = registrationGeneration;
|
|
908
1045
|
void configureOtel(config, sessionManager).then(
|
|
909
1046
|
() => {
|
|
1047
|
+
void _preloadOtelApi();
|
|
1048
|
+
maybeInstallConsoleCapture();
|
|
910
1049
|
if (config.verbose) {
|
|
911
|
-
console.info("[glasstrace]
|
|
1050
|
+
console.info("[glasstrace] OTel configured.");
|
|
912
1051
|
}
|
|
913
1052
|
},
|
|
914
1053
|
(err) => {
|
|
@@ -926,7 +1065,7 @@ function registerGlasstrace(options) {
|
|
|
926
1065
|
() => sessionManager.getSessionId(getResolvedApiKey())
|
|
927
1066
|
);
|
|
928
1067
|
if (config.verbose) {
|
|
929
|
-
console.info("[glasstrace]
|
|
1068
|
+
console.info("[glasstrace] Discovery endpoint registered (key pending).");
|
|
930
1069
|
}
|
|
931
1070
|
void (async () => {
|
|
932
1071
|
try {
|
|
@@ -942,9 +1081,10 @@ function registerGlasstrace(options) {
|
|
|
942
1081
|
() => sessionManager.getSessionId(getResolvedApiKey())
|
|
943
1082
|
);
|
|
944
1083
|
if (config.verbose) {
|
|
945
|
-
console.info("[glasstrace]
|
|
1084
|
+
console.info("[glasstrace] Background init firing.");
|
|
946
1085
|
}
|
|
947
|
-
await performInit(config, anonKey, "0.1
|
|
1086
|
+
await performInit(config, anonKey, "0.2.1");
|
|
1087
|
+
maybeInstallConsoleCapture();
|
|
948
1088
|
} catch (err) {
|
|
949
1089
|
console.warn(
|
|
950
1090
|
`[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -961,9 +1101,10 @@ function registerGlasstrace(options) {
|
|
|
961
1101
|
effectiveKey = anonKey;
|
|
962
1102
|
if (currentGeneration !== registrationGeneration) return;
|
|
963
1103
|
if (config.verbose) {
|
|
964
|
-
console.info("[glasstrace]
|
|
1104
|
+
console.info("[glasstrace] Background init firing.");
|
|
965
1105
|
}
|
|
966
|
-
await performInit(config, anonKey, "0.1
|
|
1106
|
+
await performInit(config, anonKey, "0.2.1");
|
|
1107
|
+
maybeInstallConsoleCapture();
|
|
967
1108
|
} catch (err) {
|
|
968
1109
|
console.warn(
|
|
969
1110
|
`[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -982,9 +1123,10 @@ function registerGlasstrace(options) {
|
|
|
982
1123
|
}
|
|
983
1124
|
if (currentGeneration !== registrationGeneration) return;
|
|
984
1125
|
if (config.verbose) {
|
|
985
|
-
console.info("[glasstrace]
|
|
1126
|
+
console.info("[glasstrace] Background init firing.");
|
|
986
1127
|
}
|
|
987
|
-
await performInit(config, anonKeyForInit, "0.1
|
|
1128
|
+
await performInit(config, anonKeyForInit, "0.2.1");
|
|
1129
|
+
maybeInstallConsoleCapture();
|
|
988
1130
|
} catch (err) {
|
|
989
1131
|
console.warn(
|
|
990
1132
|
`[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -993,7 +1135,7 @@ function registerGlasstrace(options) {
|
|
|
993
1135
|
})();
|
|
994
1136
|
}
|
|
995
1137
|
if (config.coverageMapEnabled && config.verbose) {
|
|
996
|
-
console.info("[glasstrace]
|
|
1138
|
+
console.info("[glasstrace] Import graph building skipped.");
|
|
997
1139
|
}
|
|
998
1140
|
} catch (err) {
|
|
999
1141
|
console.warn(
|
|
@@ -1004,6 +1146,13 @@ function registerGlasstrace(options) {
|
|
|
1004
1146
|
function getDiscoveryHandler() {
|
|
1005
1147
|
return discoveryHandler;
|
|
1006
1148
|
}
|
|
1149
|
+
function maybeInstallConsoleCapture() {
|
|
1150
|
+
if (consoleCaptureInstalled) return;
|
|
1151
|
+
if (getActiveConfig().consoleErrors) {
|
|
1152
|
+
consoleCaptureInstalled = true;
|
|
1153
|
+
void installConsoleCapture();
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1007
1156
|
function isDiscoveryEnabled(config) {
|
|
1008
1157
|
if (process.env.GLASSTRACE_DISCOVERY_ENABLED === "true") return true;
|
|
1009
1158
|
if (process.env.GLASSTRACE_DISCOVERY_ENABLED === "false") return false;
|
|
@@ -1018,9 +1167,6 @@ import * as fs from "fs/promises";
|
|
|
1018
1167
|
import * as path from "path";
|
|
1019
1168
|
import * as crypto from "crypto";
|
|
1020
1169
|
import { execSync } from "child_process";
|
|
1021
|
-
import {
|
|
1022
|
-
SourceMapUploadResponseSchema
|
|
1023
|
-
} from "@glasstrace/protocol";
|
|
1024
1170
|
async function collectSourceMaps(buildDir) {
|
|
1025
1171
|
const results = [];
|
|
1026
1172
|
try {
|
|
@@ -1088,6 +1234,10 @@ async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
|
|
|
1088
1234
|
body: JSON.stringify(body)
|
|
1089
1235
|
});
|
|
1090
1236
|
if (!response.ok) {
|
|
1237
|
+
try {
|
|
1238
|
+
await response.text();
|
|
1239
|
+
} catch {
|
|
1240
|
+
}
|
|
1091
1241
|
throw new Error(
|
|
1092
1242
|
`Source map upload failed: ${String(response.status)} ${response.statusText}`
|
|
1093
1243
|
);
|
|
@@ -1163,6 +1313,7 @@ export {
|
|
|
1163
1313
|
SdkError,
|
|
1164
1314
|
SessionManager,
|
|
1165
1315
|
buildImportGraph,
|
|
1316
|
+
captureError,
|
|
1166
1317
|
classifyFetchTarget,
|
|
1167
1318
|
collectSourceMaps,
|
|
1168
1319
|
computeBuildHash,
|