1- import pino , { destination } from 'pino' ;
1+ import pino , {
2+ destination ,
3+ type LevelWithSilent ,
4+ type Logger as PinoLogger ,
5+ type LoggerOptions as PinoLoggerOptions ,
6+ } from 'pino' ;
27
8+ import { mkdirSync } from 'fs' ;
9+ import { dirname } from 'path' ;
310import { serializeLog , serializeHttp } from './serializers.js' ;
4-
511import { envs } from '../env.js' ;
612
7- const asyncDest = envs . isProd ? [ destination ( { sync : false } ) ] : [ ] ;
13+ type LoggerFileConfig = {
14+ path : string ;
15+ sync ?: boolean ;
16+ } ;
817
9- const pinoCommonOptions = {
18+ type LoggerTargetConfig = {
19+ file ?: string | LoggerFileConfig ;
20+ level ?: LevelWithSilent ;
21+ } ;
22+
23+ export type LoggerConfig = {
24+ console ?: boolean ;
25+ pretty ?: boolean ;
26+ level ?: LevelWithSilent ;
27+ app ?: LoggerTargetConfig ;
28+ http ?: LoggerTargetConfig ;
29+ sourceMaps ?: boolean ;
30+ } ;
31+
32+ const defaultPretty = ! envs . isProd ;
33+ const defaultFileSync = envs . isProd ? false : true ;
34+
35+ const pinoCommonOptions : PinoLoggerOptions = {
1036 timestamp : ( ) => `,"time":"${ new Date ( ) [ envs . isProd ? 'toISOString' : 'toLocaleTimeString' ] ( ) } "` ,
1137 formatters : {
1238 level : ( label : string ) => ( { level : label } ) ,
1339 } ,
1440 messageKey : 'message' ,
15- transport : envs . isProd ? undefined : { target : 'pino-pretty' , options : { colorize : true } } ,
1641} ;
1742
18- const logger = pino ( pinoCommonOptions , asyncDest [ 0 ] ) ;
43+ type LoggerLike = Record < LevelWithSilent , ( ...args : any [ ] ) => void > ;
44+
45+ const noop = ( ) => { } ;
46+ const noopLogger : LoggerLike = {
47+ trace : noop ,
48+ debug : noop ,
49+ info : noop ,
50+ warn : noop ,
51+ error : noop ,
52+ fatal : noop ,
53+ silent : noop ,
54+ } ;
55+
56+ let sourceMapsRegistered = false ;
57+
58+ const enableSourceMaps = ( enabled ?: boolean ) => {
59+ if ( ! enabled || sourceMapsRegistered ) return ;
60+ sourceMapsRegistered = true ;
61+ void import ( 'source-map-support/register' ) . catch ( ( ) => {
62+ sourceMapsRegistered = false ;
63+ } ) ;
64+ } ;
65+
66+ const normalizeFileConfig = ( file ?: string | LoggerFileConfig ) => {
67+ if ( ! file ) return undefined ;
68+ if ( typeof file === 'string' ) return { path : file , sync : defaultFileSync } ;
69+ return { path : file . path , sync : file . sync ?? defaultFileSync } ;
70+ } ;
71+
72+ const ensureLogDir = ( filePath : string ) => {
73+ const dir = dirname ( filePath ) ;
74+ if ( dir && dir !== '.' ) {
75+ mkdirSync ( dir , { recursive : true } ) ;
76+ }
77+ } ;
78+
79+ const toLoggerLike = ( logger : PinoLogger ) : LoggerLike => {
80+ const call =
81+ ( method : keyof LoggerLike ) =>
82+ ( ...args : any [ ] ) =>
83+ ( logger as any ) [ method ] ( ...args ) ;
84+
85+ return {
86+ trace : call ( 'trace' ) ,
87+ debug : call ( 'debug' ) ,
88+ info : call ( 'info' ) ,
89+ warn : call ( 'warn' ) ,
90+ error : call ( 'error' ) ,
91+ fatal : call ( 'fatal' ) ,
92+ silent : noop ,
93+ } ;
94+ } ;
95+
96+ const buildConsoleLogger = ( options : PinoLoggerOptions , prettyEnabled : boolean ) => {
97+ if ( prettyEnabled ) {
98+ try {
99+ const transport = pino . transport ( {
100+ targets : [ { target : 'pino-pretty' , options : { colorize : true } } ] ,
101+ } ) ;
102+ return toLoggerLike ( pino ( options , transport ) ) ;
103+ } catch {
104+ return toLoggerLike ( pino ( options , destination ( { dest : 1 , sync : false } ) ) ) ;
105+ }
106+ }
107+ return toLoggerLike ( pino ( options , destination ( { dest : 1 , sync : false } ) ) ) ;
108+ } ;
109+
110+ const buildFileLogger = ( options : PinoLoggerOptions , fileConfig : LoggerFileConfig ) => {
111+ ensureLogDir ( fileConfig . path ) ;
112+ return toLoggerLike (
113+ pino ( options , destination ( { dest : fileConfig . path , sync : fileConfig . sync ?? defaultFileSync } ) ) ,
114+ ) ;
115+ } ;
116+
117+ const combineLoggers = ( primary ?: LoggerLike , secondary ?: LoggerLike ) : LoggerLike => {
118+ if ( ! primary && ! secondary ) return noopLogger ;
119+ const first = primary ?? noopLogger ;
120+ const second = secondary ?? noopLogger ;
121+ return {
122+ trace : ( ...args ) => {
123+ first . trace ( ...args ) ;
124+ second . trace ( ...args ) ;
125+ } ,
126+ debug : ( ...args ) => {
127+ first . debug ( ...args ) ;
128+ second . debug ( ...args ) ;
129+ } ,
130+ info : ( ...args ) => {
131+ first . info ( ...args ) ;
132+ second . info ( ...args ) ;
133+ } ,
134+ warn : ( ...args ) => {
135+ first . warn ( ...args ) ;
136+ second . warn ( ...args ) ;
137+ } ,
138+ error : ( ...args ) => {
139+ first . error ( ...args ) ;
140+ second . error ( ...args ) ;
141+ } ,
142+ fatal : ( ...args ) => {
143+ first . fatal ( ...args ) ;
144+ second . fatal ( ...args ) ;
145+ } ,
146+ silent : noop ,
147+ } ;
148+ } ;
149+
150+ const buildLogger = ( config : LoggerConfig , target ?: LoggerTargetConfig ) : LoggerLike => {
151+ const consoleEnabled = config . console ?? true ;
152+ const prettyEnabled = config . pretty ?? defaultPretty ;
153+ const fileConfig = normalizeFileConfig ( target ?. file ) ;
154+ const level = target ?. level ?? config . level ;
155+
156+ const options : PinoLoggerOptions = { ...pinoCommonOptions } ;
157+ if ( level ) options . level = level ;
158+
159+ const consoleLogger = consoleEnabled ? buildConsoleLogger ( options , prettyEnabled ) : undefined ;
160+ const fileLogger = fileConfig ? buildFileLogger ( options , fileConfig ) : undefined ;
161+
162+ return combineLoggers ( consoleLogger , fileLogger ) ;
163+ } ;
164+
165+ let appLogger : LoggerLike ;
166+ let httpLogger : LoggerLike ;
167+
168+ export const configureLogger = ( config : LoggerConfig = { } ) => {
169+ enableSourceMaps ( config . sourceMaps ) ;
170+
171+ if ( config . app || config . http ) {
172+ appLogger = buildLogger ( config , config . app ) ;
173+ httpLogger = buildLogger ( config , config . http ) ;
174+ } else {
175+ const shared = buildLogger ( config ) ;
176+ appLogger = shared ;
177+ httpLogger = shared ;
178+ }
179+ } ;
180+
181+ configureLogger ( ) ;
19182
20183interface LogMeFn {
21184 ( obj : unknown , ...args : [ ] ) : void ;
@@ -33,15 +196,15 @@ type LogMe = {
33196
34197export const logHttp = ( ...params : Parameters < typeof serializeHttp > ) => {
35198 const { level, logObject } = serializeHttp ( ...params ) ;
36- logger [ level ] ( logObject ) ;
199+ httpLogger [ level ] ( logObject ) ;
37200} ;
38201
39202// TODO: remove the test condition
40203export const logMe : LogMe = {
41- debug : ( ...args ) => ! envs . isTest && logger . debug ( serializeLog ( ...args ) ) ,
42- info : ( ...args ) => ! envs . isTest && logger . info ( serializeLog ( ...args ) ) ,
43- warn : ( ...args ) => ! envs . isTest && logger . warn ( serializeLog ( ...args ) ) ,
44- error : ( ...args ) => ! envs . isTest && logger . error ( serializeLog ( ...args ) ) ,
45- fatal : ( ...args ) => ! envs . isTest && logger . fatal ( serializeLog ( ...args ) ) ,
46- trace : ( ...args ) => ! envs . isTest && logger . trace ( serializeLog ( ...args ) ) ,
204+ debug : ( ...args ) => ! envs . isTest && appLogger . debug ( serializeLog ( ...args ) ) ,
205+ info : ( ...args ) => ! envs . isTest && appLogger . info ( serializeLog ( ...args ) ) ,
206+ warn : ( ...args ) => ! envs . isTest && appLogger . warn ( serializeLog ( ...args ) ) ,
207+ error : ( ...args ) => ! envs . isTest && appLogger . error ( serializeLog ( ...args ) ) ,
208+ fatal : ( ...args ) => ! envs . isTest && appLogger . fatal ( serializeLog ( ...args ) ) ,
209+ trace : ( ...args ) => ! envs . isTest && appLogger . trace ( serializeLog ( ...args ) ) ,
47210} ;
0 commit comments