@glasstrace/sdk 0.12.2 → 0.12.4

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.d.cts CHANGED
@@ -394,6 +394,10 @@ declare class GlasstraceExporter implements SpanExporter {
394
394
  */
395
395
  notifyKeyResolved(): void;
396
396
  shutdown(): Promise<void>;
397
+ /**
398
+ * Flushes any pending buffered spans (if the API key has resolved) and
399
+ * delegates to the underlying exporter's forceFlush to drain its queue.
400
+ */
397
401
  forceFlush(): Promise<void>;
398
402
  /**
399
403
  * Enriches a ReadableSpan with all glasstrace.* attributes.
package/dist/index.d.ts CHANGED
@@ -394,6 +394,10 @@ declare class GlasstraceExporter implements SpanExporter {
394
394
  */
395
395
  notifyKeyResolved(): void;
396
396
  shutdown(): Promise<void>;
397
+ /**
398
+ * Flushes any pending buffered spans (if the API key has resolved) and
399
+ * delegates to the underlying exporter's forceFlush to drain its queue.
400
+ */
397
401
  forceFlush(): Promise<void>;
398
402
  /**
399
403
  * Enriches a ReadableSpan with all glasstrace.* attributes.
package/dist/index.js CHANGED
@@ -9,10 +9,11 @@ import {
9
9
  maybeShowMcpNudge,
10
10
  readEnvVars,
11
11
  resolveConfig,
12
+ sdkLog,
12
13
  uploadSourceMaps,
13
14
  uploadSourceMapsAuto,
14
15
  uploadSourceMapsPresigned
15
- } from "./chunk-MSMOH6IH.js";
16
+ } from "./chunk-J576N5MN.js";
16
17
  import {
17
18
  buildImportGraph,
18
19
  discoverTestFiles,
@@ -30,6 +31,7 @@ import {
30
31
  SessionIdSchema
31
32
  } from "./chunk-OKIP4SRG.js";
32
33
  import {
34
+ DiagLogLevel,
33
35
  INVALID_SPAN_CONTEXT,
34
36
  SamplingDecision,
35
37
  SpanKind,
@@ -257,6 +259,7 @@ function loadFsSyncOrNull() {
257
259
  var currentConfig = null;
258
260
  var configCacheChecked = false;
259
261
  var rateLimitBackoff = false;
262
+ var lastInitSucceeded = false;
260
263
  function loadCachedConfig(projectRoot) {
261
264
  const modules = loadFsSyncOrNull();
262
265
  if (!modules) return null;
@@ -426,6 +429,7 @@ async function writeClaimedKey(newApiKey, projectRoot) {
426
429
  }
427
430
  }
428
431
  async function performInit(config, anonKey, sdkVersion, healthReport) {
432
+ lastInitSucceeded = false;
429
433
  if (rateLimitBackoff) {
430
434
  rateLimitBackoff = false;
431
435
  return null;
@@ -454,6 +458,7 @@ async function performInit(config, anonKey, sdkVersion, healthReport) {
454
458
  if (healthReport) {
455
459
  acknowledgeHealthReport(healthReport);
456
460
  }
461
+ lastInitSucceeded = true;
457
462
  await saveCachedConfig(result);
458
463
  if (result.claimResult) {
459
464
  try {
@@ -500,7 +505,6 @@ async function performInit(config, anonKey, sdkVersion, healthReport) {
500
505
  return null;
501
506
  }
502
507
  } catch (err) {
503
- recordInitFailure();
504
508
  console.warn(
505
509
  `[glasstrace] Unexpected init error: ${err instanceof Error ? err.message : String(err)}`
506
510
  );
@@ -530,6 +534,16 @@ function getClaimResult() {
530
534
  function _setCurrentConfig(config) {
531
535
  currentConfig = config;
532
536
  }
537
+ function consumeRateLimitFlag() {
538
+ if (rateLimitBackoff) {
539
+ rateLimitBackoff = false;
540
+ return true;
541
+ }
542
+ return false;
543
+ }
544
+ function didLastInitSucceed() {
545
+ return lastInitSucceeded;
546
+ }
533
547
 
534
548
  // src/span-processor.ts
535
549
  var GlasstraceSpanProcessor = class {
@@ -585,7 +599,12 @@ var GlasstraceExporter = class {
585
599
  const enrichedSpans = spans.map((span) => this.enrichSpan(span));
586
600
  const exporter = this.ensureDelegate();
587
601
  if (exporter) {
588
- exporter.export(enrichedSpans, resultCallback);
602
+ exporter.export(enrichedSpans, (result) => {
603
+ if (result.code !== 0) {
604
+ sdkLog("warn", `[glasstrace] Span export failed: ${result.error?.message ?? "unknown error"}`);
605
+ }
606
+ resultCallback(result);
607
+ });
589
608
  recordSpansExported(enrichedSpans.length);
590
609
  } else {
591
610
  recordSpansDropped(enrichedSpans.length);
@@ -618,7 +637,14 @@ var GlasstraceExporter = class {
618
637
  return this.delegate.shutdown();
619
638
  }
620
639
  }
640
+ /**
641
+ * Flushes any pending buffered spans (if the API key has resolved) and
642
+ * delegates to the underlying exporter's forceFlush to drain its queue.
643
+ */
621
644
  forceFlush() {
645
+ if (this.getApiKey() !== API_KEY_PENDING && this.pendingBatches.length > 0) {
646
+ this.flushPending();
647
+ }
622
648
  if (this.delegate?.forceFlush) {
623
649
  return this.delegate.forceFlush();
624
650
  }
@@ -787,7 +813,12 @@ var GlasstraceExporter = class {
787
813
  this.pendingSpanCount = 0;
788
814
  for (const batch of batches) {
789
815
  const enriched = batch.spans.map((span) => this.enrichSpan(span));
790
- exporter.export(enriched, batch.resultCallback);
816
+ exporter.export(enriched, (result) => {
817
+ if (result.code !== 0) {
818
+ sdkLog("warn", `[glasstrace] Span export failed: ${result.error?.message ?? "unknown error"}`);
819
+ }
820
+ batch.resultCallback(result);
821
+ });
791
822
  recordSpansExported(enriched.length);
792
823
  }
793
824
  }
@@ -3578,7 +3609,21 @@ async function configureOtel(config, sessionManager) {
3578
3609
  _activeExporter = null;
3579
3610
  return;
3580
3611
  }
3581
- const processor = new BatchSpanProcessor(glasstraceExporter);
3612
+ if (config.verbose) {
3613
+ diag.setLogger(
3614
+ {
3615
+ verbose: (msg) => sdkLog("info", `[otel] ${msg}`),
3616
+ debug: (msg) => sdkLog("info", `[otel] ${msg}`),
3617
+ info: (msg) => sdkLog("info", `[otel] ${msg}`),
3618
+ warn: (msg) => sdkLog("warn", `[otel] ${msg}`),
3619
+ error: (msg) => sdkLog("error", `[otel] ${msg}`)
3620
+ },
3621
+ DiagLogLevel.WARN
3622
+ );
3623
+ }
3624
+ const processor = new BatchSpanProcessor(glasstraceExporter, {
3625
+ scheduledDelayMillis: 1e3
3626
+ });
3582
3627
  const provider = new BasicTracerProvider({
3583
3628
  spanProcessors: [processor]
3584
3629
  });
@@ -3586,6 +3631,109 @@ async function configureOtel(config, sessionManager) {
3586
3631
  registerShutdownHooks(provider);
3587
3632
  }
3588
3633
 
3634
+ // src/heartbeat.ts
3635
+ var HEARTBEAT_INTERVAL_MS = 5 * 60 * 1e3;
3636
+ var BACKOFF_BASE_MS = HEARTBEAT_INTERVAL_MS;
3637
+ var BACKOFF_MAX_MS = 30 * 60 * 1e3;
3638
+ var BACKOFF_JITTER = 0.2;
3639
+ var heartbeatTimer = null;
3640
+ var heartbeatGeneration = 0;
3641
+ var backoffAttempts = 0;
3642
+ var backoffUntil = 0;
3643
+ var tickInProgress = false;
3644
+ var _shutdownHandler2 = null;
3645
+ function startHeartbeat(config, anonKey, sdkVersion, generation, onClaimTransition) {
3646
+ if (heartbeatTimer !== null) return;
3647
+ heartbeatGeneration = generation;
3648
+ heartbeatTimer = setInterval(() => {
3649
+ void heartbeatTick(config, anonKey, sdkVersion, generation, onClaimTransition);
3650
+ }, HEARTBEAT_INTERVAL_MS);
3651
+ heartbeatTimer.unref();
3652
+ registerShutdownHandlers(config, anonKey, sdkVersion);
3653
+ if (config.verbose) {
3654
+ sdkLog("info", "[glasstrace] Heartbeat started (5-minute interval).");
3655
+ }
3656
+ }
3657
+ function stopHeartbeat() {
3658
+ if (heartbeatTimer !== null) {
3659
+ clearInterval(heartbeatTimer);
3660
+ heartbeatTimer = null;
3661
+ }
3662
+ removeShutdownHandlers();
3663
+ }
3664
+ async function heartbeatTick(config, anonKey, sdkVersion, generation, onClaimTransition) {
3665
+ if (tickInProgress) return;
3666
+ tickInProgress = true;
3667
+ try {
3668
+ if (generation !== heartbeatGeneration) {
3669
+ stopHeartbeat();
3670
+ return;
3671
+ }
3672
+ if (Date.now() < backoffUntil) {
3673
+ if (config.verbose) {
3674
+ sdkLog("info", "[glasstrace] Heartbeat skipped (rate-limit backoff).");
3675
+ }
3676
+ return;
3677
+ }
3678
+ const healthReport = collectHealthReport(sdkVersion);
3679
+ const initResult = await performInit(config, anonKey, sdkVersion, healthReport);
3680
+ if (generation !== heartbeatGeneration) return;
3681
+ if (initResult === null && consumeRateLimitFlag()) {
3682
+ backoffAttempts++;
3683
+ const delay = Math.min(
3684
+ BACKOFF_BASE_MS * Math.pow(2, backoffAttempts - 1),
3685
+ BACKOFF_MAX_MS
3686
+ );
3687
+ const jitter = delay * BACKOFF_JITTER * (Math.random() * 2 - 1);
3688
+ backoffUntil = Date.now() + delay + jitter;
3689
+ if (config.verbose) {
3690
+ sdkLog("info", `[glasstrace] Heartbeat backing off for ${Math.round((delay + jitter) / 1e3)}s.`);
3691
+ }
3692
+ } else {
3693
+ backoffAttempts = 0;
3694
+ backoffUntil = 0;
3695
+ }
3696
+ if (initResult?.claimResult) {
3697
+ onClaimTransition(initResult.claimResult.newApiKey);
3698
+ }
3699
+ if (config.verbose) {
3700
+ sdkLog("info", "[glasstrace] Heartbeat completed.");
3701
+ }
3702
+ } finally {
3703
+ tickInProgress = false;
3704
+ }
3705
+ }
3706
+ function registerShutdownHandlers(config, anonKey, sdkVersion) {
3707
+ if (typeof process === "undefined" || typeof process.once !== "function") {
3708
+ return;
3709
+ }
3710
+ let shutdownFired = false;
3711
+ const handler = (signal) => {
3712
+ if (shutdownFired) return;
3713
+ shutdownFired = true;
3714
+ if (heartbeatTimer !== null) {
3715
+ clearInterval(heartbeatTimer);
3716
+ heartbeatTimer = null;
3717
+ }
3718
+ const healthReport = collectHealthReport(sdkVersion);
3719
+ void performInit(config, anonKey, sdkVersion, healthReport).catch(() => {
3720
+ }).finally(() => {
3721
+ removeShutdownHandlers();
3722
+ process.kill(process.pid, signal);
3723
+ });
3724
+ };
3725
+ _shutdownHandler2 = handler;
3726
+ process.once("SIGTERM", _shutdownHandler2);
3727
+ process.once("SIGINT", _shutdownHandler2);
3728
+ }
3729
+ function removeShutdownHandlers() {
3730
+ if (_shutdownHandler2 && typeof process !== "undefined") {
3731
+ process.removeListener("SIGTERM", _shutdownHandler2);
3732
+ process.removeListener("SIGINT", _shutdownHandler2);
3733
+ _shutdownHandler2 = null;
3734
+ }
3735
+ }
3736
+
3589
3737
  // src/register.ts
3590
3738
  var consoleCaptureInstalled = false;
3591
3739
  var discoveryHandler = null;
@@ -3739,14 +3887,20 @@ async function backgroundInit(config, anonKeyForInit, generation) {
3739
3887
  if (config.verbose) {
3740
3888
  console.info("[glasstrace] Background init firing.");
3741
3889
  }
3742
- const healthReport = collectHealthReport("0.12.2");
3743
- const initResult = await performInit(config, anonKeyForInit, "0.12.2", healthReport);
3890
+ const healthReport = collectHealthReport("0.12.4");
3891
+ const initResult = await performInit(config, anonKeyForInit, "0.12.4", healthReport);
3744
3892
  if (generation !== registrationGeneration) return;
3745
3893
  if (initResult?.claimResult) {
3746
3894
  setResolvedApiKey(initResult.claimResult.newApiKey);
3747
3895
  notifyApiKeyResolved();
3748
3896
  }
3749
3897
  maybeInstallConsoleCapture();
3898
+ if (didLastInitSucceed()) {
3899
+ startHeartbeat(config, anonKeyForInit, "0.12.4", generation, (newApiKey) => {
3900
+ setResolvedApiKey(newApiKey);
3901
+ notifyApiKeyResolved();
3902
+ });
3903
+ }
3750
3904
  }
3751
3905
  function getDiscoveryHandler() {
3752
3906
  return discoveryHandler;
@@ -3814,7 +3968,7 @@ async function handleSourceMapUpload(distDir) {
3814
3968
  );
3815
3969
  return;
3816
3970
  }
3817
- const { discoverSourceMapFiles: discoverSourceMapFiles2, computeBuildHash: computeBuildHash2, uploadSourceMaps: uploadSourceMaps2 } = await import("./source-map-uploader-ZFCYOURS.js");
3971
+ const { discoverSourceMapFiles: discoverSourceMapFiles2, computeBuildHash: computeBuildHash2, uploadSourceMaps: uploadSourceMaps2 } = await import("./source-map-uploader-ZHD654EG.js");
3818
3972
  const files = await discoverSourceMapFiles2(distDir);
3819
3973
  if (files.length === 0) {
3820
3974
  console.info("[glasstrace] No source map files found. Skipping upload.");