UNPKG

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