@appium/logger
Version:
A Universal Logger For The Appium Ecosystem
393 lines • 13.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GLOBAL_LOG = exports.Log = void 0;
exports.markSensitive = markSensitive;
const lodash_1 = __importDefault(require("lodash"));
const node_events_1 = require("node:events");
// @ts-ignore This module does not provide type definitions
const set_blocking_1 = __importDefault(require("set-blocking"));
// @ts-ignore This module does not provide type definitions
const console_control_strings_1 = __importDefault(require("console-control-strings"));
const util = __importStar(require("node:util"));
const node_async_hooks_1 = require("node:async_hooks");
const utils_1 = require("./utils");
const secure_values_preprocessor_1 = require("./secure-values-preprocessor");
const lru_cache_1 = require("lru-cache");
const DEFAULT_LOG_LEVELS = [
['silly', -Infinity, { inverse: true }, 'sill'],
['verbose', 1000, { fg: 'cyan', bg: 'black' }, 'verb'],
['debug', 1500, { fg: 'cyan', bg: 'black' }, 'dbug'],
['info', 2000, { fg: 'green' }],
['timing', 2500, { fg: 'green', bg: 'black' }],
['http', 3000, { fg: 'green', bg: 'black' }],
['notice', 3500, { fg: 'cyan', bg: 'black' }],
['warn', 4000, { fg: 'black', bg: 'yellow' }, 'WARN'],
['error', 5000, { fg: 'red', bg: 'black' }, 'ERR!'],
['silent', Infinity],
];
const DEFAULT_HISTORY_SIZE = 10000;
const SENSITIVE_MESSAGE_KEY = 'f2b06625-35a2-4ed3-939a-b0b0a4abc750';
(0, set_blocking_1.default)(true);
class Log extends node_events_1.EventEmitter {
level;
prefixStyle;
headingStyle;
heading;
stream; // Defaults to process.stderr
_asyncStorage;
_colorEnabled;
_buffer;
_style;
_levels;
_disp;
_id;
_paused;
_secureValuesPreprocessor;
_history;
_maxRecordSize;
constructor() {
super();
this.level = 'info';
this._buffer = [];
this._maxRecordSize = DEFAULT_HISTORY_SIZE;
this._history = new lru_cache_1.LRUCache({ max: this.maxRecordSize });
this.stream = process.stderr;
this.heading = '';
this.prefixStyle = { fg: 'magenta' };
this.headingStyle = { fg: 'white', bg: 'black' };
this._id = 0;
this._paused = false;
this._asyncStorage = new node_async_hooks_1.AsyncLocalStorage();
this._secureValuesPreprocessor = new secure_values_preprocessor_1.SecureValuesPreprocessor();
this._style = {};
this._levels = {};
this._disp = {};
this.initDefaultLevels();
// allow 'error' prefix
this.on('error', () => { });
}
get record() {
return [...this._history.rvalues()];
}
get maxRecordSize() {
return this._maxRecordSize;
}
set maxRecordSize(value) {
if (value === this._maxRecordSize) {
return;
}
this._maxRecordSize = value;
const newHistory = new lru_cache_1.LRUCache({ max: value });
for (const [key, value] of this._history.rentries()) {
newHistory.set(key, value);
}
this._history = newHistory;
}
useColor() {
// by default, decide based on tty-ness.
return (this._colorEnabled ?? Boolean(this.stream && 'isTTY' in this.stream && this.stream.isTTY));
}
get asyncStorage() {
return this._asyncStorage;
}
updateAsyncStorage(contextInfo, replace) {
if (!lodash_1.default.isPlainObject(contextInfo)) {
return;
}
if (replace) {
this._asyncStorage.enterWith({ ...contextInfo });
}
else {
const store = this._asyncStorage.getStore() ?? {};
Object.assign(store, contextInfo);
this._asyncStorage.enterWith(store);
}
}
enableColor() {
this._colorEnabled = true;
}
disableColor() {
this._colorEnabled = false;
}
// this functionality has been deliberately disabled
enableUnicode() { }
disableUnicode() { }
enableProgress() { }
disableProgress() { }
progressEnabled() {
return false;
}
/**
* Temporarily stop emitting, but don't drop
*/
pause() {
this._paused = true;
}
resume() {
if (!this._paused) {
return;
}
this._paused = false;
const b = this._buffer;
this._buffer = [];
for (const m of b) {
this.emitLog(m);
}
}
silly(prefix, message, ...args) {
this.log('silly', prefix, message, ...args);
}
verbose(prefix, message, ...args) {
this.log('verbose', prefix, message, ...args);
}
debug(prefix, message, ...args) {
this.log('debug', prefix, message, ...args);
}
info(prefix, message, ...args) {
this.log('info', prefix, message, ...args);
}
timing(prefix, message, ...args) {
this.log('timing', prefix, message, ...args);
}
http(prefix, message, ...args) {
this.log('http', prefix, message, ...args);
}
notice(prefix, message, ...args) {
this.log('notice', prefix, message, ...args);
}
warn(prefix, message, ...args) {
this.log('warn', prefix, message, ...args);
}
error(prefix, message, ...args) {
this.log('error', prefix, message, ...args);
}
silent(prefix, message, ...args) {
this.log('silent', prefix, message, ...args);
}
addLevel(level, n, style, disp) {
this._levels[level] = n;
this._style[level] = style;
if (!this[level]) {
this[level] = (prefix, message, ...args) => {
this.log(level, prefix, message, ...args);
};
}
// If 'disp' is null or undefined, use the level as a default
this._disp[level] = disp ?? level;
}
/**
* Creates a log message
* @param level
* @param prefix
* @param message message of the log which will be formatted using utils.format()
* @param args additional arguments appended to the log message also formatted using utils.format()
*/
log(level, prefix, message, ...args) {
const l = this._levels[level];
if (l === undefined) {
this.emit('error', new Error(util.format('Undefined log level: %j', level)));
return;
}
const messageArguments = [];
let stack;
for (const arg of [message, ...args]) {
const result = this._formatLogArgument(arg);
if (result.stack) {
stack = result.stack;
}
else {
messageArguments.push(result.arg);
}
}
if (stack) {
messageArguments.unshift(`${stack}\n`);
}
const formattedMessage = util.format(...messageArguments);
const m = {
id: this._id++,
timestamp: Date.now(),
level,
prefix: this._secureValuesPreprocessor.preprocess((0, utils_1.unleakString)(prefix || '')),
message: this._secureValuesPreprocessor.preprocess((0, utils_1.unleakString)(formattedMessage)),
};
this.emit('log', m);
this.emit('log.' + level, m);
if (m.prefix) {
this.emit(m.prefix, m);
}
this._history.set(m.id, m);
this.emitLog(m);
}
/**
* Loads the JSON file containing secure values replacement rules.
* This might be necessary to hide sensitive values that may possibly
* appear in Appium logs.
* Each call to this method replaces the previously loaded rules if any existed.
*
* @param {string|string[]|LogFiltersConfig} rulesJsonPath The full path to the JSON file containing
* the replacement rules. Each rule could either be a string to be replaced
* or an object with predefined properties.
* @throws {Error} If the given file cannot be loaded
* @returns {Promise<PreprocessingRulesLoadResult>}
*/
async loadSecureValuesPreprocessingRules(rulesJsonPath) {
const issues = await this._secureValuesPreprocessor.loadRules(rulesJsonPath);
return {
issues,
rules: lodash_1.default.cloneDeep(this._secureValuesPreprocessor.rules),
};
}
emitLog(m) {
if (this._paused) {
this._buffer.push(m);
return;
}
const l = this._levels[m.level];
if (l === undefined) {
return;
}
if (l < this._levels[this.level]) {
return;
}
if (l > 0 && !isFinite(l)) {
return;
}
// If 'disp' is null or undefined, use the lvl as a default
// Allows: '', 0 as valid disp
const disp = this._disp[m.level];
this.clearProgress();
for (const line of m.message.split(/\r?\n/)) {
const heading = this.heading;
if (heading) {
this.write(heading, this.headingStyle);
this.write(' ');
}
this.write(String(disp), this._style[m.level]);
const p = m.prefix || '';
if (p) {
this.write(' ');
}
this.write(p, this.prefixStyle);
this.write(` ${line}\n`);
}
this.showProgress();
}
_format(msg, style = {}) {
if (!this.stream) {
return;
}
let output = '';
if (this.useColor()) {
const settings = [];
if (style.fg) {
settings.push(style.fg);
}
if (style.bg) {
settings.push('bg' + style.bg[0].toUpperCase() + style.bg.slice(1));
}
if (style.bold) {
settings.push('bold');
}
if (style.underline) {
settings.push('underline');
}
if (style.inverse) {
settings.push('inverse');
}
if (settings.length) {
output += console_control_strings_1.default.color(settings);
}
if (style.bell) {
output += console_control_strings_1.default.beep();
}
}
output += msg;
if (this.useColor()) {
output += console_control_strings_1.default.color('reset');
}
return output;
}
write(msg, style = {}) {
if (!this.stream) {
return;
}
const formatted = this._format(msg, style);
if (formatted !== undefined) {
this.stream.write(formatted);
}
}
initDefaultLevels() {
for (const [level, index, style, disp] of DEFAULT_LOG_LEVELS) {
this._levels[level] = index;
this._style[level] = style;
this._disp[level] = disp ?? level;
}
}
_formatLogArgument(arg) {
const result = {
arg,
stack: undefined,
};
// mask sensitive data
if (lodash_1.default.has(result.arg, SENSITIVE_MESSAGE_KEY)) {
const { isSensitive } = this._asyncStorage.getStore() ?? {};
result.arg = isSensitive ? secure_values_preprocessor_1.DEFAULT_SECURE_REPLACER : result.arg[SENSITIVE_MESSAGE_KEY];
}
// resolve stack traces to a plain string
if (lodash_1.default.isError(result.arg) && result.arg.stack) {
result.stack = result.arg.stack + '';
Object.defineProperty(result.arg, 'stack', {
value: result.stack,
enumerable: true,
writable: true,
});
}
return result;
}
// this functionality has been deliberately disabled
clearProgress() { }
showProgress() { }
}
exports.Log = Log;
function markSensitive(logMessage) {
return { [SENSITIVE_MESSAGE_KEY]: logMessage };
}
exports.GLOBAL_LOG = new Log();
exports.default = exports.GLOBAL_LOG;
//# sourceMappingURL=log.js.map