From 5808355b4626dfa7caf674a8b98d9bf134d4ff0f Mon Sep 17 00:00:00 2001 From: StanPonomarev Date: Sat, 18 Jul 2026 14:10:08 +0200 Subject: [PATCH] fix: log all webhook host requests --- app/src/application/controller/main.ts | 6 +++- app/src/application/controller/server.ts | 30 ++++++++++++++++++++ app/src/tests/integration/controller.test.ts | 24 +++++++++++++++- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/app/src/application/controller/main.ts b/app/src/application/controller/main.ts index 59db0fe..9284aac 100644 --- a/app/src/application/controller/main.ts +++ b/app/src/application/controller/main.ts @@ -12,7 +12,7 @@ import { import { formatError, requireEnv } from "../../core/contracts.js"; import { type PublicationContext, safeFailure } from "../publication/status.js"; import { log, pumpDeliveries, pumpOutbox } from "./handlers/workers.js"; -import { handleHttp } from "./server.js"; +import { handleHttp, observeHttpRequest } from "./server.js"; async function main(): Promise { const serverUrl = validateServerUrl(requireEnv("GITEA_SERVER_URL")); @@ -97,6 +97,10 @@ async function main(): Promise { retentionTimer.unref(); const server = createServer((request, response) => { + observeHttpRequest(request, response, (entry) => { + const level = entry.statusCode >= 500 ? "error" : "info"; + console.log(log(level, "HTTP request", { ...entry })); + }); handleHttp(request, response, { store, webhookSecret, diff --git a/app/src/application/controller/server.ts b/app/src/application/controller/server.ts index 1a574bf..fe5c8c9 100644 --- a/app/src/application/controller/server.ts +++ b/app/src/application/controller/server.ts @@ -10,6 +10,36 @@ import { const bodyLimit = 1024 * 1024; class BodyTooLarge extends Error {} +export interface HttpRequestLog { + method: string; + path: string; + statusCode: number; + durationMs: number; + completed: boolean; +} + +export function observeHttpRequest( + request: IncomingMessage, + response: ServerResponse, + write: (entry: HttpRequestLog) => void, +): void { + const startedAt = Date.now(); + let observed = false; + const observe = () => { + if (observed) return; + observed = true; + write({ + method: request.method || "UNKNOWN", + path: request.url || "", + statusCode: response.statusCode, + durationMs: Date.now() - startedAt, + completed: response.writableFinished, + }); + }; + response.once("finish", observe); + response.once("close", observe); +} + export async function handleHttp( request: IncomingMessage, response: ServerResponse, diff --git a/app/src/tests/integration/controller.test.ts b/app/src/tests/integration/controller.test.ts index f563c4d..fb6d256 100644 --- a/app/src/tests/integration/controller.test.ts +++ b/app/src/tests/integration/controller.test.ts @@ -6,7 +6,11 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; import { AgentStore } from "../../adapters/database/store.js"; -import { handleHttp } from "../../application/controller/server.js"; +import { + type HttpRequestLog, + handleHttp, + observeHttpRequest, +} from "../../application/controller/server.js"; test("signed webhooks are durably admitted before acknowledgment", async () => { const root = await mkdtemp(join(tmpdir(), "agent-http-")); @@ -62,7 +66,9 @@ test("signed webhooks are durably admitted before acknowledgment", async () => { test("invalid webhook signatures are rejected without persistence", async () => { const root = await mkdtemp(join(tmpdir(), "agent-http-auth-")); const store = new AgentStore(join(root, "agent.db")); + const requests: HttpRequestLog[] = []; const server = createServer((request, response) => { + observeHttpRequest(request, response, (entry) => requests.push(entry)); handleHttp(request, response, { store, webhookSecret: "test-secret", @@ -97,6 +103,22 @@ test("invalid webhook signatures are rejected without persistence", async () => ); assert.equal(response.status, 401); assert.equal(store.leaseDelivery(), undefined); + assert.equal(requests.length, 1); + assert.deepEqual( + { + method: requests[0]?.method, + path: requests[0]?.path, + statusCode: requests[0]?.statusCode, + completed: requests[0]?.completed, + }, + { + method: "POST", + path: "/webhooks/gitea", + statusCode: 401, + completed: true, + }, + ); + assert.ok((requests[0]?.durationMs ?? -1) >= 0); } finally { await new Promise((resolve) => server.close(() => resolve())); store.close();