From 8f7744ca0daa90944f814b4374ce586a1397af62 Mon Sep 17 00:00:00 2001 From: imbytecat Date: Sat, 25 Apr 2026 13:31:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(server):=20=E6=96=B0=E5=A2=9E=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=20logger=20=E5=85=A5=E5=8F=A3=E4=B8=8E=20/health=20li?= =?UTF-8?q?veness=20=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/server/logger.ts 包一层 console.*,给后续 pino/otel 迁移留单点 - interceptors.ts 的 logError 改走 logger.error,业务侧禁止直接 console.* - /health 返回 'ok',纯 liveness(不查 DB),DB 挂时探活仍绿 --- src/routeTree.gen.ts | 24 +++++++++++++++++++++--- src/routes/health.ts | 9 +++++++++ src/server/api/interceptors.ts | 5 +++-- src/server/logger.ts | 5 +++++ 4 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 src/routes/health.ts create mode 100644 src/server/logger.ts diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 98e1a7d..f50b926 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -9,10 +9,16 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' +import { Route as HealthRouteImport } from './routes/health' import { Route as IndexRouteImport } from './routes/index' import { Route as ApiSplatRouteImport } from './routes/api/$' import { Route as ApiRpcSplatRouteImport } from './routes/api/rpc.$' +const HealthRoute = HealthRouteImport.update({ + id: '/health', + path: '/health', + getParentRoute: () => rootRouteImport, +} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', @@ -31,36 +37,47 @@ const ApiRpcSplatRoute = ApiRpcSplatRouteImport.update({ export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/health': typeof HealthRoute '/api/$': typeof ApiSplatRoute '/api/rpc/$': typeof ApiRpcSplatRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/health': typeof HealthRoute '/api/$': typeof ApiSplatRoute '/api/rpc/$': typeof ApiRpcSplatRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute + '/health': typeof HealthRoute '/api/$': typeof ApiSplatRoute '/api/rpc/$': typeof ApiRpcSplatRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/api/$' | '/api/rpc/$' + fullPaths: '/' | '/health' | '/api/$' | '/api/rpc/$' fileRoutesByTo: FileRoutesByTo - to: '/' | '/api/$' | '/api/rpc/$' - id: '__root__' | '/' | '/api/$' | '/api/rpc/$' + to: '/' | '/health' | '/api/$' | '/api/rpc/$' + id: '__root__' | '/' | '/health' | '/api/$' | '/api/rpc/$' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + HealthRoute: typeof HealthRoute ApiSplatRoute: typeof ApiSplatRoute ApiRpcSplatRoute: typeof ApiRpcSplatRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/health': { + id: '/health' + path: '/health' + fullPath: '/health' + preLoaderRoute: typeof HealthRouteImport + parentRoute: typeof rootRouteImport + } '/': { id: '/' path: '/' @@ -87,6 +104,7 @@ declare module '@tanstack/react-router' { const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + HealthRoute: HealthRoute, ApiSplatRoute: ApiSplatRoute, ApiRpcSplatRoute: ApiRpcSplatRoute, } diff --git a/src/routes/health.ts b/src/routes/health.ts new file mode 100644 index 0000000..a665f86 --- /dev/null +++ b/src/routes/health.ts @@ -0,0 +1,9 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/health')({ + server: { + handlers: { + GET: () => new Response('ok', { status: 200, headers: { 'content-type': 'text/plain' } }), + }, + }, +}) diff --git a/src/server/api/interceptors.ts b/src/server/api/interceptors.ts index 0e1d072..1b0fbb7 100644 --- a/src/server/api/interceptors.ts +++ b/src/server/api/interceptors.ts @@ -1,13 +1,14 @@ import { ORPCError, ValidationError } from '@orpc/server' import { z } from 'zod' +import { logger } from '@/server/logger' export const logError = (error: unknown) => { - console.error(error) + logger.error(error) } export const handleValidationError = (error: unknown) => { if (error instanceof ORPCError && error.code === 'BAD_REQUEST' && error.cause instanceof ValidationError) { - // If you only use Zod you can safely cast to ZodIssue[] (per ORPC official docs) + // ORPC ValidationError.issues are Zod issues in this app. const zodError = new z.ZodError(error.cause.issues as z.core.$ZodIssue[]) throw new ORPCError('INPUT_VALIDATION_FAILED', { diff --git a/src/server/logger.ts b/src/server/logger.ts new file mode 100644 index 0000000..8b33125 --- /dev/null +++ b/src/server/logger.ts @@ -0,0 +1,5 @@ +export const logger = { + error: (error: unknown) => console.error(error), + warn: (...args: unknown[]) => console.warn(...args), + info: (...args: unknown[]) => console.info(...args), +}