UNPKG

28.2 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Logger = exports.LoggerFormat = exports.LoggerLevel = void 0;
4/*
5 * Copyright (c) 2020, salesforce.com, inc.
6 * All rights reserved.
7 * Licensed under the BSD 3-Clause license.
8 * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
9 */
10const events_1 = require("events");
11const os = require("os");
12const path = require("path");
13const stream_1 = require("stream");
14// @ts-ignore
15const Bunyan = require("@salesforce/bunyan");
16const kit_1 = require("@salesforce/kit");
17const ts_types_1 = require("@salesforce/ts-types");
18const Debug = require("debug");
19const global_1 = require("./global");
20const sfdxError_1 = require("./sfdxError");
21const fs_1 = require("./util/fs");
22/**
23 * Standard `Logger` levels.
24 *
25 * **See** {@link https://github.com/forcedotcom/node-bunyan#levels|Bunyan Levels}
26 */
27var LoggerLevel;
28(function (LoggerLevel) {
29 LoggerLevel[LoggerLevel["TRACE"] = 10] = "TRACE";
30 LoggerLevel[LoggerLevel["DEBUG"] = 20] = "DEBUG";
31 LoggerLevel[LoggerLevel["INFO"] = 30] = "INFO";
32 LoggerLevel[LoggerLevel["WARN"] = 40] = "WARN";
33 LoggerLevel[LoggerLevel["ERROR"] = 50] = "ERROR";
34 LoggerLevel[LoggerLevel["FATAL"] = 60] = "FATAL";
35})(LoggerLevel = exports.LoggerLevel || (exports.LoggerLevel = {}));
36/**
37 * `Logger` format types.
38 */
39var LoggerFormat;
40(function (LoggerFormat) {
41 LoggerFormat[LoggerFormat["JSON"] = 0] = "JSON";
42 LoggerFormat[LoggerFormat["LOGFMT"] = 1] = "LOGFMT";
43})(LoggerFormat = exports.LoggerFormat || (exports.LoggerFormat = {}));
44/**
45 * A logging abstraction powered by {@link https://github.com/forcedotcom/node-bunyan|Bunyan} that provides both a default
46 * logger configuration that will log to `sfdx.log`, and a way to create custom loggers based on the same foundation.
47 *
48 * ```
49 * // Gets the root sfdx logger
50 * const logger = await Logger.root();
51 *
52 * // Creates a child logger of the root sfdx logger with custom fields applied
53 * const childLogger = await Logger.child('myRootChild', {tag: 'value'});
54 *
55 * // Creates a custom logger unaffiliated with the root logger
56 * const myCustomLogger = new Logger('myCustomLogger');
57 *
58 * // Creates a child of a custom logger unaffiliated with the root logger with custom fields applied
59 * const myCustomChildLogger = myCustomLogger.child('myCustomChild', {tag: 'value'});
60 * ```
61 * **See** https://github.com/forcedotcom/node-bunyan
62 *
63 * **See** https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_dev_cli_log_messages.htm
64 */
65class Logger {
66 /**
67 * Constructs a new `Logger`.
68 *
69 * @param optionsOrName A set of `LoggerOptions` or name to use with the default options.
70 *
71 * **Throws** *{@link SfdxError}{ name: 'RedundantRootLogger' }* More than one attempt is made to construct the root
72 * `Logger`.
73 */
74 constructor(optionsOrName) {
75 /**
76 * The default rotation period for logs. Example '1d' will rotate logs daily (at midnight).
77 * See 'period' docs here: https://github.com/forcedotcom/node-bunyan#stream-type-rotating-file
78 */
79 this.logRotationPeriod = new kit_1.Env().getString('SFDX_LOG_ROTATION_PERIOD') || '1d';
80 /**
81 * The number of backup rotated log files to keep.
82 * Example: '3' will have the base sfdx.log file, and the past 3 (period) log files.
83 * See 'count' docs here: https://github.com/forcedotcom/node-bunyan#stream-type-rotating-file
84 */
85 this.logRotationCount = new kit_1.Env().getNumber('SFDX_LOG_ROTATION_COUNT') || 2;
86 /**
87 * Whether debug is enabled for this Logger.
88 */
89 this.debugEnabled = false;
90 this.uncaughtExceptionHandler = (err) => {
91 // W-7558552
92 // Only log uncaught exceptions in root logger
93 if (this === Logger.rootLogger) {
94 // log the exception
95 // FIXME: good chance this won't be logged because
96 // process.exit was called before this is logged
97 // https://github.com/trentm/node-bunyan/issues/95
98 this.fatal(err);
99 }
100 };
101 this.exitHandler = () => {
102 this.close();
103 };
104 let options;
105 if (typeof optionsOrName === 'string') {
106 options = {
107 name: optionsOrName,
108 level: Logger.DEFAULT_LEVEL,
109 serializers: Bunyan.stdSerializers,
110 };
111 }
112 else {
113 options = optionsOrName;
114 }
115 if (Logger.rootLogger && options.name === Logger.ROOT_NAME) {
116 throw new sfdxError_1.SfdxError('RedundantRootLogger');
117 }
118 // Inspect format to know what logging format to use then delete from options to
119 // ensure it doesn't conflict with Bunyan.
120 this.format = options.format || LoggerFormat.JSON;
121 delete options.format;
122 // If the log format is LOGFMT, we need to convert any stream(s) into a LOGFMT type stream.
123 if (this.format === LoggerFormat.LOGFMT && options.stream) {
124 const ls = this.createLogFmtFormatterStream({ stream: options.stream });
125 options.stream = ls.stream;
126 }
127 if (this.format === LoggerFormat.LOGFMT && options.streams) {
128 const logFmtConvertedStreams = [];
129 options.streams.forEach((ls) => {
130 logFmtConvertedStreams.push(this.createLogFmtFormatterStream(ls));
131 });
132 options.streams = logFmtConvertedStreams;
133 }
134 this.bunyan = new Bunyan(options);
135 this.bunyan.name = options.name;
136 this.bunyan.filters = [];
137 if (!options.streams && !options.stream) {
138 this.bunyan.streams = [];
139 }
140 // all SFDX loggers must filter sensitive data
141 this.addFilter((...args) => _filter(...args));
142 if (global_1.Global.getEnvironmentMode() !== global_1.Mode.TEST) {
143 Logger.lifecycle.on('uncaughtException', this.uncaughtExceptionHandler);
144 Logger.lifecycle.on('exit', this.exitHandler);
145 }
146 this.trace(`Created '${this.getName()}' logger instance`);
147 }
148 /**
149 * Gets the root logger with the default level, file stream, and DEBUG enabled.
150 */
151 static async root() {
152 if (this.rootLogger) {
153 return this.rootLogger;
154 }
155 const rootLogger = (this.rootLogger = new Logger(Logger.ROOT_NAME).setLevel());
156 // disable log file writing, if applicable
157 if (process.env.SFDX_DISABLE_LOG_FILE !== 'true' && global_1.Global.getEnvironmentMode() !== global_1.Mode.TEST) {
158 await rootLogger.addLogFileStream(global_1.Global.LOG_FILE_PATH);
159 }
160 rootLogger.enableDEBUG();
161 return rootLogger;
162 }
163 /**
164 * Gets the root logger with the default level, file stream, and DEBUG enabled.
165 */
166 static getRoot() {
167 if (this.rootLogger) {
168 return this.rootLogger;
169 }
170 const rootLogger = (this.rootLogger = new Logger(Logger.ROOT_NAME).setLevel());
171 // disable log file writing, if applicable
172 if (process.env.SFDX_DISABLE_LOG_FILE !== 'true' && global_1.Global.getEnvironmentMode() !== global_1.Mode.TEST) {
173 rootLogger.addLogFileStreamSync(global_1.Global.LOG_FILE_PATH);
174 }
175 rootLogger.enableDEBUG();
176 return rootLogger;
177 }
178 /**
179 * Destroys the root `Logger`.
180 *
181 * @ignore
182 */
183 static destroyRoot() {
184 if (this.rootLogger) {
185 this.rootLogger.close();
186 this.rootLogger = undefined;
187 }
188 }
189 /**
190 * Create a child of the root logger, inheriting this instance's configuration such as `level`, `streams`, etc.
191 *
192 * @param name The name of the child logger.
193 * @param fields Additional fields included in all log lines.
194 */
195 static async child(name, fields) {
196 return (await Logger.root()).child(name, fields);
197 }
198 /**
199 * Create a child of the root logger, inheriting this instance's configuration such as `level`, `streams`, etc.
200 *
201 * @param name The name of the child logger.
202 * @param fields Additional fields included in all log lines.
203 */
204 static childFromRoot(name, fields) {
205 return Logger.getRoot().child(name, fields);
206 }
207 /**
208 * Gets a numeric `LoggerLevel` value by string name.
209 *
210 * @param {string} levelName The level name to convert to a `LoggerLevel` enum value.
211 *
212 * **Throws** *{@link SfdxError}{ name: 'UnrecognizedLoggerLevelName' }* The level name was not case-insensitively recognized as a valid `LoggerLevel` value.
213 * @see {@Link LoggerLevel}
214 */
215 static getLevelByName(levelName) {
216 levelName = levelName.toUpperCase();
217 if (!ts_types_1.isKeyOf(LoggerLevel, levelName)) {
218 throw new sfdxError_1.SfdxError('UnrecognizedLoggerLevelName');
219 }
220 return LoggerLevel[levelName];
221 }
222 /**
223 * Adds a stream.
224 *
225 * @param stream The stream configuration to add.
226 * @param defaultLevel The default level of the stream.
227 */
228 addStream(stream, defaultLevel) {
229 if (this.format === LoggerFormat.LOGFMT) {
230 stream = this.createLogFmtFormatterStream(stream);
231 }
232 this.bunyan.addStream(stream, defaultLevel);
233 }
234 /**
235 * Adds a file stream to this logger. Resolved or rejected upon completion of the addition.
236 *
237 * @param logFile The path to the log file. If it doesn't exist it will be created.
238 */
239 async addLogFileStream(logFile) {
240 try {
241 // Check if we have write access to the log file (i.e., we created it already)
242 await fs_1.fs.access(logFile, fs_1.fs.constants.W_OK);
243 }
244 catch (err1) {
245 try {
246 await fs_1.fs.mkdirp(path.dirname(logFile), {
247 mode: fs_1.fs.DEFAULT_USER_DIR_MODE,
248 });
249 }
250 catch (err2) {
251 // noop; directory exists already
252 }
253 try {
254 await fs_1.fs.writeFile(logFile, '', { mode: fs_1.fs.DEFAULT_USER_FILE_MODE });
255 }
256 catch (err3) {
257 throw sfdxError_1.SfdxError.wrap(err3);
258 }
259 }
260 // avoid multiple streams to same log file
261 if (!this.bunyan.streams.find(
262 // No bunyan typings
263 // eslint-disable-next-line @typescript-eslint/no-explicit-any
264 (stream) => stream.type === 'rotating-file' && stream.path === logFile)) {
265 this.addStream({
266 type: 'rotating-file',
267 path: logFile,
268 period: this.logRotationPeriod,
269 count: this.logRotationCount,
270 level: this.bunyan.level(),
271 });
272 }
273 }
274 /**
275 * Adds a file stream to this logger. Resolved or rejected upon completion of the addition.
276 *
277 * @param logFile The path to the log file. If it doesn't exist it will be created.
278 */
279 addLogFileStreamSync(logFile) {
280 try {
281 // Check if we have write access to the log file (i.e., we created it already)
282 fs_1.fs.accessSync(logFile, fs_1.fs.constants.W_OK);
283 }
284 catch (err1) {
285 try {
286 fs_1.fs.mkdirpSync(path.dirname(logFile), {
287 mode: fs_1.fs.DEFAULT_USER_DIR_MODE,
288 });
289 }
290 catch (err2) {
291 // noop; directory exists already
292 }
293 try {
294 fs_1.fs.writeFileSync(logFile, '', { mode: fs_1.fs.DEFAULT_USER_FILE_MODE });
295 }
296 catch (err3) {
297 throw sfdxError_1.SfdxError.wrap(err3);
298 }
299 }
300 // avoid multiple streams to same log file
301 if (!this.bunyan.streams.find(
302 // No bunyan typings
303 // eslint-disable-next-line @typescript-eslint/no-explicit-any
304 (stream) => stream.type === 'rotating-file' && stream.path === logFile)) {
305 this.addStream({
306 type: 'rotating-file',
307 path: logFile,
308 period: this.logRotationPeriod,
309 count: this.logRotationCount,
310 level: this.bunyan.level(),
311 });
312 }
313 }
314 /**
315 * Gets the name of this logger.
316 */
317 getName() {
318 return this.bunyan.name;
319 }
320 /**
321 * Gets the current level of this logger.
322 */
323 getLevel() {
324 return this.bunyan.level();
325 }
326 /**
327 * Set the logging level of all streams for this logger. If a specific `level` is not provided, this method will
328 * attempt to read it from the environment variable `SFDX_LOG_LEVEL`, and if not found,
329 * {@link Logger.DEFAULT_LOG_LEVEL} will be used instead. For convenience `this` object is returned.
330 *
331 * @param {LoggerLevelValue} [level] The logger level.
332 *
333 * **Throws** *{@link SfdxError}{ name: 'UnrecognizedLoggerLevelName' }* A value of `level` read from `SFDX_LOG_LEVEL`
334 * was invalid.
335 *
336 * ```
337 * // Sets the level from the environment or default value
338 * logger.setLevel()
339 *
340 * // Set the level from the INFO enum
341 * logger.setLevel(LoggerLevel.INFO)
342 *
343 * // Sets the level case-insensitively from a string value
344 * logger.setLevel(Logger.getLevelByName('info'))
345 * ```
346 */
347 setLevel(level) {
348 if (level == null) {
349 level = process.env.SFDX_LOG_LEVEL ? Logger.getLevelByName(process.env.SFDX_LOG_LEVEL) : Logger.DEFAULT_LEVEL;
350 }
351 this.bunyan.level(level);
352 return this;
353 }
354 /**
355 * Gets the underlying Bunyan logger.
356 */
357 // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
358 getBunyanLogger() {
359 return this.bunyan;
360 }
361 /**
362 * Compares the requested log level with the current log level. Returns true if
363 * the requested log level is greater than or equal to the current log level.
364 *
365 * @param level The requested log level to compare against the currently set log level.
366 */
367 shouldLog(level) {
368 if (typeof level === 'string') {
369 level = Bunyan.levelFromName(level);
370 }
371 return level >= this.getLevel();
372 }
373 /**
374 * Use in-memory logging for this logger instance instead of any parent streams. Useful for testing.
375 * For convenience this object is returned.
376 *
377 * **WARNING: This cannot be undone for this logger instance.**
378 */
379 useMemoryLogging() {
380 this.bunyan.streams = [];
381 this.bunyan.ringBuffer = new Bunyan.RingBuffer({ limit: 5000 });
382 this.addStream({
383 type: 'raw',
384 stream: this.bunyan.ringBuffer,
385 level: this.bunyan.level(),
386 });
387 return this;
388 }
389 /**
390 * Gets an array of log line objects. Each element is an object that corresponds to a log line.
391 */
392 getBufferedRecords() {
393 if (this.bunyan.ringBuffer) {
394 return this.bunyan.ringBuffer.records;
395 }
396 return [];
397 }
398 /**
399 * Reads a text blob of all the log lines contained in memory or the log file.
400 */
401 readLogContentsAsText() {
402 if (this.bunyan.ringBuffer) {
403 return this.getBufferedRecords().reduce((accum, line) => {
404 accum += JSON.stringify(line) + os.EOL;
405 return accum;
406 }, '');
407 }
408 else {
409 let content = '';
410 // No bunyan typings
411 // eslint-disable-next-line @typescript-eslint/no-explicit-any
412 this.bunyan.streams.forEach(async (stream) => {
413 if (stream.type === 'file') {
414 content += await fs_1.fs.readFile(stream.path, 'utf8');
415 }
416 });
417 return content;
418 }
419 }
420 /**
421 * Adds a filter to be applied to all logged messages.
422 *
423 * @param filter A function with signature `(...args: any[]) => any[]` that transforms log message arguments.
424 */
425 addFilter(filter) {
426 // eslint disable-line @typescript-eslint/no-explicit-any
427 if (!this.bunyan.filters) {
428 this.bunyan.filters = [];
429 }
430 this.bunyan.filters.push(filter);
431 }
432 /**
433 * Close the logger, including any streams, and remove all listeners.
434 *
435 * @param fn A function with signature `(stream: LoggerStream) => void` to call for each stream with
436 * the stream as an arg.
437 */
438 close(fn) {
439 if (this.bunyan.streams) {
440 try {
441 this.bunyan.streams.forEach((entry) => {
442 if (fn) {
443 fn(entry);
444 }
445 // close file streams, flush buffer to disk
446 // eslint-disable-next-line @typescript-eslint/unbound-method
447 if (entry.type === 'file' && entry.stream && ts_types_1.isFunction(entry.stream.end)) {
448 entry.stream.end();
449 }
450 });
451 }
452 finally {
453 Logger.lifecycle.removeListener('uncaughtException', this.uncaughtExceptionHandler);
454 Logger.lifecycle.removeListener('exit', this.exitHandler);
455 }
456 }
457 }
458 /**
459 * Create a child logger, typically to add a few log record fields. For convenience this object is returned.
460 *
461 * @param name The name of the child logger that is emitted w/ log line as `log:<name>`.
462 * @param fields Additional fields included in all log lines for the child logger.
463 */
464 child(name, fields = {}) {
465 if (!name) {
466 throw new sfdxError_1.SfdxError('LoggerNameRequired');
467 }
468 fields.log = name;
469 const child = new Logger(name);
470 // only support including additional fields on log line (no config)
471 child.bunyan = this.bunyan.child(fields, true);
472 child.bunyan.name = name;
473 child.bunyan.filters = this.bunyan.filters;
474 this.trace(`Setup child '${name}' logger instance`);
475 return child;
476 }
477 /**
478 * Add a field to all log lines for this logger. For convenience `this` object is returned.
479 *
480 * @param name The name of the field to add.
481 * @param value The value of the field to be logged.
482 */
483 addField(name, value) {
484 this.bunyan.fields[name] = value;
485 return this;
486 }
487 /**
488 * Logs at `trace` level with filtering applied. For convenience `this` object is returned.
489 *
490 * @param args Any number of arguments to be logged.
491 */
492 // eslint-disable-next-line @typescript-eslint/no-explicit-any
493 trace(...args) {
494 this.bunyan.trace(this.applyFilters(LoggerLevel.TRACE, ...args));
495 return this;
496 }
497 /**
498 * Logs at `debug` level with filtering applied. For convenience `this` object is returned.
499 *
500 * @param args Any number of arguments to be logged.
501 */
502 debug(...args) {
503 this.bunyan.debug(this.applyFilters(LoggerLevel.DEBUG, ...args));
504 return this;
505 }
506 /**
507 * Logs at `debug` level with filtering applied.
508 *
509 * @param cb A callback that returns on array objects to be logged.
510 */
511 debugCallback(cb) {
512 if (this.getLevel() === LoggerLevel.DEBUG || process.env.DEBUG) {
513 const result = cb();
514 if (ts_types_1.isArray(result)) {
515 this.bunyan.debug(this.applyFilters(LoggerLevel.DEBUG, ...result));
516 }
517 else {
518 this.bunyan.debug(this.applyFilters(LoggerLevel.DEBUG, ...[result]));
519 }
520 }
521 }
522 /**
523 * Logs at `info` level with filtering applied. For convenience `this` object is returned.
524 *
525 * @param args Any number of arguments to be logged.
526 */
527 info(...args) {
528 this.bunyan.info(this.applyFilters(LoggerLevel.INFO, ...args));
529 return this;
530 }
531 /**
532 * Logs at `warn` level with filtering applied. For convenience `this` object is returned.
533 *
534 * @param args Any number of arguments to be logged.
535 */
536 warn(...args) {
537 this.bunyan.warn(this.applyFilters(LoggerLevel.WARN, ...args));
538 return this;
539 }
540 /**
541 * Logs at `error` level with filtering applied. For convenience `this` object is returned.
542 *
543 * @param args Any number of arguments to be logged.
544 */
545 error(...args) {
546 this.bunyan.error(this.applyFilters(LoggerLevel.ERROR, ...args));
547 return this;
548 }
549 /**
550 * Logs at `fatal` level with filtering applied. For convenience `this` object is returned.
551 *
552 * @param args Any number of arguments to be logged.
553 */
554 fatal(...args) {
555 // always show fatal to stderr
556 // eslint-disable-next-line no-console
557 console.error(...args);
558 this.bunyan.fatal(this.applyFilters(LoggerLevel.FATAL, ...args));
559 return this;
560 }
561 /**
562 * Enables logging to stdout when the DEBUG environment variable is used. It uses the logger
563 * name as the debug name, so you can do DEBUG=<logger-name> to filter the results to your logger.
564 */
565 enableDEBUG() {
566 // The debug library does this for you, but no point setting up the stream if it isn't there
567 if (process.env.DEBUG && !this.debugEnabled) {
568 const debuggers = {};
569 debuggers.core = Debug(`${this.getName()}:core`);
570 this.addStream({
571 name: 'debug',
572 stream: new stream_1.Writable({
573 write: (chunk, encoding, next) => {
574 try {
575 const json = kit_1.parseJsonMap(chunk.toString());
576 const logLevel = ts_types_1.ensureNumber(json.level);
577 if (this.getLevel() <= logLevel) {
578 let debuggerName = 'core';
579 if (ts_types_1.isString(json.log)) {
580 debuggerName = json.log;
581 if (!debuggers[debuggerName]) {
582 debuggers[debuggerName] = Debug(`${this.getName()}:${debuggerName}`);
583 }
584 }
585 const level = LoggerLevel[logLevel];
586 ts_types_1.ensure(debuggers[debuggerName])(`${level} ${json.msg}`);
587 }
588 }
589 catch (err) {
590 // do nothing
591 }
592 next();
593 },
594 }),
595 // Consume all levels
596 level: 0,
597 });
598 this.debugEnabled = true;
599 }
600 }
601 applyFilters(logLevel, ...args) {
602 if (this.shouldLog(logLevel)) {
603 // No bunyan typings
604 // eslint-disable-next-line @typescript-eslint/no-explicit-any
605 this.bunyan.filters.forEach((filter) => (args = filter(...args)));
606 }
607 return args && args.length === 1 ? args[0] : args;
608 }
609 createLogFmtFormatterStream(loggerStream) {
610 const logFmtWriteableStream = new stream_1.Writable({
611 write: (chunk, enc, cb) => {
612 try {
613 const parsedJSON = JSON.parse(chunk.toString());
614 const keys = Object.keys(parsedJSON);
615 let logEntry = '';
616 keys.forEach((key) => {
617 let logMsg = `${parsedJSON[key]}`;
618 if (logMsg.trim().includes(' ')) {
619 logMsg = `"${logMsg}"`;
620 }
621 logEntry += `${key}=${logMsg} `;
622 });
623 if (loggerStream.stream) {
624 loggerStream.stream.write(logEntry.trimRight() + '\n');
625 }
626 }
627 catch (error) {
628 if (loggerStream.stream) {
629 loggerStream.stream.write(chunk.toString());
630 }
631 }
632 cb(null);
633 },
634 });
635 return Object.assign({}, loggerStream, { stream: logFmtWriteableStream });
636 }
637}
638exports.Logger = Logger;
639/**
640 * The name of the root sfdx `Logger`.
641 */
642Logger.ROOT_NAME = 'sfdx';
643/**
644 * The default `LoggerLevel` when constructing new `Logger` instances.
645 */
646Logger.DEFAULT_LEVEL = LoggerLevel.WARN;
647/**
648 * A list of all lower case `LoggerLevel` names.
649 *
650 * **See** {@link LoggerLevel}
651 */
652Logger.LEVEL_NAMES = Object.values(LoggerLevel)
653 .filter(ts_types_1.isString)
654 .map((v) => v.toLowerCase());
655// Rollup all instance-specific process event listeners together to prevent global `MaxListenersExceededWarning`s.
656Logger.lifecycle = (() => {
657 const events = new events_1.EventEmitter();
658 events.setMaxListeners(0); // never warn on listener counts
659 process.on('uncaughtException', (err) => events.emit('uncaughtException', err));
660 process.on('exit', () => events.emit('exit'));
661 return events;
662})();
663// Ok to log clientid
664const FILTERED_KEYS = [
665 'sid',
666 'Authorization',
667 // Any json attribute that contains the words "access" and "token" will have the attribute/value hidden
668 { name: 'access_token', regex: 'access[^\'"]*token' },
669 // Any json attribute that contains the words "refresh" and "token" will have the attribute/value hidden
670 { name: 'refresh_token', regex: 'refresh[^\'"]*token' },
671 'clientsecret',
672 // Any json attribute that contains the words "sfdx", "auth", and "url" will have the attribute/value hidden
673 { name: 'sfdxauthurl', regex: 'sfdx[^\'"]*auth[^\'"]*url' },
674];
675// SFDX code and plugins should never show tokens or connect app information in the logs
676const _filter = (...args) => {
677 return args.map((arg) => {
678 if (ts_types_1.isArray(arg)) {
679 return _filter(...arg);
680 }
681 if (arg) {
682 let _arg;
683 // Normalize all objects into a string. This include errors.
684 if (arg instanceof Buffer) {
685 _arg = '<Buffer>';
686 }
687 else if (ts_types_1.isObject(arg)) {
688 _arg = JSON.stringify(arg);
689 }
690 else if (ts_types_1.isString(arg)) {
691 _arg = arg;
692 }
693 else {
694 _arg = '';
695 }
696 const HIDDEN = 'HIDDEN';
697 FILTERED_KEYS.forEach((key) => {
698 let expElement = key;
699 let expName = key;
700 // Filtered keys can be strings or objects containing regular expression components.
701 if (ts_types_1.isPlainObject(key)) {
702 expElement = key.regex;
703 expName = key.name;
704 }
705 const hiddenAttrMessage = `"<${expName} - ${HIDDEN}>"`;
706 // Match all json attribute values case insensitive: ex. {" Access*^&(*()^* Token " : " 45143075913458901348905 \n\t" ...}
707 const regexTokens = new RegExp(`(['"][^'"]*${expElement}[^'"]*['"]\\s*:\\s*)['"][^'"]*['"]`, 'gi');
708 _arg = _arg.replace(regexTokens, `$1${hiddenAttrMessage}`);
709 // Match all key value attribute case insensitive: ex. {" key\t" : ' access_token ' , " value " : " dsafgasr431 " ....}
710 const keyRegex = new RegExp(`(['"]\\s*key\\s*['"]\\s*:)\\s*['"]\\s*${expElement}\\s*['"]\\s*.\\s*['"]\\s*value\\s*['"]\\s*:\\s*['"]\\s*[^'"]*['"]`, 'gi');
711 _arg = _arg.replace(keyRegex, `$1${hiddenAttrMessage}`);
712 });
713 _arg = _arg.replace(/(00D\w{12,15})![.\w]*/, `<${HIDDEN}>`);
714 // return an object if an object was logged; otherwise return the filtered string.
715 return ts_types_1.isObject(arg) ? kit_1.parseJson(_arg) : _arg;
716 }
717 else {
718 return arg;
719 }
720 });
721};
722//# sourceMappingURL=logger.js.map
\No newline at end of file