- Replace Bun with pnpm 9 + Turborepo + tsx; apps/api renamed to apps/backend - Split backend into http/, modules/, lib/ layers mirroring tai-specguard - Add use-case + Result pattern, Problem Details RFC 7807, basePath /api/v1 - Mount Better Auth at /api/v1/auth, keep session-auth whitelist - Split Prisma schema into prisma/models/*, generate into generated/ - Rework packages/shared into lib/ + schemas/ with pagination DTOs - Implement health, groups, students, payments, waitlist modules - Add vitest suite with prisma mocks (21 tests), biome lint - Point web client to /api/v1/auth and /api/v1/groups/from-organization
72 lines
1.8 KiB
TypeScript
72 lines
1.8 KiB
TypeScript
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
import pino from 'pino';
|
|
import pinoPretty from 'pino-pretty';
|
|
|
|
type LoggerEnvironment = {
|
|
nodeEnv?: string;
|
|
};
|
|
|
|
type RequestLogContext = {
|
|
requestId: string;
|
|
};
|
|
|
|
const requestLogContext = new AsyncLocalStorage<RequestLogContext>();
|
|
|
|
const prettyOptions = {
|
|
colorize: true,
|
|
translateTime: 'SYS:standard',
|
|
ignore: 'pid,hostname',
|
|
};
|
|
|
|
const getLoggerEnvironment = (): LoggerEnvironment => ({
|
|
nodeEnv: process.env.NODE_ENV,
|
|
});
|
|
|
|
const isDevelopmentEnvironment = (environment: LoggerEnvironment) =>
|
|
environment.nodeEnv !== 'production';
|
|
|
|
const isTestEnvironment = (environment: LoggerEnvironment) => environment.nodeEnv === 'test';
|
|
|
|
export const runWithRequestLogContext = <T>(requestId: string, callback: () => T): T =>
|
|
requestLogContext.run({ requestId }, callback);
|
|
|
|
const getRequestLogMetadata = () => {
|
|
const context = requestLogContext.getStore();
|
|
|
|
if (!context) {
|
|
return {};
|
|
}
|
|
|
|
return { requestId: context.requestId };
|
|
};
|
|
|
|
export const createLoggerOptions = (environment: LoggerEnvironment): pino.LoggerOptions => ({
|
|
enabled: !isTestEnvironment(environment),
|
|
level: isDevelopmentEnvironment(environment) ? 'debug' : 'info',
|
|
mixin: () => getRequestLogMetadata(),
|
|
formatters: {
|
|
level(label, number) {
|
|
return {
|
|
level: number,
|
|
severity: label.toUpperCase(),
|
|
};
|
|
},
|
|
},
|
|
});
|
|
|
|
export const createLogger = (
|
|
environment: LoggerEnvironment = getLoggerEnvironment(),
|
|
) => {
|
|
const options = createLoggerOptions(environment);
|
|
if (isTestEnvironment(environment)) {
|
|
return pino({ ...options, level: 'silent' });
|
|
}
|
|
|
|
if (isDevelopmentEnvironment(environment)) {
|
|
return pino(options, pinoPretty(prettyOptions));
|
|
}
|
|
|
|
return pino(options);
|
|
};
|
|
|
|
export const logger = createLogger(); |