// Copyright Abridged, Inc. 2021,2024. All Rights Reserved. // Node module: @collabland/common // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import debug from 'debug'; import lodash from 'lodash'; import util from 'util'; import {AnyType} from '../types.js'; import {getEnvVar, setEnvVar} from './env.js'; const {cloneDeepWith} = lodash; export {Debugger} from 'debug'; export const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom'); export enum LogLevel { ERROR = 'error', WARN = 'warn', INFO = 'info', DEBUG = 'debug', TRACE = 'trace', } /** * Cached secret values */ const secretValues: string[] = []; /** * Load secret values * @returns */ function getSecretValues() { if (secretValues.length) return secretValues; const secrets = globalThis.collabLandSecrets ?? {}; Object.entries(secrets) .filter(([k, v]) => { k = k.toUpperCase(); return ( k.includes('KEY') || k.includes('SECRET') || k.includes('PRIVATE') || k.includes('PASS') || k.includes('TOKEN') ); }) .forEach(([k, v]) => secretValues.push(v)); return secretValues; } /** * Check if the namespace is enabled by log level * @param namespace - Debug namespace * @returns */ export function isLogEnabled(namespace: string) { if (!isDebugEnabled(namespace)) return false; const levels = Object.values(LogLevel); const enabledLevel = getLogLevelForNamespace(namespace); const logLevel = getLogLevel(); const index = levels.indexOf(logLevel); return levels.indexOf(enabledLevel) <= index; } export function setLogLevel(level: LogLevel) { return setEnvVar('LOG_LEVEL', level, true); } export function getLogLevel() { const logLevelSetting = getEnvVar( 'LOG_LEVEL', // First check for LOG_LEVEL getEnvVar('DEBUG_PINO', LogLevel.TRACE), // Then check for DEBUG_PINO ); const levels = Object.values(LogLevel); const logLevel = levels.find(level => level === logLevelSetting.toLowerCase()) ?? LogLevel.TRACE; return logLevel; } /** * Get the highest log level for the given namespace * @param namespace - Debug namespace * @returns */ export function getLogLevelForNamespace(namespace: string) { namespace = namespace.toLowerCase(); const levels = Object.values(LogLevel); const names = namespace.split(':'); const enabledLevels = levels.filter(level => names.includes(level)); if (enabledLevels.length === 0) { // Default to debug return LogLevel.DEBUG; } // Return the last level matched return enabledLevels[enabledLevels.length - 1]; } /** * React arguments to hide secrets * @param args - Args * @returns */ export function redactData( args: AnyType, secretKeys: string[] = [], secretVals?: string[], ) { const secrets = secretVals ?? getSecretValues(); const result = cloneDeepWith(args, (value, key, obj, stack) => { if (typeof key === 'string' && secretKeys.includes(key)) { return '******'; } if (typeof value === 'string') { for (const s of secrets) { if (value.includes(s)) { value = value.replaceAll(s, '******'); } } return value; } return undefined; }); return result; } /** * Enable debug for the given namespaces * @param namespaces - Namespace list */ export function enableDebug(namespaces: string) { debug.enable(namespaces); } /** * Check if a debug namespace is enabled * @param namespace - Debug namespace */ export function isDebugEnabled(namespace: string) { return debug.enabled(namespace); } /** * Set debug flags * @param settings - Debug settings */ export function setDebugSettings(settings: Record) { const list: string[] = []; for (const ns in settings) { if (settings[ns]) { list.push(ns); } else { // Disable with `-` list.push(`-${ns}`); } } enableDebug(list.join(',')); } /** * Use `util.inspect` to print out the value * @param value - Value * @param depth - Depth, default to 8 * @returns */ export function inspectJson(value: unknown, depth = 8) { return util.inspect(value, {depth, colors: false, showHidden: false}); } /** * Stringify the value with 2-space indentation * @param value - Value * @returns */ export function stringify(value: unknown) { return JSON.stringify(value, null, 2); }