Skip to content

Commit

Permalink
fix(logger): export Logger again to keep module backward compatible (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
tripodsan authored Jul 24, 2019
1 parent d79d740 commit 697260a
Show file tree
Hide file tree
Showing 9 changed files with 698 additions and 5 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"semantic-release": "semantic-release",
"docs": "npx jsonschema2md -d src/schemas -o docs && npx jsdoc2md -c .jsdoc.json --files src/*.js > docs/API.md",
"test": "nyc --reporter=text --reporter=lcov --check-coverage --branches 96 --statements 97 --lines 97 mocha",
"test": "nyc --reporter=text --reporter=lcov --check-coverage --branches 95 --statements 98 --lines 99 mocha",
"lint": "./node_modules/.bin/eslint .",
"junit": "mocha --exit -R mocha-junit-reporter",
"snyk-protect": "snyk protect",
Expand Down
10 changes: 10 additions & 0 deletions src/HelixConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class HelixConfig {
this._source = '';
this._cfg = null;
this._document = null;
this._logger = console;
this._version = '';
this._strains = new Strains();
}
Expand All @@ -57,6 +58,11 @@ class HelixConfig {
return this;
}

withLogger(logger) {
this._logger = logger;
return this;
}

withDirectory(cwd) {
this._cwd = cwd;
return this;
Expand Down Expand Up @@ -120,6 +126,10 @@ class HelixConfig {
return this._strains;
}

get log() {
return this._logger;
}

async hasFile() {
return isFile(this.configPath);
}
Expand Down
185 changes: 185 additions & 0 deletions src/Logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright 2018 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

'use strict';

const path = require('path');
const stream = require('stream');
const uuidv4 = require('uuid/v4');
const winston = require('winston');
const fs = require('fs-extra');
const { MESSAGE, LEVEL } = require('triple-beam');

const ANSI_REGEXP = RegExp([
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))',
].join('|'), 'g');


const MY_LEVELS = {
levels: {
error: 0,
warn: 1,
info: 2,
verbose: 3,
debug: 4,
silly: 5,
},
colors: {
// use default colors
},
};

/**
* Winston format that suppresses messages when the `info.progress` is `true` and console._stdout
* is a TTY. This is used to log steps during a progress meter.
*/
const progressFormat = winston.format((info) => {
// eslint-disable-next-line no-underscore-dangle,no-console
if (info.progress && console._stdout.isTTY) {
return false;
}
return info;
});

/**
* Winston format that is used for a command line application where `info` messages are rendered
* without level.
*/
const commandLineFormat = winston.format((info) => {
if (info[LEVEL] === 'info') {
// eslint-disable-next-line no-param-reassign
info[MESSAGE] = `${info.message}`;
} else {
// eslint-disable-next-line no-param-reassign
info[MESSAGE] = `${info.level}: ${info.message}`;
}
return info;
});


