| b69ab31 | | | 1 | /** |
| b69ab31 | | | 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| b69ab31 | | | 3 | * |
| b69ab31 | | | 4 | * This source code is licensed under the MIT license found in the |
| b69ab31 | | | 5 | * LICENSE file in the root directory of this source tree. |
| b69ab31 | | | 6 | */ |
| b69ab31 | | | 7 | |
| b69ab31 | | | 8 | export type Level = 'log' | 'info' | 'warn' | 'error'; |
| b69ab31 | | | 9 | |
| b69ab31 | | | 10 | const tzOptions: Intl.DateTimeFormatOptions & { |
| b69ab31 | | | 11 | // This is an ES2021 feature which is not properly reflected in the types but is available. TODO: update target to ES2021 to fix this. |
| b69ab31 | | | 12 | // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#fractionalseconddigits |
| b69ab31 | | | 13 | fractionalSecondDigits: 1 | 2 | 3; |
| b69ab31 | | | 14 | } = { |
| b69ab31 | | | 15 | timeZoneName: 'short', |
| b69ab31 | | | 16 | // Show millisecond resolution for extra timing information in the logs |
| b69ab31 | | | 17 | fractionalSecondDigits: 3, |
| b69ab31 | | | 18 | // After setting fractionalSecondDigits, it requires setting all fields we want printed to 'numeric'. |
| b69ab31 | | | 19 | year: 'numeric', |
| b69ab31 | | | 20 | month: 'numeric', |
| b69ab31 | | | 21 | day: 'numeric', |
| b69ab31 | | | 22 | hour: 'numeric', |
| b69ab31 | | | 23 | minute: 'numeric', |
| b69ab31 | | | 24 | second: 'numeric', |
| b69ab31 | | | 25 | }; |
| b69ab31 | | | 26 | |
| b69ab31 | | | 27 | /** Standardized logging interface for the server. |
| b69ab31 | | | 28 | * Format: [YYY-MM-DD HH:MM:SS,MILLISECONDS TIMEZONE] [LEVEL] your message here |
| b69ab31 | | | 29 | * Example: [2025-01-14 17:54:55,092 GMT−8] [INFO] Setup analytics |
| b69ab31 | | | 30 | */ |
| b69ab31 | | | 31 | export abstract class Logger { |
| b69ab31 | | | 32 | abstract write(level: Level, timeStr: string, ...args: Parameters<typeof console.log>): void; |
| b69ab31 | | | 33 | |
| b69ab31 | | | 34 | private writeLog(level: Level, ...args: Parameters<typeof console.info>): void { |
| b69ab31 | | | 35 | const timeStr = `[${new Date().toLocaleString('sv', tzOptions)}]`; |
| b69ab31 | | | 36 | this.write(level, timeStr, ...args); |
| b69ab31 | | | 37 | } |
| b69ab31 | | | 38 | |
| b69ab31 | | | 39 | /** |
| b69ab31 | | | 40 | * @deprecated use .info instead |
| b69ab31 | | | 41 | * TODO: we should just use info everywhere, I don't know the distinction between log and info, |
| b69ab31 | | | 42 | * this was just for compatibility with console.log which isn't particularly important. |
| b69ab31 | | | 43 | */ |
| b69ab31 | | | 44 | log(...args: Parameters<typeof console.info>): void { |
| b69ab31 | | | 45 | this.writeLog('log', ...args); |
| b69ab31 | | | 46 | } |
| b69ab31 | | | 47 | |
| b69ab31 | | | 48 | info(...args: Parameters<typeof console.info>): void { |
| b69ab31 | | | 49 | this.writeLog('info', ...args); |
| b69ab31 | | | 50 | } |
| b69ab31 | | | 51 | |
| b69ab31 | | | 52 | warn(...args: Parameters<typeof console.info>): void { |
| b69ab31 | | | 53 | this.writeLog('warn', ...args); |
| b69ab31 | | | 54 | } |
| b69ab31 | | | 55 | |
| b69ab31 | | | 56 | error(...args: Parameters<typeof console.info>): void { |
| b69ab31 | | | 57 | this.writeLog('error', ...args); |
| b69ab31 | | | 58 | } |
| b69ab31 | | | 59 | |
| b69ab31 | | | 60 | /** Get all previously logged contents, usually for filing a bug report. */ |
| b69ab31 | | | 61 | getLogFileContents?(): Promise<string>; |
| b69ab31 | | | 62 | |
| b69ab31 | | | 63 | levelToString(level: Level): string { |
| b69ab31 | | | 64 | switch (level) { |
| b69ab31 | | | 65 | case 'log': |
| b69ab31 | | | 66 | return ' [LOG]'; |
| b69ab31 | | | 67 | case 'info': |
| b69ab31 | | | 68 | return ' [INFO]'; |
| b69ab31 | | | 69 | case 'warn': |
| b69ab31 | | | 70 | return ' [WARN]'; |
| b69ab31 | | | 71 | case 'error': |
| b69ab31 | | | 72 | return '[ERROR]'; |
| b69ab31 | | | 73 | } |
| b69ab31 | | | 74 | } |
| b69ab31 | | | 75 | } |
| b69ab31 | | | 76 | |
| b69ab31 | | | 77 | const GREY = '\x1b[38;5;8m'; |
| b69ab31 | | | 78 | const RED = '\x1b[38;5;9m'; |
| b69ab31 | | | 79 | const YELLOW = '\x1b[38;5;11m'; |
| b69ab31 | | | 80 | const CLEAR = '\x1b[0m'; |
| b69ab31 | | | 81 | |
| b69ab31 | | | 82 | /** |
| b69ab31 | | | 83 | * Logger that prints to stdout via `console`, with ANSI escape coloring for easy reading. |
| b69ab31 | | | 84 | * Typically used in dev mode. |
| b69ab31 | | | 85 | */ |
| b69ab31 | | | 86 | export class StdoutLogger extends Logger { |
| b69ab31 | | | 87 | write(level: Level, timeStr: string, ...args: Parameters<typeof console.log>): void { |
| b69ab31 | | | 88 | // eslint-disable-next-line no-console |
| b69ab31 | | | 89 | console[level]('%s%s%s%s', GREY, timeStr, this.levelToString(level), CLEAR, ...args); |
| b69ab31 | | | 90 | } |
| b69ab31 | | | 91 | |
| b69ab31 | | | 92 | levelToString(level: Level): string { |
| b69ab31 | | | 93 | switch (level) { |
| b69ab31 | | | 94 | case 'log': |
| b69ab31 | | | 95 | return GREY + ' [LOG]'; |
| b69ab31 | | | 96 | case 'info': |
| b69ab31 | | | 97 | return GREY + ' [INFO]'; |
| b69ab31 | | | 98 | case 'warn': |
| b69ab31 | | | 99 | return YELLOW + ' [WARN]'; |
| b69ab31 | | | 100 | case 'error': |
| b69ab31 | | | 101 | return RED + '[ERROR]'; |
| b69ab31 | | | 102 | } |
| b69ab31 | | | 103 | } |
| b69ab31 | | | 104 | } |