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