import { NextRequest } from 'next/server' import { join } from 'path' import pino from 'pino' /* 실행 모드 */ const isProduction = process.env.NODE_ENV === 'production' /* 로그 데이터 인터페이스 */ interface ApiLogData { responseStatus: number method: string url: string // headers: { [k: string]: string } query: { [k: string]: string } body: string | undefined } /* 현재 날짜 반환 함수 (YYYY-MM-DD 형식) */ const getCurrentDate = (): string => { return new Date().toISOString().split('T')[0] } /* 날짜별 로그 파일 경로 생성 함수 */ const getLogFilePath = (): string => { const today: string = getCurrentDate() return join(process.cwd(), 'logs', `onsite-survey-${today}.log`) } /* 날짜별 로거 생성 클래스 */ class DailyLogger { private currentDate: string private logger: pino.Logger private destination?: ReturnType constructor() { this.currentDate = getCurrentDate() this.logger = this.createLogger() /* kill signal 핸들러 등록 */ process.on('SIGTERM', this.handleShutdown.bind(this)) process.on('SIGINT', this.handleShutdown.bind(this)) } private async handleShutdown(): Promise { if (isProduction && this.destination) { this.destination.flushSync() this.destination.end() } this.logger.flush() } private createLogger(): pino.Logger { if (!isProduction) return pino({ level: 'silent' }) /* 기존 destination 종료 */ if (this.destination) { this.destination.flushSync() this.destination.end() } /* 새로운 destination 생성 */ this.destination = pino.destination({ dest: getLogFilePath(), mkdir: true, sync: false, }) return pino( { level: 'info', timestamp: pino.stdTimeFunctions.isoTime, }, this.destination, ) } public info(obj: any, msg?: string): void { try { const today: string = getCurrentDate() if (today !== this.currentDate) { this.currentDate = today this.logger = this.createLogger() } this.logger.info(obj, msg) } catch (error) { console.error(`[DailyLogger] Failed to write log: ${error}`) } } } /* 로거 인스턴스 */ const dailyLogger = new DailyLogger() /* API 로그 기록 함수 */ export const writeApiLog = async (request: NextRequest, responseStatus: number): Promise => { if (!isProduction) return let bodyString: string | undefined if ( request.method === 'POST' && (request.headers.get('content-type') === 'multipart/form-data' || request.headers.get('content-type') === 'application/x-www-form-urlencoded') ) { const formData = await request.formData() bodyString = JSON.stringify(Object.fromEntries(formData)) } else { bodyString = await request.text() } const logData: ApiLogData = { responseStatus: responseStatus, method: request.method, url: request.url, // headers: Object.fromEntries(request.headers), query: Object.fromEntries(new URL(request.url).searchParams), body: bodyString, } dailyLogger.info(logData, 'API Request') }