From 9a1ce616049814aa3cc85c96162bac562f5006f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sat, 6 Jun 2026 21:41:28 +0200 Subject: [PATCH] feat(journal): surface Fedify logs via LogTape console sink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inbound Mastodon Follows are being rejected 401 (signature verification) on staging and Fedify's diagnostics were invisible: it logs through LogTape, which is silent until configured. - Configure LogTape with a console sink for the 'fedify' category when the federation instance is built; level via FEDERATION_LOG_LEVEL (default info). - Staging deploy sets FEDERATION_LOG_LEVEL=debug for the soak — signature-verification detail per request; dial down once proven. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/cd-staging.yml | 3 +++ apps/journal/app/lib/federation.server.ts | 29 +++++++++++++++++++++++ apps/journal/package.json | 1 + infrastructure/docker-compose.staging.yml | 1 + pnpm-lock.yaml | 3 +++ 5 files changed, 37 insertions(+) diff --git a/.github/workflows/cd-staging.yml b/.github/workflows/cd-staging.yml index 25bd2e5..4ed9aba 100644 --- a/.github/workflows/cd-staging.yml +++ b/.github/workflows/cd-staging.yml @@ -131,6 +131,9 @@ jobs: # rollout 12.2). FEDERATION_KEY_ENCRYPTION_KEY comes from the # SOPS env decrypted above; previews never set this flag. echo "FEDERATION_ENABLED=true" + # Verbose Fedify logs during the soak (signature verification + # detail). Dial down to info once federation is proven out. + echo "FEDERATION_LOG_LEVEL=debug" } >> infrastructure/staging.env - name: Copy compose file + env to server diff --git a/apps/journal/app/lib/federation.server.ts b/apps/journal/app/lib/federation.server.ts index ccac7e2..f3fd101 100644 --- a/apps/journal/app/lib/federation.server.ts +++ b/apps/journal/app/lib/federation.server.ts @@ -25,6 +25,7 @@ // 202 for activity types without a registered listener, which is // exactly the "acknowledge and drop" the spec wants; the same applies // to wrapped types we inspect and ignore (e.g. Undo(Like)). +import { configure, getConsoleSink } from "@logtape/logtape"; import { createFederation, type Federation } from "@fedify/fedify"; import { Accept, Follow, Person, Reject, Undo } from "@fedify/fedify/vocab"; import { eq } from "drizzle-orm"; @@ -96,8 +97,36 @@ async function findLocalPublicUserByIri(iri: URL | null): Promise | null = null; +let _logtapeConfigured = false; + +/** + * Fedify logs through LogTape, which is silent until configured — and + * an unconfigured LogTape means signature-verification failures (the + * single most debuggable federation problem) vanish without a trace. + * Routed to the console so docker logs / Loki pick them up alongside + * pino. Level via FEDERATION_LOG_LEVEL (default "info"; set "debug" + * to see per-request signature verification detail). + */ +function configureFederationLogging(): void { + if (_logtapeConfigured) return; + _logtapeConfigured = true; + const level = (process.env.FEDERATION_LOG_LEVEL ?? "info") as "debug" | "info" | "warning"; + configure({ + sinks: { console: getConsoleSink() }, + loggers: [ + { category: "fedify", lowestLevel: level, sinks: ["console"] }, + // LogTape's meta logger warns about itself; keep it quiet unless + // something is really wrong. + { category: ["logtape", "meta"], lowestLevel: "warning", sinks: ["console"] }, + ], + }).catch(() => { + // configure() throws if something else configured LogTape first — + // fine, their sinks win. + }); +} function buildFederation(): Federation { + configureFederationLogging(); const federation = createFederation({ kv: new PostgresKvStore(), // Canonical origin so generated IRIs are correct behind the Caddy diff --git a/apps/journal/package.json b/apps/journal/package.json index 0869f53..1ad1555 100644 --- a/apps/journal/package.json +++ b/apps/journal/package.json @@ -14,6 +14,7 @@ "dependencies": { "@fedify/fedify": "2.1.16", "@js-temporal/polyfill": "^0.5.1", + "@logtape/logtape": "^2.0.5", "@react-router/node": "catalog:", "@react-router/serve": "catalog:", "@sentry/node": "catalog:", diff --git a/infrastructure/docker-compose.staging.yml b/infrastructure/docker-compose.staging.yml index dac301a..fe71e9b 100644 --- a/infrastructure/docker-compose.staging.yml +++ b/infrastructure/docker-compose.staging.yml @@ -66,6 +66,7 @@ services: # empty (flag off) so preview journals never emit federation traffic. FEDERATION_ENABLED: ${FEDERATION_ENABLED:-} FEDERATION_KEY_ENCRYPTION_KEY: ${FEDERATION_KEY_ENCRYPTION_KEY:-} + FEDERATION_LOG_LEVEL: ${FEDERATION_LOG_LEVEL:-} healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:3000/api/health || exit 1"] interval: 15s diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 69f51b7..4f28e71 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -209,6 +209,9 @@ importers: '@js-temporal/polyfill': specifier: ^0.5.1 version: 0.5.1 + '@logtape/logtape': + specifier: ^2.0.5 + version: 2.1.1 '@react-router/node': specifier: 'catalog:' version: 7.16.0(react-router@7.16.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3)