Skip to content

Commit 3a0da52

Browse files
committed
Applied gemini comments
1 parent d0bd55e commit 3a0da52

5 files changed

Lines changed: 211 additions & 24 deletions

File tree

deno.lock

Lines changed: 27 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/manual/opentelemetry.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,9 @@ spans:
174174
| `activitypub.outbox` | Consumer | Dequeues the ActivityPub activity to send. |
175175
| `activitypub.outbox` | Producer | Enqueues the ActivityPub activity to send. |
176176
| `activitypub.parse_object` | Internal | Parses the Activity Streams object. |
177+
| `activitypub.fetch_document` | Client | Fetches a remote JSON-LD document. |
177178
| `activitypub.send_activity` | Client | Sends the ActivityPub activity. |
179+
| `activitypub.verify_key_ownership` | Internal | Verifies actor ownership of a key. |
178180
| `http_signatures.sign` | Internal | Signs the HTTP request. |
179181
| `http_signatures.verify` | Internal | Verifies the HTTP request signature. |
180182
| `ld_signatures.sign` | Internal | Makes the Linked Data signature. |
@@ -189,6 +191,47 @@ More operations will be instrumented in the future releases.
189191
[Span kind]: https://opentelemetry.io/docs/specs/otel/trace/api/#spankind
190192

191193

194+
Span events
195+
-----------
196+
197+
In addition to spans, Fedify also records [span events] to capture rich,
198+
structured data about key operations. Span events allow recording complex data
199+
that wouldn't fit in span attributes (which are limited to primitive values).
200+
201+
The following span events are recorded:
202+
203+
| Event name | Recorded on span | Description |
204+
|------------------------------------|------------------------------|----------------------------------------------------------------------------------|
205+
| `activitypub.activity.received` | `activitypub.inbox` | Records full activity JSON and verification status when an activity is received. |
206+
| `activitypub.activity.sent` | `activitypub.send_activity` | Records full activity JSON and delivery details when an activity is sent. |
207+
| `activitypub.object.fetched` | `activitypub.lookup_object` | Records full object JSON when successfully fetched. |
208+
209+
### Event attributes
210+
211+
Each span event includes attributes with detailed information:
212+
213+
**`activitypub.activity.received` event attributes:**
214+
215+
- `activitypub.activity.json`: The complete activity JSON
216+
- `activitypub.activity.verified`: Whether the activity was verified (`true`/`false`)
217+
- `ld_signatures.verified`: Whether Linked Data Signatures were verified (`true`/`false`)
218+
- `http_signatures.verified`: Whether HTTP Signatures were verified (`true`/`false`)
219+
- `http_signatures.key_id`: The key ID used for HTTP signature verification
220+
221+
**`activitypub.activity.sent` event attributes:**
222+
223+
- `activitypub.activity.json`: The complete activity JSON being sent
224+
- `activitypub.inbox.url`: The inbox URL where the activity was delivered
225+
- `activitypub.activity.id`: The activity ID
226+
227+
**`activitypub.object.fetched` event attributes:**
228+
229+
- `activitypub.object.type`: The type URI of the fetched object
230+
- `activitypub.object.json`: The complete object JSON
231+
232+
[span events]: https://opentelemetry.io/docs/concepts/signals/traces/#span-events
233+
234+
192235
Semantic [attributes] for ActivityPub
193236
-------------------------------------
194237

@@ -209,6 +252,9 @@ for ActivityPub:
209252
| `activitypub.actor.id` | string | The URI of the actor object. | `"https://example.com/actor/1"` |
210253
| `activitypub.actor.key.cached` | boolean | Whether the actor's public keys are cached. | `true` |
211254
| `activitypub.actor.type` | string[] | The qualified URI(s) of the actor type(s). | `["https://www.w3.org/ns/activitystreams#Person"]` |
255+
| `activitypub.key.id` | string | The URI of the cryptographic key being verified. | `"https://example.com/actor/1#main-key"` |
256+
| `activitypub.key_ownership.method` | string | The method used to verify key ownership (`owner_id` or `actor_fetch`). | `"actor_fetch"` |
257+
| `activitypub.key_ownership.verified` | boolean | Whether the key ownership was successfully verified. | `true` |
212258
| `activitypub.collection.id` | string | The URI of the collection object. | `"https://example.com/collection/1"` |
213259
| `activitypub.collection.type` | string[] | The qualified URI(s) of the collection type(s). | `["https://www.w3.org/ns/activitystreams#OrderedCollection"]` |
214260
| `activitypub.collection.total_items` | int | The total number of items in the collection. | `42` |
@@ -217,12 +263,16 @@ for ActivityPub:
217263
| `activitypub.object.in_reply_to` | string[] | The URI(s) of the original object to which the object reply. | `["https://example.com/object/1"]` |
218264
| `activitypub.inboxes` | int | The number of inboxes the activity is sent to. | `12` |
219265
| `activitypub.shared_inbox` | boolean | Whether the activity is sent to the shared inbox. | `true` |
266+
| `docloader.context_url` | string | The URL of the JSON-LD context document (if provided via Link header). | `"https://www.w3.org/ns/activitystreams"` |
267+
| `docloader.document_url` | string | The final URL of the fetched document (after following redirects). | `"https://example.com/object/1"` |
220268
| `fedify.actor.identifier` | string | The identifier of the actor. | `"1"` |
221269
| `fedify.inbox.recipient` | string | The identifier of the inbox recipient. | `"1"` |
222270
| `fedify.object.type` | string | The URI of the object type. | `"https://www.w3.org/ns/activitystreams#Note"` |
223271
| `fedify.object.values.{parameter}` | string[] | The argument values of the object dispatcher. | `["1", "2"]` |
224272
| `fedify.collection.cursor` | string | The cursor of the collection. | `"eyJpZCI6IjEiLCJ0eXBlIjoiT3JkZXJlZENvbGxlY3Rpb24ifQ=="` |
225273
| `fedify.collection.items` | number | The number of items in the collection page. It can be less than the total items. | `10` |
274+
| `http.redirect.url` | string | The redirect URL when a document fetch results in a redirect. | `"https://example.com/new-location"` |
275+
| `http.response.status_code` | int | The HTTP response status code. | `200` |
226276
| `http_signatures.signature` | string | The signature of the HTTP request in hexadecimal. | `"73a74c990beabe6e59cc68f9c6db7811b59cbb22fd12dcffb3565b651540efe9"` |
227277
| `http_signatures.algorithm` | string | The algorithm of the HTTP request signature. | `"rsa-sha256"` |
228278
| `http_signatures.key_id` | string | The public key ID of the HTTP request signature. | `"https://example.com/actor/1#main-key"` |
@@ -233,8 +283,114 @@ for ActivityPub:
233283
| `object_integrity_proofs.cryptosuite` | string | The cryptographic suite of the object integrity proof. | `"eddsa-jcs-2022"` |
234284
| `object_integrity_proofs.key_id` | string | The public key ID of the object integrity proof. | `"https://example.com/actor/1#main-key"` |
235285
| `object_integrity_proofs.signature` | string | The integrity proof of the object in hexadecimal. | `"73a74c990beabe6e59cc68f9c6db7811b59cbb22fd12dcffb3565b651540efe9"` |
286+
| `url.full` | string | The full URL being fetched by the document loader. | `"https://example.com/actor/1"` |
236287
| `webfinger.resource` | string | The queried resource URI. | `"acct:fedify@hollo.social"` |
237288
| `webfinger.resource.scheme` | string | The scheme of the queried resource URI. | `"acct"` |
238289

