@jeffjassky/telemetry 0.4.0 → 0.5.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 +147 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +146 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/ui/_assets/index-kvwB9_3A.js +41 -0
- package/dist/ui/_assets/{index-Deqhu-hT.js.map → index-kvwB9_3A.js.map} +1 -1
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/types/index.d.ts +88 -0
- package/types/test-d.ts +20 -0
- package/dist/ui/_assets/index-Deqhu-hT.js +0 -41
package/dist/index.cjs
CHANGED
|
@@ -70,7 +70,13 @@ var newCounters = () => ({
|
|
|
70
70
|
deduped: 0,
|
|
71
71
|
truncated: 0,
|
|
72
72
|
rollupSkippedBy: {},
|
|
73
|
-
undeclaredAttrs: {}
|
|
73
|
+
undeclaredAttrs: {},
|
|
74
|
+
subjectsLinked: 0,
|
|
75
|
+
subjectLinkMisses: 0,
|
|
76
|
+
subjectLinkErrors: 0,
|
|
77
|
+
subjectLinkTimeouts: 0,
|
|
78
|
+
subjectLinkUndeclared: 0,
|
|
79
|
+
subjectLinkCapped: 0
|
|
74
80
|
});
|
|
75
81
|
var COUNTER_MAP_MAX = 1e3;
|
|
76
82
|
var COUNTER_OVERFLOW_KEY = "(other)|(other)";
|
|
@@ -672,6 +678,110 @@ function noteUndeclaredAttrs(counters, name, spec, attrs) {
|
|
|
672
678
|
bumpCounterMap(counters.undeclaredAttrs, `${name}|${key}`);
|
|
673
679
|
}
|
|
674
680
|
}
|
|
681
|
+
var SUBJECT_MAX = 8;
|
|
682
|
+
var SUBJECT_LINK_TIMEOUT_MS = 50;
|
|
683
|
+
var LINK_TIMEOUT = /* @__PURE__ */ Symbol("telemetry.subjectLink.timeout");
|
|
684
|
+
function createSubjectLinking(opts) {
|
|
685
|
+
const { linker, counters, logger } = opts;
|
|
686
|
+
if (!linker) return null;
|
|
687
|
+
const timeoutMs = opts.timeoutMs ?? SUBJECT_LINK_TIMEOUT_MS;
|
|
688
|
+
const warned = /* @__PURE__ */ new Set();
|
|
689
|
+
const warnOnce = (key, msg) => {
|
|
690
|
+
if (warned.has(key) || warned.size >= COUNTER_MAP_MAX) return;
|
|
691
|
+
warned.add(key);
|
|
692
|
+
logger.warn(msg);
|
|
693
|
+
};
|
|
694
|
+
return async function linkSubjects(name, spec, tenantId, declared) {
|
|
695
|
+
const have = Array.isArray(declared) ? declared : [];
|
|
696
|
+
const seen = /* @__PURE__ */ new Set();
|
|
697
|
+
const view = [];
|
|
698
|
+
for (const s of have) {
|
|
699
|
+
const ref = wellFormed(s);
|
|
700
|
+
if (!ref) continue;
|
|
701
|
+
seen.add(`${ref.type}:${ref.id}`);
|
|
702
|
+
view.push(ref);
|
|
703
|
+
}
|
|
704
|
+
let out;
|
|
705
|
+
let timer;
|
|
706
|
+
try {
|
|
707
|
+
out = await Promise.race([
|
|
708
|
+
// the async wrapper turns a SYNCHRONOUS throw into a rejection, so a
|
|
709
|
+
// linker that dies on its first line lands in the same catch as one
|
|
710
|
+
// whose promise rejects
|
|
711
|
+
(async () => linker.link(view, { name, tenantId }))(),
|
|
712
|
+
new Promise((_, reject) => {
|
|
713
|
+
timer = setTimeout(() => reject(LINK_TIMEOUT), timeoutMs);
|
|
714
|
+
})
|
|
715
|
+
]);
|
|
716
|
+
} catch (e) {
|
|
717
|
+
if (e === LINK_TIMEOUT) {
|
|
718
|
+
counters.subjectLinkTimeouts++;
|
|
719
|
+
warnOnce(
|
|
720
|
+
"timeout",
|
|
721
|
+
`[telemetry] subjectLinker.link() exceeded ${timeoutMs}ms \u2014 records are being written UNLINKED rather than waiting. The hook is expected to answer from a cache; a resolver that queries per record cannot keep up with ingest. Warned once \u2014 the count is counters.subjectLinkTimeouts.`
|
|
722
|
+
);
|
|
723
|
+
} else {
|
|
724
|
+
counters.subjectLinkErrors++;
|
|
725
|
+
warnOnce(
|
|
726
|
+
"threw",
|
|
727
|
+
`[telemetry] subjectLinker.link() threw \u2014 records are being written unlinked: ${e}. Warned once \u2014 the count is counters.subjectLinkErrors.`
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
} finally {
|
|
732
|
+
clearTimeout(timer);
|
|
733
|
+
}
|
|
734
|
+
if (!Array.isArray(out)) {
|
|
735
|
+
counters.subjectLinkErrors++;
|
|
736
|
+
warnOnce(
|
|
737
|
+
"shape",
|
|
738
|
+
`[telemetry] subjectLinker.link() resolved to ${typeof out}, not an array \u2014 records are being written unlinked. Return [] when nothing links. Warned once \u2014 the count is counters.subjectLinkErrors.`
|
|
739
|
+
);
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
if (!out.length) {
|
|
743
|
+
counters.subjectLinkMisses++;
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
let room = Math.max(0, SUBJECT_MAX - have.length);
|
|
747
|
+
let capped2 = 0;
|
|
748
|
+
const add = [];
|
|
749
|
+
for (const s of out) {
|
|
750
|
+
const ref = wellFormed(s);
|
|
751
|
+
if (!ref) {
|
|
752
|
+
counters.subjectLinkErrors++;
|
|
753
|
+
warnOnce(
|
|
754
|
+
"entry",
|
|
755
|
+
"[telemetry] subjectLinker returned an entry that is not { type, id } \u2014 dropped. Warned once \u2014 the count is counters.subjectLinkErrors."
|
|
756
|
+
);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const key = `${ref.type}:${ref.id}`;
|
|
760
|
+
if (seen.has(key)) continue;
|
|
761
|
+
if (!spec.subjects.includes(ref.type)) {
|
|
762
|
+
counters.subjectLinkUndeclared++;
|
|
763
|
+
}
|
|
764
|
+
if (room <= 0) {
|
|
765
|
+
capped2++;
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
seen.add(key);
|
|
769
|
+
room--;
|
|
770
|
+
add.push(ref);
|
|
771
|
+
}
|
|
772
|
+
counters.subjectLinkCapped += capped2;
|
|
773
|
+
if (!add.length) return null;
|
|
774
|
+
counters.subjectsLinked += add.length;
|
|
775
|
+
return [...have, ...add];
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function wellFormed(s) {
|
|
779
|
+
if (!s || typeof s !== "object") return null;
|
|
780
|
+
const { type, id, role } = s;
|
|
781
|
+
if (typeof type !== "string" || !type) return null;
|
|
782
|
+
if (typeof id !== "string" || !id) return null;
|
|
783
|
+
return typeof role === "string" && role ? { type, id, role } : { type, id };
|
|
784
|
+
}
|
|
675
785
|
function createEmitter(ctx) {
|
|
676
786
|
const { registry, byKind, RollupModel, rejects, counters } = ctx;
|
|
677
787
|
const burstBuckets = /* @__PURE__ */ new Map();
|
|
@@ -708,11 +818,15 @@ function createEmitter(ctx) {
|
|
|
708
818
|
const durable = kind === TelemetryKind.Usage || (doc.durable ?? spec.durable ?? false);
|
|
709
819
|
const Model = byKind[kind];
|
|
710
820
|
const { forceKeep: _drop, durable: _durable, ...rest } = doc;
|
|
821
|
+
const linked = ctx.linkSubjects ? await ctx.linkSubjects(name, spec, doc.tenantId, doc.subjects) : null;
|
|
711
822
|
const safe = (o) => new Map(Object.entries(o ?? {}).map(([k, v]) => [k.replace(/\./g, "_"), v]));
|
|
712
823
|
const payload = {
|
|
713
824
|
...rest,
|
|
714
825
|
_id: id,
|
|
715
826
|
name,
|
|
827
|
+
// computed like everything below it, and absent when nothing linked, so a
|
|
828
|
+
// host with no linker hands the model the exact object 0.4.0 did
|
|
829
|
+
...linked ? { subjects: linked } : {},
|
|
716
830
|
sampleRate: forced ? 1 : baseRate,
|
|
717
831
|
forced,
|
|
718
832
|
attrs: safe(doc.attrs),
|
|
@@ -1221,6 +1335,8 @@ function createIngest(opts) {
|
|
|
1221
1335
|
const occurredAt = Number.isFinite(occurredRaw) ? new Date(occurredRaw - clockSkewMs) : receivedAt;
|
|
1222
1336
|
const safeMap = (o) => o && typeof o === "object" ? new Map(Object.entries(o).map(([k, v]) => [k.replace(/\./g, "_"), v])) : /* @__PURE__ */ new Map();
|
|
1223
1337
|
noteUndeclaredAttrs(t.counters, name, spec, rec.attrs);
|
|
1338
|
+
const subjects = mergeSubjects(rec.subjects);
|
|
1339
|
+
const linked = t.linkSubjects ? await t.linkSubjects(name, spec, tenantId, subjects) : null;
|
|
1224
1340
|
const Model = t.models.byKind[spec.kind];
|
|
1225
1341
|
const d = new Model({
|
|
1226
1342
|
// facts the wire may not assert: tenant, service, env, origin, plane
|
|
@@ -1231,7 +1347,7 @@ function createIngest(opts) {
|
|
|
1231
1347
|
tenantId,
|
|
1232
1348
|
occurredAt,
|
|
1233
1349
|
severity: typeof rec.severity === "string" ? rec.severity : void 0,
|
|
1234
|
-
subjects:
|
|
1350
|
+
subjects: linked ?? subjects,
|
|
1235
1351
|
actor: ctx.actor ?? (typeof rec.actor === "string" ? rec.actor : batchActor),
|
|
1236
1352
|
onBehalfOf: typeof rec.onBehalfOf === "string" ? rec.onBehalfOf : void 0,
|
|
1237
1353
|
service: key.service,
|
|
@@ -3634,7 +3750,22 @@ function createTelemetry(config) {
|
|
|
3634
3750
|
inFlight.add(p);
|
|
3635
3751
|
void p.finally(() => inFlight.delete(p));
|
|
3636
3752
|
};
|
|
3637
|
-
const
|
|
3753
|
+
const linkSubjects = createSubjectLinking({
|
|
3754
|
+
linker: config.subjectLinker,
|
|
3755
|
+
timeoutMs: config.subjectLinkTimeoutMs,
|
|
3756
|
+
counters,
|
|
3757
|
+
logger
|
|
3758
|
+
});
|
|
3759
|
+
const emit = createEmitter({
|
|
3760
|
+
registry,
|
|
3761
|
+
byKind,
|
|
3762
|
+
RollupModel,
|
|
3763
|
+
rejects,
|
|
3764
|
+
counters,
|
|
3765
|
+
logger,
|
|
3766
|
+
track,
|
|
3767
|
+
linkSubjects
|
|
3768
|
+
});
|
|
3638
3769
|
const forget = createForget({
|
|
3639
3770
|
TelemetryModel,
|
|
3640
3771
|
RollupModel,
|
|
@@ -3697,6 +3828,17 @@ function createTelemetry(config) {
|
|
|
3697
3828
|
counters,
|
|
3698
3829
|
/** the registry, exposed for the router factories — hosts should import their own */
|
|
3699
3830
|
registry,
|
|
3831
|
+
/**
|
|
3832
|
+
* Write-time subject linking, exposed for the router factories. `null` when
|
|
3833
|
+
* no `subjectLinker` is configured.
|
|
3834
|
+
*
|
|
3835
|
+
* The wire path does not go through emit() — createIngest() builds its
|
|
3836
|
+
* record itself, because at-least-once delivery inverts the plane order
|
|
3837
|
+
* (insert first, THEN aggregate). So it reaches the linker the same way it
|
|
3838
|
+
* reaches the registry and the models: off the instance, running the one
|
|
3839
|
+
* implementation, rather than growing a second copy of the rules.
|
|
3840
|
+
*/
|
|
3841
|
+
linkSubjects,
|
|
3700
3842
|
logger,
|
|
3701
3843
|
/** mint an ingest key; the full key string is returned once, never again */
|
|
3702
3844
|
createKey: (input) => createKey(KeyModel, input),
|
|
@@ -3727,6 +3869,8 @@ exports.PLATFORM_SCOPE = PLATFORM_SCOPE;
|
|
|
3727
3869
|
exports.RETENTION_DAYS = RETENTION_DAYS;
|
|
3728
3870
|
exports.SAMPLE_RATE = SAMPLE_RATE;
|
|
3729
3871
|
exports.SCHEMA_VERSION = SCHEMA_VERSION;
|
|
3872
|
+
exports.SUBJECT_LINK_TIMEOUT_MS = SUBJECT_LINK_TIMEOUT_MS;
|
|
3873
|
+
exports.SUBJECT_MAX = SUBJECT_MAX;
|
|
3730
3874
|
exports.TelemetryKind = TelemetryKind;
|
|
3731
3875
|
exports.TenantMode = TenantMode;
|
|
3732
3876
|
exports.boundedMeta = boundedMeta;
|