feat(server): 新增统一 logger 入口与 /health liveness 端点

- src/server/logger.ts 包一层 console.*,给后续 pino/otel 迁移留单点
- interceptors.ts 的 logError 改走 logger.error,业务侧禁止直接 console.*
- /health 返回 'ok',纯 liveness(不查 DB),DB 挂时探活仍绿
This commit is contained in:
2026-04-25 13:31:25 +08:00
parent 830c908712
commit 8f7744ca0d
4 changed files with 38 additions and 5 deletions
+21 -3
View File
@@ -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. // 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 rootRouteImport } from './routes/__root'
import { Route as HealthRouteImport } from './routes/health'
import { Route as IndexRouteImport } from './routes/index' import { Route as IndexRouteImport } from './routes/index'
import { Route as ApiSplatRouteImport } from './routes/api/$' import { Route as ApiSplatRouteImport } from './routes/api/$'
import { Route as ApiRpcSplatRouteImport } from './routes/api/rpc.$' import { Route as ApiRpcSplatRouteImport } from './routes/api/rpc.$'
const HealthRoute = HealthRouteImport.update({
id: '/health',
path: '/health',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({ const IndexRoute = IndexRouteImport.update({
id: '/', id: '/',
path: '/', path: '/',
@@ -31,36 +37,47 @@ const ApiRpcSplatRoute = ApiRpcSplatRouteImport.update({
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/health': typeof HealthRoute
'/api/$': typeof ApiSplatRoute '/api/$': typeof ApiSplatRoute
'/api/rpc/$': typeof ApiRpcSplatRoute '/api/rpc/$': typeof ApiRpcSplatRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/health': typeof HealthRoute
'/api/$': typeof ApiSplatRoute '/api/$': typeof ApiSplatRoute
'/api/rpc/$': typeof ApiRpcSplatRoute '/api/rpc/$': typeof ApiRpcSplatRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport
'/': typeof IndexRoute '/': typeof IndexRoute
'/health': typeof HealthRoute
'/api/$': typeof ApiSplatRoute '/api/$': typeof ApiSplatRoute
'/api/rpc/$': typeof ApiRpcSplatRoute '/api/rpc/$': typeof ApiRpcSplatRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/api/$' | '/api/rpc/$' fullPaths: '/' | '/health' | '/api/$' | '/api/rpc/$'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' | '/api/$' | '/api/rpc/$' to: '/' | '/health' | '/api/$' | '/api/rpc/$'
id: '__root__' | '/' | '/api/$' | '/api/rpc/$' id: '__root__' | '/' | '/health' | '/api/$' | '/api/rpc/$'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
HealthRoute: typeof HealthRoute
ApiSplatRoute: typeof ApiSplatRoute ApiSplatRoute: typeof ApiSplatRoute
ApiRpcSplatRoute: typeof ApiRpcSplatRoute ApiRpcSplatRoute: typeof ApiRpcSplatRoute
} }
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
interface FileRoutesByPath { interface FileRoutesByPath {
'/health': {
id: '/health'
path: '/health'
fullPath: '/health'
preLoaderRoute: typeof HealthRouteImport
parentRoute: typeof rootRouteImport
}
'/': { '/': {
id: '/' id: '/'
path: '/' path: '/'
@@ -87,6 +104,7 @@ declare module '@tanstack/react-router' {
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
HealthRoute: HealthRoute,
ApiSplatRoute: ApiSplatRoute, ApiSplatRoute: ApiSplatRoute,
ApiRpcSplatRoute: ApiRpcSplatRoute, ApiRpcSplatRoute: ApiRpcSplatRoute,
} }
+9
View File
@@ -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' } }),
},
},
})
+3 -2
View File
@@ -1,13 +1,14 @@
import { ORPCError, ValidationError } from '@orpc/server' import { ORPCError, ValidationError } from '@orpc/server'
import { z } from 'zod' import { z } from 'zod'
import { logger } from '@/server/logger'
export const logError = (error: unknown) => { export const logError = (error: unknown) => {
console.error(error) logger.error(error)
} }
export const handleValidationError = (error: unknown) => { export const handleValidationError = (error: unknown) => {
if (error instanceof ORPCError && error.code === 'BAD_REQUEST' && error.cause instanceof ValidationError) { 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[]) const zodError = new z.ZodError(error.cause.issues as z.core.$ZodIssue[])
throw new ORPCError('INPUT_VALIDATION_FAILED', { throw new ORPCError('INPUT_VALIDATION_FAILED', {
+5
View File
@@ -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),
}