Skip to content

Commit f386a47

Browse files
committed
Preserve outbound trace activity details
Outbound send activity events now keep the lightweight activity type and actor identifiers that the debug exporter needs after the full activity JSON payload was removed from the event attributes. #755 (comment) Assisted-by: gpt-5.5
1 parent e7249cc commit f386a47

5 files changed

Lines changed: 80 additions & 9 deletions

File tree

docs/manual/opentelemetry.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,14 @@ Each span event includes attributes with detailed information:
265265

266266
- `activitypub.inbox.url`: The inbox URL where the activity was delivered
267267
- `activitypub.activity.id`: The activity ID
268+
- `activitypub.activity.type` (optional): The qualified activity type URI
269+
- `activitypub.actor.id` (optional): The sender actor ID
268270

269-
The `activitypub.activity.sent` event records delivery metadata only. It does
270-
not include the full `activitypub.activity.json` payload; if you need the full
271-
outbound activity for auditing, store it in your application before delivery
272-
and correlate it with `activitypub.activity.id`.
271+
The `activitypub.activity.sent` event records delivery metadata and lightweight
272+
activity identifiers only. It does not include the full
273+
`activitypub.activity.json` payload; if you need the full outbound activity for
274+
auditing, store it in your application before delivery and correlate it with
275+
`activitypub.activity.id`.
273276

274277
**`activitypub.delivery.failed` event attributes:**
275278

