@fedify/fedify 2.3.0-dev.994 → 2.3.0-pr.809.37
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/README.md +16 -7
- package/dist/{assert-DikXweDx.mjs → assert-OguE97r2.mjs} +1 -1
- package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
- package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
- package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DN60FHPX.mjs} +28 -3
- package/dist/{assert_strict_equals-Dmjbg-bA.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
- package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
- package/dist/{builder-yAlpiIqP.mjs → builder-Dl5Sdmc9.mjs} +96 -42
- package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
- package/dist/circuit-breaker-CSWsyoef.mjs +337 -0
- package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
- package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
- package/dist/compat/mod.d.cts +2 -1
- package/dist/compat/mod.d.ts +2 -3
- package/dist/compat/mod.js +3 -3
- package/dist/compat/outgoing-jsonld.test.mjs +4 -4
- package/dist/compat/public-audience.test.mjs +4 -4
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/{context-BzH2-ajs.d.ts → context-BBVLF7lx.d.cts} +342 -269
- package/dist/{context-DJGagtNd.d.cts → context-BU6jSQdo.d.ts} +344 -266
- package/dist/{context-Dk_tacqz.mjs → context-DVoTs_wM.mjs} +64 -5
- package/dist/{deno-XBVlKnOX.mjs → deno-BID9qTPk.mjs} +1 -1
- package/dist/{docloader-DPp-X6gk.mjs → docloader-BYFuVwwi.mjs} +4 -4
- package/dist/{esm-DVILvP5e.mjs → esm-vrlUxr60.mjs} +4 -7
- package/dist/federation/builder.test.mjs +163 -11
- package/dist/federation/circuit-breaker.test.d.mts +2 -0
- package/dist/federation/circuit-breaker.test.mjs +446 -0
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +1770 -29
- package/dist/federation/idempotency.test.mjs +5 -5
- package/dist/federation/inbox.test.mjs +4 -4
- package/dist/federation/keycache.test.mjs +5 -5
- package/dist/federation/kv.test.mjs +2 -2
- package/dist/federation/metrics.test.d.mts +2 -0
- package/dist/federation/metrics.test.mjs +634 -0
- package/dist/federation/middleware.test.mjs +4036 -146
- package/dist/federation/mod.cjs +195 -14
- package/dist/federation/mod.d.cts +6 -4
- package/dist/federation/mod.d.ts +6 -6
- package/dist/federation/mod.js +192 -14
- package/dist/federation/mq.test.mjs +176 -15
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/retry.test.mjs +3 -3
- package/dist/federation/router.test.mjs +190 -9
- package/dist/federation/send.test.mjs +153 -15
- package/dist/federation/temporal.test.d.mts +2 -0
- package/dist/federation/temporal.test.mjs +71 -0
- package/dist/federation/webfinger.test.mjs +150 -5
- package/dist/{http-B4rrzxtg.js → http-B47Jg9iX.js} +994 -64
- package/dist/{http-CSlLA54v.mjs → http-CO59u1Yk.mjs} +146 -47
- package/dist/{http-BrGqJDXL.cjs → http-CjZwUPiT.cjs} +1099 -61
- package/dist/{http-aQzN9Ayi.d.ts → http-VyDTd4G3.d.cts} +15 -3
- package/dist/{http-CrGuipxe.d.cts → http-lf8Hsd91.d.ts} +15 -1
- package/dist/{key-Bzx--sHD.mjs → key-Dqdlo3mG.mjs} +43 -18
- package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
- package/dist/{kv-cache-DI-Sk7-I.cjs → kv-cache-BQ1-zDKX.cjs} +20 -3
- package/dist/{kv-cache-EMIqoIuB.js → kv-cache-Dh2ZH9Gr.js} +20 -3
- package/dist/{kv-cache-U__xU4qR.mjs → kv-cache-DhLkLv0P.mjs} +21 -3
- package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
- package/dist/ld-JbCByP5P.mjs +626 -0
- package/dist/metrics-ColY3QKj.mjs +815 -0
- package/dist/{middleware-zjZ6AFCW.mjs → middleware-Buqzuiaf.mjs} +1 -1
- package/dist/{middleware-An4DjES4.cjs → middleware-CWQ3Yi07.cjs} +2318 -623
- package/dist/{middleware-K1g6bEkV.mjs → middleware-Cg4EyWsq.mjs} +1639 -373
- package/dist/{middleware-nidH7VZH.js → middleware-iAJY2aU-.js} +2260 -556
- package/dist/{mod-CR8soWa9.d.ts → mod-B0hW12_O.d.cts} +26 -4
- package/dist/mod-C0F6kvgS.d.cts +182 -0
- package/dist/{mod-Cr3f-ACa.d.cts → mod-COIAjwRS.d.ts} +26 -2
- package/dist/{mod-CMEbIaNh.d.cts → mod-DFvNJcNb.d.ts} +56 -4
- package/dist/mod-vPYVoa5n.d.ts +182 -0
- package/dist/{mod-CLgIXe9w.d.ts → mod-yvIXFAEi.d.cts} +56 -6
- package/dist/mod.cjs +9 -6
- package/dist/mod.d.cts +12 -10
- package/dist/mod.d.ts +12 -12
- package/dist/mod.js +11 -11
- package/dist/mq-D-nlpY04.d.ts +208 -0
- package/dist/mq-D8uSFzxe.d.cts +208 -0
- package/dist/{negotiation-SQvQgUqe.mjs → negotiation-DDstyBvc.mjs} +29 -0
- package/dist/nodeinfo/client.test.mjs +4 -4
- package/dist/nodeinfo/handler.test.mjs +4 -4
- package/dist/nodeinfo/mod.d.cts +2 -1
- package/dist/nodeinfo/mod.d.ts +2 -3
- package/dist/nodeinfo/mod.js +3 -3
- package/dist/nodeinfo/types.test.mjs +3 -3
- package/dist/otel/exporter.test.mjs +28 -25
- package/dist/otel/mod.cjs +6 -5
- package/dist/otel/mod.d.cts +5 -3
- package/dist/otel/mod.d.ts +5 -5
- package/dist/otel/mod.js +8 -7
- package/dist/{outgoing-jsonld-CNmZLixq.mjs → outgoing-jsonld-L_DbOaFe.mjs} +2 -2
- package/dist/{owner-C-tqfvxn.mjs → owner-B4LgOmzV.mjs} +2 -2
- package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
- package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
- package/dist/{proof-BlmRPzrK.mjs → proof-BoSGOq9q.mjs} +26 -8
- package/dist/{proof-Bbbwf8_x.js → proof-CzhZuawt.js} +378 -15
- package/dist/{proof-BW89QMVB.cjs → proof-DvMI-3vq.cjs} +432 -15
- package/dist/runtime/mod.d.cts +1 -0
- package/dist/runtime/mod.d.ts +1 -2
- package/dist/runtime/mod.js +3 -3
- package/dist/{send-05pJLcb6.mjs → send-ByK1Q7DK.mjs} +72 -26
- package/dist/sig/http.test.mjs +356 -33
- package/dist/sig/key.test.mjs +103 -6
- package/dist/sig/ld.test.mjs +696 -7
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +4 -3
- package/dist/sig/mod.d.ts +4 -5
- package/dist/sig/mod.js +4 -4
- package/dist/sig/owner.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +169 -8
- package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BBjXFNOb.mjs} +5 -30
- package/dist/temporal-LpjyAjKb.mjs +95 -0
- package/dist/testing/mod.d.mts +110 -10
- package/dist/testing/mod.mjs +1 -2
- package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
- package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
- package/dist/utils/docloader.test.mjs +6 -6
- package/dist/utils/kv-cache.test.mjs +67 -2
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +2 -1
- package/dist/utils/mod.d.ts +2 -3
- package/dist/utils/mod.js +3 -3
- package/dist/vocab/cjs.test.mjs +1 -1
- package/dist/vocab/mod.d.cts +1 -0
- package/dist/vocab/mod.d.ts +1 -2
- package/dist/vocab/mod.js +2 -2
- package/package.json +16 -16
- package/dist/ld-DR78beiv.mjs +0 -279
- package/dist/middleware-BFBaR0mF.cjs +0 -4
- package/dist/mod-2d12ffz3.d.ts +0 -64
- package/dist/mod-D35TRn09.d.cts +0 -62
- package/dist/router-CrMLXoOr.mjs +0 -114
- package/dist/{activity-listener-ell7W1s9.mjs → activity-listener-tztVvlNb.mjs} +0 -0
- package/dist/{assert_equals-Ew3jOFa3.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
- package/dist/{client-D_1QpnWt.mjs → client-ByXmQhYD.mjs} +1 -1
- package/dist/{collection-D-HqUuA2.mjs → collection-Cc3DVAhE.mjs} +0 -0
- package/dist/{keycache-EGATflN-.mjs → keycache-BeU0LCII.mjs} +0 -0
- package/dist/{public-audience-DYFHzm_c.mjs → public-audience-Cvbr2Gzt.mjs} +1 -1
- /package/dist/{retry-bMXBL97A.mjs → retry-CXg_MBI-.mjs} +0 -0
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
import "@js-temporal/polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { n as version, t as name } from "./deno-BID9qTPk.mjs";
|
|
5
|
+
import { metrics } from "@opentelemetry/api";
|
|
6
|
+
import { FetchError } from "@fedify/vocab-runtime";
|
|
7
|
+
//#region src/federation/metrics.ts
|
|
8
|
+
var FederationMetrics = class {
|
|
9
|
+
deliverySent;
|
|
10
|
+
deliveryPermanentFailure;
|
|
11
|
+
signatureVerificationFailure;
|
|
12
|
+
signatureVerificationDuration;
|
|
13
|
+
signatureKeyFetchDuration;
|
|
14
|
+
deliveryDuration;
|
|
15
|
+
inboxProcessingDuration;
|
|
16
|
+
httpServerRequestCount;
|
|
17
|
+
httpServerRequestDuration;
|
|
18
|
+
queueTaskEnqueued;
|
|
19
|
+
queueTaskStarted;
|
|
20
|
+
queueTaskCompleted;
|
|
21
|
+
queueTaskFailed;
|
|
22
|
+
queueTaskDuration;
|
|
23
|
+
queueTaskInFlight;
|
|
24
|
+
queueDepth;
|
|
25
|
+
fanoutRecipients;
|
|
26
|
+
inboxActivity;
|
|
27
|
+
outboxActivity;
|
|
28
|
+
circuitBreakerStateChange;
|
|
29
|
+
keyLookup;
|
|
30
|
+
keyLookupDuration;
|
|
31
|
+
documentFetch;
|
|
32
|
+
documentFetchDuration;
|
|
33
|
+
documentCache;
|
|
34
|
+
webFingerHandle;
|
|
35
|
+
webFingerHandleDuration;
|
|
36
|
+
collectionRequest;
|
|
37
|
+
collectionDispatchDuration;
|
|
38
|
+
collectionPageItems;
|
|
39
|
+
collectionTotalItems;
|
|
40
|
+
constructor(meterProvider) {
|
|
41
|
+
const meter = meterProvider.getMeter(name, version);
|
|
42
|
+
this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
|
|
43
|
+
description: "ActivityPub delivery attempts.",
|
|
44
|
+
unit: "{attempt}"
|
|
45
|
+
});
|
|
46
|
+
this.deliveryPermanentFailure = meter.createCounter("activitypub.delivery.permanent_failure", {
|
|
47
|
+
description: "ActivityPub deliveries abandoned as permanent failures.",
|
|
48
|
+
unit: "{failure}"
|
|
49
|
+
});
|
|
50
|
+
this.signatureVerificationFailure = meter.createCounter("activitypub.signature.verification_failure", {
|
|
51
|
+
description: "ActivityPub signature verification failures.",
|
|
52
|
+
unit: "{failure}"
|
|
53
|
+
});
|
|
54
|
+
this.signatureVerificationDuration = meter.createHistogram("activitypub.signature.verification.duration", {
|
|
55
|
+
description: "Duration of ActivityPub signature verification, including local key lookup and remote key fetches.",
|
|
56
|
+
unit: "ms",
|
|
57
|
+
advice: { explicitBucketBoundaries: [
|
|
58
|
+
.1,
|
|
59
|
+
.25,
|
|
60
|
+
.5,
|
|
61
|
+
1,
|
|
62
|
+
2.5,
|
|
63
|
+
5,
|
|
64
|
+
10,
|
|
65
|
+
25,
|
|
66
|
+
50,
|
|
67
|
+
100,
|
|
68
|
+
250,
|
|
69
|
+
500,
|
|
70
|
+
1e3
|
|
71
|
+
] }
|
|
72
|
+
});
|
|
73
|
+
this.signatureKeyFetchDuration = meter.createHistogram("activitypub.signature.key_fetch.duration", {
|
|
74
|
+
description: "Duration of public key lookup performed during ActivityPub signature verification.",
|
|
75
|
+
unit: "ms"
|
|
76
|
+
});
|
|
77
|
+
this.deliveryDuration = meter.createHistogram("activitypub.delivery.duration", {
|
|
78
|
+
description: "Duration of ActivityPub delivery attempts.",
|
|
79
|
+
unit: "ms"
|
|
80
|
+
});
|
|
81
|
+
this.inboxProcessingDuration = meter.createHistogram("activitypub.inbox.processing_duration", {
|
|
82
|
+
description: "Duration of ActivityPub inbox listener processing.",
|
|
83
|
+
unit: "ms"
|
|
84
|
+
});
|
|
85
|
+
this.httpServerRequestCount = meter.createCounter("fedify.http.server.request.count", {
|
|
86
|
+
description: "HTTP requests handled by Federation.fetch().",
|
|
87
|
+
unit: "{request}"
|
|
88
|
+
});
|
|
89
|
+
this.httpServerRequestDuration = meter.createHistogram("fedify.http.server.request.duration", {
|
|
90
|
+
description: "Duration of HTTP requests handled by Federation.fetch().",
|
|
91
|
+
unit: "ms",
|
|
92
|
+
advice: { explicitBucketBoundaries: [
|
|
93
|
+
5,
|
|
94
|
+
10,
|
|
95
|
+
25,
|
|
96
|
+
50,
|
|
97
|
+
75,
|
|
98
|
+
100,
|
|
99
|
+
250,
|
|
100
|
+
500,
|
|
101
|
+
750,
|
|
102
|
+
1e3,
|
|
103
|
+
2500,
|
|
104
|
+
5e3,
|
|
105
|
+
7500,
|
|
106
|
+
1e4
|
|
107
|
+
] }
|
|
108
|
+
});
|
|
109
|
+
this.queueTaskEnqueued = meter.createCounter("fedify.queue.task.enqueued", {
|
|
110
|
+
description: "Tasks Fedify enqueued for inbox, outbox, or fanout work.",
|
|
111
|
+
unit: "{task}"
|
|
112
|
+
});
|
|
113
|
+
this.queueTaskStarted = meter.createCounter("fedify.queue.task.started", {
|
|
114
|
+
description: "Tasks Fedify began processing as a queue worker.",
|
|
115
|
+
unit: "{task}"
|
|
116
|
+
});
|
|
117
|
+
this.queueTaskCompleted = meter.createCounter("fedify.queue.task.completed", {
|
|
118
|
+
description: "Queue tasks Fedify finished processing without throwing.",
|
|
119
|
+
unit: "{task}"
|
|
120
|
+
});
|
|
121
|
+
this.queueTaskFailed = meter.createCounter("fedify.queue.task.failed", {
|
|
122
|
+
description: "Queue tasks Fedify abandoned because processing threw.",
|
|
123
|
+
unit: "{task}"
|
|
124
|
+
});
|
|
125
|
+
this.queueTaskDuration = meter.createHistogram("fedify.queue.task.duration", {
|
|
126
|
+
description: "Duration of queue task processing in Fedify workers.",
|
|
127
|
+
unit: "ms",
|
|
128
|
+
advice: { explicitBucketBoundaries: [
|
|
129
|
+
5,
|
|
130
|
+
10,
|
|
131
|
+
25,
|
|
132
|
+
50,
|
|
133
|
+
75,
|
|
134
|
+
100,
|
|
135
|
+
250,
|
|
136
|
+
500,
|
|
137
|
+
750,
|
|
138
|
+
1e3,
|
|
139
|
+
2500,
|
|
140
|
+
5e3,
|
|
141
|
+
7500,
|
|
142
|
+
1e4
|
|
143
|
+
] }
|
|
144
|
+
});
|
|
145
|
+
this.queueTaskInFlight = meter.createUpDownCounter("fedify.queue.task.in_flight", {
|
|
146
|
+
description: "Queue tasks currently being processed in this Fedify process.",
|
|
147
|
+
unit: "{task}"
|
|
148
|
+
});
|
|
149
|
+
this.queueDepth = meter.createObservableGauge("fedify.queue.depth", {
|
|
150
|
+
description: "Messages waiting in configured Fedify queues, as reported by the queue backend.",
|
|
151
|
+
unit: "{message}"
|
|
152
|
+
});
|
|
153
|
+
this.fanoutRecipients = meter.createHistogram("activitypub.fanout.recipients", {
|
|
154
|
+
description: "Number of recipient inboxes produced by an ActivityPub fanout task.",
|
|
155
|
+
unit: "{recipient}"
|
|
156
|
+
});
|
|
157
|
+
this.inboxActivity = meter.createCounter("activitypub.inbox.activity", {
|
|
158
|
+
description: "ActivityPub activities observed at the inbox lifecycle level: queued, processed, retried, rejected, or abandoned.",
|
|
159
|
+
unit: "{activity}"
|
|
160
|
+
});
|
|
161
|
+
this.outboxActivity = meter.createCounter("activitypub.outbox.activity", {
|
|
162
|
+
description: "ActivityPub activities observed at the outbox lifecycle level: queued, retried, or abandoned. Per-recipient delivery counters live on `activitypub.delivery.*`.",
|
|
163
|
+
unit: "{activity}"
|
|
164
|
+
});
|
|
165
|
+
this.circuitBreakerStateChange = meter.createCounter("activitypub.circuit_breaker.state_change", {
|
|
166
|
+
description: "Outbound ActivityPub delivery circuit breaker changes.",
|
|
167
|
+
unit: "{change}"
|
|
168
|
+
});
|
|
169
|
+
this.keyLookup = meter.createCounter("activitypub.key.lookup", {
|
|
170
|
+
description: "Public-key lookup attempts performed by Fedify, including both cache hits and remote fetches.",
|
|
171
|
+
unit: "{lookup}"
|
|
172
|
+
});
|
|
173
|
+
this.keyLookupDuration = meter.createHistogram("activitypub.key.lookup.duration", {
|
|
174
|
+
description: "Duration of public-key lookups performed by Fedify, including any remote fetch.",
|
|
175
|
+
unit: "ms",
|
|
176
|
+
advice: { explicitBucketBoundaries: [
|
|
177
|
+
5,
|
|
178
|
+
10,
|
|
179
|
+
25,
|
|
180
|
+
50,
|
|
181
|
+
75,
|
|
182
|
+
100,
|
|
183
|
+
250,
|
|
184
|
+
500,
|
|
185
|
+
750,
|
|
186
|
+
1e3,
|
|
187
|
+
2500,
|
|
188
|
+
5e3,
|
|
189
|
+
7500,
|
|
190
|
+
1e4
|
|
191
|
+
] }
|
|
192
|
+
});
|
|
193
|
+
this.documentFetch = meter.createCounter("activitypub.document.fetch", {
|
|
194
|
+
description: "Remote JSON-LD document loader invocations made by Fedify-wrapped loaders.",
|
|
195
|
+
unit: "{fetch}"
|
|
196
|
+
});
|
|
197
|
+
this.documentFetchDuration = meter.createHistogram("activitypub.document.fetch.duration", {
|
|
198
|
+
description: "Duration of remote JSON-LD document loader invocations made by Fedify-wrapped loaders.",
|
|
199
|
+
unit: "ms",
|
|
200
|
+
advice: { explicitBucketBoundaries: [
|
|
201
|
+
5,
|
|
202
|
+
10,
|
|
203
|
+
25,
|
|
204
|
+
50,
|
|
205
|
+
75,
|
|
206
|
+
100,
|
|
207
|
+
250,
|
|
208
|
+
500,
|
|
209
|
+
750,
|
|
210
|
+
1e3,
|
|
211
|
+
2500,
|
|
212
|
+
5e3,
|
|
213
|
+
7500,
|
|
214
|
+
1e4
|
|
215
|
+
] }
|
|
216
|
+
});
|
|
217
|
+
this.documentCache = meter.createCounter("activitypub.document.cache", {
|
|
218
|
+
description: "KV-backed document loader cache lookups, with `hit` or `miss` classification.",
|
|
219
|
+
unit: "{lookup}"
|
|
220
|
+
});
|
|
221
|
+
this.webFingerHandle = meter.createCounter("webfinger.handle", {
|
|
222
|
+
description: "Incoming WebFinger requests handled by Fedify, classified by terminal outcome.",
|
|
223
|
+
unit: "{request}"
|
|
224
|
+
});
|
|
225
|
+
this.webFingerHandleDuration = meter.createHistogram("webfinger.handle.duration", {
|
|
226
|
+
description: "Duration of incoming WebFinger request handling in Fedify.",
|
|
227
|
+
unit: "ms",
|
|
228
|
+
advice: { explicitBucketBoundaries: [
|
|
229
|
+
5,
|
|
230
|
+
10,
|
|
231
|
+
25,
|
|
232
|
+
50,
|
|
233
|
+
75,
|
|
234
|
+
100,
|
|
235
|
+
250,
|
|
236
|
+
500,
|
|
237
|
+
750,
|
|
238
|
+
1e3,
|
|
239
|
+
2500,
|
|
240
|
+
5e3,
|
|
241
|
+
7500,
|
|
242
|
+
1e4
|
|
243
|
+
] }
|
|
244
|
+
});
|
|
245
|
+
this.collectionRequest = meter.createCounter("activitypub.collection.request", {
|
|
246
|
+
description: "ActivityPub collection and collection-page requests handled by Fedify.",
|
|
247
|
+
unit: "{request}"
|
|
248
|
+
});
|
|
249
|
+
this.collectionDispatchDuration = meter.createHistogram("activitypub.collection.dispatch.duration", {
|
|
250
|
+
description: "Duration of ActivityPub collection dispatcher callbacks.",
|
|
251
|
+
unit: "ms",
|
|
252
|
+
advice: { explicitBucketBoundaries: [
|
|
253
|
+
5,
|
|
254
|
+
10,
|
|
255
|
+
25,
|
|
256
|
+
50,
|
|
257
|
+
75,
|
|
258
|
+
100,
|
|
259
|
+
250,
|
|
260
|
+
500,
|
|
261
|
+
750,
|
|
262
|
+
1e3,
|
|
263
|
+
2500,
|
|
264
|
+
5e3,
|
|
265
|
+
7500,
|
|
266
|
+
1e4
|
|
267
|
+
] }
|
|
268
|
+
});
|
|
269
|
+
this.collectionPageItems = meter.createHistogram("activitypub.collection.page.items", {
|
|
270
|
+
description: "Number of items Fedify materialized for an ActivityPub collection response.",
|
|
271
|
+
unit: "{item}"
|
|
272
|
+
});
|
|
273
|
+
this.collectionTotalItems = meter.createHistogram("activitypub.collection.total_items", {
|
|
274
|
+
description: "Total item count reported by ActivityPub collection counters.",
|
|
275
|
+
unit: "{item}"
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
recordDelivery(inbox, durationMs, success, activityType) {
|
|
279
|
+
const deliveryAttributes = {
|
|
280
|
+
"activitypub.remote.host": getRemoteHost(inbox),
|
|
281
|
+
"activitypub.delivery.success": success
|
|
282
|
+
};
|
|
283
|
+
if (activityType != null) deliveryAttributes["activitypub.activity.type"] = activityType;
|
|
284
|
+
this.deliverySent.add(1, deliveryAttributes);
|
|
285
|
+
this.deliveryDuration.record(durationMs, deliveryAttributes);
|
|
286
|
+
}
|
|
287
|
+
recordPermanentFailure(inbox, statusCode) {
|
|
288
|
+
this.deliveryPermanentFailure.add(1, {
|
|
289
|
+
"activitypub.remote.host": getRemoteHost(inbox),
|
|
290
|
+
"http.response.status_code": statusCode
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
recordSignatureVerificationFailure(reason, remoteHost) {
|
|
294
|
+
const attributes = { "activitypub.verification.failure_reason": reason };
|
|
295
|
+
if (remoteHost != null) attributes["activitypub.remote.host"] = remoteHost;
|
|
296
|
+
this.signatureVerificationFailure.add(1, attributes);
|
|
297
|
+
}
|
|
298
|
+
recordSignatureVerificationDuration(durationMs, kind, result, extra = {}) {
|
|
299
|
+
const attributes = {
|
|
300
|
+
"activitypub.signature.kind": kind,
|
|
301
|
+
"activitypub.signature.result": result
|
|
302
|
+
};
|
|
303
|
+
if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
|
|
304
|
+
if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
|
|
305
|
+
if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
|
|
306
|
+
if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
|
|
307
|
+
this.signatureVerificationDuration.record(durationMs, attributes);
|
|
308
|
+
}
|
|
309
|
+
recordSignatureKeyFetchDuration(durationMs, kind, result) {
|
|
310
|
+
this.signatureKeyFetchDuration.record(durationMs, {
|
|
311
|
+
"activitypub.signature.kind": kind,
|
|
312
|
+
"activitypub.signature.key_fetch.result": result
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
recordInboxProcessingDuration(activityType, durationMs) {
|
|
316
|
+
this.inboxProcessingDuration.record(durationMs, { "activitypub.activity.type": activityType });
|
|
317
|
+
}
|
|
318
|
+
recordHttpServerRequest(method, endpoint, durationMs, options = {}) {
|
|
319
|
+
const attributes = {
|
|
320
|
+
"http.request.method": normalizeHttpMethod(method),
|
|
321
|
+
"fedify.endpoint": endpoint
|
|
322
|
+
};
|
|
323
|
+
if (options.statusCode != null) attributes["http.response.status_code"] = options.statusCode;
|
|
324
|
+
if (options.routeTemplate != null) attributes["fedify.route.template"] = options.routeTemplate;
|
|
325
|
+
this.httpServerRequestCount.add(1, attributes);
|
|
326
|
+
this.httpServerRequestDuration.record(durationMs, attributes);
|
|
327
|
+
}
|
|
328
|
+
recordQueueTaskEnqueued(common, attempt) {
|
|
329
|
+
const attributes = buildQueueTaskAttributes(common);
|
|
330
|
+
attributes["fedify.queue.task.attempt"] = attempt;
|
|
331
|
+
this.queueTaskEnqueued.add(1, attributes);
|
|
332
|
+
}
|
|
333
|
+
recordQueueTaskStarted(common) {
|
|
334
|
+
this.queueTaskStarted.add(1, buildQueueTaskAttributes(common));
|
|
335
|
+
}
|
|
336
|
+
incrementQueueTaskInFlight(common) {
|
|
337
|
+
this.queueTaskInFlight.add(1, buildQueueTaskInFlightAttributes(common));
|
|
338
|
+
}
|
|
339
|
+
decrementQueueTaskInFlight(common) {
|
|
340
|
+
this.queueTaskInFlight.add(-1, buildQueueTaskInFlightAttributes(common));
|
|
341
|
+
}
|
|
342
|
+
recordQueueTaskOutcome(common, result, durationMs) {
|
|
343
|
+
const attributes = buildQueueTaskAttributes(common);
|
|
344
|
+
attributes["fedify.queue.task.result"] = result;
|
|
345
|
+
if (result === "completed") this.queueTaskCompleted.add(1, attributes);
|
|
346
|
+
else if (result === "failed") this.queueTaskFailed.add(1, attributes);
|
|
347
|
+
this.queueTaskDuration.record(durationMs, attributes);
|
|
348
|
+
}
|
|
349
|
+
recordFanoutRecipients(recipientCount, activityType) {
|
|
350
|
+
const attributes = {};
|
|
351
|
+
if (activityType != null) attributes["activitypub.activity.type"] = activityType;
|
|
352
|
+
this.fanoutRecipients.record(recipientCount, attributes);
|
|
353
|
+
}
|
|
354
|
+
recordInboxActivity(result, activityType) {
|
|
355
|
+
this.inboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
|
|
356
|
+
}
|
|
357
|
+
recordOutboxActivity(result, activityType) {
|
|
358
|
+
this.outboxActivity.add(1, buildActivityLifecycleAttributes(result, activityType));
|
|
359
|
+
}
|
|
360
|
+
recordCircuitBreakerStateChange(remoteHost, state) {
|
|
361
|
+
this.circuitBreakerStateChange.add(1, {
|
|
362
|
+
"activitypub.remote.host": remoteHost,
|
|
363
|
+
"activitypub.circuit_breaker.state": state
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
recordKeyLookup(attrs) {
|
|
367
|
+
const attributes = {
|
|
368
|
+
"activitypub.lookup.kind": "public_key",
|
|
369
|
+
"activitypub.lookup.result": attrs.result,
|
|
370
|
+
"activitypub.cache.enabled": attrs.cacheEnabled
|
|
371
|
+
};
|
|
372
|
+
if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
|
|
373
|
+
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
374
|
+
this.keyLookup.add(1, attributes);
|
|
375
|
+
this.keyLookupDuration.record(attrs.durationMs, attributes);
|
|
376
|
+
}
|
|
377
|
+
recordDocumentFetch(attrs) {
|
|
378
|
+
const attributes = {
|
|
379
|
+
"activitypub.lookup.kind": attrs.kind,
|
|
380
|
+
"activitypub.lookup.result": attrs.result
|
|
381
|
+
};
|
|
382
|
+
if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
|
|
383
|
+
if (attrs.cacheEnabled != null) attributes["activitypub.cache.enabled"] = attrs.cacheEnabled;
|
|
384
|
+
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
385
|
+
this.documentFetch.add(1, attributes);
|
|
386
|
+
this.documentFetchDuration.record(attrs.durationMs, attributes);
|
|
387
|
+
}
|
|
388
|
+
recordDocumentCache(attrs) {
|
|
389
|
+
const attributes = {
|
|
390
|
+
"activitypub.lookup.kind": attrs.kind,
|
|
391
|
+
"activitypub.lookup.result": attrs.result
|
|
392
|
+
};
|
|
393
|
+
if (attrs.remoteUrl != null) attributes["activitypub.remote.host"] = getRemoteHost(attrs.remoteUrl);
|
|
394
|
+
this.documentCache.add(1, attributes);
|
|
395
|
+
}
|
|
396
|
+
recordWebFingerHandle(attrs) {
|
|
397
|
+
const attributes = { "webfinger.handle.result": attrs.result };
|
|
398
|
+
if (attrs.scheme != null) attributes["webfinger.resource.scheme"] = attrs.scheme;
|
|
399
|
+
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
400
|
+
this.webFingerHandle.add(1, attributes);
|
|
401
|
+
this.webFingerHandleDuration.record(attrs.durationMs, attributes);
|
|
402
|
+
}
|
|
403
|
+
recordCollectionRequest(attrs) {
|
|
404
|
+
this.collectionRequest.add(1, buildCollectionAttributes(attrs));
|
|
405
|
+
}
|
|
406
|
+
recordCollectionDispatchDuration(durationMs, attrs) {
|
|
407
|
+
this.collectionDispatchDuration.record(durationMs, buildCollectionAttributes(attrs));
|
|
408
|
+
}
|
|
409
|
+
recordCollectionPageItems(itemCount, attrs) {
|
|
410
|
+
this.collectionPageItems.record(itemCount, buildCollectionAttributes(attrs));
|
|
411
|
+
}
|
|
412
|
+
recordCollectionTotalItems(totalItems, attrs) {
|
|
413
|
+
this.collectionTotalItems.record(totalItems, buildCollectionAttributes(attrs));
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
function buildCollectionAttributes(attrs) {
|
|
417
|
+
const attributes = {
|
|
418
|
+
"activitypub.collection.kind": attrs.kind,
|
|
419
|
+
"activitypub.collection.page": attrs.page,
|
|
420
|
+
"activitypub.collection.result": attrs.result,
|
|
421
|
+
"fedify.collection.dispatcher": attrs.dispatcher
|
|
422
|
+
};
|
|
423
|
+
if (attrs.statusCode != null) attributes["http.response.status_code"] = attrs.statusCode;
|
|
424
|
+
return attributes;
|
|
425
|
+
}
|
|
426
|
+
function buildActivityLifecycleAttributes(result, activityType) {
|
|
427
|
+
const attributes = { "activitypub.processing.result": result };
|
|
428
|
+
if (activityType != null) attributes["activitypub.activity.type"] = activityType;
|
|
429
|
+
return attributes;
|
|
430
|
+
}
|
|
431
|
+
function buildQueueTaskAttributes(common) {
|
|
432
|
+
const attributes = { "fedify.queue.role": common.role };
|
|
433
|
+
const backend = getQueueBackend(common.queue);
|
|
434
|
+
if (backend != null) attributes["fedify.queue.backend"] = backend;
|
|
435
|
+
const nativeRetrial = common.queue?.nativeRetrial;
|
|
436
|
+
if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
|
|
437
|
+
if (common.activityType != null) attributes["activitypub.activity.type"] = common.activityType;
|
|
438
|
+
return attributes;
|
|
439
|
+
}
|
|
440
|
+
function buildQueueTaskInFlightAttributes(common) {
|
|
441
|
+
return buildQueueTaskAttributes({
|
|
442
|
+
role: common.role,
|
|
443
|
+
queue: common.queue
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Returns the constructor name of the given message queue, when it is a
|
|
448
|
+
* meaningful identifier. Used as a best-effort `fedify.queue.backend`
|
|
449
|
+
* attribute on queue task metrics; returns `undefined` for plain object
|
|
450
|
+
* literals (whose constructor is `Object`) so the attribute does not appear
|
|
451
|
+
* with a non-informative value.
|
|
452
|
+
* @since 2.3.0
|
|
453
|
+
*/
|
|
454
|
+
function getQueueBackend(queue) {
|
|
455
|
+
const name = queue?.constructor?.name;
|
|
456
|
+
if (name == null || name === "" || name === "Object") return void 0;
|
|
457
|
+
return name;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Registers a callback for observing queue backend depth.
|
|
461
|
+
* @since 2.3.0
|
|
462
|
+
*/
|
|
463
|
+
function registerQueueDepthGauge(meterProvider, entries, options = {}) {
|
|
464
|
+
const uniqueQueues = /* @__PURE__ */ new Map();
|
|
465
|
+
for (const { role, queue } of entries) {
|
|
466
|
+
if (queue?.getDepth == null) continue;
|
|
467
|
+
const roles = uniqueQueues.get(queue);
|
|
468
|
+
if (roles == null) uniqueQueues.set(queue, [role]);
|
|
469
|
+
else if (!roles.includes(role)) roles.push(role);
|
|
470
|
+
}
|
|
471
|
+
if (uniqueQueues.size < 1) return;
|
|
472
|
+
const queueEntries = Array.from(uniqueQueues.entries());
|
|
473
|
+
getFederationMetrics(meterProvider).queueDepth.addCallback(async (observableResult) => {
|
|
474
|
+
await Promise.all(queueEntries.map(async ([queue, roles]) => {
|
|
475
|
+
let depth;
|
|
476
|
+
try {
|
|
477
|
+
depth = await queue.getDepth();
|
|
478
|
+
} catch {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (depth == null) return;
|
|
482
|
+
const attributes = buildQueueDepthAttributes(queue, roles, options);
|
|
483
|
+
observableResult.observe(depth.queued, {
|
|
484
|
+
...attributes,
|
|
485
|
+
"fedify.queue.depth.state": "queued"
|
|
486
|
+
});
|
|
487
|
+
if (depth.ready != null) observableResult.observe(depth.ready, {
|
|
488
|
+
...attributes,
|
|
489
|
+
"fedify.queue.depth.state": "ready"
|
|
490
|
+
});
|
|
491
|
+
if (depth.delayed != null) observableResult.observe(depth.delayed, {
|
|
492
|
+
...attributes,
|
|
493
|
+
"fedify.queue.depth.state": "delayed"
|
|
494
|
+
});
|
|
495
|
+
}));
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
function buildQueueDepthAttributes(queue, roles, options) {
|
|
499
|
+
const sortedRoles = roles.toSorted();
|
|
500
|
+
const role = sortedRoles.length === 1 ? sortedRoles[0] : "shared";
|
|
501
|
+
const attributes = { "fedify.queue.role": role };
|
|
502
|
+
if (options.sourceId != null) attributes["fedify.federation.instance_id"] = options.sourceId;
|
|
503
|
+
if (role === "shared") attributes["fedify.queue.roles"] = sortedRoles.join(",");
|
|
504
|
+
const backend = getQueueBackend(queue);
|
|
505
|
+
if (backend != null) attributes["fedify.queue.backend"] = backend;
|
|
506
|
+
const nativeRetrial = queue.nativeRetrial;
|
|
507
|
+
if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
|
|
508
|
+
return attributes;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue and,
|
|
512
|
+
* for the initial attempt, also records
|
|
513
|
+
* `activitypub.outbox.activity{queued}`.
|
|
514
|
+
*
|
|
515
|
+
* Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
|
|
516
|
+
* outbox messages with the same metric attributes (role, queue, activity
|
|
517
|
+
* type, attempt), so they share this helper rather than each defining a local
|
|
518
|
+
* closure. Retry enqueues (attempt > 0) intentionally do not record a
|
|
519
|
+
* second `activitypub.outbox.activity{queued}`; retries are reported as
|
|
520
|
+
* `result=retried` from the retry-scheduling site, which has the failure
|
|
521
|
+
* context.
|
|
522
|
+
* @since 2.3.0
|
|
523
|
+
*/
|
|
524
|
+
function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
|
|
525
|
+
const metrics = getFederationMetrics(meterProvider);
|
|
526
|
+
metrics.recordQueueTaskEnqueued({
|
|
527
|
+
role: "outbox",
|
|
528
|
+
queue: outboxQueue,
|
|
529
|
+
activityType: message.activityType
|
|
530
|
+
}, message.attempt);
|
|
531
|
+
if (message.attempt === 0) metrics.recordOutboxActivity("queued", message.activityType);
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Records `activitypub.fanout.recipients` with the number of recipient
|
|
535
|
+
* inboxes a single fanout produced. The histogram is unitless count
|
|
536
|
+
* (one measurement per fanout enqueue). Recipient URLs are deliberately
|
|
537
|
+
* not recorded; only the activity type, when known.
|
|
538
|
+
* @since 2.3.0
|
|
539
|
+
*/
|
|
540
|
+
function recordFanoutRecipients(meterProvider, recipientCount, activityType) {
|
|
541
|
+
getFederationMetrics(meterProvider).recordFanoutRecipients(recipientCount, activityType);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Records one `activitypub.inbox.activity` measurement. The
|
|
545
|
+
* `activitypub.processing.result` attribute is always present;
|
|
546
|
+
* `activitypub.activity.type` is recorded only when Fedify already knows
|
|
547
|
+
* the activity type.
|
|
548
|
+
* @since 2.3.0
|
|
549
|
+
*/
|
|
550
|
+
function recordInboxActivity(meterProvider, result, activityType) {
|
|
551
|
+
getFederationMetrics(meterProvider).recordInboxActivity(result, activityType);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Records one `activitypub.outbox.activity` measurement. The
|
|
555
|
+
* `activitypub.processing.result` attribute is always present;
|
|
556
|
+
* `activitypub.activity.type` is recorded only when Fedify already knows
|
|
557
|
+
* the activity type (it is always known for outbox lifecycle events).
|
|
558
|
+
* @since 2.3.0
|
|
559
|
+
*/
|
|
560
|
+
function recordOutboxActivity(meterProvider, result, activityType) {
|
|
561
|
+
getFederationMetrics(meterProvider).recordOutboxActivity(result, activityType);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Records one outbound delivery circuit breaker state transition.
|
|
565
|
+
* @since 2.3.0
|
|
566
|
+
*/
|
|
567
|
+
function recordCircuitBreakerStateChange(meterProvider, remoteHost, state) {
|
|
568
|
+
getFederationMetrics(meterProvider).recordCircuitBreakerStateChange(remoteHost, state);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Records one measurement on `activitypub.key.lookup` (counter) and
|
|
572
|
+
* `activitypub.key.lookup.duration` (histogram) for a public-key lookup.
|
|
573
|
+
*
|
|
574
|
+
* `activitypub.lookup.kind` is always recorded as `public_key`; the result
|
|
575
|
+
* classification, remote host, HTTP status code (when an HTTP response was
|
|
576
|
+
* received), and `activitypub.cache.enabled` are recorded as attributes on
|
|
577
|
+
* both measurements. Full key URLs and key IDs are deliberately omitted to
|
|
578
|
+
* keep cardinality bounded.
|
|
579
|
+
* @since 2.3.0
|
|
580
|
+
*/
|
|
581
|
+
function recordKeyLookup(meterProvider, attrs) {
|
|
582
|
+
getFederationMetrics(meterProvider).recordKeyLookup(attrs);
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Records one measurement each on `activitypub.document.fetch` (counter)
|
|
586
|
+
* and `activitypub.document.fetch.duration` (histogram) for one remote
|
|
587
|
+
* JSON-LD document loader invocation, with bounded
|
|
588
|
+
* `activitypub.lookup.kind` and `activitypub.lookup.result` attributes
|
|
589
|
+
* plus the optional remote-host, cache-enabled, and HTTP status-code
|
|
590
|
+
* attributes. Counter and histogram are always recorded together so
|
|
591
|
+
* aggregate rate and latency views stay in sync.
|
|
592
|
+
* @since 2.3.0
|
|
593
|
+
*/
|
|
594
|
+
function recordDocumentFetch(meterProvider, attrs) {
|
|
595
|
+
getFederationMetrics(meterProvider).recordDocumentFetch(attrs);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Records one `activitypub.document.cache` measurement, classifying the
|
|
599
|
+
* lookup as `hit` (the cache returned an entry) or `miss` (the cache was
|
|
600
|
+
* consulted and returned nothing, prompting a delegate fetch).
|
|
601
|
+
* @since 2.3.0
|
|
602
|
+
*/
|
|
603
|
+
function recordDocumentCache(meterProvider, attrs) {
|
|
604
|
+
getFederationMetrics(meterProvider).recordDocumentCache(attrs);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Records one measurement on `webfinger.handle` (counter) and
|
|
608
|
+
* `webfinger.handle.duration` (histogram) for an incoming WebFinger
|
|
609
|
+
* request handled by Fedify. Counter and histogram are always recorded
|
|
610
|
+
* together, with `webfinger.handle.result` set to one of `resolved`,
|
|
611
|
+
* `invalid`, `not_found`, `tombstoned`, or `error`. The queried
|
|
612
|
+
* resource string is deliberately excluded; it remains on the
|
|
613
|
+
* `webfinger.handle` span for trace-level investigation.
|
|
614
|
+
* @since 2.3.0
|
|
615
|
+
*/
|
|
616
|
+
function recordWebFingerHandle(meterProvider, attrs) {
|
|
617
|
+
getFederationMetrics(meterProvider).recordWebFingerHandle(attrs);
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Records one `activitypub.collection.request` measurement for a
|
|
621
|
+
* collection or collection-page request handled by Fedify.
|
|
622
|
+
* @since 2.3.0
|
|
623
|
+
*/
|
|
624
|
+
function recordCollectionRequest(meterProvider, attrs) {
|
|
625
|
+
getFederationMetrics(meterProvider).recordCollectionRequest(attrs);
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Records one `activitypub.collection.dispatch.duration` measurement for a
|
|
629
|
+
* collection dispatcher callback invocation.
|
|
630
|
+
* @since 2.3.0
|
|
631
|
+
*/
|
|
632
|
+
function recordCollectionDispatchDuration(meterProvider, durationMs, attrs) {
|
|
633
|
+
getFederationMetrics(meterProvider).recordCollectionDispatchDuration(durationMs, attrs);
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Records one `activitypub.collection.page.items` measurement when Fedify
|
|
637
|
+
* has materialized collection items in memory.
|
|
638
|
+
* @since 2.3.0
|
|
639
|
+
*/
|
|
640
|
+
function recordCollectionPageItems(meterProvider, itemCount, attrs) {
|
|
641
|
+
getFederationMetrics(meterProvider).recordCollectionPageItems(itemCount, attrs);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Records one `activitypub.collection.total_items` measurement when a
|
|
645
|
+
* collection counter has already reported a total item count.
|
|
646
|
+
* @since 2.3.0
|
|
647
|
+
*/
|
|
648
|
+
function recordCollectionTotalItems(meterProvider, totalItems, attrs) {
|
|
649
|
+
getFederationMetrics(meterProvider).recordCollectionTotalItems(totalItems, attrs);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Classifies a thrown value from a key or document fetch into the bounded
|
|
653
|
+
* {@link LookupResult} taxonomy and, when an HTTP response was received,
|
|
654
|
+
* surfaces its status code.
|
|
655
|
+
*
|
|
656
|
+
* - `FetchError` with a `Response` whose status is `404` or `410`:
|
|
657
|
+
* `result=not_found` and the response status code.
|
|
658
|
+
* - `FetchError` with any other `Response`: `result=error` and the
|
|
659
|
+
* response status code.
|
|
660
|
+
* - `FetchError` without a `Response`: `result=network_error`.
|
|
661
|
+
* - An `AbortError` (typically from a cancelled fetch): `result=network_error`.
|
|
662
|
+
* - A bare `TypeError` (the shape native `fetch()` raises on DNS, connect,
|
|
663
|
+
* and TLS failures before any response is observed):
|
|
664
|
+
* `result=network_error`.
|
|
665
|
+
* - Any other value: `result=error`.
|
|
666
|
+
* @since 2.3.0
|
|
667
|
+
*/
|
|
668
|
+
function classifyFetchError(error) {
|
|
669
|
+
if (error instanceof FetchError) {
|
|
670
|
+
if (error.response != null) {
|
|
671
|
+
const status = error.response.status;
|
|
672
|
+
return {
|
|
673
|
+
result: status === 404 || status === 410 ? "not_found" : "error",
|
|
674
|
+
statusCode: status
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
return { result: "network_error" };
|
|
678
|
+
}
|
|
679
|
+
if (isAbortError(error)) return { result: "network_error" };
|
|
680
|
+
if (error instanceof TypeError) return { result: "network_error" };
|
|
681
|
+
return { result: "error" };
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Wraps a {@link DocumentLoader} so each invocation records one
|
|
685
|
+
* measurement on `activitypub.document.fetch` (counter) and one on
|
|
686
|
+
* `activitypub.document.fetch.duration` (histogram), classifying the
|
|
687
|
+
* outcome via {@link classifyFetchError} when the wrapped loader throws
|
|
688
|
+
* and as `fetched` on success. The wrapper rethrows whatever the
|
|
689
|
+
* wrapped loader throws so caller behavior is unchanged.
|
|
690
|
+
*
|
|
691
|
+
* The wrapper records the host of the requested URL, including any
|
|
692
|
+
* non-default port, on `activitypub.remote.host` when the URL parses; full
|
|
693
|
+
* URLs, paths, and query strings are deliberately excluded to keep
|
|
694
|
+
* cardinality bounded.
|
|
695
|
+
* HTTP status codes are recorded only when the failure carries a
|
|
696
|
+
* `Response` (currently, when the wrapped loader throws a
|
|
697
|
+
* {@link FetchError} with a non-`null` `response`).
|
|
698
|
+
* @since 2.3.0
|
|
699
|
+
*/
|
|
700
|
+
function instrumentDocumentLoader(loader, options) {
|
|
701
|
+
const meterProvider = options.meterProvider;
|
|
702
|
+
if (meterProvider == null) return loader;
|
|
703
|
+
return async (url, opts) => {
|
|
704
|
+
const start = performance.now();
|
|
705
|
+
let remoteUrl;
|
|
706
|
+
try {
|
|
707
|
+
remoteUrl = new URL(url);
|
|
708
|
+
} catch {
|
|
709
|
+
remoteUrl = void 0;
|
|
710
|
+
}
|
|
711
|
+
try {
|
|
712
|
+
const result = await loader(url, opts);
|
|
713
|
+
recordDocumentFetch(meterProvider, {
|
|
714
|
+
durationMs: getDurationMs(start),
|
|
715
|
+
kind: options.kind,
|
|
716
|
+
result: "fetched",
|
|
717
|
+
remoteUrl,
|
|
718
|
+
cacheEnabled: options.cacheEnabled
|
|
719
|
+
});
|
|
720
|
+
return result;
|
|
721
|
+
} catch (error) {
|
|
722
|
+
const classified = classifyFetchError(error);
|
|
723
|
+
recordDocumentFetch(meterProvider, {
|
|
724
|
+
durationMs: getDurationMs(start),
|
|
725
|
+
kind: options.kind,
|
|
726
|
+
result: classified.result,
|
|
727
|
+
remoteUrl,
|
|
728
|
+
cacheEnabled: options.cacheEnabled,
|
|
729
|
+
statusCode: classified.statusCode
|
|
730
|
+
});
|
|
731
|
+
throw error;
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Times an awaited public key fetch and records exactly one
|
|
737
|
+
* `activitypub.signature.key_fetch.duration` measurement, classifying the
|
|
738
|
+
* outcome as `hit`, `fetched`, or `error` based on the `cached` flag and
|
|
739
|
+
* whether the returned key is non-null. Errors thrown by the fetch are
|
|
740
|
+
* reported as `error` and rethrown, so verifier behavior is unchanged.
|
|
741
|
+
*
|
|
742
|
+
* Shared by the three signature verifiers (HTTP, Linked Data, Object
|
|
743
|
+
* Integrity Proofs); the only per-call variation is the
|
|
744
|
+
* `activitypub.signature.kind` attribute value.
|
|
745
|
+
* @since 2.3.0
|
|
746
|
+
*/
|
|
747
|
+
async function measureSignatureKeyFetch(meterProvider, kind, fetch) {
|
|
748
|
+
const start = performance.now();
|
|
749
|
+
try {
|
|
750
|
+
const result = await fetch();
|
|
751
|
+
getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, result.key != null ? result.cached ? "hit" : "fetched" : "error");
|
|
752
|
+
return result;
|
|
753
|
+
} catch (error) {
|
|
754
|
+
getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, "error");
|
|
755
|
+
throw error;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Whether the given thrown value is an `AbortError`.
|
|
760
|
+
*
|
|
761
|
+
* `processQueuedTask` distinguishes aborted tasks (recorded as
|
|
762
|
+
* `fedify.queue.task.result=aborted`) from other failures so that backend
|
|
763
|
+
* shutdown signals do not inflate the `fedify.queue.task.failed` counter.
|
|
764
|
+
* @since 2.3.0
|
|
765
|
+
*/
|
|
766
|
+
function isAbortError(error) {
|
|
767
|
+
if (error == null || typeof error !== "object") return false;
|
|
768
|
+
const name = error.name;
|
|
769
|
+
return typeof name === "string" && name === "AbortError";
|
|
770
|
+
}
|
|
771
|
+
const KNOWN_HTTP_METHODS = new Set([
|
|
772
|
+
"CONNECT",
|
|
773
|
+
"DELETE",
|
|
774
|
+
"GET",
|
|
775
|
+
"HEAD",
|
|
776
|
+
"OPTIONS",
|
|
777
|
+
"PATCH",
|
|
778
|
+
"POST",
|
|
779
|
+
"PUT",
|
|
780
|
+
"QUERY",
|
|
781
|
+
"TRACE"
|
|
782
|
+
]);
|
|
783
|
+
function normalizeHttpMethod(method) {
|
|
784
|
+
const upper = method.toUpperCase();
|
|
785
|
+
return KNOWN_HTTP_METHODS.has(upper) ? upper : "_OTHER";
|
|
786
|
+
}
|
|
787
|
+
const federationMetrics = /* @__PURE__ */ new WeakMap();
|
|
788
|
+
/**
|
|
789
|
+
* Gets the cached Fedify metric instruments for a meter provider.
|
|
790
|
+
* @since 2.3.0
|
|
791
|
+
*/
|
|
792
|
+
function getFederationMetrics(meterProvider = metrics.getMeterProvider()) {
|
|
793
|
+
let instruments = federationMetrics.get(meterProvider);
|
|
794
|
+
if (instruments == null) {
|
|
795
|
+
instruments = new FederationMetrics(meterProvider);
|
|
796
|
+
federationMetrics.set(meterProvider, instruments);
|
|
797
|
+
}
|
|
798
|
+
return instruments;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Gets the bounded remote host attribute value for a URL.
|
|
802
|
+
* @since 2.3.0
|
|
803
|
+
*/
|
|
804
|
+
function getRemoteHost(url) {
|
|
805
|
+
return url.host;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Gets an elapsed duration in milliseconds from a `performance.now()` value.
|
|
809
|
+
* @since 2.3.0
|
|
810
|
+
*/
|
|
811
|
+
function getDurationMs(start) {
|
|
812
|
+
return Math.max(0, performance.now() - start);
|
|
813
|
+
}
|
|
814
|
+
//#endregion
|
|
815
|
+
export { recordKeyLookup as _, instrumentDocumentLoader as a, recordWebFingerHandle as b, recordCircuitBreakerStateChange as c, recordCollectionRequest as d, recordCollectionTotalItems as f, recordInboxActivity as g, recordFanoutRecipients as h, getRemoteHost as i, recordCollectionDispatchDuration as l, recordDocumentFetch as m, getDurationMs as n, isAbortError as o, recordDocumentCache as p, getFederationMetrics as r, measureSignatureKeyFetch as s, classifyFetchError as t, recordCollectionPageItems as u, recordOutboxActivity as v, registerQueueDepthGauge as x, recordOutboxEnqueue as y };
|