@glasstrace/sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +253 -99
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -5
- package/dist/index.d.ts +37 -5
- package/dist/index.js +252 -99
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(src_exports, {
|
|
|
35
35
|
SdkError: () => SdkError,
|
|
36
36
|
SessionManager: () => SessionManager,
|
|
37
37
|
buildImportGraph: () => buildImportGraph,
|
|
38
|
+
captureError: () => captureError,
|
|
38
39
|
classifyFetchTarget: () => classifyFetchTarget,
|
|
39
40
|
collectSourceMaps: () => collectSourceMaps,
|
|
40
41
|
computeBuildHash: () => computeBuildHash,
|
|
@@ -126,17 +127,18 @@ function isAnonymousMode(config) {
|
|
|
126
127
|
var import_node_crypto = require("crypto");
|
|
127
128
|
var import_protocol = require("@glasstrace/protocol");
|
|
128
129
|
var FOUR_HOURS_MS = 4 * 60 * 60 * 1e3;
|
|
130
|
+
var cachedGlasstraceEnv = process.env.GLASSTRACE_ENV;
|
|
131
|
+
var cachedPort = process.env.PORT ?? "3000";
|
|
129
132
|
function deriveSessionId(apiKey, origin, date, windowIndex) {
|
|
130
133
|
const input = JSON.stringify([apiKey, origin, date, windowIndex]);
|
|
131
134
|
const hash = (0, import_node_crypto.createHash)("sha256").update(input).digest("hex").slice(0, 16);
|
|
132
135
|
return import_protocol.SessionIdSchema.parse(hash);
|
|
133
136
|
}
|
|
134
137
|
function getOrigin() {
|
|
135
|
-
if (
|
|
136
|
-
return
|
|
138
|
+
if (cachedGlasstraceEnv) {
|
|
139
|
+
return cachedGlasstraceEnv;
|
|
137
140
|
}
|
|
138
|
-
|
|
139
|
-
return `localhost:${port}`;
|
|
141
|
+
return `localhost:${cachedPort}`;
|
|
140
142
|
}
|
|
141
143
|
function getDateString() {
|
|
142
144
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -190,6 +192,7 @@ var SessionManager = class {
|
|
|
190
192
|
};
|
|
191
193
|
|
|
192
194
|
// src/fetch-classifier.ts
|
|
195
|
+
var cachedPort2 = process.env.PORT ?? "3000";
|
|
193
196
|
function classifyFetchTarget(url) {
|
|
194
197
|
let parsed;
|
|
195
198
|
try {
|
|
@@ -204,8 +207,7 @@ function classifyFetchTarget(url) {
|
|
|
204
207
|
if (hostname === "stripe.com" || hostname.endsWith(".stripe.com")) {
|
|
205
208
|
return "stripe";
|
|
206
209
|
}
|
|
207
|
-
const
|
|
208
|
-
const internalOrigin = `localhost:${port}`;
|
|
210
|
+
const internalOrigin = `localhost:${cachedPort2}`;
|
|
209
211
|
const parsedPort = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
|
|
210
212
|
const urlOrigin = `${hostname}:${parsedPort}`;
|
|
211
213
|
if (urlOrigin === internalOrigin) {
|
|
@@ -350,6 +352,10 @@ async function sendInitRequest(config, anonKey, sdkVersion, importGraph, healthR
|
|
|
350
352
|
signal
|
|
351
353
|
});
|
|
352
354
|
if (!response.ok) {
|
|
355
|
+
try {
|
|
356
|
+
await response.text();
|
|
357
|
+
} catch {
|
|
358
|
+
}
|
|
353
359
|
const error = new Error(`Init request failed with status ${response.status}`);
|
|
354
360
|
error.status = response.status;
|
|
355
361
|
throw error;
|
|
@@ -472,6 +478,7 @@ var GlasstraceExporter = class {
|
|
|
472
478
|
endpointUrl;
|
|
473
479
|
createDelegateFn;
|
|
474
480
|
delegate = null;
|
|
481
|
+
delegateKey = null;
|
|
475
482
|
pendingBatches = [];
|
|
476
483
|
pendingSpanCount = 0;
|
|
477
484
|
overflowLogged = false;
|
|
@@ -484,12 +491,12 @@ var GlasstraceExporter = class {
|
|
|
484
491
|
this.createDelegateFn = options.createDelegate;
|
|
485
492
|
}
|
|
486
493
|
export(spans, resultCallback) {
|
|
487
|
-
const enrichedSpans = spans.map((span) => this.enrichSpan(span));
|
|
488
494
|
const currentKey = this.getApiKey();
|
|
489
495
|
if (currentKey === API_KEY_PENDING) {
|
|
490
|
-
this.bufferSpans(
|
|
496
|
+
this.bufferSpans(spans, resultCallback);
|
|
491
497
|
return;
|
|
492
498
|
}
|
|
499
|
+
const enrichedSpans = spans.map((span) => this.enrichSpan(span));
|
|
493
500
|
const exporter = this.ensureDelegate();
|
|
494
501
|
if (exporter) {
|
|
495
502
|
exporter.export(enrichedSpans, resultCallback);
|
|
@@ -531,58 +538,43 @@ var GlasstraceExporter = class {
|
|
|
531
538
|
/**
|
|
532
539
|
* Enriches a ReadableSpan with all glasstrace.* attributes.
|
|
533
540
|
* Returns a new ReadableSpan wrapper; the original span is not mutated.
|
|
534
|
-
*
|
|
535
|
-
*
|
|
541
|
+
*
|
|
542
|
+
* External function calls (getSessionId, deriveErrorCategory,
|
|
543
|
+
* deriveOrmProvider, classifyFetchTarget) are individually guarded so a
|
|
544
|
+
* failure in one does not prevent the remaining attributes from being set.
|
|
545
|
+
* On total failure, returns the original span unchanged.
|
|
536
546
|
*/
|
|
537
547
|
enrichSpan(span) {
|
|
538
|
-
const attrs = span.attributes ?? {};
|
|
539
|
-
const name = span.name ?? "";
|
|
540
|
-
const extra = {};
|
|
541
548
|
try {
|
|
549
|
+
const attrs = span.attributes ?? {};
|
|
550
|
+
const name = span.name ?? "";
|
|
551
|
+
const extra = {};
|
|
542
552
|
extra[ATTR.TRACE_TYPE] = "server";
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
} catch {
|
|
549
|
-
}
|
|
550
|
-
try {
|
|
553
|
+
try {
|
|
554
|
+
const sessionId = this.sessionManager.getSessionId(this.getApiKey());
|
|
555
|
+
extra[ATTR.SESSION_ID] = sessionId;
|
|
556
|
+
} catch {
|
|
557
|
+
}
|
|
551
558
|
const env = this.environment ?? process.env.GLASSTRACE_ENV;
|
|
552
559
|
if (env) {
|
|
553
560
|
extra[ATTR.ENVIRONMENT] = env;
|
|
554
561
|
}
|
|
555
|
-
} catch {
|
|
556
|
-
}
|
|
557
|
-
try {
|
|
558
562
|
const existingCid = attrs["glasstrace.correlation.id"];
|
|
559
563
|
if (typeof existingCid === "string") {
|
|
560
564
|
extra[ATTR.CORRELATION_ID] = existingCid;
|
|
561
565
|
}
|
|
562
|
-
} catch {
|
|
563
|
-
}
|
|
564
|
-
try {
|
|
565
566
|
const route = attrs["http.route"] ?? name;
|
|
566
567
|
if (route) {
|
|
567
568
|
extra[ATTR.ROUTE] = route;
|
|
568
569
|
}
|
|
569
|
-
} catch {
|
|
570
|
-
}
|
|
571
|
-
try {
|
|
572
570
|
const method = attrs["http.method"] ?? attrs["http.request.method"];
|
|
573
571
|
if (method) {
|
|
574
572
|
extra[ATTR.HTTP_METHOD] = method;
|
|
575
573
|
}
|
|
576
|
-
} catch {
|
|
577
|
-
}
|
|
578
|
-
try {
|
|
579
574
|
const statusCode = attrs["http.status_code"] ?? attrs["http.response.status_code"];
|
|
580
575
|
if (statusCode !== void 0) {
|
|
581
576
|
extra[ATTR.HTTP_STATUS_CODE] = statusCode;
|
|
582
577
|
}
|
|
583
|
-
} catch {
|
|
584
|
-
}
|
|
585
|
-
try {
|
|
586
578
|
if (span.startTime && span.endTime) {
|
|
587
579
|
const [startSec, startNano] = span.startTime;
|
|
588
580
|
const [endSec, endNano] = span.endTime;
|
|
@@ -591,72 +583,78 @@ var GlasstraceExporter = class {
|
|
|
591
583
|
extra[ATTR.HTTP_DURATION_MS] = durationMs;
|
|
592
584
|
}
|
|
593
585
|
}
|
|
594
|
-
} catch {
|
|
595
|
-
}
|
|
596
|
-
try {
|
|
597
586
|
const errorMessage = attrs["exception.message"];
|
|
598
587
|
if (errorMessage) {
|
|
599
588
|
extra[ATTR.ERROR_MESSAGE] = errorMessage;
|
|
600
589
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
590
|
+
try {
|
|
591
|
+
const errorType = attrs["exception.type"];
|
|
592
|
+
if (errorType) {
|
|
593
|
+
extra[ATTR.ERROR_CODE] = errorType;
|
|
594
|
+
extra[ATTR.ERROR_CATEGORY] = deriveErrorCategory(errorType);
|
|
595
|
+
}
|
|
596
|
+
} catch {
|
|
608
597
|
}
|
|
609
|
-
} catch {
|
|
610
|
-
}
|
|
611
|
-
try {
|
|
612
598
|
const errorField = attrs["error.field"];
|
|
613
599
|
if (errorField) {
|
|
614
600
|
extra[ATTR.ERROR_FIELD] = errorField;
|
|
615
601
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
extra[ATTR.ORM_OPERATION] = operation;
|
|
602
|
+
try {
|
|
603
|
+
const spanAny = span;
|
|
604
|
+
const instrumentationName = spanAny.instrumentationScope?.name ?? spanAny.instrumentationLibrary?.name ?? "";
|
|
605
|
+
const ormProvider = deriveOrmProvider(instrumentationName);
|
|
606
|
+
if (ormProvider) {
|
|
607
|
+
extra[ATTR.ORM_PROVIDER] = ormProvider;
|
|
608
|
+
const model = attrs["db.sql.table"] ?? attrs["db.prisma.model"];
|
|
609
|
+
if (model) {
|
|
610
|
+
extra[ATTR.ORM_MODEL] = model;
|
|
611
|
+
}
|
|
612
|
+
const operation = attrs["db.operation"];
|
|
613
|
+
if (operation) {
|
|
614
|
+
extra[ATTR.ORM_OPERATION] = operation;
|
|
615
|
+
}
|
|
631
616
|
}
|
|
617
|
+
} catch {
|
|
632
618
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
619
|
+
try {
|
|
620
|
+
const url = attrs["http.url"] ?? attrs["url.full"];
|
|
621
|
+
if (url && span.kind === import_api.SpanKind.CLIENT) {
|
|
622
|
+
extra[ATTR.FETCH_TARGET] = classifyFetchTarget(url);
|
|
623
|
+
}
|
|
624
|
+
} catch {
|
|
639
625
|
}
|
|
626
|
+
return createEnrichedSpan(span, extra);
|
|
640
627
|
} catch {
|
|
628
|
+
return span;
|
|
641
629
|
}
|
|
642
|
-
return createEnrichedSpan(span, extra);
|
|
643
630
|
}
|
|
644
631
|
/**
|
|
645
632
|
* Lazily creates the delegate OTLP exporter once the API key is resolved.
|
|
633
|
+
* Recreates the delegate if the key has changed (e.g., after key rotation)
|
|
634
|
+
* so the Authorization header stays current.
|
|
646
635
|
*/
|
|
647
636
|
ensureDelegate() {
|
|
648
637
|
if (!this.createDelegateFn) return null;
|
|
649
|
-
if (this.delegate) return this.delegate;
|
|
650
638
|
const currentKey = this.getApiKey();
|
|
651
639
|
if (currentKey === API_KEY_PENDING) return null;
|
|
640
|
+
if (this.delegate && this.delegateKey === currentKey) {
|
|
641
|
+
return this.delegate;
|
|
642
|
+
}
|
|
643
|
+
if (this.delegate) {
|
|
644
|
+
void this.delegate.shutdown?.().catch(() => {
|
|
645
|
+
});
|
|
646
|
+
}
|
|
652
647
|
this.delegate = this.createDelegateFn(this.endpointUrl, {
|
|
653
648
|
Authorization: `Bearer ${currentKey}`
|
|
654
649
|
});
|
|
650
|
+
this.delegateKey = currentKey;
|
|
655
651
|
return this.delegate;
|
|
656
652
|
}
|
|
657
653
|
/**
|
|
658
|
-
* Buffers
|
|
654
|
+
* Buffers raw (unenriched) spans while the API key is pending.
|
|
659
655
|
* Evicts oldest batches if the buffer exceeds MAX_PENDING_SPANS.
|
|
656
|
+
* Re-checks the key after buffering to close the race window where
|
|
657
|
+
* the key resolves between the caller's check and this buffer call.
|
|
660
658
|
*/
|
|
661
659
|
bufferSpans(spans, resultCallback) {
|
|
662
660
|
this.pendingBatches.push({ spans, resultCallback });
|
|
@@ -672,10 +670,14 @@ var GlasstraceExporter = class {
|
|
|
672
670
|
);
|
|
673
671
|
}
|
|
674
672
|
}
|
|
673
|
+
if (this.getApiKey() !== API_KEY_PENDING) {
|
|
674
|
+
this.flushPending();
|
|
675
|
+
}
|
|
675
676
|
}
|
|
676
677
|
/**
|
|
677
678
|
* Flushes all buffered spans through the delegate exporter.
|
|
678
|
-
*
|
|
679
|
+
* Enriches spans at flush time (not buffer time) so that session IDs
|
|
680
|
+
* are computed with the resolved API key instead of the "pending" sentinel.
|
|
679
681
|
*/
|
|
680
682
|
flushPending() {
|
|
681
683
|
if (this.pendingBatches.length === 0) return;
|
|
@@ -692,7 +694,8 @@ var GlasstraceExporter = class {
|
|
|
692
694
|
this.pendingBatches = [];
|
|
693
695
|
this.pendingSpanCount = 0;
|
|
694
696
|
for (const batch of batches) {
|
|
695
|
-
|
|
697
|
+
const enriched = batch.spans.map((span) => this.enrichSpan(span));
|
|
698
|
+
exporter.export(enriched, batch.resultCallback);
|
|
696
699
|
}
|
|
697
700
|
}
|
|
698
701
|
};
|
|
@@ -816,6 +819,7 @@ function createDiscoveryHandler(getAnonKey, getSessionId) {
|
|
|
816
819
|
// src/otel-config.ts
|
|
817
820
|
var _resolvedApiKey = API_KEY_PENDING;
|
|
818
821
|
var _activeExporter = null;
|
|
822
|
+
var _shutdownHandler = null;
|
|
819
823
|
function setResolvedApiKey(key) {
|
|
820
824
|
_resolvedApiKey = key;
|
|
821
825
|
}
|
|
@@ -832,6 +836,33 @@ async function tryImport(moduleId) {
|
|
|
832
836
|
return null;
|
|
833
837
|
}
|
|
834
838
|
}
|
|
839
|
+
function registerShutdownHooks(provider) {
|
|
840
|
+
if (typeof process === "undefined" || typeof process.once !== "function") {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (_shutdownHandler) {
|
|
844
|
+
process.removeListener("SIGTERM", _shutdownHandler);
|
|
845
|
+
process.removeListener("SIGINT", _shutdownHandler);
|
|
846
|
+
}
|
|
847
|
+
let shutdownCalled = false;
|
|
848
|
+
const shutdown = (signal) => {
|
|
849
|
+
if (shutdownCalled) return;
|
|
850
|
+
shutdownCalled = true;
|
|
851
|
+
void provider.shutdown().catch((err) => {
|
|
852
|
+
console.warn(
|
|
853
|
+
`[glasstrace] Error during OTel shutdown: ${err instanceof Error ? err.message : String(err)}`
|
|
854
|
+
);
|
|
855
|
+
}).finally(() => {
|
|
856
|
+
process.removeListener("SIGTERM", _shutdownHandler);
|
|
857
|
+
process.removeListener("SIGINT", _shutdownHandler);
|
|
858
|
+
process.kill(process.pid, signal);
|
|
859
|
+
});
|
|
860
|
+
};
|
|
861
|
+
const handler = (signal) => shutdown(signal);
|
|
862
|
+
_shutdownHandler = handler;
|
|
863
|
+
process.once("SIGTERM", handler);
|
|
864
|
+
process.once("SIGINT", handler);
|
|
865
|
+
}
|
|
835
866
|
async function configureOtel(config, sessionManager) {
|
|
836
867
|
const exporterUrl = `${config.endpoint}/v1/traces`;
|
|
837
868
|
let createOtlpExporter = null;
|
|
@@ -872,7 +903,16 @@ async function configureOtel(config, sessionManager) {
|
|
|
872
903
|
}
|
|
873
904
|
try {
|
|
874
905
|
const otelSdk = await import("@opentelemetry/sdk-trace-base");
|
|
875
|
-
const
|
|
906
|
+
const otelApi3 = await import("@opentelemetry/api");
|
|
907
|
+
const existingProvider = otelApi3.trace.getTracerProvider();
|
|
908
|
+
const probeTracer = existingProvider.getTracer("glasstrace-probe");
|
|
909
|
+
if (probeTracer.constructor.name !== "ProxyTracer") {
|
|
910
|
+
console.warn(
|
|
911
|
+
"[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."
|
|
912
|
+
);
|
|
913
|
+
_activeExporter = null;
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
876
916
|
if (!createOtlpExporter) {
|
|
877
917
|
const consoleExporter = new otelSdk.ConsoleSpanExporter();
|
|
878
918
|
const consoleGlasstraceExporter = new GlasstraceExporter({
|
|
@@ -891,20 +931,16 @@ async function configureOtel(config, sessionManager) {
|
|
|
891
931
|
const provider2 = new otelSdk.BasicTracerProvider({
|
|
892
932
|
spanProcessors: [processor2]
|
|
893
933
|
});
|
|
894
|
-
|
|
934
|
+
otelApi3.trace.setGlobalTracerProvider(provider2);
|
|
935
|
+
registerShutdownHooks(provider2);
|
|
895
936
|
return;
|
|
896
937
|
}
|
|
897
|
-
const processor = new otelSdk.
|
|
938
|
+
const processor = new otelSdk.BatchSpanProcessor(glasstraceExporter);
|
|
898
939
|
const provider = new otelSdk.BasicTracerProvider({
|
|
899
940
|
spanProcessors: [processor]
|
|
900
941
|
});
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
console.warn(
|
|
904
|
-
"[glasstrace] An existing OpenTelemetry TracerProvider was detected and will be replaced. If you use another tracing tool, configure Glasstrace as an additional exporter instead."
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
otelApi.trace.setGlobalTracerProvider(provider);
|
|
942
|
+
otelApi3.trace.setGlobalTracerProvider(provider);
|
|
943
|
+
registerShutdownHooks(provider);
|
|
908
944
|
} catch {
|
|
909
945
|
console.warn(
|
|
910
946
|
"[glasstrace] Neither @vercel/otel nor @opentelemetry/sdk-trace-base available. Tracing disabled."
|
|
@@ -912,7 +948,108 @@ async function configureOtel(config, sessionManager) {
|
|
|
912
948
|
}
|
|
913
949
|
}
|
|
914
950
|
|
|
951
|
+
// src/console-capture.ts
|
|
952
|
+
var isGlasstraceLog = false;
|
|
953
|
+
var originalError = null;
|
|
954
|
+
var originalWarn = null;
|
|
955
|
+
var installed = false;
|
|
956
|
+
var otelApi = null;
|
|
957
|
+
function formatArgs(args) {
|
|
958
|
+
return args.map((arg) => {
|
|
959
|
+
if (typeof arg === "string") return arg;
|
|
960
|
+
if (arg instanceof Error) return arg.stack ?? arg.message;
|
|
961
|
+
try {
|
|
962
|
+
return JSON.stringify(arg);
|
|
963
|
+
} catch {
|
|
964
|
+
return String(arg);
|
|
965
|
+
}
|
|
966
|
+
}).join(" ");
|
|
967
|
+
}
|
|
968
|
+
function isSdkMessage(args) {
|
|
969
|
+
return typeof args[0] === "string" && args[0].startsWith("[glasstrace]");
|
|
970
|
+
}
|
|
971
|
+
async function installConsoleCapture() {
|
|
972
|
+
if (installed) return;
|
|
973
|
+
try {
|
|
974
|
+
otelApi = await import("@opentelemetry/api");
|
|
975
|
+
} catch {
|
|
976
|
+
otelApi = null;
|
|
977
|
+
}
|
|
978
|
+
originalError = console.error;
|
|
979
|
+
originalWarn = console.warn;
|
|
980
|
+
installed = true;
|
|
981
|
+
console.error = (...args) => {
|
|
982
|
+
originalError.apply(console, args);
|
|
983
|
+
if (isGlasstraceLog || isSdkMessage(args)) return;
|
|
984
|
+
if (otelApi) {
|
|
985
|
+
const span = otelApi.trace.getSpan(otelApi.context.active());
|
|
986
|
+
if (span) {
|
|
987
|
+
span.addEvent("console.error", {
|
|
988
|
+
"console.message": formatArgs(args)
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
console.warn = (...args) => {
|
|
994
|
+
originalWarn.apply(console, args);
|
|
995
|
+
if (isGlasstraceLog || isSdkMessage(args)) return;
|
|
996
|
+
if (otelApi) {
|
|
997
|
+
const span = otelApi.trace.getSpan(otelApi.context.active());
|
|
998
|
+
if (span) {
|
|
999
|
+
span.addEvent("console.warn", {
|
|
1000
|
+
"console.message": formatArgs(args)
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/capture-error.ts
|
|
1008
|
+
var otelApi2 = null;
|
|
1009
|
+
var otelLoadAttempted = false;
|
|
1010
|
+
var otelLoadPromise = null;
|
|
1011
|
+
async function _preloadOtelApi() {
|
|
1012
|
+
if (otelLoadAttempted) return;
|
|
1013
|
+
otelLoadAttempted = true;
|
|
1014
|
+
try {
|
|
1015
|
+
otelApi2 = await import("@opentelemetry/api");
|
|
1016
|
+
} catch {
|
|
1017
|
+
otelApi2 = null;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
function captureError(error) {
|
|
1021
|
+
if (otelApi2) {
|
|
1022
|
+
recordError(otelApi2, error);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (!otelLoadAttempted) {
|
|
1026
|
+
otelLoadPromise ??= _preloadOtelApi();
|
|
1027
|
+
}
|
|
1028
|
+
if (otelLoadPromise) {
|
|
1029
|
+
void otelLoadPromise.then(() => {
|
|
1030
|
+
if (otelApi2) {
|
|
1031
|
+
recordError(otelApi2, error);
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
function recordError(api, error) {
|
|
1037
|
+
try {
|
|
1038
|
+
const span = api.trace.getSpan(api.context.active());
|
|
1039
|
+
if (!span) return;
|
|
1040
|
+
const attributes = {
|
|
1041
|
+
"error.message": String(error)
|
|
1042
|
+
};
|
|
1043
|
+
if (error instanceof Error) {
|
|
1044
|
+
attributes["error.type"] = error.constructor.name;
|
|
1045
|
+
}
|
|
1046
|
+
span.addEvent("glasstrace.error", attributes);
|
|
1047
|
+
} catch {
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
915
1051
|
// src/register.ts
|
|
1052
|
+
var consoleCaptureInstalled = false;
|
|
916
1053
|
var discoveryHandler = null;
|
|
917
1054
|
var isRegistered = false;
|
|
918
1055
|
var registrationGeneration = 0;
|
|
@@ -923,7 +1060,7 @@ function registerGlasstrace(options) {
|
|
|
923
1060
|
}
|
|
924
1061
|
const config = resolveConfig(options);
|
|
925
1062
|
if (config.verbose) {
|
|
926
|
-
console.info("[glasstrace]
|
|
1063
|
+
console.info("[glasstrace] Config resolved.");
|
|
927
1064
|
}
|
|
928
1065
|
if (isProductionDisabled(config)) {
|
|
929
1066
|
console.warn(
|
|
@@ -932,7 +1069,7 @@ function registerGlasstrace(options) {
|
|
|
932
1069
|
return;
|
|
933
1070
|
}
|
|
934
1071
|
if (config.verbose) {
|
|
935
|
-
console.info("[glasstrace]
|
|
1072
|
+
console.info("[glasstrace] Not production-disabled.");
|
|
936
1073
|
}
|
|
937
1074
|
const anonymous = isAnonymousMode(config);
|
|
938
1075
|
let effectiveKey = config.apiKey;
|
|
@@ -941,7 +1078,7 @@ function registerGlasstrace(options) {
|
|
|
941
1078
|
}
|
|
942
1079
|
if (config.verbose) {
|
|
943
1080
|
console.info(
|
|
944
|
-
`[glasstrace]
|
|
1081
|
+
`[glasstrace] Auth mode = ${anonymous ? "anonymous" : "dev-key"}.`
|
|
945
1082
|
);
|
|
946
1083
|
}
|
|
947
1084
|
const cachedInitResponse = loadCachedConfig();
|
|
@@ -950,19 +1087,21 @@ function registerGlasstrace(options) {
|
|
|
950
1087
|
}
|
|
951
1088
|
if (config.verbose) {
|
|
952
1089
|
console.info(
|
|
953
|
-
`[glasstrace]
|
|
1090
|
+
`[glasstrace] Cached config ${cachedInitResponse ? "loaded and applied" : "not found"}.`
|
|
954
1091
|
);
|
|
955
1092
|
}
|
|
956
1093
|
const sessionManager = new SessionManager();
|
|
957
1094
|
if (config.verbose) {
|
|
958
|
-
console.info("[glasstrace]
|
|
1095
|
+
console.info("[glasstrace] SessionManager created.");
|
|
959
1096
|
}
|
|
960
1097
|
isRegistered = true;
|
|
961
1098
|
const currentGeneration = registrationGeneration;
|
|
962
1099
|
void configureOtel(config, sessionManager).then(
|
|
963
1100
|
() => {
|
|
1101
|
+
void _preloadOtelApi();
|
|
1102
|
+
maybeInstallConsoleCapture();
|
|
964
1103
|
if (config.verbose) {
|
|
965
|
-
console.info("[glasstrace]
|
|
1104
|
+
console.info("[glasstrace] OTel configured.");
|
|
966
1105
|
}
|
|
967
1106
|
},
|
|
968
1107
|
(err) => {
|
|
@@ -980,7 +1119,7 @@ function registerGlasstrace(options) {
|
|
|
980
1119
|
() => sessionManager.getSessionId(getResolvedApiKey())
|
|
981
1120
|
);
|
|
982
1121
|
if (config.verbose) {
|
|
983
|
-
console.info("[glasstrace]
|
|
1122
|
+
console.info("[glasstrace] Discovery endpoint registered (key pending).");
|
|
984
1123
|
}
|
|
985
1124
|
void (async () => {
|
|
986
1125
|
try {
|
|
@@ -996,9 +1135,10 @@ function registerGlasstrace(options) {
|
|
|
996
1135
|
() => sessionManager.getSessionId(getResolvedApiKey())
|
|
997
1136
|
);
|
|
998
1137
|
if (config.verbose) {
|
|
999
|
-
console.info("[glasstrace]
|
|
1138
|
+
console.info("[glasstrace] Background init firing.");
|
|
1000
1139
|
}
|
|
1001
|
-
await performInit(config, anonKey, "0.
|
|
1140
|
+
await performInit(config, anonKey, "0.2.0");
|
|
1141
|
+
maybeInstallConsoleCapture();
|
|
1002
1142
|
} catch (err) {
|
|
1003
1143
|
console.warn(
|
|
1004
1144
|
`[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1015,9 +1155,10 @@ function registerGlasstrace(options) {
|
|
|
1015
1155
|
effectiveKey = anonKey;
|
|
1016
1156
|
if (currentGeneration !== registrationGeneration) return;
|
|
1017
1157
|
if (config.verbose) {
|
|
1018
|
-
console.info("[glasstrace]
|
|
1158
|
+
console.info("[glasstrace] Background init firing.");
|
|
1019
1159
|
}
|
|
1020
|
-
await performInit(config, anonKey, "0.
|
|
1160
|
+
await performInit(config, anonKey, "0.2.0");
|
|
1161
|
+
maybeInstallConsoleCapture();
|
|
1021
1162
|
} catch (err) {
|
|
1022
1163
|
console.warn(
|
|
1023
1164
|
`[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1036,9 +1177,10 @@ function registerGlasstrace(options) {
|
|
|
1036
1177
|
}
|
|
1037
1178
|
if (currentGeneration !== registrationGeneration) return;
|
|
1038
1179
|
if (config.verbose) {
|
|
1039
|
-
console.info("[glasstrace]
|
|
1180
|
+
console.info("[glasstrace] Background init firing.");
|
|
1040
1181
|
}
|
|
1041
|
-
await performInit(config, anonKeyForInit, "0.
|
|
1182
|
+
await performInit(config, anonKeyForInit, "0.2.0");
|
|
1183
|
+
maybeInstallConsoleCapture();
|
|
1042
1184
|
} catch (err) {
|
|
1043
1185
|
console.warn(
|
|
1044
1186
|
`[glasstrace] Background init failed: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -1047,7 +1189,7 @@ function registerGlasstrace(options) {
|
|
|
1047
1189
|
})();
|
|
1048
1190
|
}
|
|
1049
1191
|
if (config.coverageMapEnabled && config.verbose) {
|
|
1050
|
-
console.info("[glasstrace]
|
|
1192
|
+
console.info("[glasstrace] Import graph building skipped.");
|
|
1051
1193
|
}
|
|
1052
1194
|
} catch (err) {
|
|
1053
1195
|
console.warn(
|
|
@@ -1058,6 +1200,13 @@ function registerGlasstrace(options) {
|
|
|
1058
1200
|
function getDiscoveryHandler() {
|
|
1059
1201
|
return discoveryHandler;
|
|
1060
1202
|
}
|
|
1203
|
+
function maybeInstallConsoleCapture() {
|
|
1204
|
+
if (consoleCaptureInstalled) return;
|
|
1205
|
+
if (getActiveConfig().consoleErrors) {
|
|
1206
|
+
consoleCaptureInstalled = true;
|
|
1207
|
+
void installConsoleCapture();
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1061
1210
|
function isDiscoveryEnabled(config) {
|
|
1062
1211
|
if (process.env.GLASSTRACE_DISCOVERY_ENABLED === "true") return true;
|
|
1063
1212
|
if (process.env.GLASSTRACE_DISCOVERY_ENABLED === "false") return false;
|
|
@@ -1140,6 +1289,10 @@ async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
|
|
|
1140
1289
|
body: JSON.stringify(body)
|
|
1141
1290
|
});
|
|
1142
1291
|
if (!response.ok) {
|
|
1292
|
+
try {
|
|
1293
|
+
await response.text();
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1143
1296
|
throw new Error(
|
|
1144
1297
|
`Source map upload failed: ${String(response.status)} ${response.statusText}`
|
|
1145
1298
|
);
|
|
@@ -1379,6 +1532,7 @@ async function buildImportGraph(projectRoot) {
|
|
|
1379
1532
|
SdkError,
|
|
1380
1533
|
SessionManager,
|
|
1381
1534
|
buildImportGraph,
|
|
1535
|
+
captureError,
|
|
1382
1536
|
classifyFetchTarget,
|
|
1383
1537
|
collectSourceMaps,
|
|
1384
1538
|
computeBuildHash,
|