Skip to content

Commit 459ac2d

Browse files
authored
Improve logging integration with VIP-CLI (#170)
This refactor the logging to be better integrated in VIP-CLI
1 parent 2c84440 commit 459ac2d

5 files changed

Lines changed: 140 additions & 95 deletions

File tree

lib/app.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ module.exports = class App {
9696
* @since 3.0.0
9797
* @alias app.shell
9898
*/
99-
this.log = new Log(_.merge({}, lando.config, {logName: this.name}));
99+
if (lando.log && typeof lando.log.child === 'function') {
100+
this.log = lando.log.child(this.name);
101+
} else {
102+
this.log = new Log(_.merge({}, lando.config, {logName: this.name}));
103+
}
100104
this.shell = new Shell(this.log);
101105
this.engine = bootstrap.setupEngine(
102106
lando.config,

lib/lando.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,9 @@ module.exports = class Lando {
201201
const Cli = require('./cli');
202202
const Log = require('./logger');
203203
const ErrorHandler = require('./error');
204+
this.log = new Log(this.config);
204205
this.cache = bootstrap.setupCache(this.log, this.config);
205206
this.cli = new Cli();
206-
this.log = new Log(this.config);
207207
this.error = new ErrorHandler(this.log);
208208
this.events = new AsyncEvents(this.log);
209209
this.user = require('./user');

lib/logger.js

Lines changed: 128 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -44,45 +44,47 @@ const sanitizeKeys = (sanitizedKeys = []) => winston.format(info => {
4444
});
4545

4646
// Custom console format that mimics the old formatter behavior
47-
const consoleFormat = (logName, logLevelConsole) => winston.format.printf(({level, message, timestamp, ...meta}) => {
48-
// Get da prefixes
49-
const element = (logName === 'lando') ? 'lando' : logName;
50-
const elementColor = (logName === 'lando') ? 'lando' : 'app';
51-
// Set the leftmost column width
52-
fcw = _.max([fcw, _.size(element)]);
53-
54-
// Extract the splat array (format arguments) if available
55-
const splat = meta[Symbol.for('splat')] || [];
56-
57-
// Format the message with splat arguments using util.format
58-
const formattedMessage = splat.length > 0 ?
59-
util.format(message, ...splat) :
60-
message;
61-
62-
// Remove splat from meta before serializing
63-
const metaWithoutSplat = {...meta};
64-
delete metaWithoutSplat[Symbol.for('splat')];
65-
66-
// Serialize remaining metadata
67-
const serializedMeta = Object.keys(metaWithoutSplat).length ?
68-
' ' + JSON.stringify(metaWithoutSplat) :
69-
'';
70-
71-
// Default output
72-
const output = [
73-
winston.format.colorize({colors: logColors}).colorize(elementColor, _.padEnd(element.toLowerCase(), fcw)),
74-
winston.format.colorize({colors: logColors}).colorize('timestamp', timestamp),
75-
winston.format.colorize().colorize(level, level.toUpperCase()),
76-
'==>',
77-
formattedMessage + serializedMeta,
78-
];
79-
80-
// If this is a warning or error and we aren't verbose then omit prefixes
81-
if (_.includes(userLevels, level) && _.includes(userLevels, logLevelConsole)) {
82-
return _.drop(output, 2).join(' ');
83-
}
84-
return output.join(' ');
85-
});
47+
const consoleFormat = (logLevelConsole, defaultLogName) =>
48+
winston.format.printf(({level, message, timestamp, logName, ...meta}) => {
49+
// Get da prefixes
50+
const resolvedLogName = logName || defaultLogName;
51+
const element = (resolvedLogName === 'lando') ? 'lando' : resolvedLogName;
52+
const elementColor = (resolvedLogName === 'lando') ? 'lando' : 'app';
53+
// Set the leftmost column width
54+
fcw = _.max([fcw, _.size(element)]);
55+
56+
// Extract the splat array (format arguments) if available
57+
const splat = meta[Symbol.for('splat')] || [];
58+
59+
// Format the message with splat arguments using util.format
60+
const formattedMessage = splat.length > 0 ?
61+
util.format(message, ...splat) :
62+
message;
63+
64+
// Remove splat from meta before serializing
65+
const metaWithoutSplat = {...meta};
66+
delete metaWithoutSplat[Symbol.for('splat')];
67+
68+
// Serialize remaining metadata
69+
const serializedMeta = Object.keys(metaWithoutSplat).length ?
70+
' ' + JSON.stringify(metaWithoutSplat) :
71+
'';
72+
73+
// Default output
74+
const output = [
75+
winston.format.colorize({colors: logColors}).colorize(elementColor, _.padEnd(element.toLowerCase(), fcw)),
76+
winston.format.colorize({colors: logColors}).colorize('timestamp', timestamp),
77+
winston.format.colorize().colorize(level, level.toUpperCase()),
78+
'==>',
79+
formattedMessage + serializedMeta,
80+
];
81+
82+
// If this is a warning or error and we aren't verbose then omit prefixes
83+
if (_.includes(userLevels, level) && _.includes(userLevels, logLevelConsole)) {
84+
return _.drop(output, 2).join(' ');
85+
}
86+
return output.join(' ');
87+
});
8688

8789
/**
8890
* Logs a debug message.
@@ -178,43 +180,52 @@ const consoleFormat = (logName, logLevelConsole) => winston.format.printf(({leve
178180
* // Log a warning message
179181
* lando.log.warning('Something is up with app %s in directory %s', appName, dir);
180182
*/
181-
module.exports = class Log extends EventEmitter {
182-
constructor({logDir, logLevelConsole = 'warn', logLevel = 'debug', logName = 'lando'} = {}) {
183+
class Log extends EventEmitter {
184+
constructor({logDir, logFile, logLevelConsole = 'warn', logLevel = 'debug', logName = 'lando', logger} = {}) {
183185
super();
184186

187+
if (process.env.DEBUG) {
188+
try {
189+
const debugLib = require('debug');
190+
debugLib.disable('winston:*');
191+
} catch {
192+
// Ignore if debug is unavailable.
193+
}
194+
}
195+
185196
// If loglevelconsole is numeric lets map it!
186197
if (_.isInteger(logLevelConsole)) logLevelConsole = logLevels[logLevelConsole];
187198

188199
// Initialize sanitized keys
189200
this.sanitizedKeys = ['auth', 'token', 'password', 'key', 'api_key', 'secret', 'machine_token'];
190201

191-
// Create formats
192-
const formats = [
193-
winston.format.timestamp({format: () => new Date().toISOString().slice(11, 19)}),
194-
sanitizeKeys(this.sanitizedKeys)(),
195-
];
196-
197-
// The default console transport
198-
const transports = [
199-
new winston.transports.Console({
200-
format: winston.format.combine(
201-
...formats,
202-
consoleFormat(logName, logLevelConsole),
203-
),
204-
level: logLevelConsole,
205-
}),
206-
];
202+
if (logger) {
203+
this.sanitizedKeys = logger.sanitizedKeys || this.sanitizedKeys;
204+
logger.sanitizedKeys = this.sanitizedKeys;
205+
this.logger = logName ? logger.child({logName}) : logger;
206+
} else {
207+
// Create formats
208+
const formats = [
209+
winston.format.timestamp({format: () => new Date().toISOString().slice(11, 19)}),
210+
sanitizeKeys(this.sanitizedKeys)(),
211+
];
207212

208-
// If we have a log path then let's add in some file transports
209-
if (logDir) {
210-
// Ensure the log dir actually exists
211-
fs.mkdirSync(logDir, {recursive: true});
213+
// The default console transport
214+
const transports = [
215+
new winston.transports.Console({
216+
format: winston.format.combine(
217+
...formats,
218+
consoleFormat(logLevelConsole, logName),
219+
),
220+
level: logLevelConsole,
221+
}),
222+
];
212223

213224
// File format without colorization
214225
const fileFormat = winston.format.combine(
215226
winston.format.timestamp(),
216227
sanitizeKeys(this.sanitizedKeys)(),
217-
winston.format.printf(({timestamp, level, message, ...meta}) => {
228+
winston.format.printf(({timestamp, level, message, logName, ...meta}) => {
218229
// Extract the splat array (format arguments) if available
219230
const splat = meta[Symbol.for('splat')] || [];
220231

@@ -232,35 +243,55 @@ module.exports = class Log extends EventEmitter {
232243
' ' + JSON.stringify(metaWithoutSplat) :
233244
'';
234245

235-
return `${timestamp} [${logName}] ${level.toUpperCase()}: ${formattedMessage}${serializedMeta}`;
246+
const resolvedLogName = logName || 'lando';
247+
return `${timestamp} [${resolvedLogName}] ${level.toUpperCase()}: ${formattedMessage}${serializedMeta}`;
236248
}),
237249
);
238250

239-
// Add in our generic and error logs
240-
transports.push(new winston.transports.File({
241-
format: fileFormat,
242-
level: 'warn',
243-
maxsize: 500000,
244-
maxFiles: 2,
245-
filename: path.join(logDir, `${logName}-error.log`),
246-
}));
247-
transports.push(new winston.transports.File({
248-
format: fileFormat,
249-
level: logLevel,
250-
maxsize: 500000,
251-
maxFiles: 3,
252-
filename: path.join(logDir, `${logName}.log`),
253-
}));
254-
}
251+
if (logFile) {
252+
const resolvedLogFile = path.isAbsolute(logFile) ?
253+
logFile :
254+
path.join(logDir || '', logFile);
255+
fs.mkdirSync(path.dirname(resolvedLogFile), {recursive: true});
256+
transports.push(new winston.transports.File({
257+
format: fileFormat,
258+
level: logLevel,
259+
maxsize: 500000,
260+
maxFiles: 3,
261+
filename: resolvedLogFile,
262+
}));
263+
} else if (logDir) {
264+
// Ensure the log dir actually exists
265+
fs.mkdirSync(logDir, {recursive: true});
255266

256-
// Add custom colors to winston
257-
winston.addColors(logColors);
267+
// Add in our generic and error logs
268+
transports.push(new winston.transports.File({
269+
format: fileFormat,
270+
level: 'warn',
271+
maxsize: 500000,
272+
maxFiles: 2,
273+
filename: path.join(logDir, `${logName}-error.log`),
274+
}));
275+
transports.push(new winston.transports.File({
276+
format: fileFormat,
277+
level: logLevel,
278+
maxsize: 500000,
279+
maxFiles: 3,
280+
filename: path.join(logDir, `${logName}.log`),
281+
}));
282+
}
258283

259-
// Create the winston logger
260-
this.logger = winston.createLogger({
261-
transports: transports,
262-
exitOnError: true,
263-
});
284+
// Add custom colors to winston
285+
winston.addColors(logColors);
286+
287+
// Create the winston logger
288+
this.logger = winston.createLogger({
289+
transports: transports,
290+
exitOnError: true,
291+
defaultMeta: {logName},
292+
});
293+
this.logger.sanitizedKeys = this.sanitizedKeys;
294+
}
264295

265296
// Create aliases for winston methods to maintain compatibility
266297
['error', 'warn', 'info', 'verbose', 'debug', 'silly'].forEach(level => {
@@ -269,15 +300,12 @@ module.exports = class Log extends EventEmitter {
269300

270301
// Expose transports for compatibility with tests
271302
this.transports = [];
272-
transports.forEach((transport, index) => {
303+
const loggerTransports = this.logger && this.logger.transports ? this.logger.transports : [];
304+
loggerTransports.forEach(transport => {
273305
if (transport instanceof winston.transports.Console) {
274306
this.transports.push(transport);
275307
} else if (transport instanceof winston.transports.File) {
276-
if (transport.filename && transport.filename.includes('-error.log')) {
277-
this.transports.push(transport);
278-
} else if (transport.filename) {
279-
this.transports.push(transport);
280-
}
308+
if (transport.filename) this.transports.push(transport);
281309
}
282310
});
283311

@@ -288,7 +316,14 @@ module.exports = class Log extends EventEmitter {
288316
// Method to help other things add sanitizations
289317
alsoSanitize(key) {
290318
this.sanitizedKeys.push(key);
319+
if (this.logger) this.logger.sanitizedKeys = this.sanitizedKeys;
291320
// We need to recreate the format with the updated keys
292321
// For simplicity, we'll just add to the array and it will be used in next log call
293322
}
294-
};
323+
324+
child(logName) {
325+
return new Log({logger: this.logger, logName});
326+
}
327+
}
328+
329+
module.exports = Log;

package-lock.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,5 +136,8 @@
136136
"rimraf": "^6.1.2",
137137
"sinon": "^4.3.0",
138138
"sinon-chai": "^3.7.0"
139+
},
140+
"peerDependencies": {
141+
"debug": "^4.4.3"
139142
}
140143
}

0 commit comments

Comments
 (0)