logging.js
4.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import npmlog from 'npmlog';
import _ from 'lodash';
import { unleakString } from './util';
import moment from 'moment';
import SECURE_VALUES_PREPROCESSOR from './log-internal';
// levels that are available from `npmlog`
const NPM_LEVELS = ['silly', 'verbose', 'debug', 'info', 'http', 'warn', 'error'];
const MAX_LOG_RECORDS_COUNT = 3000;
const PREFIX_TIMESTAMP_FORMAT = 'HH-mm-ss:SSS';
// mock log object used in testing mode
let mockLog = {};
for (let level of NPM_LEVELS) {
mockLog[level] = () => {};
}
function patchLogger (logger) {
if (!logger.debug) {
logger.addLevel('debug', 1000, { fg: 'blue', bg: 'black' }, 'dbug');
}
}
function _getLogger () {
// check if the user set the `_TESTING` or `_FORCE_LOGS` flag
const testingMode = parseInt(process.env._TESTING, 10) === 1;
const forceLogMode = parseInt(process.env._FORCE_LOGS, 10) === 1;
// if is possible that there is a logger instance that is already around,
// in which case we want t o use that
const usingGlobalLog = !!global._global_npmlog;
let logger;
if (testingMode && !forceLogMode) {
// in testing mode, use a mock logger object that we can query
logger = mockLog;
} else {
// otherwise, either use the global, or a new `npmlog` object
logger = global._global_npmlog || npmlog;
// The default value is 10000, which causes excessive memory usage
logger.maxRecordSize = MAX_LOG_RECORDS_COUNT;
}
patchLogger(logger);
return [logger, usingGlobalLog];
}
function getActualPrefix (prefix, logTimestamp = false) {
let actualPrefix = _.isFunction(prefix) ? prefix() : prefix;
if (logTimestamp) {
actualPrefix = `[${moment().format(PREFIX_TIMESTAMP_FORMAT)}] ${actualPrefix}`;
}
return actualPrefix;
}
function getLogger (prefix = null) {
let [logger, usingGlobalLog] = _getLogger();
// wrap the logger so that we can catch and modify any logging
let wrappedLogger = {unwrap: () => logger};
// allow access to the level of the underlying logger
Object.defineProperty(wrappedLogger, 'level', {
get () {
return logger.level;
},
set (newValue) {
logger.level = newValue;
},
enumerable: true,
configurable: true
});
const logTimestamp = parseInt(process.env._LOG_TIMESTAMP, 10) === 1;
// add all the levels from `npmlog`, and map to the underlying logger
for (const level of NPM_LEVELS) {
wrappedLogger[level] = function (...args) {
const actualPrefix = getActualPrefix(prefix, logTimestamp);
for (const arg of args) {
const out = (_.isError(arg) && arg.stack) ? arg.stack : `${arg}`;
for (const line of out.split('\n')) {
// it is necessary to unleak each line because `split` call
// creates "views" to the original string as well as the `substring` one
const unleakedLine = unleakString(line);
logger[level](actualPrefix, SECURE_VALUES_PREPROCESSOR.preprocess(unleakedLine));
}
}
};
}
// add method to log an error, and throw it, for convenience
wrappedLogger.errorAndThrow = function (err) {
this.error(err);
// make sure we have an `Error` object. Wrap if necessary
throw (_.isError(err) ? err : new Error(unleakString(err)));
};
if (!usingGlobalLog) {
// if we're not using a global log specified from some top-level package,
// set the log level to a default of verbose. Otherwise, let the top-level
// package set the log level
wrappedLogger.level = 'verbose';
}
wrappedLogger.levels = NPM_LEVELS;
return wrappedLogger;
}
/**
* @typedef {Object} LoadResult
* @property {List<string>} issues The list of rule parsing issues (one item per rule).
* Rules with issues are skipped. An empty list is returned if no parsing issues exist.
* @property {List<SecureValuePreprocessingRule>} rules The list of successfully loaded
* replacement rules. The list could be empty if no rules were loaded.
*/
/**
* 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} 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. See the `Rule` type definition in
* `log-internals.js` to get more details on its format.
* @throws {Error} If the given file cannot be loaded
* @returns {LoadResult}
*/
async function loadSecureValuesPreprocessingRules (rulesJsonPath) {
const issues = await SECURE_VALUES_PREPROCESSOR.loadRules(rulesJsonPath);
return {
issues,
rules: _.cloneDeep(SECURE_VALUES_PREPROCESSOR.rules),
};
}
// export a default logger with no prefix
const log = getLogger();
export { log, patchLogger, getLogger, loadSecureValuesPreprocessingRules };
export default log;