Skip to content
This repository has been archived by the owner on May 30, 2024. It is now read-only.

Commit

Permalink
add logger adapter shim + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
maxwellgerber committed Apr 23, 2020
1 parent 00c6da8 commit 66353c2
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 19 deletions.
20 changes: 5 additions & 15 deletions configuration.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const winston = require('winston');
const InMemoryFeatureStore = require('./feature_store');
const messages = require('./messages');
const LoggerWrapper = require('./logger_wrapper');

module.exports = (function() {
const defaults = function() {
Expand Down Expand Up @@ -147,20 +148,12 @@ module.exports = (function() {
}
}

function validateLogger(logger) {
['error', 'warn', 'info', 'debug'].forEach(function (level) {
if (!logger[level] || typeof logger[level] !== 'function') {
throw new Error('Provided logger instance must support logger.' + level + '(...) method');
}
})
}

function defaultLogger() {
function fallbackLogger() {
return new winston.Logger({
level: 'info',
transports: [
new winston.transports.Console({
formatter: function (options) {
formatter: function(options) {
return '[LaunchDarkly] ' + (options.message ? options.message : '');
},
}),
Expand All @@ -170,11 +163,8 @@ module.exports = (function() {

function validate(options) {
let config = Object.assign({}, options || {});
if (config.logger) {
validateLogger(config.logger);
} else {
config.logger = defaultLogger();
}

config.logger = config.logger ? LoggerWrapper(config.logger, fallbackLogger()) : fallbackLogger();

checkDeprecatedOptions(config);

Expand Down
39 changes: 39 additions & 0 deletions logger_wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const logLevels = ['error', 'warn', 'info', 'debug'];

/**
* Asserts that the caller-supplied logger contains all required methods
* and wraps it in an exception handler that falls back to the fallbackLogger
* @param {LDLogger} logger
* @param {LDLogger} fallbackLogger
*/
function LoggerWrapper(logger, fallbackLogger) {
validateLogger(logger);

const wrappedLogger = {};
logLevels.forEach(level => {
wrappedLogger[level] = wrapLoggerLevel(logger, fallbackLogger, level);
});

return wrappedLogger;
}

function validateLogger(logger) {
logLevels.forEach(level => {
if (!logger[level] || typeof logger[level] !== 'function') {
throw new Error('Provided logger instance must support logger.' + level + '(...) method');
}
});
}

function wrapLoggerLevel(logger, fallbackLogger, level) {
return function wrappedLoggerMethod() {
try {
return logger[level].apply(logger, arguments);
} catch (err) {
fallbackLogger.error('Error calling provided logger instance method ' + level + ': ' + err);
fallbackLogger[level].apply(fallbackLogger, arguments);
}
};
}

module.exports = LoggerWrapper;
8 changes: 4 additions & 4 deletions test/configuration-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ describe('configuration', function() {

function checkDeprecated(oldName, newName, value) {
it('allows "' + oldName + '" as a deprecated equivalent to "' + newName + '"', function() {
var config0 = emptyConfigWithMockLogger();
config0[oldName] = value;
var config1 = configuration.validate(config0);
const configIn = emptyConfigWithMockLogger();
configIn[oldName] = value;
const config1 = configuration.validate(configIn);
expect(config1[newName]).toEqual(value);
expect(config1[oldName]).toBeUndefined();
expect(config1.logger.warn).toHaveBeenCalledTimes(1);
expect(configIn.logger.warn).toHaveBeenCalledTimes(1);
});
}

Expand Down
53 changes: 53 additions & 0 deletions test/logger_wrapper-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const LoggerWrapper = require('../logger_wrapper');

describe('LoggerWrapper', function () {

function mockLogger() {
return {
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
};
}

const levels = ['error', 'warn', 'info', 'debug'];

it('throws an error if you pass in a logger that does not conform to the LDLogger schema', () => {
const fallbackLogger = mockLogger();

// If the method does not exist
levels.forEach(method => {
const logger = mockLogger();
delete logger[method];
expect(() => LoggerWrapper(logger, fallbackLogger)).toThrow(/Provided logger instance must support .* method/);
});

// If the method is not a function
levels.forEach(method => {
const logger = mockLogger();
logger[method] = 'invalid';
expect(() => LoggerWrapper(logger, fallbackLogger)).toThrow(/Provided logger instance must support .* method/);
});
});

it('If a logger method throws an error, the error is caught and logged, then the fallback logger is called', () => {
const err = Error('Something bad happened');

levels.forEach(level => {
const logger = mockLogger();
logger[level] = jest.fn(() => {
throw err
});
const fallbackLogger = mockLogger();
const wrappedLogger = LoggerWrapper(logger, fallbackLogger);

expect(() => wrappedLogger[level]('this is a logline', 'with multiple', 'arguments')).not.toThrow();

expect(fallbackLogger.error).toHaveBeenNthCalledWith(1, 'Error calling provided logger instance method ' + level + ': ' + err);

const nthCall = level === 'error' ? 2 : 1;
expect(fallbackLogger[level]).toHaveBeenNthCalledWith(nthCall, 'this is a logline', 'with multiple', 'arguments');
});
});
});

0 comments on commit 66353c2

Please sign in to comment.