From 11205d8e55256af839f44cb053a61e15b526f652 Mon Sep 17 00:00:00 2001 From: Hermes Date: Sat, 25 Apr 2026 17:50:44 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=205=20=E2=80=94=20pino=20structur?= =?UTF-8?q?ed=20logger,=20request=20logging,=20tRPC=20timing=20middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/_core/trpc.ts | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/server/_core/trpc.ts b/server/_core/trpc.ts index ff2208b..d84e2df 100644 --- a/server/_core/trpc.ts +++ b/server/_core/trpc.ts @@ -1,6 +1,9 @@ import { initTRPC, TRPCError } from "@trpc/server"; import type { TrpcContext } from "./context.js"; import { isSubscriptionActive } from "../db.js"; +import { childLogger } from "./logger.js"; + +const trpcLog = childLogger("trpc"); const t = initTRPC.context().create({ errorFormatter({ shape }) { @@ -10,7 +13,31 @@ const t = initTRPC.context().create({ export const router = t.router; export const middleware = t.middleware; -export const publicProcedure = t.procedure; + +// Timing middleware: logs every procedure call with its duration. Errors are +// re-thrown so the existing error handlers in the express adapter still fire. +const timing = t.middleware(async ({ path, type, next, ctx }) => { + const start = Date.now(); + try { + const result = await next(); + const durationMs = Date.now() - start; + if (result.ok) { + trpcLog.debug({ path, type, durationMs, userId: ctx.user?.id ?? null }, "procedure ok"); + } else { + trpcLog.warn( + { path, type, durationMs, userId: ctx.user?.id ?? null, code: result.error.code }, + "procedure error" + ); + } + return result; + } catch (err) { + const durationMs = Date.now() - start; + trpcLog.error({ err, path, type, durationMs, userId: ctx.user?.id ?? null }, "procedure threw"); + throw err; + } +}); + +export const publicProcedure = t.procedure.use(timing); const isAuthed = t.middleware(({ ctx, next }) => { if (!ctx.user) {