local-storage-filter.ts
1 export type Level = 'debug' | 'info' | 'warn' | 'error'; 2 // Numbers correspond to the levels above, with 0 meaning "no level" 3 type LevelNum = 4 | 3 | 2 | 1 | 0; 4 5 interface Rules { 6 named?: Record<string, LevelNum>; 7 defaultLevel?: LevelNum; 8 } 9 10 const LEVEL_TO_NUM: Record<Level | 'off' | '*' | '', LevelNum> = { 11 '*': 4, 12 debug: 4, 13 info: 3, 14 warn: 2, 15 error: 1, 16 off: 0, 17 '': 0, 18 }; 19 20 /** 21 * Parses log filtering instructions from localStorage.onyxLog. 22 * The instructions are a series of comma separated directives that restrict 23 * logging. Restrictions indicate the highest log level that a named logger 24 * will emit. The name of the logger is the string passed to 25 * LoggerFactory.loggerFor. 26 * 27 * By default (ex. empty rule string), no logs will be emitted. 28 * 29 * The format of the directives is NAME=LEVEL. LEVEL can be one of: 30 * 31 * - * - all levels are logged (debug, info, warn, error) 32 * - debug - same as above 33 * - info - everything but debug is logged 34 * - warn - everything but info and debug is logged 35 * - error - only errors are logged 36 * - off (or empty string, ex. "MyClass=") - nothing will be logged 37 * 38 * Some examples: 39 * 40 * - '*=*' will emit all log levels from all loggers 41 * - '*=info,Foo=off' will emit everything but debug except or logs from 42 * the named logger Foo (which will be entirely suppressed) 43 * - 'Bar=error,Baz=warn' will emit errors from Bar and Baz and warnings from 44 * Baz 45 * 46 * NOTE: Keep this in sync with README.md! 47 */ 48 function parseRules(): Rules { 49 const onyxLog: string = (() => { 50 try { 51 // The typeof check is for SSR 52 return ( 53 (typeof window !== 'undefined' 54 ? window.localStorage.onyxLog 55 : '') || '' 56 ); 57 } catch { 58 // window.localStorage will throw when referenced (at all) when 59 // Chrome has it disabled 60 // See: rdar://93367396 (Guard localStorage and sessionStorage use) 61 return ''; 62 } 63 })(); 64 65 const PRODUCTION_DEFAULT = {}; // no logs unless specified 66 const DEV_DEFAULT = { 67 defaultLevel: LEVEL_TO_NUM['*'], // All logs unless specified 68 }; 69 const isDevelopment = (() => { 70 // This is a little tricky. The ENV var is not real. It's replaced by 71 // rollup-plugin-replace. Thus, we can't do the usual of testing for 72 // the existence of `process` and then doing `process?.env` etc. 73 // Instead, we just try the whole thing and try/catch. This way, 74 // rollup-plugin-replace sees that entire string verbatim and can 75 // replace it with the proper environment. 76 try { 77 // @ts-ignore 78 return process.env.NODE_ENV !== 'production'; 79 } catch { 80 return false; 81 } 82 })(); 83 const defaultRules = isDevelopment ? DEV_DEFAULT : PRODUCTION_DEFAULT; 84 85 // If the localStorage is specified, start from a clean slate. Otherwise, 86 // use the environment default 87 const rules: Rules = onyxLog.length > 0 ? {} : defaultRules; 88 89 for (const directive of onyxLog.split(',').filter((v) => v)) { 90 // Invalid directive, must be of the form 'name=level' 91 const parts = directive.split('='); 92 if (parts.length !== 2) { 93 continue; 94 } 95 96 const [name, maxLevelName] = parts; 97 const maxLevel = 98 LEVEL_TO_NUM[maxLevelName as keyof typeof LEVEL_TO_NUM]; 99 100 // Invalid level 101 if (typeof maxLevel === 'undefined') { 102 continue; 103 } 104 105 if (name === '*') { 106 rules.defaultLevel = maxLevel; 107 } else { 108 rules.named = rules.named ?? {}; 109 rules.named[name] = maxLevel; 110 } 111 } 112 113 return rules; 114 } 115 116 export function shouldLog(name: string, level: Level): boolean { 117 const rules = parseRules(); 118 119 // Rules for the named logger take precedence over the default 120 const maxLevel = (rules.named || {})[name] ?? rules.defaultLevel ?? 0; 121 return LEVEL_TO_NUM[level] <= maxLevel; 122 }