@glasstrace/sdk 0.12.3 → 0.12.5
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 +162 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +159 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -361,6 +361,7 @@ interface GlasstraceExporterOptions {
|
|
|
361
361
|
environment: string | undefined;
|
|
362
362
|
endpointUrl: string;
|
|
363
363
|
createDelegate: ((url: string, headers: Record<string, string>) => SpanExporter) | null;
|
|
364
|
+
verbose?: boolean;
|
|
364
365
|
}
|
|
365
366
|
/**
|
|
366
367
|
* A SpanExporter that enriches spans with glasstrace.* attributes at export
|
|
@@ -381,6 +382,7 @@ declare class GlasstraceExporter implements SpanExporter {
|
|
|
381
382
|
private readonly environment;
|
|
382
383
|
private readonly endpointUrl;
|
|
383
384
|
private readonly createDelegateFn;
|
|
385
|
+
private readonly verbose;
|
|
384
386
|
private delegate;
|
|
385
387
|
private delegateKey;
|
|
386
388
|
private pendingBatches;
|
package/dist/index.d.ts
CHANGED
|
@@ -361,6 +361,7 @@ interface GlasstraceExporterOptions {
|
|
|
361
361
|
environment: string | undefined;
|
|
362
362
|
endpointUrl: string;
|
|
363
363
|
createDelegate: ((url: string, headers: Record<string, string>) => SpanExporter) | null;
|
|
364
|
+
verbose?: boolean;
|
|
364
365
|
}
|
|
365
366
|
/**
|
|
366
367
|
* A SpanExporter that enriches spans with glasstrace.* attributes at export
|
|
@@ -381,6 +382,7 @@ declare class GlasstraceExporter implements SpanExporter {
|
|
|
381
382
|
private readonly environment;
|
|
382
383
|
private readonly endpointUrl;
|
|
383
384
|
private readonly createDelegateFn;
|
|
385
|
+
private readonly verbose;
|
|
384
386
|
private delegate;
|
|
385
387
|
private delegateKey;
|
|
386
388
|
private pendingBatches;
|
package/dist/index.js
CHANGED
|
@@ -259,6 +259,7 @@ function loadFsSyncOrNull() {
|
|
|
259
259
|
var currentConfig = null;
|
|
260
260
|
var configCacheChecked = false;
|
|
261
261
|
var rateLimitBackoff = false;
|
|
262
|
+
var lastInitSucceeded = false;
|
|
262
263
|
function loadCachedConfig(projectRoot) {
|
|
263
264
|
const modules = loadFsSyncOrNull();
|
|
264
265
|
if (!modules) return null;
|
|
@@ -428,6 +429,7 @@ async function writeClaimedKey(newApiKey, projectRoot) {
|
|
|
428
429
|
}
|
|
429
430
|
}
|
|
430
431
|
async function performInit(config, anonKey, sdkVersion, healthReport) {
|
|
432
|
+
lastInitSucceeded = false;
|
|
431
433
|
if (rateLimitBackoff) {
|
|
432
434
|
rateLimitBackoff = false;
|
|
433
435
|
return null;
|
|
@@ -456,6 +458,7 @@ async function performInit(config, anonKey, sdkVersion, healthReport) {
|
|
|
456
458
|
if (healthReport) {
|
|
457
459
|
acknowledgeHealthReport(healthReport);
|
|
458
460
|
}
|
|
461
|
+
lastInitSucceeded = true;
|
|
459
462
|
await saveCachedConfig(result);
|
|
460
463
|
if (result.claimResult) {
|
|
461
464
|
try {
|
|
@@ -502,7 +505,6 @@ async function performInit(config, anonKey, sdkVersion, healthReport) {
|
|
|
502
505
|
return null;
|
|
503
506
|
}
|
|
504
507
|
} catch (err) {
|
|
505
|
-
recordInitFailure();
|
|
506
508
|
console.warn(
|
|
507
509
|
`[glasstrace] Unexpected init error: ${err instanceof Error ? err.message : String(err)}`
|
|
508
510
|
);
|
|
@@ -532,6 +534,16 @@ function getClaimResult() {
|
|
|
532
534
|
function _setCurrentConfig(config) {
|
|
533
535
|
currentConfig = config;
|
|
534
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
|
+
}
|
|
535
547
|
|
|
536
548
|
// src/span-processor.ts
|
|
537
549
|
var GlasstraceSpanProcessor = class {
|
|
@@ -565,6 +577,7 @@ var GlasstraceExporter = class {
|
|
|
565
577
|
environment;
|
|
566
578
|
endpointUrl;
|
|
567
579
|
createDelegateFn;
|
|
580
|
+
verbose;
|
|
568
581
|
delegate = null;
|
|
569
582
|
delegateKey = null;
|
|
570
583
|
pendingBatches = [];
|
|
@@ -577,6 +590,7 @@ var GlasstraceExporter = class {
|
|
|
577
590
|
this.environment = options.environment;
|
|
578
591
|
this.endpointUrl = options.endpointUrl;
|
|
579
592
|
this.createDelegateFn = options.createDelegate;
|
|
593
|
+
this.verbose = options.verbose ?? false;
|
|
580
594
|
}
|
|
581
595
|
export(spans, resultCallback) {
|
|
582
596
|
const currentKey = this.getApiKey();
|
|
@@ -585,11 +599,16 @@ var GlasstraceExporter = class {
|
|
|
585
599
|
return;
|
|
586
600
|
}
|
|
587
601
|
const enrichedSpans = spans.map((span) => this.enrichSpan(span));
|
|
602
|
+
if (this.verbose) {
|
|
603
|
+
sdkLog("info", `[glasstrace:diag] Export batch: ${enrichedSpans.length} spans`);
|
|
604
|
+
}
|
|
588
605
|
const exporter = this.ensureDelegate();
|
|
589
606
|
if (exporter) {
|
|
590
607
|
exporter.export(enrichedSpans, (result) => {
|
|
591
608
|
if (result.code !== 0) {
|
|
592
609
|
sdkLog("warn", `[glasstrace] Span export failed: ${result.error?.message ?? "unknown error"}`);
|
|
610
|
+
} else if (this.verbose) {
|
|
611
|
+
sdkLog("info", `[glasstrace:diag] Export success: ${enrichedSpans.length} spans delivered`);
|
|
593
612
|
}
|
|
594
613
|
resultCallback(result);
|
|
595
614
|
});
|
|
@@ -682,6 +701,21 @@ var GlasstraceExporter = class {
|
|
|
682
701
|
if (statusCode !== void 0) {
|
|
683
702
|
extra[ATTR.HTTP_STATUS_CODE] = statusCode;
|
|
684
703
|
}
|
|
704
|
+
if (method && span.status?.code === SpanStatusCode.ERROR) {
|
|
705
|
+
if (statusCode === void 0 || statusCode === 0 || statusCode === 200) {
|
|
706
|
+
const httpErrorType = attrs["error.type"];
|
|
707
|
+
if (typeof httpErrorType === "string") {
|
|
708
|
+
const parsed = parseInt(httpErrorType, 10);
|
|
709
|
+
if (!isNaN(parsed) && parsed >= 400 && parsed <= 599) {
|
|
710
|
+
extra[ATTR.HTTP_STATUS_CODE] = parsed;
|
|
711
|
+
} else {
|
|
712
|
+
extra[ATTR.HTTP_STATUS_CODE] = 500;
|
|
713
|
+
}
|
|
714
|
+
} else {
|
|
715
|
+
extra[ATTR.HTTP_STATUS_CODE] = 500;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
685
719
|
if (span.startTime && span.endTime) {
|
|
686
720
|
const [startSec, startNano] = span.startTime;
|
|
687
721
|
const [endSec, endNano] = span.endTime;
|
|
@@ -761,11 +795,17 @@ var GlasstraceExporter = class {
|
|
|
761
795
|
bufferSpans(spans, resultCallback) {
|
|
762
796
|
this.pendingBatches.push({ spans, resultCallback });
|
|
763
797
|
this.pendingSpanCount += spans.length;
|
|
798
|
+
if (this.verbose) {
|
|
799
|
+
sdkLog("info", `[glasstrace:diag] Buffering ${spans.length} spans (key pending, total: ${this.pendingSpanCount})`);
|
|
800
|
+
}
|
|
764
801
|
while (this.pendingSpanCount > MAX_PENDING_SPANS && this.pendingBatches.length > 1) {
|
|
765
802
|
const evicted = this.pendingBatches.shift();
|
|
766
803
|
this.pendingSpanCount -= evicted.spans.length;
|
|
767
804
|
recordSpansDropped(evicted.spans.length);
|
|
768
805
|
evicted.resultCallback({ code: 0 });
|
|
806
|
+
if (this.verbose) {
|
|
807
|
+
sdkLog("info", `[glasstrace:diag] Buffer overflow: evicted ${evicted.spans.length} spans (total pending: ${this.pendingSpanCount})`);
|
|
808
|
+
}
|
|
769
809
|
if (!this.overflowLogged) {
|
|
770
810
|
this.overflowLogged = true;
|
|
771
811
|
console.warn(
|
|
@@ -804,6 +844,8 @@ var GlasstraceExporter = class {
|
|
|
804
844
|
exporter.export(enriched, (result) => {
|
|
805
845
|
if (result.code !== 0) {
|
|
806
846
|
sdkLog("warn", `[glasstrace] Span export failed: ${result.error?.message ?? "unknown error"}`);
|
|
847
|
+
} else if (this.verbose) {
|
|
848
|
+
sdkLog("info", `[glasstrace:diag] Flush export success: ${enriched.length} spans delivered`);
|
|
807
849
|
}
|
|
808
850
|
batch.resultCallback(result);
|
|
809
851
|
});
|
|
@@ -3569,7 +3611,8 @@ async function configureOtel(config, sessionManager) {
|
|
|
3569
3611
|
getConfig: () => getActiveConfig(),
|
|
3570
3612
|
environment: config.environment,
|
|
3571
3613
|
endpointUrl: exporterUrl,
|
|
3572
|
-
createDelegate: createOtlpExporter
|
|
3614
|
+
createDelegate: createOtlpExporter,
|
|
3615
|
+
verbose: config.verbose
|
|
3573
3616
|
});
|
|
3574
3617
|
_activeExporter = glasstraceExporter;
|
|
3575
3618
|
const vercelOtel = await tryImport("@vercel/otel");
|
|
@@ -3612,6 +3655,9 @@ async function configureOtel(config, sessionManager) {
|
|
|
3612
3655
|
const processor = new BatchSpanProcessor(glasstraceExporter, {
|
|
3613
3656
|
scheduledDelayMillis: 1e3
|
|
3614
3657
|
});
|
|
3658
|
+
if (config.verbose) {
|
|
3659
|
+
sdkLog("info", "[glasstrace:diag] BatchSpanProcessor configured: scheduledDelayMillis=1000");
|
|
3660
|
+
}
|
|
3615
3661
|
const provider = new BasicTracerProvider({
|
|
3616
3662
|
spanProcessors: [processor]
|
|
3617
3663
|
});
|
|
@@ -3619,6 +3665,109 @@ async function configureOtel(config, sessionManager) {
|
|
|
3619
3665
|
registerShutdownHooks(provider);
|
|
3620
3666
|
}
|
|
3621
3667
|
|
|
3668
|
+
// src/heartbeat.ts
|
|
3669
|
+
var HEARTBEAT_INTERVAL_MS = 5 * 60 * 1e3;
|
|
3670
|
+
var BACKOFF_BASE_MS = HEARTBEAT_INTERVAL_MS;
|
|
3671
|
+
var BACKOFF_MAX_MS = 30 * 60 * 1e3;
|
|
3672
|
+
var BACKOFF_JITTER = 0.2;
|
|
3673
|
+
var heartbeatTimer = null;
|
|
3674
|
+
var heartbeatGeneration = 0;
|
|
3675
|
+
var backoffAttempts = 0;
|
|
3676
|
+
var backoffUntil = 0;
|
|
3677
|
+
var tickInProgress = false;
|
|
3678
|
+
var _shutdownHandler2 = null;
|
|
3679
|
+
function startHeartbeat(config, anonKey, sdkVersion, generation, onClaimTransition) {
|
|
3680
|
+
if (heartbeatTimer !== null) return;
|
|
3681
|
+
heartbeatGeneration = generation;
|
|
3682
|
+
heartbeatTimer = setInterval(() => {
|
|
3683
|
+
void heartbeatTick(config, anonKey, sdkVersion, generation, onClaimTransition);
|
|
3684
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
3685
|
+
heartbeatTimer.unref();
|
|
3686
|
+
registerShutdownHandlers(config, anonKey, sdkVersion);
|
|
3687
|
+
if (config.verbose) {
|
|
3688
|
+
sdkLog("info", "[glasstrace] Heartbeat started (5-minute interval).");
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
function stopHeartbeat() {
|
|
3692
|
+
if (heartbeatTimer !== null) {
|
|
3693
|
+
clearInterval(heartbeatTimer);
|
|
3694
|
+
heartbeatTimer = null;
|
|
3695
|
+
}
|
|
3696
|
+
removeShutdownHandlers();
|
|
3697
|
+
}
|
|
3698
|
+
async function heartbeatTick(config, anonKey, sdkVersion, generation, onClaimTransition) {
|
|
3699
|
+
if (tickInProgress) return;
|
|
3700
|
+
tickInProgress = true;
|
|
3701
|
+
try {
|
|
3702
|
+
if (generation !== heartbeatGeneration) {
|
|
3703
|
+
stopHeartbeat();
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
if (Date.now() < backoffUntil) {
|
|
3707
|
+
if (config.verbose) {
|
|
3708
|
+
sdkLog("info", "[glasstrace] Heartbeat skipped (rate-limit backoff).");
|
|
3709
|
+
}
|
|
3710
|
+
return;
|
|
3711
|
+
}
|
|
3712
|
+
const healthReport = collectHealthReport(sdkVersion);
|
|
3713
|
+
const initResult = await performInit(config, anonKey, sdkVersion, healthReport);
|
|
3714
|
+
if (generation !== heartbeatGeneration) return;
|
|
3715
|
+
if (initResult === null && consumeRateLimitFlag()) {
|
|
3716
|
+
backoffAttempts++;
|
|
3717
|
+
const delay = Math.min(
|
|
3718
|
+
BACKOFF_BASE_MS * Math.pow(2, backoffAttempts - 1),
|
|
3719
|
+
BACKOFF_MAX_MS
|
|
3720
|
+
);
|
|
3721
|
+
const jitter = delay * BACKOFF_JITTER * (Math.random() * 2 - 1);
|
|
3722
|
+
backoffUntil = Date.now() + delay + jitter;
|
|
3723
|
+
if (config.verbose) {
|
|
3724
|
+
sdkLog("info", `[glasstrace] Heartbeat backing off for ${Math.round((delay + jitter) / 1e3)}s.`);
|
|
3725
|
+
}
|
|
3726
|
+
} else {
|
|
3727
|
+
backoffAttempts = 0;
|
|
3728
|
+
backoffUntil = 0;
|
|
3729
|
+
}
|
|
3730
|
+
if (initResult?.claimResult) {
|
|
3731
|
+
onClaimTransition(initResult.claimResult.newApiKey);
|
|
3732
|
+
}
|
|
3733
|
+
if (config.verbose) {
|
|
3734
|
+
sdkLog("info", "[glasstrace] Heartbeat completed.");
|
|
3735
|
+
}
|
|
3736
|
+
} finally {
|
|
3737
|
+
tickInProgress = false;
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
function registerShutdownHandlers(config, anonKey, sdkVersion) {
|
|
3741
|
+
if (typeof process === "undefined" || typeof process.once !== "function") {
|
|
3742
|
+
return;
|
|
3743
|
+
}
|
|
3744
|
+
let shutdownFired = false;
|
|
3745
|
+
const handler = (signal) => {
|
|
3746
|
+
if (shutdownFired) return;
|
|
3747
|
+
shutdownFired = true;
|
|
3748
|
+
if (heartbeatTimer !== null) {
|
|
3749
|
+
clearInterval(heartbeatTimer);
|
|
3750
|
+
heartbeatTimer = null;
|
|
3751
|
+
}
|
|
3752
|
+
const healthReport = collectHealthReport(sdkVersion);
|
|
3753
|
+
void performInit(config, anonKey, sdkVersion, healthReport).catch(() => {
|
|
3754
|
+
}).finally(() => {
|
|
3755
|
+
removeShutdownHandlers();
|
|
3756
|
+
process.kill(process.pid, signal);
|
|
3757
|
+
});
|
|
3758
|
+
};
|
|
3759
|
+
_shutdownHandler2 = handler;
|
|
3760
|
+
process.once("SIGTERM", _shutdownHandler2);
|
|
3761
|
+
process.once("SIGINT", _shutdownHandler2);
|
|
3762
|
+
}
|
|
3763
|
+
function removeShutdownHandlers() {
|
|
3764
|
+
if (_shutdownHandler2 && typeof process !== "undefined") {
|
|
3765
|
+
process.removeListener("SIGTERM", _shutdownHandler2);
|
|
3766
|
+
process.removeListener("SIGINT", _shutdownHandler2);
|
|
3767
|
+
_shutdownHandler2 = null;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3622
3771
|
// src/register.ts
|
|
3623
3772
|
var consoleCaptureInstalled = false;
|
|
3624
3773
|
var discoveryHandler = null;
|
|
@@ -3772,14 +3921,20 @@ async function backgroundInit(config, anonKeyForInit, generation) {
|
|
|
3772
3921
|
if (config.verbose) {
|
|
3773
3922
|
console.info("[glasstrace] Background init firing.");
|
|
3774
3923
|
}
|
|
3775
|
-
const healthReport = collectHealthReport("0.12.
|
|
3776
|
-
const initResult = await performInit(config, anonKeyForInit, "0.12.
|
|
3924
|
+
const healthReport = collectHealthReport("0.12.5");
|
|
3925
|
+
const initResult = await performInit(config, anonKeyForInit, "0.12.5", healthReport);
|
|
3777
3926
|
if (generation !== registrationGeneration) return;
|
|
3778
3927
|
if (initResult?.claimResult) {
|
|
3779
3928
|
setResolvedApiKey(initResult.claimResult.newApiKey);
|
|
3780
3929
|
notifyApiKeyResolved();
|
|
3781
3930
|
}
|
|
3782
3931
|
maybeInstallConsoleCapture();
|
|
3932
|
+
if (didLastInitSucceed()) {
|
|
3933
|
+
startHeartbeat(config, anonKeyForInit, "0.12.5", generation, (newApiKey) => {
|
|
3934
|
+
setResolvedApiKey(newApiKey);
|
|
3935
|
+
notifyApiKeyResolved();
|
|
3936
|
+
});
|
|
3937
|
+
}
|
|
3783
3938
|
}
|
|
3784
3939
|
function getDiscoveryHandler() {
|
|
3785
3940
|
return discoveryHandler;
|