Unverified Commit 61d94afc authored by Hong Minhee's avatar Hong Minhee
Browse files

Instrument collection dispatchers

parent defb36a3
Loading
Loading
Loading
Loading
+24 −17
Original line number Diff line number Diff line
@@ -118,10 +118,12 @@ Fedify automatically instruments the following operations with OpenTelemetry
spans:

| Span name                                           | [Span kind] | Description                                 |
|----------------------------------------|-------------|---------------------------------------------|
|-----------------------------------------------------|-------------|---------------------------------------------|
| `{method} {template}`                               | Server      | Serves the incoming HTTP request.           |
| `activitypub.dispatch_actor`                        | Server      | Dispatches the ActivityPub actor.           |
| `activitypub.dispatch_actor_key_pairs`              | Server      | Dispatches the ActivityPub actor key pairs. |
| `activitypub.dispatch_collection {collection}`      | Server      | Dispatches the ActivityPub collection.      |
| `activitypub.dispatch_collection_page {collection}` | Server      | Dispatches the ActivityPub collection page. |
| `activitypub.dispatch_object`                       | Server      | Dispatches the Activity Streams object.     |
| `activitypub.get_actor_handle`                      | Client      | Resolves the actor handle.                  |
| `activitypub.lookup_object`                         | Client      | Looks up the Activity Streams object.       |
@@ -157,6 +159,9 @@ for ActivityPub:
| `activitypub.activity.retries`        | int      | The ordinal number of activity resending attempt (if and only if it's retried).          | `3`                                                                  |
| `activitypub.actor.id`                | string   | The URI of the actor object.                                                             | `"https://example.com/actor/1"`                                      |
| `activitypub.actor.type`              | string[] | The qualified URI(s) of the actor type(s).                                               | `["https://www.w3.org/ns/activitystreams#Person"]`                   |
| `activitypub.collection.id`           | string   | The URI of the collection object.                                                        | `"https://example.com/collection/1"`                                 |
| `activitypub.collection.type`         | string[] | The qualified URI(s) of the collection type(s).                                          | `["https://www.w3.org/ns/activitystreams#OrderedCollection"]`        |
| `activitypub.collection.total_items`  | int      | The total number of items in the collection.                                             | `42`                                                                 |
| `activitypub.object.id`               | string   | The URI of the object or the object enclosed by the activity.                            | `"https://example.com/object/1"`                                     |
| `activitypub.object.type`             | string[] | The qualified URI(s) of the object type(s).                                              | `["https://www.w3.org/ns/activitystreams#Note"]`                     |
| `activitypub.object.in_reply_to`      | string[] | The URI(s) of the original object to which the object reply.                             | `["https://example.com/object/1"]`                                   |
@@ -165,6 +170,8 @@ for ActivityPub:
| `fedify.actor.identifier`             | string   | The identifier of the actor.                                                             | `"1"`                                                                |
| `fedify.object.type`                  | string   | The URI of the object type.                                                              | `"https://www.w3.org/ns/activitystreams#Note"`                       |
| `fedify.object.values.{parameter}`    | string[] | The argument values of the object dispatcher.                                            | `["1", "2"]`                                                         |
| `fedify.collection.cursor`            | string   | The cursor of the collection.                                                            | `"eyJpZCI6IjEiLCJ0eXBlIjoiT3JkZXJlZENvbGxlY3Rpb24ifQ=="`             |
| `fedify.collection.items`             | number   | The number of items in the collection page.  It can be less than the total items.        | `10`                                                                 |
| `http_signatures.signature`           | string   | The signature of the HTTP request in hexadecimal.                                        | `"73a74c990beabe6e59cc68f9c6db7811b59cbb22fd12dcffb3565b651540efe9"` |
| `http_signatures.algorithm`           | string   | The algorithm of the HTTP request signature.                                             | `"rsa-sha256"`                                                       |
| `http_signatures.key_id`              | string   | The public key ID of the HTTP request signature.                                         | `"https://example.com/actor/1#main-key"`                             |
+61 −6
Original line number Diff line number Diff line
import { getLogger } from "@logtape/logtape";
import type { TracerProvider } from "@opentelemetry/api";
import type { Span, TracerProvider } from "@opentelemetry/api";
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
import { accepts } from "@std/http/negotiation";
import metadata from "../deno.json" with { type: "json" };
import type { DocumentLoader } from "../runtime/docloader.ts";
import { verifyRequest } from "../sig/http.ts";
import type { KeyCache } from "../sig/key.ts";
@@ -8,6 +10,7 @@ import { detachSignature, verifyJsonLd } from "../sig/ld.ts";
import { doesActorOwnKey } from "../sig/owner.ts";
import { verifyObject } from "../sig/proof.ts";
import type { Recipient } from "../vocab/actor.ts";
import { getTypeId } from "../vocab/type.ts";
import {
  Activity,
  CryptographicKey,
@@ -188,18 +191,52 @@ export interface CollectionHandlerParameters<
    TContextData,
    TFilter
  >;
  tracerProvider?: TracerProvider;
  onUnauthorized(request: Request): Response | Promise<Response>;
  onNotFound(request: Request): Response | Promise<Response>;
  onNotAcceptable(request: Request): Response | Promise<Response>;
}

export async function handleCollection<
export function handleCollection<
  TItem extends URL | Object | Link | Recipient,
  TContext extends RequestContext<TContextData>,
  TContextData,
  TFilter,
>(
  request: Request,
  params: CollectionHandlerParameters<TItem, TContext, TContextData, TFilter>,
): Promise<Response> {
  const name = params.name.trim().replace(/\s+/g, "_");
  const tracerProvider = params.tracerProvider ?? trace.getTracerProvider();
  const tracer = tracerProvider.getTracer(metadata.name, metadata.version);
  const url = new URL(request.url);
  const cursor = url.searchParams.get("cursor");
  return tracer.startActiveSpan(
    cursor == null
      ? `activitypub.dispatch_collection ${name}`
      : `activitypub.dispatch_collection_page ${name}`,
    { kind: SpanKind.SERVER },
    async (span) => {
      try {
        return await handleCollectionInternal(request, cursor, params, span);
      } catch (e) {
        span.setStatus({ code: SpanStatusCode.ERROR, message: String(e) });
        throw e;
      } finally {
        span.end();
      }
    },
  );
}

async function handleCollectionInternal<
  TItem extends URL | Object | Link | Recipient,
  TContext extends RequestContext<TContextData>,
  TContextData,
  TFilter,
>(
  request: Request,
  cursor: string | null,
  {
    name,
    identifier,
@@ -212,10 +249,9 @@ export async function handleCollection<
    onNotFound,
    onNotAcceptable,
  }: CollectionHandlerParameters<TItem, TContext, TContextData, TFilter>,
  span: Span,
): Promise<Response> {
  if (collectionCallbacks == null) return await onNotFound(request);
  const url = new URL(request.url);
  const cursor = url.searchParams.get("cursor");
  let collection: OrderedCollection | OrderedCollectionPage;
  const baseUri = uriGetter(identifier);
  if (cursor == null) {
@@ -224,6 +260,12 @@ export async function handleCollection<
      identifier,
    );
    const totalItems = await collectionCallbacks.counter?.(context, identifier);
    if (totalItems != null) {
      span.setAttribute(
        "activitypub.collection.total_items",
        Number(totalItems),
      );
    }
    if (firstCursor == null) {
      const page = await collectionCallbacks.dispatcher(
        context,
@@ -231,8 +273,12 @@ export async function handleCollection<
        null,
        filter,
      );
      if (page == null) return await onNotFound(request);
      if (page == null) {
        span.setStatus({ code: SpanStatusCode.ERROR });
        return await onNotFound(request);
      }
      const { items } = page;
      span.setAttribute("fedify.collection.items", items.length);
      collection = new OrderedCollection({
        id: baseUri,
        totalItems: totalItems == null ? null : Number(totalItems),
@@ -258,6 +304,7 @@ export async function handleCollection<
      });
    }
  } else {
    span.setAttribute("fedify.collection.cursor", cursor);
    const uri = new URL(baseUri);
    uri.searchParams.set("cursor", cursor);
    const page = await collectionCallbacks.dispatcher(
@@ -266,8 +313,12 @@ export async function handleCollection<
      cursor,
      filter,
    );
    if (page == null) return await onNotFound(request);
    if (page == null) {
      span.setStatus({ code: SpanStatusCode.ERROR });
      return await onNotFound(request);
    }
    const { items, prevCursor, nextCursor } = page;
    span.setAttribute("fedify.collection.items", items.length);
    let prev = null;
    if (prevCursor != null) {
      prev = new URL(context.url);
@@ -303,6 +354,10 @@ export async function handleCollection<
      return await onUnauthorized(request);
    }
  }
  if (collection.id != null) {
    span.setAttribute("activitypub.collection.id", collection.id.href);
  }
  span.setAttribute("activitypub.collection.type", getTypeId(collection).href);
  const jsonLd = await collection.toJsonLd(context);
  return new Response(JSON.stringify(jsonLd), {
    headers: {
+7 −0
Original line number Diff line number Diff line
@@ -2131,6 +2131,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> {
          uriGetter: context.getOutboxUri.bind(context),
          context,
          collectionCallbacks: this.outboxCallbacks,
          tracerProvider: this.tracerProvider,
          onUnauthorized,
          onNotFound,
          onNotAcceptable,
@@ -2143,6 +2144,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> {
            uriGetter: context.getInboxUri.bind(context),
            context,
            collectionCallbacks: this.inboxCallbacks,
            tracerProvider: this.tracerProvider,
            onUnauthorized,
            onNotFound,
            onNotAcceptable,
@@ -2190,6 +2192,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> {
          uriGetter: context.getFollowingUri.bind(context),
          context,
          collectionCallbacks: this.followingCallbacks,
          tracerProvider: this.tracerProvider,
          onUnauthorized,
          onNotFound,
          onNotAcceptable,
@@ -2213,6 +2216,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> {
              ))
            : undefined,
          collectionCallbacks: this.followersCallbacks,
          tracerProvider: this.tracerProvider,
          onUnauthorized,
          onNotFound,
          onNotAcceptable,
@@ -2225,6 +2229,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> {
          uriGetter: context.getLikedUri.bind(context),
          context,
          collectionCallbacks: this.likedCallbacks,
          tracerProvider: this.tracerProvider,
          onUnauthorized,
          onNotFound,
          onNotAcceptable,
@@ -2236,6 +2241,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> {
          uriGetter: context.getFeaturedUri.bind(context),
          context,
          collectionCallbacks: this.featuredCallbacks,
          tracerProvider: this.tracerProvider,
          onUnauthorized,
          onNotFound,
          onNotAcceptable,
@@ -2247,6 +2253,7 @@ export class FederationImpl<TContextData> implements Federation<TContextData> {
          uriGetter: context.getFeaturedTagsUri.bind(context),
          context,
          collectionCallbacks: this.featuredTagsCallbacks,
          tracerProvider: this.tracerProvider,
          onUnauthorized,
          onNotFound,
          onNotAcceptable,