this repo has no description
at main 230 lines 6.4 kB view raw
1const COLOURS = { 2 RESET: "\x1b[0m", 3 BRIGHT: "\x1b[1m", 4 DIM: "\x1b[2m", 5 UNDERSCORE: "\x1b[4m", 6 BLINK: "\x1b[5m", 7 REVERSE: "\x1b[7m", 8 HIDDEN: "\x1b[8m", 9 10 FG_BLACK: "\x1b[30m", 11 FG_RED: "\x1b[31m", 12 FG_GREEN: "\x1b[32m", 13 FG_YELLOW: "\x1b[33m", 14 FG_BLUE: "\x1b[34m", 15 FG_MAGENTA: "\x1b[35m", 16 FG_CYAN: "\x1b[36m", 17 FG_WHITE: "\x1b[37m", 18 19 BG_BLACK: "\x1b[40m", 20 BG_RED: "\x1b[41m", 21 BG_GREEN: "\x1b[42m", 22 BG_YELLOW: "\x1b[43m", 23 BG_BLUE: "\x1b[44m", 24 BG_MAGENTA: "\x1b[45m", 25 BG_CYAN: "\x1b[46m", 26 BG_WHITE: "\x1b[47m", 27}; 28 29enum LogLevel { 30 DEBUG = 0, 31 INFO = 1, 32 WARN = 2, 33 ERROR = 3, 34 FATAL = 4, 35 OFF = 5, 36} 37 38const methodMaps = { 39 [LogLevel.DEBUG]: console.debug, 40 [LogLevel.INFO]: console.info, 41 [LogLevel.WARN]: console.warn, 42 [LogLevel.ERROR]: console.error, 43 [LogLevel.FATAL]: console.error, 44 [LogLevel.OFF]: () => {}, 45}; 46 47interface LogOptions { 48 timestamp?: boolean; 49 colourize?: boolean; 50 scopecolour?: string; 51} 52 53class Logger { 54 private static logLevel: LogLevel = LogLevel.INFO; 55 private scope: string[]; 56 private options: LogOptions; 57 private static readonly MAX_LEVEL_LENGTH = Math.max( 58 ...Object.keys(LogLevel) 59 .filter((k) => Number.isNaN(Number(k))) 60 .map((k) => k.length), 61 ); 62 63 private static readonly SCOPE_COLOURS = [ 64 COLOURS.FG_RED, 65 COLOURS.FG_GREEN, 66 COLOURS.FG_YELLOW, 67 COLOURS.FG_BLUE, 68 COLOURS.FG_MAGENTA, 69 COLOURS.FG_CYAN, 70 ]; 71 72 private static scopeColours: { [scopeName: string]: string } = {}; 73 74 constructor(scope: string | string[] = [], options: LogOptions = {}) { 75 this.scope = Array.isArray(scope) ? scope : [scope]; 76 this.options = { 77 timestamp: true, 78 colourize: true, 79 scopecolour: this.getScopecolour(this.scope.join("/")), 80 ...options, 81 }; 82 83 if (typeof window === "undefined") { 84 if (process.env.LOG_LEVEL) { 85 try { 86 const envLogLevel = process.env.LOG_LEVEL.toUpperCase(); 87 if (envLogLevel in LogLevel) { 88 Logger.setLogLevel(LogLevel[envLogLevel as keyof typeof LogLevel]); 89 } else { 90 console.warn( 91 `Invalid LOG_LEVEL environment variable: ${process.env.LOG_LEVEL}. Using default: INFO.`, 92 ); 93 } 94 } catch (e) { 95 console.warn(`Error parsing LOG_LEVEL: ${e}. Using default: INFO.`); 96 } 97 } 98 } 99 } 100 101 static setLogLevel(level: LogLevel): void { 102 Logger.logLevel = level; 103 } 104 105 private getLevelcolour(level: LogLevel): string { 106 switch (level) { 107 case LogLevel.DEBUG: 108 return COLOURS.FG_WHITE; 109 case LogLevel.INFO: 110 return COLOURS.FG_GREEN; 111 case LogLevel.WARN: 112 return COLOURS.FG_YELLOW; 113 case LogLevel.ERROR: 114 return COLOURS.FG_RED; 115 case LogLevel.FATAL: 116 return COLOURS.BG_RED + COLOURS.FG_WHITE; 117 default: 118 return COLOURS.RESET; 119 } 120 } 121 122 private getScopecolour(scopeName: string): string { 123 if (Logger.scopeColours[scopeName]) { 124 return Logger.scopeColours[scopeName]; 125 } 126 127 const colourIndex = 128 Object.keys(Logger.scopeColours).length % Logger.SCOPE_COLOURS.length; 129 const colour = Logger.SCOPE_COLOURS[colourIndex]; 130 Logger.scopeColours[scopeName] = colour; 131 return colour; 132 } 133 134 private formatMessage(level: LogLevel, message: string): string { 135 const now = new Date(); 136 const timestamp = this.options.timestamp 137 ? `${COLOURS.DIM}[${now.toLocaleString()}]${COLOURS.RESET} ` 138 : ""; 139 const levelString = LogLevel[level]; 140 const scopeString = 141 this.scope.length > 0 ? `[${this.scope.join("/")}] ` : ""; 142 const levelcolour = this.getLevelcolour(level); 143 const colourStart = this.options.colourize ? levelcolour : ""; 144 const colourEnd = this.options.colourize ? COLOURS.RESET : ""; 145 146 const formattedMessage = `${timestamp}${colourStart}[${levelString}]${colourEnd} ${this.options.colourize && this.options.scopecolour ? this.options.scopecolour : ""}${scopeString}${COLOURS.RESET}${message}`; 147 return formattedMessage; 148 } 149 150 private log(level: LogLevel, message: string, ...args: unknown[]): void { 151 if (level >= Logger.logLevel && level !== LogLevel.OFF) { 152 const log = methodMaps[level]; 153 154 if (typeof window !== "undefined") { 155 const levelString = LogLevel[level]; 156 const scopeString = 157 this.scope.length > 0 ? `[${this.scope.join("/")}]` : ""; 158 159 let levelStyle = ""; 160 switch (level) { 161 case LogLevel.DEBUG: 162 levelStyle = "color: gray; font-weight: normal;"; 163 break; 164 case LogLevel.INFO: 165 levelStyle = "color: green; font-weight: bold;"; 166 break; 167 case LogLevel.WARN: 168 levelStyle = "color: orange; font-weight: bold;"; 169 break; 170 case LogLevel.ERROR: 171 levelStyle = "color: red; font-weight: bold;"; 172 break; 173 case LogLevel.FATAL: 174 levelStyle = 175 "color: white; background-color: red; font-weight: bold; padding: 2px 5px;"; 176 break; 177 } 178 179 const scopeStyle = "color: magenta; font-weight: bold;"; 180 181 log( 182 `%c[${levelString}]%c ${scopeString} ${message}`, 183 levelStyle, 184 scopeStyle, 185 ...args, 186 ); 187 return; 188 } 189 190 const formattedMessage = this.formatMessage(level, message); 191 log(formattedMessage, ...args); 192 } 193 } 194 195 debug(message: string, ...args: unknown[]): void { 196 this.log(LogLevel.DEBUG, message, ...args); 197 } 198 199 info(message: string, ...args: unknown[]): void { 200 this.log(LogLevel.INFO, message, ...args); 201 } 202 203 warn(message: string, ...args: unknown[]): void { 204 this.log(LogLevel.WARN, message, ...args); 205 } 206 207 error(message: string, ...args: unknown[]): void { 208 this.log(LogLevel.ERROR, message, ...args); 209 } 210 211 fatal(message: string, ...args: unknown[]): void { 212 this.log(LogLevel.FATAL, message, ...args); 213 } 214 215 child(scope: string | string[], options: LogOptions = {}): Logger { 216 const newScope = Array.isArray(scope) 217 ? [...this.scope, ...scope] 218 : [...this.scope, scope]; 219 return new Logger(newScope, { ...this.options, ...options }); 220 } 221} 222 223const singleton = new Logger("global", { 224 timestamp: true, 225 colourize: true, 226 scopecolour: COLOURS.FG_MAGENTA, 227}); 228 229export default singleton; 230export { Logger, LogLevel, COLOURS };