@@ -462,9 +465,17 @@ export class FedifyDebugExporter implements SpanExporter {
462465
const inboxUrl = event.attributes[
463466
"activitypub.inbox.url"
464467
] as string | undefined;
468+
const activityType = event.attributes[
469+
"activitypub.activity.type"
470+
] as string | undefined;
471+
const actorId = event.attributes[
472+
"activitypub.actor.id"
473+
] as string | undefined;
465474
this.activities.push({
466475
direction: "outbound",
467476
activityId,
477+
activityType,
478+
actorId,
468479
inboxUrl,
469480
timestamp: new Date(span.startTime[0] * 1000),
470481
});

packages/fedify/src/federation/send.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,14 @@ test("sendActivity() records OpenTelemetry span events", async (t) => {
498498
event.attributes["activitypub.activity.id"],
499499
"https://example.com/activity",
500500
);
501+
assertEquals(
502+
event.attributes["activitypub.activity.type"],
503+
"https://www.w3.org/ns/activitystreams#Create",
504+
);
505+
assertEquals(
506+
event.attributes["activitypub.actor.id"],
507+
"https://example.com/person",
508+
);
501509
assertEquals(event.attributes["activitypub.activity.json"], undefined);
502510

503511
exporter.clear();

packages/fedify/src/federation/send.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Recipient } from "@fedify/vocab";
22
import { getLogger } from "@logtape/logtape";
33
import {
4+
type Attributes,
45
type MeterProvider,
56
type Span,
67
SpanKind,
@@ -198,6 +199,29 @@ export function sendActivity(
198199

199200
const MAX_ERROR_RESPONSE_BODY_BYTES = 1024;
200201

202+
function getActivityActorId(activity: unknown): string | undefined {
203+
if (!isRecord(activity)) return undefined;
204+
return getIdValue(activity.actor);
205+
}
206+
207+
function getIdValue(value: unknown): string | undefined {
208+
if (typeof value === "string" && value !== "") return value;
209+
if (value instanceof URL) return value.href;
210+
if (Array.isArray(value)) {
211+
for (const item of value) {
212+
const id = getIdValue(item);
213+
if (id != null) return id;
214+
}
215+
return undefined;
216+
}
217+
if (isRecord(value)) return getIdValue(value.id);
218+
return undefined;
219+
}
220+
221+
function isRecord(value: unknown): value is Record<string, unknown> {
222+
return typeof value === "object" && value != null;
223+
}
224+
201225
async function readLimitedResponseBody(
202226
response: Response,
203227
maxBytes: number,
@@ -340,10 +364,18 @@ async function sendActivityInternal(
340364
deliverySuccess = true;
341365

342366
// Record the sent activity with delivery details
343-
span.addEvent("activitypub.activity.sent", {
367+
const eventAttributes: Attributes = {
344368
"activitypub.inbox.url": inbox.href,
345369
"activitypub.activity.id": activityId ?? "",
346-
});
370+
};
371+
if (activityType != null) {
372+
eventAttributes["activitypub.activity.type"] = activityType;
373+
}
374+
const actorId = getActivityActorId(activity);
375+
if (actorId != null) {
376+
eventAttributes["activitypub.actor.id"] = actorId;
377+
}
378+
span.addEvent("activitypub.activity.sent", eventAttributes);
347379
} finally {
348380
federationMetrics.recordDelivery(
349381
inbox,

packages/fedify/src/otel/exporter.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,19 @@ function createActivitySentEvent(options: {
9999
activityJson?: string;
100100
inboxUrl: string;
101101
activityId?: string;
102+
activityType?: string;
103+
actorId?: string;
102104
}): TimedEvent {
103105
const attributes: Attributes = {
104106
"activitypub.inbox.url": options.inboxUrl,
105107
"activitypub.activity.id": options.activityId ?? "",
106108
};
109+
if (options.activityType != null) {
110+
attributes["activitypub.activity.type"] = options.activityType;
111+
}
112+
if (options.actorId != null) {
113+
attributes["activitypub.actor.id"] = options.actorId;
114+
}
107115
if (options.activityJson != null) {
108116
attributes["activitypub.activity.json"] = options.activityJson;
109117
}
@@ -180,6 +188,8 @@ test("FedifySpanExporter", async (t) => {
180188
const spanId = "span012";
181189
const inboxUrl = "https://example.com/users/alice/inbox";
182190
const activityId = "https://myserver.com/activities/789";
191+
const activityType = "https://www.w3.org/ns/activitystreams#Accept";
192+
const actorId = "https://myserver.com/users/bob";
183193

184194
const span = createMockSpan({
185195
traceId,
@@ -189,6 +199,8 @@ test("FedifySpanExporter", async (t) => {
189199
createActivitySentEvent({
190200
inboxUrl,
191201
activityId,
202+
activityType,
203+
actorId,
192204
}),
193205
],
194206
});
@@ -205,8 +217,9 @@ test("FedifySpanExporter", async (t) => {
205217
assertEquals(activities[0].traceId, traceId);
206218
assertEquals(activities[0].spanId, spanId);
207219
assertEquals(activities[0].direction, "outbound");
208-
assertEquals(activities[0].activityType, "Unknown");
220+
assertEquals(activities[0].activityType, activityType);
209221
assertEquals(activities[0].activityId, activityId);
222+
assertEquals(activities[0].actorId, actorId);
210223
assertEquals(activities[0].activityJson, undefined);
211224
assertEquals(activities[0].inboxUrl, inboxUrl);
212225
},

packages/fedify/src/otel/exporter.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,18 +432,25 @@ export class FedifySpanExporter implements SpanExporter {
432432

433433
const inboxUrl = attrs["activitypub.inbox.url"];
434434
const explicitActivityId = attrs["activitypub.activity.id"];
435+
const explicitActivityType = attrs["activitypub.activity.type"];
436+
const explicitActorId = attrs["activitypub.actor.id"];
435437

436438
return {
437439
traceId,
438440
spanId,
439441
parentSpanId,
440442
direction: "outbound",
441-
activityType,
443+
activityType:
444+
typeof explicitActivityType === "string" && explicitActivityType !== ""
445+
? explicitActivityType
446+
: activityType,
442447
activityId: activityId ??
443448
(typeof explicitActivityId === "string" && explicitActivityId !== ""
444449
? explicitActivityId
445450
: undefined),
446-
actorId,
451+
actorId: typeof explicitActorId === "string" && explicitActorId !== ""
452+
? explicitActorId
453+
: actorId,
447454
...(typeof activityJson === "string" ? { activityJson } : {}),
448455
timestamp: new Date(
449456
event.time[0] * 1000 + event.time[1] / 1e6,

0 commit comments

Comments
 (0)