@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/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-BKMITIEZ.js";
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 (process.env.GLASSTRACE_ENV) {
78
- return process.env.GLASSTRACE_ENV;
87
+ if (cachedGlasstraceEnv) {
88
+ return cachedGlasstraceEnv;
79
89
  }
80
- const port = process.env.PORT ?? "3000";
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 port = process.env.PORT ?? "3000";
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(enrichedSpans, resultCallback);
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
- * Each attribute derivation is wrapped in its own try-catch for partial
481
- * enrichment resilience.
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
- } catch {
490
- }
491
- try {
492
- const sessionId = this.sessionManager.getSessionId(this.getApiKey());
493
- extra[ATTR.SESSION_ID] = sessionId;
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
- } catch {
548
- }
549
- try {
550
- const errorType = attrs["exception.type"];
551
- if (errorType) {
552
- extra[ATTR.ERROR_CODE] = errorType;
553
- extra[ATTR.ERROR_CATEGORY] = deriveErrorCategory(errorType);
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
- } catch {
563
- }
564
- try {
565
- const spanAny = span;
566
- const instrumentationName = spanAny.instrumentationScope?.name ?? spanAny.instrumentationLibrary?.name ?? "";
567
- const ormProvider = deriveOrmProvider(instrumentationName);
568
- if (ormProvider) {
569
- extra[ATTR.ORM_PROVIDER] = ormProvider;
570
- const model = attrs["db.sql.table"] ?? attrs["db.prisma.model"];
571
- if (model) {
572
- extra[ATTR.ORM_MODEL] = model;
573
- }
574
- const operation = attrs["db.operation"];
575
- if (operation) {
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
- } catch {
580
- }
581
- try {
582
- const url = attrs["http.url"] ?? attrs["url.full"];
583
- if (url && span.kind === SpanKind.CLIENT) {
584
- extra[ATTR.FETCH_TARGET] = classifyFetchTarget(url);
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 enriched spans while the API key is pending.
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
- * Called when the API key resolves.
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
- exporter.export(batch.spans, batch.resultCallback);
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 otelApi = await import("@opentelemetry/api");
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
- otelApi.trace.setGlobalTracerProvider(provider2);
880
+ otelApi3.trace.setGlobalTracerProvider(provider2);
881
+ registerShutdownHooks(provider2);
841
882
  return;
842
883
  }
843
- const processor = new otelSdk.SimpleSpanProcessor(glasstraceExporter);
884
+ const processor = new otelSdk.BatchSpanProcessor(glasstraceExporter);
844
885
  const provider = new otelSdk.BasicTracerProvider({
845
886
  spanProcessors: [processor]
846
887
  });
847
- const existingProvider = otelApi.trace.getTracerProvider();
848
- if (existingProvider && existingProvider.constructor.name !== "ProxyTracerProvider") {
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] Step 1: Config resolved.");
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] Step 2: Not production-disabled.");
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] Step 3: Auth mode = ${anonymous ? "anonymous" : "dev-key"}.`
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] Step 4: Cached config ${cachedInitResponse ? "loaded and applied" : "not found"}.`
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] Step 5: SessionManager created.");
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] Step 6: OTel configured.");
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] Step 8: Discovery endpoint registered (key pending).");
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] Step 7: Background init firing.");
1084
+ console.info("[glasstrace] Background init firing.");
946
1085
  }
947
- await performInit(config, anonKey, "0.1.0");
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] Step 7: Background init firing.");
1104
+ console.info("[glasstrace] Background init firing.");
965
1105
  }
966
- await performInit(config, anonKey, "0.1.0");
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] Step 7: Background init firing.");
1126
+ console.info("[glasstrace] Background init firing.");
986
1127
  }
987
- await performInit(config, anonKeyForInit, "0.1.0");
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] Step 9: Import graph building skipped.");
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,