@fedify/fedify 2.3.14 → 2.3.15
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/{builder-C6yXExXl.mjs → builder-B6vSXYxn.mjs} +2 -2
- package/dist/{client-CAM_bQXx.d.cts → client-BIBzqEIp.d.cts} +0 -1
- package/dist/{client-1He2YuBF.mjs → client-DpUNYKla.mjs} +113 -64
- package/dist/{client-CSddvgWN.d.ts → client-jE7oGvRh.d.ts} +0 -1
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{context-MOrCC7yl.d.cts → context-CE_Tj_Oc.d.cts} +28 -2
- package/dist/{context-BWbdE_MZ.d.ts → context-H_5tZXg-.d.ts} +28 -2
- package/dist/{deno-y3L4Oa94.mjs → deno-BCuL3mid.mjs} +1 -1
- package/dist/docloader-D2dr1_6B.mjs +125 -0
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +455 -29
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/send.test.mjs +498 -88
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{http-DNM9KyIO.js → http-BkjxY3U0.js} +172 -103
- package/dist/{http-BCSf1KIY.mjs → http-CSDcThvz.mjs} +196 -98
- package/dist/{http-DQmNpfym.d.ts → http-CZPYFK3b.d.cts} +1 -2
- package/dist/{http-DiWQB8Mr.d.cts → http-DmP0sr2l.d.ts} +1 -2
- package/dist/{http-D77Wjrcg.cjs → http-gOkAiIe1.cjs} +173 -104
- package/dist/{inbox-NObX26c9.mjs → inbox-BHxez4d5.mjs} +2 -2
- package/dist/{key-APURZ0ky.mjs → key-D8IMQ72M.mjs} +2 -2
- package/dist/{kv-cache-Bg4utAuv.cjs → kv-cache-Alm5UmJa.cjs} +59 -17
- package/dist/{kv-cache-C69dzgQi.mjs → kv-cache-Blr9nDDp.mjs} +1 -1
- package/dist/{kv-cache-LBCwHNLu.js → kv-cache-CkXgU-ak.js} +60 -18
- package/dist/{ld-BoLqbDaL.mjs → ld-CVxxE1Pp.mjs} +3 -3
- package/dist/{metrics-DBSl8wEr.mjs → metrics-SYIZHFf_.mjs} +1 -1
- package/dist/{middleware-BQUQ0pPl.cjs → middleware-4yPyyoF5.cjs} +237 -99
- package/dist/{middleware-CZ8tI1qL.mjs → middleware-CfskJGfV.mjs} +81 -41
- package/dist/{middleware-DAqo2eOY.mjs → middleware-D3JWfiCu.mjs} +1 -1
- package/dist/{middleware-CRd1JmuL.js → middleware-DhN7c65B.js} +238 -100
- package/dist/{mod-LmD03uRA.d.ts → mod-CDFFESfL.d.ts} +1 -1
- package/dist/{mod-Bid79LFU.d.cts → mod-C_gFmeNq.d.cts} +1 -1
- package/dist/{mod-Co1M3Ux0.d.cts → mod-DDVkmZ_h.d.cts} +2 -8
- package/dist/{mod-B6PwkUMU.d.cts → mod-DDjtbkZO.d.cts} +1 -1
- package/dist/{mod-Br8yJNcE.d.ts → mod-DMjhnFbg.d.ts} +2 -8
- package/dist/{mod-TLEvgT1i.d.ts → mod-HQXFvOHs.d.ts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +337 -19
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/otel/exporter.test.mjs +292 -30
- package/dist/otel/mod.cjs +36 -30
- package/dist/otel/mod.d.cts +13 -0
- package/dist/otel/mod.d.ts +13 -0
- package/dist/otel/mod.js +36 -30
- package/dist/{owner-DjKvngeb.mjs → owner-BluLVPf0.mjs} +2 -2
- package/dist/{proof-CWTnYhjS.cjs → proof-CR-HwI0l.cjs} +1 -1
- package/dist/{proof-D2oFOdNA.mjs → proof-Co6nQBVR.mjs} +3 -3
- package/dist/{proof-s9sy44kx.js → proof-Dg0d9QBo.js} +1 -1
- package/dist/{send-xDK5UnL-.mjs → send-D_DnqOFA.mjs} +171 -73
- package/dist/sig/http.test.mjs +528 -2
- package/dist/sig/key.test.mjs +2 -2
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/proof.test.mjs +1 -1
- package/dist/{temporal-C6icc5n9.mjs → temporal-B8FUNkXB.mjs} +1 -1
- package/dist/testing/mod.d.mts +26 -0
- package/dist/{types-miAYfiGO.js → types-B1_pRFE_.js} +112 -63
- package/dist/{types-DRPjmHVV.cjs → types-DOvyymM4.cjs} +111 -62
- package/dist/utils/docloader.test.mjs +318 -34
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +8 -8
- package/dist/docloader-yuyzZC9a.mjs +0 -83
|
@@ -3,6 +3,7 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
|
|
5
5
|
import "../std__assert-BjJn2daJ.mjs";
|
|
6
|
+
import { t as assert } from "../assert-CEO06XCw.mjs";
|
|
6
7
|
import { t as MemoryKvStore } from "../kv-mI4Ix2EU.mjs";
|
|
7
8
|
import { getLogger } from "@logtape/logtape";
|
|
8
9
|
import { ExportResultCode } from "@opentelemetry/core";
|
|
@@ -194,33 +195,6 @@ var FedifySpanExporter = class {
|
|
|
194
195
|
record.spanId
|
|
195
196
|
];
|
|
196
197
|
await this.#kv.set(key, record, options);
|
|
197
|
-
await this.#updateTraceSummary(record, options);
|
|
198
|
-
}
|
|
199
|
-
async #setWithCasRetry(key, transform, options) {
|
|
200
|
-
if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
|
|
201
|
-
const existing = await this.#kv.get(key);
|
|
202
|
-
const newValue = transform(existing);
|
|
203
|
-
if (await this.#kv.cas(key, existing, newValue, options)) return;
|
|
204
|
-
}
|
|
205
|
-
const newValue = transform(await this.#kv.get(key));
|
|
206
|
-
await this.#kv.set(key, newValue, options);
|
|
207
|
-
}
|
|
208
|
-
async #updateTraceSummary(record, options) {
|
|
209
|
-
const summaryKey = [
|
|
210
|
-
...this.#keyPrefix,
|
|
211
|
-
"_summaries",
|
|
212
|
-
record.traceId
|
|
213
|
-
];
|
|
214
|
-
await this.#setWithCasRetry(summaryKey, (existing) => {
|
|
215
|
-
const activityCount = existing != null ? existing.activityCount + 1 : 1;
|
|
216
|
-
const activityTypes = existing != null ? existing.activityTypes.includes(record.activityType) ? existing.activityTypes : [...existing.activityTypes, record.activityType] : [record.activityType];
|
|
217
|
-
return {
|
|
218
|
-
traceId: existing?.traceId ?? record.traceId,
|
|
219
|
-
timestamp: existing?.timestamp ?? record.timestamp,
|
|
220
|
-
activityCount,
|
|
221
|
-
activityTypes
|
|
222
|
-
};
|
|
223
|
-
}, options);
|
|
224
198
|
}
|
|
225
199
|
/**
|
|
226
200
|
* Gets all activity records for a specific trace ID.
|
|
@@ -237,13 +211,46 @@ var FedifySpanExporter = class {
|
|
|
237
211
|
/**
|
|
238
212
|
* Gets recent traces with summary information.
|
|
239
213
|
*
|
|
214
|
+
* Each summary is computed directly from the activity records currently
|
|
215
|
+
* stored for each trace, rather than from a separately maintained
|
|
216
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
217
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
218
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
219
|
+
* exporter) never completed, and that the count always reflects
|
|
220
|
+
* records that have not expired. It costs a full scan of every stored
|
|
221
|
+
* record on every call, including full inbound activity JSON. The
|
|
222
|
+
* `limit` option only truncates the returned summaries and does not
|
|
223
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
224
|
+
* account for the total retained data when choosing a TTL and polling
|
|
225
|
+
* interval.
|
|
226
|
+
*
|
|
240
227
|
* @param options Options for the query.
|
|
241
228
|
* @returns An array of trace summaries.
|
|
242
229
|
*/
|
|
243
230
|
async getRecentTraces(options) {
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
for await (const entry of this.#kv.list(
|
|
231
|
+
const aggregates = /* @__PURE__ */ new Map();
|
|
232
|
+
const traceIdIndex = this.#keyPrefix.length;
|
|
233
|
+
for await (const entry of this.#kv.list(this.#keyPrefix)) {
|
|
234
|
+
const traceId = entry.key[traceIdIndex];
|
|
235
|
+
if (traceId == null || traceId === "_summaries") continue;
|
|
236
|
+
const record = entry.value;
|
|
237
|
+
if (record == null || typeof record.timestamp !== "string") continue;
|
|
238
|
+
const aggregate = aggregates.get(traceId);
|
|
239
|
+
if (aggregate == null) aggregates.set(traceId, {
|
|
240
|
+
timestamp: record.timestamp,
|
|
241
|
+
activityCount: 1,
|
|
242
|
+
activityTypes: [record.activityType]
|
|
243
|
+
});
|
|
244
|
+
else {
|
|
245
|
+
aggregate.activityCount++;
|
|
246
|
+
if (record.timestamp < aggregate.timestamp) aggregate.timestamp = record.timestamp;
|
|
247
|
+
if (!aggregate.activityTypes.includes(record.activityType)) aggregate.activityTypes.push(record.activityType);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const summaries = [...aggregates].map(([traceId, aggregate]) => ({
|
|
251
|
+
traceId,
|
|
252
|
+
...aggregate
|
|
253
|
+
}));
|
|
247
254
|
summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
248
255
|
if (options?.limit != null) return summaries.slice(0, options.limit);
|
|
249
256
|
return summaries;
|
|
@@ -509,6 +516,261 @@ test("FedifySpanExporter", async (t) => {
|
|
|
509
516
|
}
|
|
510
517
|
assertEquals((await exporter.getRecentTraces()).length >= 3, true);
|
|
511
518
|
});
|
|
519
|
+
function createOutboundSpan(traceId, spanId) {
|
|
520
|
+
return createMockSpan({
|
|
521
|
+
traceId,
|
|
522
|
+
spanId,
|
|
523
|
+
name: "activitypub.send_activity",
|
|
524
|
+
events: [createActivitySentEvent({
|
|
525
|
+
activityJson: JSON.stringify({
|
|
526
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
527
|
+
type: "Create",
|
|
528
|
+
id: `https://local.example/activities/${spanId}`
|
|
529
|
+
}),
|
|
530
|
+
inboxUrl: "https://follower.example/inbox"
|
|
531
|
+
})]
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
async function exportSpans(exporter, spans) {
|
|
535
|
+
assertEquals((await new Promise((resolve) => {
|
|
536
|
+
exporter.export(spans, resolve);
|
|
537
|
+
})).code, 0);
|
|
538
|
+
}
|
|
539
|
+
for (const withCas of [true, false]) {
|
|
540
|
+
const createStore = () => {
|
|
541
|
+
const memory = new MemoryKvStore();
|
|
542
|
+
return withCas ? memory : {
|
|
543
|
+
get: memory.get.bind(memory),
|
|
544
|
+
set: memory.set.bind(memory),
|
|
545
|
+
delete: memory.delete.bind(memory),
|
|
546
|
+
list: memory.list.bind(memory)
|
|
547
|
+
};
|
|
548
|
+
};
|
|
549
|
+
await t.step(`re-export counts each retained span once (CAS=${withCas})`, async () => {
|
|
550
|
+
const kv = createStore();
|
|
551
|
+
const exporters = [new FedifySpanExporter(kv), new FedifySpanExporter(kv)];
|
|
552
|
+
const span = createOutboundSpan("reexport-trace", "span-a");
|
|
553
|
+
await exportSpans(exporters[0], [span]);
|
|
554
|
+
await exportSpans(exporters[0], [span]);
|
|
555
|
+
assertEquals((await exporters[1].getRecentTraces())[0].activityCount, 1);
|
|
556
|
+
await Promise.all(exporters.map((exporter) => exportSpans(exporter, [span])));
|
|
557
|
+
assertEquals((await exporters[0].getActivitiesByTraceId("reexport-trace")).length, 1);
|
|
558
|
+
assertEquals((await exporters[1].getRecentTraces())[0].activityCount, 1);
|
|
559
|
+
});
|
|
560
|
+
await t.step(`concurrent distinct spans are all counted (CAS=${withCas})`, async () => {
|
|
561
|
+
const kv = createStore();
|
|
562
|
+
const exporters = [new FedifySpanExporter(kv), new FedifySpanExporter(kv)];
|
|
563
|
+
await Promise.all(exporters.map((exporter, i) => exportSpans(exporter, [createOutboundSpan("concurrent-count-trace", `span-${i}`)])));
|
|
564
|
+
const reader = new FedifySpanExporter(kv);
|
|
565
|
+
assertEquals((await reader.getActivitiesByTraceId("concurrent-count-trace")).length, 2);
|
|
566
|
+
assertEquals((await reader.getRecentTraces())[0].activityCount, 2);
|
|
567
|
+
});
|
|
568
|
+
await t.step(`legacy summaries cannot hide or inflate retained records (CAS=${withCas})`, async () => {
|
|
569
|
+
const kv = createStore();
|
|
570
|
+
const exporter = new FedifySpanExporter(kv);
|
|
571
|
+
await exportSpans(exporter, [createOutboundSpan("legacy-trace", "span-a")]);
|
|
572
|
+
await kv.delete([
|
|
573
|
+
"fedify",
|
|
574
|
+
"traces",
|
|
575
|
+
"_summaries",
|
|
576
|
+
"legacy-trace"
|
|
577
|
+
]);
|
|
578
|
+
const originalSet = kv.set.bind(kv);
|
|
579
|
+
let writes = 0;
|
|
580
|
+
kv.set = (...args) => {
|
|
581
|
+
writes++;
|
|
582
|
+
return originalSet(...args);
|
|
583
|
+
};
|
|
584
|
+
assertEquals((await exporter.getRecentTraces())[0].activityCount, 1);
|
|
585
|
+
assertEquals(writes, 0);
|
|
586
|
+
const stale = {
|
|
587
|
+
traceId: "legacy-trace",
|
|
588
|
+
timestamp: "2000-01-01T00:00:00.000Z",
|
|
589
|
+
activityCount: 99,
|
|
590
|
+
activityTypes: ["Bogus"]
|
|
591
|
+
};
|
|
592
|
+
await kv.set([
|
|
593
|
+
"fedify",
|
|
594
|
+
"traces",
|
|
595
|
+
"_summaries",
|
|
596
|
+
"legacy-trace"
|
|
597
|
+
], stale);
|
|
598
|
+
await kv.set([
|
|
599
|
+
"fedify",
|
|
600
|
+
"traces",
|
|
601
|
+
"_summaries",
|
|
602
|
+
"ghost-trace"
|
|
603
|
+
], {
|
|
604
|
+
...stale,
|
|
605
|
+
traceId: "ghost-trace"
|
|
606
|
+
});
|
|
607
|
+
const records = await exporter.getActivitiesByTraceId("legacy-trace");
|
|
608
|
+
assertEquals(await exporter.getRecentTraces(), [{
|
|
609
|
+
traceId: "legacy-trace",
|
|
610
|
+
timestamp: records[0].timestamp,
|
|
611
|
+
activityCount: 1,
|
|
612
|
+
activityTypes: ["Create"]
|
|
613
|
+
}]);
|
|
614
|
+
await kv.delete([
|
|
615
|
+
"fedify",
|
|
616
|
+
"traces",
|
|
617
|
+
"legacy-trace",
|
|
618
|
+
"span-a"
|
|
619
|
+
]);
|
|
620
|
+
assertEquals(await exporter.getRecentTraces(), []);
|
|
621
|
+
});
|
|
622
|
+
await t.step(`summary metadata follows retained records (CAS=${withCas})`, async () => {
|
|
623
|
+
const kv = createStore();
|
|
624
|
+
const exporter = new FedifySpanExporter(kv, { keyPrefix: ["custom"] });
|
|
625
|
+
function span(traceId, spanId, type, seconds) {
|
|
626
|
+
return createMockSpan({
|
|
627
|
+
traceId,
|
|
628
|
+
spanId,
|
|
629
|
+
events: [{
|
|
630
|
+
...createActivitySentEvent({
|
|
631
|
+
activityJson: JSON.stringify({ type }),
|
|
632
|
+
inboxUrl: "https://example.com/inbox"
|
|
633
|
+
}),
|
|
634
|
+
time: [seconds, 0]
|
|
635
|
+
}]
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
await exportSpans(exporter, [
|
|
639
|
+
span("older-trace", "late", "Like", 1700000002),
|
|
640
|
+
span("older-trace", "early", "Create", 17e8),
|
|
641
|
+
span("older-trace", "duplicate-type", "Like", 1700000003),
|
|
642
|
+
span("newer-trace", "only", "Follow", 1700000001)
|
|
643
|
+
]);
|
|
644
|
+
const recent = await exporter.getRecentTraces();
|
|
645
|
+
assertEquals(recent.map((summary) => summary.traceId), ["newer-trace", "older-trace"]);
|
|
646
|
+
assertEquals(recent[1], {
|
|
647
|
+
traceId: "older-trace",
|
|
648
|
+
timestamp: (/* @__PURE__ */ new Date(17e11)).toISOString(),
|
|
649
|
+
activityCount: 3,
|
|
650
|
+
activityTypes: ["Like", "Create"]
|
|
651
|
+
});
|
|
652
|
+
assertEquals(await exporter.getRecentTraces({ limit: 1 }), [recent[0]]);
|
|
653
|
+
await kv.delete([
|
|
654
|
+
"custom",
|
|
655
|
+
"older-trace",
|
|
656
|
+
"early"
|
|
657
|
+
]);
|
|
658
|
+
assertEquals((await exporter.getRecentTraces())[0], {
|
|
659
|
+
traceId: "older-trace",
|
|
660
|
+
timestamp: (/* @__PURE__ */ new Date(1700000002e3)).toISOString(),
|
|
661
|
+
activityCount: 2,
|
|
662
|
+
activityTypes: ["Like"]
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
await t.step("a trace with only one record is discoverable via getRecentTraces(), with no further write", async () => {
|
|
667
|
+
const exporter = new FedifySpanExporter(new MemoryKvStore());
|
|
668
|
+
const traceId = "first-write-discoverable-trace";
|
|
669
|
+
await new Promise((resolve) => {
|
|
670
|
+
exporter.export([createOutboundSpan(traceId, "span-a")], () => resolve());
|
|
671
|
+
});
|
|
672
|
+
assertEquals((await exporter.getRecentTraces()).find((s) => s.traceId === traceId)?.activityCount, 1);
|
|
673
|
+
});
|
|
674
|
+
await t.step("getRecentTraces() needs no additional writes to discover a trace or correct its activityCount", async () => {
|
|
675
|
+
const kv = new MemoryKvStore();
|
|
676
|
+
const exporter = new FedifySpanExporter(kv);
|
|
677
|
+
const traceId = "query-time-correction-trace";
|
|
678
|
+
await new Promise((resolve) => {
|
|
679
|
+
exporter.export([createOutboundSpan(traceId, "span-a")], () => resolve());
|
|
680
|
+
});
|
|
681
|
+
await new Promise((resolve) => {
|
|
682
|
+
exporter.export([createOutboundSpan(traceId, "span-b")], () => resolve());
|
|
683
|
+
});
|
|
684
|
+
const originalSet = kv.set.bind(kv);
|
|
685
|
+
let writeCalls = 0;
|
|
686
|
+
kv.set = (key, value, options) => {
|
|
687
|
+
writeCalls++;
|
|
688
|
+
return originalSet(key, value, options);
|
|
689
|
+
};
|
|
690
|
+
assertEquals((await exporter.getRecentTraces()).find((s) => s.traceId === traceId)?.activityCount, 2);
|
|
691
|
+
assertEquals(writeCalls, 0);
|
|
692
|
+
});
|
|
693
|
+
await t.step("getRecentTraces() reflects only currently-retained records, e.g., after some have expired", async () => {
|
|
694
|
+
const kv = new MemoryKvStore();
|
|
695
|
+
const exporter = new FedifySpanExporter(kv);
|
|
696
|
+
const traceId = "expiry-reflects-retained-trace";
|
|
697
|
+
await new Promise((resolve) => {
|
|
698
|
+
exporter.export([createOutboundSpan(traceId, "span-a"), createOutboundSpan(traceId, "span-b")], () => resolve());
|
|
699
|
+
});
|
|
700
|
+
let recent = await exporter.getRecentTraces();
|
|
701
|
+
assertEquals(recent.find((s) => s.traceId === traceId)?.activityCount, 2);
|
|
702
|
+
await kv.delete([
|
|
703
|
+
"fedify",
|
|
704
|
+
"traces",
|
|
705
|
+
traceId,
|
|
706
|
+
"span-a"
|
|
707
|
+
]);
|
|
708
|
+
recent = await exporter.getRecentTraces();
|
|
709
|
+
assertEquals(recent.find((s) => s.traceId === traceId)?.activityCount, 1);
|
|
710
|
+
});
|
|
711
|
+
await t.step("getRecentTraces() polling cost scales with every retained record, not just records changed since the last poll, and limit does not reduce it", async () => {
|
|
712
|
+
const kv = new MemoryKvStore();
|
|
713
|
+
const exporter = new FedifySpanExporter(kv);
|
|
714
|
+
const traceCount = 20;
|
|
715
|
+
const recordsPerTrace = 5;
|
|
716
|
+
for (let i = 0; i < traceCount; i++) {
|
|
717
|
+
const traceId = `poll-cost-trace-${i}`;
|
|
718
|
+
const spans = Array.from({ length: recordsPerTrace }, (_, j) => createOutboundSpan(traceId, `span-${j}`));
|
|
719
|
+
await new Promise((resolve) => {
|
|
720
|
+
exporter.export(spans, () => resolve());
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
const originalList = kv.list.bind(kv);
|
|
724
|
+
let entriesRead = 0;
|
|
725
|
+
let bytesRead = 0;
|
|
726
|
+
kv.list = async function* (prefix) {
|
|
727
|
+
for await (const entry of originalList(prefix)) {
|
|
728
|
+
entriesRead++;
|
|
729
|
+
bytesRead += new TextEncoder().encode(JSON.stringify(entry.value)).byteLength;
|
|
730
|
+
yield entry;
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
assertEquals((await exporter.getRecentTraces()).length, traceCount);
|
|
734
|
+
assertEquals(entriesRead, traceCount * recordsPerTrace);
|
|
735
|
+
assert(bytesRead > 0);
|
|
736
|
+
const entriesReadForFullScan = entriesRead;
|
|
737
|
+
const bytesReadForFullScan = bytesRead;
|
|
738
|
+
entriesRead = 0;
|
|
739
|
+
bytesRead = 0;
|
|
740
|
+
assertEquals((await exporter.getRecentTraces({ limit: 3 })).length, 3);
|
|
741
|
+
assertEquals(entriesRead, entriesReadForFullScan);
|
|
742
|
+
assertEquals(bytesRead, bytesReadForFullScan);
|
|
743
|
+
});
|
|
744
|
+
await t.step("recent-list scans include full inbound payloads even with limit", async () => {
|
|
745
|
+
const kv = new MemoryKvStore();
|
|
746
|
+
const exporter = new FedifySpanExporter(kv);
|
|
747
|
+
const activityJson = JSON.stringify({
|
|
748
|
+
type: "Create",
|
|
749
|
+
object: {
|
|
750
|
+
type: "Note",
|
|
751
|
+
content: "한".repeat(64 * 1024)
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
await exportSpans(exporter, Array.from({ length: 10 }, (_, i) => createMockSpan({
|
|
755
|
+
traceId: `large-inbound-${i}`,
|
|
756
|
+
spanId: "inbound",
|
|
757
|
+
events: [createActivityReceivedEvent({ activityJson })]
|
|
758
|
+
})));
|
|
759
|
+
const originalList = kv.list.bind(kv);
|
|
760
|
+
let entriesRead = 0;
|
|
761
|
+
let bytesRead = 0;
|
|
762
|
+
kv.list = async function* (prefix) {
|
|
763
|
+
for await (const entry of originalList(prefix)) {
|
|
764
|
+
entriesRead++;
|
|
765
|
+
bytesRead += new TextEncoder().encode(JSON.stringify(entry.value)).byteLength;
|
|
766
|
+
yield entry;
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
assertEquals((await exporter.getRecentTraces({ limit: 1 })).length, 1);
|
|
770
|
+
assertEquals(entriesRead, 10);
|
|
771
|
+
assert(bytesRead >= new TextEncoder().encode(activityJson).byteLength * 10);
|
|
772
|
+
assert(bytesRead > 1.8 * 1024 * 1024);
|
|
773
|
+
});
|
|
512
774
|
await t.step("forceFlush() returns resolved promise", async () => {
|
|
513
775
|
await new FedifySpanExporter(new MemoryKvStore()).forceFlush();
|
|
514
776
|
});
|
package/dist/otel/mod.cjs
CHANGED
|
@@ -190,33 +190,6 @@ var FedifySpanExporter = class {
|
|
|
190
190
|
record.spanId
|
|
191
191
|
];
|
|
192
192
|
await this.#kv.set(key, record, options);
|
|
193
|
-
await this.#updateTraceSummary(record, options);
|
|
194
|
-
}
|
|
195
|
-
async #setWithCasRetry(key, transform, options) {
|
|
196
|
-
if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
|
|
197
|
-
const existing = await this.#kv.get(key);
|
|
198
|
-
const newValue = transform(existing);
|
|
199
|
-
if (await this.#kv.cas(key, existing, newValue, options)) return;
|
|
200
|
-
}
|
|
201
|
-
const newValue = transform(await this.#kv.get(key));
|
|
202
|
-
await this.#kv.set(key, newValue, options);
|
|
203
|
-
}
|
|
204
|
-
async #updateTraceSummary(record, options) {
|
|
205
|
-
const summaryKey = [
|
|
206
|
-
...this.#keyPrefix,
|
|
207
|
-
"_summaries",
|
|
208
|
-
record.traceId
|
|
209
|
-
];
|
|
210
|
-
await this.#setWithCasRetry(summaryKey, (existing) => {
|
|
211
|
-
const activityCount = existing != null ? existing.activityCount + 1 : 1;
|
|
212
|
-
const activityTypes = existing != null ? existing.activityTypes.includes(record.activityType) ? existing.activityTypes : [...existing.activityTypes, record.activityType] : [record.activityType];
|
|
213
|
-
return {
|
|
214
|
-
traceId: existing?.traceId ?? record.traceId,
|
|
215
|
-
timestamp: existing?.timestamp ?? record.timestamp,
|
|
216
|
-
activityCount,
|
|
217
|
-
activityTypes
|
|
218
|
-
};
|
|
219
|
-
}, options);
|
|
220
193
|
}
|
|
221
194
|
/**
|
|
222
195
|
* Gets all activity records for a specific trace ID.
|
|
@@ -233,13 +206,46 @@ var FedifySpanExporter = class {
|
|
|
233
206
|
/**
|
|
234
207
|
* Gets recent traces with summary information.
|
|
235
208
|
*
|
|
209
|
+
* Each summary is computed directly from the activity records currently
|
|
210
|
+
* stored for each trace, rather than from a separately maintained
|
|
211
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
212
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
213
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
214
|
+
* exporter) never completed, and that the count always reflects
|
|
215
|
+
* records that have not expired. It costs a full scan of every stored
|
|
216
|
+
* record on every call, including full inbound activity JSON. The
|
|
217
|
+
* `limit` option only truncates the returned summaries and does not
|
|
218
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
219
|
+
* account for the total retained data when choosing a TTL and polling
|
|
220
|
+
* interval.
|
|
221
|
+
*
|
|
236
222
|
* @param options Options for the query.
|
|
237
223
|
* @returns An array of trace summaries.
|
|
238
224
|
*/
|
|
239
225
|
async getRecentTraces(options) {
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
for await (const entry of this.#kv.list(
|
|
226
|
+
const aggregates = /* @__PURE__ */ new Map();
|
|
227
|
+
const traceIdIndex = this.#keyPrefix.length;
|
|
228
|
+
for await (const entry of this.#kv.list(this.#keyPrefix)) {
|
|
229
|
+
const traceId = entry.key[traceIdIndex];
|
|
230
|
+
if (traceId == null || traceId === "_summaries") continue;
|
|
231
|
+
const record = entry.value;
|
|
232
|
+
if (record == null || typeof record.timestamp !== "string") continue;
|
|
233
|
+
const aggregate = aggregates.get(traceId);
|
|
234
|
+
if (aggregate == null) aggregates.set(traceId, {
|
|
235
|
+
timestamp: record.timestamp,
|
|
236
|
+
activityCount: 1,
|
|
237
|
+
activityTypes: [record.activityType]
|
|
238
|
+
});
|
|
239
|
+
else {
|
|
240
|
+
aggregate.activityCount++;
|
|
241
|
+
if (record.timestamp < aggregate.timestamp) aggregate.timestamp = record.timestamp;
|
|
242
|
+
if (!aggregate.activityTypes.includes(record.activityType)) aggregate.activityTypes.push(record.activityType);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const summaries = [...aggregates].map(([traceId, aggregate]) => ({
|
|
246
|
+
traceId,
|
|
247
|
+
...aggregate
|
|
248
|
+
}));
|
|
243
249
|
summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
244
250
|
if (options?.limit != null) return summaries.slice(0, options.limit);
|
|
245
251
|
return summaries;
|
package/dist/otel/mod.d.cts
CHANGED
|
@@ -226,6 +226,19 @@ declare class FedifySpanExporter implements SpanExporter {
|
|
|
226
226
|
/**
|
|
227
227
|
* Gets recent traces with summary information.
|
|
228
228
|
*
|
|
229
|
+
* Each summary is computed directly from the activity records currently
|
|
230
|
+
* stored for each trace, rather than from a separately maintained
|
|
231
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
232
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
233
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
234
|
+
* exporter) never completed, and that the count always reflects
|
|
235
|
+
* records that have not expired. It costs a full scan of every stored
|
|
236
|
+
* record on every call, including full inbound activity JSON. The
|
|
237
|
+
* `limit` option only truncates the returned summaries and does not
|
|
238
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
239
|
+
* account for the total retained data when choosing a TTL and polling
|
|
240
|
+
* interval.
|
|
241
|
+
*
|
|
229
242
|
* @param options Options for the query.
|
|
230
243
|
* @returns An array of trace summaries.
|
|
231
244
|
*/
|
package/dist/otel/mod.d.ts
CHANGED
|
@@ -226,6 +226,19 @@ declare class FedifySpanExporter implements SpanExporter {
|
|
|
226
226
|
/**
|
|
227
227
|
* Gets recent traces with summary information.
|
|
228
228
|
*
|
|
229
|
+
* Each summary is computed directly from the activity records currently
|
|
230
|
+
* stored for each trace, rather than from a separately maintained
|
|
231
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
232
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
233
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
234
|
+
* exporter) never completed, and that the count always reflects
|
|
235
|
+
* records that have not expired. It costs a full scan of every stored
|
|
236
|
+
* record on every call, including full inbound activity JSON. The
|
|
237
|
+
* `limit` option only truncates the returned summaries and does not
|
|
238
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
239
|
+
* account for the total retained data when choosing a TTL and polling
|
|
240
|
+
* interval.
|
|
241
|
+
*
|
|
229
242
|
* @param options Options for the query.
|
|
230
243
|
* @returns An array of trace summaries.
|
|
231
244
|
*/
|
package/dist/otel/mod.js
CHANGED
|
@@ -188,33 +188,6 @@ var FedifySpanExporter = class {
|
|
|
188
188
|
record.spanId
|
|
189
189
|
];
|
|
190
190
|
await this.#kv.set(key, record, options);
|
|
191
|
-
await this.#updateTraceSummary(record, options);
|
|
192
|
-
}
|
|
193
|
-
async #setWithCasRetry(key, transform, options) {
|
|
194
|
-
if (this.#kv.cas != null) for (let attempt = 0; attempt < 3; attempt++) {
|
|
195
|
-
const existing = await this.#kv.get(key);
|
|
196
|
-
const newValue = transform(existing);
|
|
197
|
-
if (await this.#kv.cas(key, existing, newValue, options)) return;
|
|
198
|
-
}
|
|
199
|
-
const newValue = transform(await this.#kv.get(key));
|
|
200
|
-
await this.#kv.set(key, newValue, options);
|
|
201
|
-
}
|
|
202
|
-
async #updateTraceSummary(record, options) {
|
|
203
|
-
const summaryKey = [
|
|
204
|
-
...this.#keyPrefix,
|
|
205
|
-
"_summaries",
|
|
206
|
-
record.traceId
|
|
207
|
-
];
|
|
208
|
-
await this.#setWithCasRetry(summaryKey, (existing) => {
|
|
209
|
-
const activityCount = existing != null ? existing.activityCount + 1 : 1;
|
|
210
|
-
const activityTypes = existing != null ? existing.activityTypes.includes(record.activityType) ? existing.activityTypes : [...existing.activityTypes, record.activityType] : [record.activityType];
|
|
211
|
-
return {
|
|
212
|
-
traceId: existing?.traceId ?? record.traceId,
|
|
213
|
-
timestamp: existing?.timestamp ?? record.timestamp,
|
|
214
|
-
activityCount,
|
|
215
|
-
activityTypes
|
|
216
|
-
};
|
|
217
|
-
}, options);
|
|
218
191
|
}
|
|
219
192
|
/**
|
|
220
193
|
* Gets all activity records for a specific trace ID.
|
|
@@ -231,13 +204,46 @@ var FedifySpanExporter = class {
|
|
|
231
204
|
/**
|
|
232
205
|
* Gets recent traces with summary information.
|
|
233
206
|
*
|
|
207
|
+
* Each summary is computed directly from the activity records currently
|
|
208
|
+
* stored for each trace, rather than from a separately maintained
|
|
209
|
+
* counter. This guarantees that a trace is discoverable, and its
|
|
210
|
+
* `activityCount` accurate, as soon as its records are stored, even if
|
|
211
|
+
* an earlier attempt at summarizing it (in a previous version of this
|
|
212
|
+
* exporter) never completed, and that the count always reflects
|
|
213
|
+
* records that have not expired. It costs a full scan of every stored
|
|
214
|
+
* record on every call, including full inbound activity JSON. The
|
|
215
|
+
* `limit` option only truncates the returned summaries and does not
|
|
216
|
+
* reduce the scan. Callers that poll this method frequently should
|
|
217
|
+
* account for the total retained data when choosing a TTL and polling
|
|
218
|
+
* interval.
|
|
219
|
+
*
|
|
234
220
|
* @param options Options for the query.
|
|
235
221
|
* @returns An array of trace summaries.
|
|
236
222
|
*/
|
|
237
223
|
async getRecentTraces(options) {
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
for await (const entry of this.#kv.list(
|
|
224
|
+
const aggregates = /* @__PURE__ */ new Map();
|
|
225
|
+
const traceIdIndex = this.#keyPrefix.length;
|
|
226
|
+
for await (const entry of this.#kv.list(this.#keyPrefix)) {
|
|
227
|
+
const traceId = entry.key[traceIdIndex];
|
|
228
|
+
if (traceId == null || traceId === "_summaries") continue;
|
|
229
|
+
const record = entry.value;
|
|
230
|
+
if (record == null || typeof record.timestamp !== "string") continue;
|
|
231
|
+
const aggregate = aggregates.get(traceId);
|
|
232
|
+
if (aggregate == null) aggregates.set(traceId, {
|
|
233
|
+
timestamp: record.timestamp,
|
|
234
|
+
activityCount: 1,
|
|
235
|
+
activityTypes: [record.activityType]
|
|
236
|
+
});
|
|
237
|
+
else {
|
|
238
|
+
aggregate.activityCount++;
|
|
239
|
+
if (record.timestamp < aggregate.timestamp) aggregate.timestamp = record.timestamp;
|
|
240
|
+
if (!aggregate.activityTypes.includes(record.activityType)) aggregate.activityTypes.push(record.activityType);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const summaries = [...aggregates].map(([traceId, aggregate]) => ({
|
|
244
|
+
traceId,
|
|
245
|
+
...aggregate
|
|
246
|
+
}));
|
|
241
247
|
summaries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
242
248
|
if (options?.limit != null) return summaries.slice(0, options.limit);
|
|
243
249
|
return summaries;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { c as verifyKeyOwnership, n as fetchActorDocument, t as exportJwk } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-BCuL3mid.mjs";
|
|
5
|
+
import { c as verifyKeyOwnership, n as fetchActorDocument, t as exportJwk } from "./key-D8IMQ72M.mjs";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
8
8
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { Temporal } = require("@js-temporal/polyfill");
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
const require_chunk = require("./chunk-DDcVe30Y.cjs");
|
|
4
|
-
const require_http = require("./http-
|
|
4
|
+
const require_http = require("./http-gOkAiIe1.cjs");
|
|
5
5
|
let _logtape_logtape = require("@logtape/logtape");
|
|
6
6
|
let _fedify_vocab = require("@fedify/vocab");
|
|
7
7
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-
|
|
6
|
-
import { r as fetchKey, s as validateCryptoKey } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-BCuL3mid.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-SYIZHFf_.mjs";
|
|
6
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-D8IMQ72M.mjs";
|
|
7
7
|
import { n as preloadedOnlyDocumentLoader } from "./public-audience-Dp4ZScSr.mjs";
|
|
8
8
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-D4iTv7uM.mjs";
|
|
9
9
|
import { getLogger } from "@logtape/logtape";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import { I as name, L as version, c as fetchKey, f as validateCryptoKey, h as getFederationMetrics, m as getDurationMs, o as exportJwk, p as verifyKeyOwnership, s as fetchActorDocument, y as measureSignatureKeyFetch } from "./http-
|
|
3
|
+
import { I as name, L as version, c as fetchKey, f as validateCryptoKey, h as getFederationMetrics, m as getDurationMs, o as exportJwk, p as verifyKeyOwnership, s as fetchActorDocument, y as measureSignatureKeyFetch } from "./http-BkjxY3U0.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
import { Activity, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1, PUBLIC_COLLECTION, getTypeId, isActor } from "@fedify/vocab";
|
|
6
6
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|