239290
[attributes]: https://opentelemetry.io/docs/specs/otel/common/#attribute
240291
[OpenTelemetry Semantic Conventions]: https://opentelemetry.io/docs/specs/semconv/
292+
293+
294+
Building observability tools with OpenTelemetry
295+
------------------------------------------------
296+
297+
The OpenTelemetry instrumentation in Fedify provides a powerful foundation for
298+
building custom observability tools. By implementing a custom [SpanExporter],
299+
you can capture and process all the telemetry data generated by Fedify to build
300+
tools like debug dashboards, activity monitors, or analytics systems.
301+
302+
### Example: ActivityPub debug dashboard
303+
304+
Here's an example of how you might implement a custom `SpanExporter` to capture
305+
ActivityPub activities for a debug dashboard:
306+
307+
~~~~ typescript
308+
import type { SpanExporter, ReadableSpan } from "@opentelemetry/sdk-trace-base";
309+
import { ExportResultCode } from "@opentelemetry/core";
310+
311+
interface ActivityRecord {
312+
direction: "inbound" | "outbound";
313+
activity: unknown;
314+
timestamp: Date;
315+
verified?: boolean;
316+
}
317+
318+
export class FedifyDebugExporter implements SpanExporter {
319+
private activities: ActivityRecord[] = [];
320+
321+
export(spans: ReadableSpan[], resultCallback: (result: { code: ExportResultCode }) => void): void {
322+
for (const span of spans) {
323+
// Capture inbound activities
324+
if (span.name === "activitypub.inbox") {
325+
const event = span.events.find(
326+
(e) => e.name === "activitypub.activity.received"
327+
);
328+
if (event && event.attributes) {
329+
this.activities.push({
330+
direction: "inbound",
331+
activity: JSON.parse(
332+
event.attributes["activitypub.activity.json"] as string
333+
),
334+
timestamp: new Date(span.startTime[0] * 1000),
335+
verified: event.attributes["activitypub.activity.verified"] as boolean,
336+
});
337+
}
338+
}
339+
340+
// Capture outbound activities
341+
if (span.name === "activitypub.send_activity") {
342+
const event = span.events.find(
343+
(e) => e.name === "activitypub.activity.sent"
344+
);
345+
if (event && event.attributes) {
346+
this.activities.push({
347+
direction: "outbound",
348+
activity: JSON.parse(
349+
event.attributes["activitypub.activity.json"] as string
350+
),
351+
timestamp: new Date(span.startTime[0] * 1000),
352+
});
353+
}
354+
}
355+
}
356+
resultCallback({ code: ExportResultCode.SUCCESS });
357+
}
358+
359+
async forceFlush(): Promise<void> {
360+
// Flush any pending data
361+
}
362+
363+
async shutdown(): Promise<void> {
364+
// Clean up resources
365+
}
366+
367+
getActivities(): ActivityRecord[] {
368+
return this.activities;
369+
}
370+
}
371+
~~~~
372+
373+
### Integrating the custom exporter
374+
375+
To use the custom exporter, add it to your OpenTelemetry SDK configuration:
376+
377+
~~~~ typescript
378+
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
379+
import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
380+
import { createFederation } from "@fedify/fedify";
381+
382+
const debugExporter = new FedifyDebugExporter();
383+
const tracerProvider = new NodeTracerProvider();
384+
tracerProvider.addSpanProcessor(new SimpleSpanProcessor(debugExporter));
385+
386+
const federation = createFederation({
387+
kv: /* your KV store */,
388+
tracerProvider,
389+
});
390+
~~~~
391+
392+
Now the `debugExporter` will receive all telemetry data from Fedify, and you
393+
can use `debugExporter.getActivities()` to access the captured activities for
394+
your debug dashboard or other observability tools.
395+
396+
[SpanExporter]: https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_sdk_trace_base.SpanExporter.html

0 commit comments

Comments
 (0)