11import type { NextFunction , Request , Response } from 'express' ;
22
3- // ── ANSI 색상 (개발 모드용) ────────────────────────────────────
4-
5- const IS_DEV = process . env [ 'NODE_ENV' ] !== 'production' ;
6-
7- const c = IS_DEV
8- ? {
9- reset : '\x1b[0m' ,
10- dim : '\x1b[2m' ,
11- bold : '\x1b[1m' ,
12- green : '\x1b[32m' ,
13- yellow : '\x1b[33m' ,
14- red : '\x1b[31m' ,
15- cyan : '\x1b[36m' ,
16- magenta : '\x1b[35m' ,
17- blue : '\x1b[34m' ,
18- gray : '\x1b[90m' ,
19- }
20- : Object . fromEntries (
21- [ 'reset' , 'dim' , 'bold' , 'green' , 'yellow' , 'red' , 'cyan' , 'magenta' , 'blue' , 'gray' ] . map (
22- ( k ) => [ k , '' ] ,
23- ) ,
24- ) ;
3+ import { LOG_COLORS , formatLogLine } from '../logging/format' ;
254
26- // ── 타임스탬프 ────────────────────────────────────────────────
5+ // ── 스킵 경로 ────────────────────────────────────────────────
6+ // health 체크는 매 30초마다 호출되어 로그를 오염시키므로 제외
277
28- function ts ( ) : string {
29- return new Date ( ) . toISOString ( ) . replace ( 'T' , ' ' ) . slice ( 0 , 23 ) ;
30- }
8+ const SKIP_PATHS = new Set ( [ '/health' , '/health/' ] ) ;
319
3210// ── 상태코드 → 색상 ───────────────────────────────────────────
3311
3412function statusColor ( status : number ) : string {
35- if ( status >= 500 ) return c [ ' red' ] as string ;
36- if ( status >= 400 ) return c [ ' yellow' ] as string ;
37- if ( status >= 300 ) return c [ ' cyan' ] as string ;
38- return c [ ' green' ] as string ;
13+ if ( status >= 500 ) return LOG_COLORS . red ;
14+ if ( status >= 400 ) return LOG_COLORS . yellow ;
15+ if ( status >= 300 ) return LOG_COLORS . cyan ;
16+ return LOG_COLORS . green ;
3917}
4018
4119// ── HTTP 메서드 → 색상 ────────────────────────────────────────
4220
4321function methodColor ( method : string ) : string {
4422 switch ( method . toUpperCase ( ) ) {
4523 case 'GET' :
46- return c [ ' blue' ] as string ;
24+ return LOG_COLORS . blue ;
4725 case 'POST' :
48- return c [ ' green' ] as string ;
26+ return LOG_COLORS . green ;
4927 case 'PUT' :
5028 case 'PATCH' :
51- return c [ ' yellow' ] as string ;
29+ return LOG_COLORS . yellow ;
5230 case 'DELETE' :
53- return c [ ' red' ] as string ;
31+ return LOG_COLORS . red ;
5432 default :
55- return c [ ' gray' ] as string ;
33+ return LOG_COLORS . gray ;
5634 }
5735}
5836
59- // ── 스킵 경로 ────────────────────────────────────────────────
60- // health 체크는 매 30초마다 호출되어 로그를 오염시키므로 제외
61-
62- const SKIP_PATHS = new Set ( [ '/health' , '/health/' ] ) ;
63-
6437// ── HTTP 요청 로거 미들웨어 ────────────────────────────────────
6538
6639export function requestLogger ( req : Request , res : Response , next : NextFunction ) : void {
@@ -70,23 +43,29 @@ export function requestLogger(req: Request, res: Response, next: NextFunction):
7043 }
7144
7245 const start = Date . now ( ) ;
73- const method = req . method . padEnd ( 6 ) ;
46+ const method = req . method . toUpperCase ( ) . padEnd ( 6 ) ;
7447 const path = req . path ;
7548 const query = Object . keys ( req . query ) . length > 0 ? `?${ new URLSearchParams ( req . query as Record < string , string > ) } ` : '' ;
49+ // 메서드/쿼리 문자열은 메시지 본문에서만 색상을 주고,
50+ // 공통 포맷(formatLogLine)의 timestamp/tag 포맷은 그대로 유지한다.
51+ const methodStyled = `${ methodColor ( req . method ) } ${ method } ${ LOG_COLORS . reset } ` ;
52+ const queryStyled = query . length > 0 ? `${ LOG_COLORS . dim } ${ query } ${ LOG_COLORS . reset } ` : '' ;
7653
7754 // 요청 수신 로그
78- console . log (
79- `${ c [ 'gray' ] } ${ ts ( ) } ${ c [ 'reset' ] } ${ c [ 'dim' ] } →${ c [ 'reset' ] } ${ methodColor ( req . method ) } ${ method } ${ c [ 'reset' ] } ${ path } ${ c [ 'dim' ] } ${ query } ${ c [ 'reset' ] } ` ,
80- ) ;
55+ console . log ( formatLogLine ( 'http' , `${ LOG_COLORS . dim } →${ LOG_COLORS . reset } ${ methodStyled } ${ path } ${ queryStyled } ` ) ) ;
8156
8257 // 응답 완료 후 로그
8358 res . on ( 'finish' , ( ) => {
8459 const ms = Date . now ( ) - start ;
8560 const status = res . statusCode ;
8661 const durationStr = ms < 1000 ? `${ ms } ms` : `${ ( ms / 1000 ) . toFixed ( 2 ) } s` ;
8762
63+ const statusStyled = `${ statusColor ( status ) } ${ status } ${ LOG_COLORS . reset } ` ;
8864 console . log (
89- `${ c [ 'gray' ] } ${ ts ( ) } ${ c [ 'reset' ] } ${ c [ 'dim' ] } ←${ c [ 'reset' ] } ${ statusColor ( status ) } ${ status } ${ c [ 'reset' ] } ${ methodColor ( req . method ) } ${ method } ${ c [ 'reset' ] } ${ path } ${ c [ 'dim' ] } (${ durationStr } )${ c [ 'reset' ] } ` ,
65+ formatLogLine (
66+ 'http' ,
67+ `${ LOG_COLORS . dim } ←${ LOG_COLORS . reset } ${ statusStyled } ${ methodStyled } ${ path } ${ LOG_COLORS . dim } (${ durationStr } )${ LOG_COLORS . reset } ` ,
68+ ) ,
9069 ) ;
9170 } ) ;
9271
@@ -98,35 +77,26 @@ export function requestLogger(req: Request, res: Response, next: NextFunction):
9877
9978export const log = {
10079 info ( tag : string , msg : string , meta ?: Record < string , unknown > ) : void {
101- const metaStr = meta ? ` ${ c [ 'dim' ] } ${ JSON . stringify ( meta ) } ${ c [ 'reset' ] } ` : '' ;
102- console . log (
103- `${ c [ 'gray' ] } ${ ts ( ) } ${ c [ 'reset' ] } ${ c [ 'cyan' ] } [${ tag } ]${ c [ 'reset' ] } ${ msg } ${ metaStr } ` ,
104- ) ;
80+ const metaStr = meta ? ` ${ JSON . stringify ( meta ) } ` : '' ;
81+ console . log ( formatLogLine ( tag , `${ msg } ${ metaStr } ` , 'info' ) ) ;
10582 } ,
10683
10784 warn ( tag : string , msg : string , meta ?: Record < string , unknown > ) : void {
108- const metaStr = meta ? ` ${ c [ 'dim' ] } ${ JSON . stringify ( meta ) } ${ c [ 'reset' ] } ` : '' ;
109- console . warn (
110- `${ c [ 'gray' ] } ${ ts ( ) } ${ c [ 'reset' ] } ${ c [ 'yellow' ] } [${ tag } ]${ c [ 'reset' ] } ${ c [ 'yellow' ] } ${ msg } ${ c [ 'reset' ] } ${ metaStr } ` ,
111- ) ;
85+ const metaStr = meta ? ` ${ JSON . stringify ( meta ) } ` : '' ;
86+ console . warn ( formatLogLine ( tag , `${ msg } ${ metaStr } ` , 'warn' ) ) ;
11287 } ,
11388
11489 error ( tag : string , msg : string , err ?: unknown ) : void {
115- const errStr =
116- err instanceof Error
117- ? ` — ${ err . message } ${ IS_DEV && err . stack ? `\n${ c [ 'dim' ] } ${ err . stack } ${ c [ 'reset' ] } ` : '' } `
118- : err != null
119- ? ` — ${ String ( err ) } `
120- : '' ;
121- console . error (
122- `${ c [ 'gray' ] } ${ ts ( ) } ${ c [ 'reset' ] } ${ c [ 'red' ] } [${ tag } ]${ c [ 'reset' ] } ${ c [ 'red' ] } ${ msg } ${ c [ 'reset' ] } ${ errStr } ` ,
123- ) ;
90+ const errStr = err == null
91+ ? ''
92+ : err instanceof Error
93+ ? ` — ${ err . stack ?? err . message } `
94+ : ` — ${ String ( err ) } ` ;
95+ console . error ( formatLogLine ( tag , `${ msg } ${ errStr } ` , 'error' ) ) ;
12496 } ,
12597
12698 success ( tag : string , msg : string , meta ?: Record < string , unknown > ) : void {
127- const metaStr = meta ? ` ${ c [ 'dim' ] } ${ JSON . stringify ( meta ) } ${ c [ 'reset' ] } ` : '' ;
128- console . log (
129- `${ c [ 'gray' ] } ${ ts ( ) } ${ c [ 'reset' ] } ${ c [ 'green' ] } [${ tag } ]${ c [ 'reset' ] } ${ c [ 'green' ] } ${ msg } ${ c [ 'reset' ] } ${ metaStr } ` ,
130- ) ;
99+ const metaStr = meta ? ` ${ JSON . stringify ( meta ) } ` : '' ;
100+ console . log ( formatLogLine ( tag , `${ msg } ${ metaStr } ` , 'success' ) ) ;
131101 } ,
132102} ;
0 commit comments