function getLogger(config) {
let categ;
if (typeof config === 'string') {
categ = config;
} else {
categ = (config && config.category) || 'hlx';
}

if (winston.loggers.has(categ)) {
return winston.loggers.get(categ);
}

const level = (config && config.level) || (this && this.level) || 'info';
const logsDir = path.normalize((config && config.logsDir) || 'logs');

const logFiles = config && Array.isArray(config.logFile)
? config.logFile
: ['-', (config && config.logFile) || path.join(logsDir, `${categ}-server.log`)];

const transports = [];
logFiles.forEach((logFile) => {
if (logFile === '-') {
const formats = [];
if (categ === 'cli') {
formats.push(progressFormat());
formats.push(winston.format.colorize());
formats.push(commandLineFormat());
} else {
formats.push(winston.format.colorize());
formats.push(winston.format.printf(info => `[${categ}] ${info.level}: ${info.message}`));
}
transports.push(new winston.transports.Console({
level,
format: winston.format.combine(...formats),
}));
} else {
fs.ensureDirSync(path.dirname(logFile));

const formats = [winston.format.timestamp()];
if (/\.json/.test(logFile)) {
formats.push(winston.format.logstash());
} else {
formats.push(winston.format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`));
}

transports.push(new winston.transports.File({
level: 'debug',
filename: logFile,
format: winston.format.combine(...formats),
}));
}
});

const logger = winston.loggers.add(categ, {
level,
levels: MY_LEVELS.levels,
transports,
});
logger.getLogger = getLogger.bind(logger);
winston.addColors(MY_LEVELS.colors);
return logger;
}

/**
* Creates a test logger that logs to the console but also to an internal buffer. The contents of
* the buffer can be retrieved with {@code Logger#getOutput()} which will flush also close the
* logger. Each test logger will be registered with a unique category, so that there is no risk of
* reusing a logger in between tests.
* @returns {winston.Logger}
*/
function getTestLogger(config = {}) {
class StringStream extends stream.Writable {
constructor() {
super();
this.data = '';
}

_write(chunk, enc, next) {
// add chunk but strip ansi control characters
this.data += chunk.toString().replace(ANSI_REGEXP, '');
next();
}
}

// discard category
const cfg = typeof config === 'string' ? {} : config;
cfg.category = uuidv4();
if (!cfg.logFile) {
cfg.logFile = ['-'];
}
const logger = getLogger(cfg);
const s = new StringStream();

logger.add(new winston.transports.Stream({
stream: s,
format: winston.format.simple(),
}));

const finishPromise = new Promise((resolve) => {
logger.on('finish', () => {
resolve(s.data);
});
});

logger.getOutput = async () => {
logger.end();
return finishPromise;
};
return logger;
}

// configure with defaults
module.exports = {
getLogger,
getTestLogger,
};
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const utils = require('./utils.js');
const string = require('./string.js');
const dom = require('./dom.js');
const log = require('./log.js');
const Logger = require('./Logger.js');
const async_ = require('./async.js');

module.exports = {
Expand All @@ -26,5 +27,6 @@ module.exports = {
dom,
utils,
log,
Logger,
async: async_,
};
4 changes: 2 additions & 2 deletions src/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ class StreamLogger {
}

log(msg, opts = {}) {
const { level = 'info' } = opts || {};
const { level = 'info' } = opts;
if (numericLogLevel(level) > numericLogLevel(this.level)) {
return;
}
Expand Down Expand Up @@ -376,7 +376,7 @@ class MemLogger {
}

log(msg, opts = {}) {
const { level = 'info' } = opts || {};
const { level = 'info' } = opts;
if (numericLogLevel(level) <= numericLogLevel(this.level)) {
this.buf.push(`[${level.toUpperCase()}] ${serializeMessage(msg, this.serializeOpts)}`);
}
Expand Down
45 changes: 44 additions & 1 deletion test/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
const assert = require('assert');
const fs = require('fs-extra');
const path = require('path');
const { HelixConfig } = require('../src/index.js');
const { HelixConfig, Logger } = require('../src/index.js');

const Strain = require('../src/Strain.js');
const GitUrl = require('../src/GitUrl.js');

Expand Down Expand Up @@ -140,6 +141,36 @@ describe('Helix Config Loading', () => {
.init();
assert.deepEqual(cfg1.toJSON(), cfg2.toJSON());
});

it('sets logger', async () => {
const logger = Logger.getTestLogger();
const cfg = await new HelixConfig()
.withLogger(logger)
.withConfigPath(path.resolve(SPEC_ROOT, 'minimal.yaml'))
.init();
cfg.log.info('Hello, world.');
assert.ok((await logger.getOutput()).indexOf('Hello, world.') > 0);
});

it('sets directory', async () => {
const source = await fs.readFile(path.resolve(SPEC_ROOT, 'full.yaml'), 'utf-8');
const cfg = await new HelixConfig()
.withDirectory(SPEC_ROOT)
.withSource(source)
.init();
assert.equal(cfg.directory, SPEC_ROOT);
});

it('fails with corrupt file path', async () => {
try {
await new HelixConfig()
.withConfigPath('no-such-file.yaml')
.init();
assert.fail('should fail.');
} catch (e) {
assert.equal(e.toString(), 'Error: Invalid configuration:\n\n\nA list of strains and a strain with the name "default" is required.');
}
});
});

describe('Helix Config Merging', () => {
Expand Down Expand Up @@ -186,6 +217,18 @@ describe('Helix Config Serializing', () => {
assert.equal(actual, source);
});

it('can serialize to canonical form with missing document', async () => {
const source = await fs.readFile(path.resolve(SPEC_ROOT, 'full.yaml'), 'utf-8');
const cfg = await new HelixConfig()
.withSource(source)
.init();
delete cfg._document;

const actual = cfg.toYAML();
const expected = await fs.readFile(path.resolve(SPEC_ROOT, 'full-canonical.yaml'), 'utf-8');
assert.equal(actual, expected);
});

it('can save config', async () => {
const testDir = path.resolve(__dirname, 'tmp', `test${Math.random()}`);
await fs.ensureDir(testDir);
Expand Down
23 changes: 22 additions & 1 deletion test/log.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ const logOutputDebug = multiline(`
[DEBUG] Nooo\n
`);

const logOutputInfo = multiline(`
[ERROR] { foo: 23 }
[FATAL]
[ERROR] 42
[WARN] Hello 42 World
[INFO] { foo: 42 }\n
`);

const logOutputWarn = multiline(`
[ERROR] { foo: 23 }
[FATAL]
Expand Down Expand Up @@ -207,6 +215,9 @@ it('ConsoleLogger', async () => {
it('StreamLogger', () => {
const ss = new StringStream();

testLogger(new StreamLogger(ss));
assert.strictEqual(ss.extract(), logOutputInfo);

testLogger(new StreamLogger(ss, { level: 'debug' }));
assert.strictEqual(ss.extract(), logOutputDebug);

Expand Down Expand Up @@ -237,13 +248,23 @@ it('FileLogger', async () => {
testLogger(logger);
await endStreamAndSync(logger.stream);
assert.strictEqual(await readFile(tmpfile, { encoding: 'utf-8' }), `${logOutputDebug}${logOutputWarn}`);

// Tests that append mode is properly used
logger = new FileLogger(tmpfile);
testLogger(logger);
await endStreamAndSync(logger.stream);
assert.strictEqual(await readFile(tmpfile, { encoding: 'utf-8' }), `${logOutputDebug}${logOutputWarn}${logOutputInfo}`);
} finally {
await unlink(tmpfile);
}
});

it('MemLogger', () => {
let logger = new MemLogger({ level: 'debug' });
let logger = new MemLogger();
testLogger(logger);
assert.strictEqual(`${join(logger.buf, '\n')}\n`, logOutputInfo);

logger = new MemLogger({ level: 'debug' });
testLogger(logger);
assert.strictEqual(`${join(logger.buf, '\n')}\n`, logOutputDebug);

Expand Down
Loading

0 comments on commit 697260a

Please sign in to comment.