From 5a6e00e12b2f045ead4f343344c294208ddd8aba Mon Sep 17 00:00:00 2001 From: Ersun Warncke Date: Wed, 6 Mar 2019 16:08:38 -0400 Subject: [PATCH] decaf --- libraries/logger/index.js | 3 +- .../loggingManagerTests.coffee | 0 libraries/logger/logging-manager.coffee | 129 --- libraries/logger/logging-manager.js | 221 +++++ libraries/logger/package-lock.json | 826 ++++++++++++++++++ libraries/logger/package.json | 5 +- .../logger/test/unit/loggingManagerTests.js | 302 +++++++ 7 files changed, 1352 insertions(+), 134 deletions(-) rename libraries/logger/{test/unit/coffee => logger-tests-coffee}/loggingManagerTests.coffee (100%) delete mode 100644 libraries/logger/logging-manager.coffee create mode 100644 libraries/logger/logging-manager.js create mode 100644 libraries/logger/package-lock.json create mode 100644 libraries/logger/test/unit/loggingManagerTests.js diff --git a/libraries/logger/index.js b/libraries/logger/index.js index dedfe4be5d..b8877921d5 100755 --- a/libraries/logger/index.js +++ b/libraries/logger/index.js @@ -1,2 +1 @@ -require("coffee-script/register") -module.exports = require('./logging-manager'); +module.exports = require('./logging-manager.js'); diff --git a/libraries/logger/test/unit/coffee/loggingManagerTests.coffee b/libraries/logger/logger-tests-coffee/loggingManagerTests.coffee similarity index 100% rename from libraries/logger/test/unit/coffee/loggingManagerTests.coffee rename to libraries/logger/logger-tests-coffee/loggingManagerTests.coffee diff --git a/libraries/logger/logging-manager.coffee b/libraries/logger/logging-manager.coffee deleted file mode 100644 index a7d4e6d27b..0000000000 --- a/libraries/logger/logging-manager.coffee +++ /dev/null @@ -1,129 +0,0 @@ -bunyan = require('bunyan') -request = require('request') - -module.exports = Logger = - initialize: (name) -> - isProduction = process.env['NODE_ENV']?.toLowerCase() == 'production' - @defaultLevel = process.env['LOG_LEVEL'] or if isProduction then "warn" else "debug" - @loggerName = name - @logger = bunyan.createLogger - name: name - serializers: bunyan.stdSerializers - level: @defaultLevel - if isProduction - # check for log level override on startup - @.checkLogLevel() - # re-check log level every minute - checkLogLevel = () => @.checkLogLevel() - setInterval(checkLogLevel, 1000 * 60) - return @ - - checkLogLevel: () -> - options = - headers: - "Metadata-Flavor": "Google" - uri: "http://metadata.google.internal/computeMetadata/v1/project/attributes/#{@loggerName}-setLogLevelEndTime" - request options, (err, response, body) => - if parseInt(body) > Date.now() - @logger.level('trace') - else - @logger.level(@defaultLevel) - - initializeErrorReporting: (sentry_dsn, options) -> - raven = require "raven" - @raven = new raven.Client(sentry_dsn, options) - @lastErrorTimeStamp = 0 # for rate limiting on sentry reporting - @lastErrorCount = 0 - - captureException: (attributes, message, level) -> - # handle case of logger.error "message" - if typeof attributes is 'string' - attributes = {err: new Error(attributes)} - # extract any error object - error = attributes.err or attributes.error - # avoid reporting errors twice - for key, value of attributes - return if value instanceof Error && value.reportedToSentry - # include our log message in the error report - if not error? - error = {message: message} if typeof message is 'string' - else if message? - attributes.description = message - # report the error - if error? - # capture attributes and use *_id objects as tags - tags = {} - extra = {} - for key, value of attributes - tags[key] = value if key.match(/_id/) and typeof value == 'string' - extra[key] = value - # capture req object if available - req = attributes.req - if req? - extra.req = - method: req.method - url: req.originalUrl - query: req.query - headers: req.headers - ip: req.ip - # recreate error objects that have been converted to a normal object - if !(error instanceof Error) and typeof error is "object" - newError = new Error(error.message) - for own key, value of error - newError[key] = value - error = newError - # filter paths from the message to avoid duplicate errors in sentry - # (e.g. errors from `fs` methods which have a path attribute) - try - error.message = error.message.replace(" '#{error.path}'", '') if error.path - # send the error to sentry - try - @raven.captureException(error, {tags: tags, extra: extra, level: level}) - # put a flag on the errors to avoid reporting them multiple times - for key, value of attributes - value.reportedToSentry = true if value instanceof Error - catch - return # ignore any errors - - debug : () -> - @logger.debug.apply(@logger, arguments) - info : ()-> - @logger.info.apply(@logger, arguments) - log : ()-> - @logger.info.apply(@logger, arguments) - error: (attributes, message, args...)-> - @logger.error(attributes, message, args...) - if @raven? - MAX_ERRORS = 5 # maximum number of errors in 1 minute - now = new Date() - # have we recently reported an error? - recentSentryReport = (now - @lastErrorTimeStamp) < 60 * 1000 - # if so, increment the error count - if recentSentryReport - @lastErrorCount++ - else - @lastErrorCount = 0 - @lastErrorTimeStamp = now - # only report 5 errors every minute to avoid overload - if @lastErrorCount < MAX_ERRORS - # add a note if the rate limit has been hit - note = if @lastErrorCount+1 is MAX_ERRORS then "(rate limited)" else "" - # report the exception - @captureException(attributes, message, "error#{note}") - err: () -> - @error.apply(this, arguments) - warn: ()-> - @logger.warn.apply(@logger, arguments) - fatal: (attributes, message, callback = () ->) -> - @logger.fatal(attributes, message) - if @raven? - cb = (e) -> # call the callback once after 'logged' or 'error' event - callback() - cb = () -> - @captureException(attributes, message, "fatal") - @raven.once 'logged', cb - @raven.once 'error', cb - else - callback() - -Logger.initialize("default-sharelatex") diff --git a/libraries/logger/logging-manager.js b/libraries/logger/logging-manager.js new file mode 100644 index 0000000000..f57ce9ca9d --- /dev/null +++ b/libraries/logger/logging-manager.js @@ -0,0 +1,221 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS203: Remove `|| {}` from converted for-own loops + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ + +const bunyan = require('bunyan'); +const request = require('request'); + +const LOG_METHODS = [ + 'debug', 'info', 'log', 'error', 'err', 'warn', 'fatal' +] + +class Logger { + + constructor(name) { + const isProduction = (process.env['NODE_ENV'] || '').toLowerCase() === 'production'; + this.defaultLevel = process.env['LOG_LEVEL'] || (isProduction ? "warn" : "debug"); + this.loggerName = name; + this.logger = bunyan.createLogger({ + name, + serializers: bunyan.stdSerializers, + level: this.defaultLevel + }); + if (isProduction) { + // check for log level override on startup + this.checkLogLevel(); + // re-check log level every minute + const checkLogLevel = () => this.checkLogLevel(); + setInterval(checkLogLevel, 1000 * 60); + } + return this; + } + + checkLogLevel() { + const options = { + headers: { + "Metadata-Flavor": "Google" + }, + uri: `http://metadata.google.internal/computeMetadata/v1/project/attributes/${this.loggerName}-setLogLevelEndTime` + }; + return request(options, (err, response, body) => { + if (parseInt(body) > Date.now()) { + return this.logger.level('trace'); + } else { + return this.logger.level(this.defaultLevel); + } + }); + } + + initializeErrorReporting(sentry_dsn, options) { + const raven = require("raven"); + this.raven = new raven.Client(sentry_dsn, options); + this.lastErrorTimeStamp = 0; // for rate limiting on sentry reporting + return this.lastErrorCount = 0; + } + + captureException(attributes, message, level) { + // handle case of logger.error "message" + let key, value; + if (typeof attributes === 'string') { + attributes = {err: new Error(attributes)}; + } + // extract any error object + let error = attributes.err || attributes.error; + // avoid reporting errors twice + for (key in attributes) { + value = attributes[key]; + if (value instanceof Error && value.reportedToSentry) { return; } + } + // include our log message in the error report + if ((error == null)) { + if (typeof message === 'string') { error = {message}; } + } else if (message != null) { + attributes.description = message; + } + // report the error + if (error != null) { + // capture attributes and use *_id objects as tags + const tags = {}; + const extra = {}; + for (key in attributes) { + value = attributes[key]; + if (key.match(/_id/) && (typeof value === 'string')) { tags[key] = value; } + extra[key] = value; + } + // capture req object if available + const { req } = attributes; + if (req != null) { + extra.req = { + method: req.method, + url: req.originalUrl, + query: req.query, + headers: req.headers, + ip: req.ip + }; + } + // recreate error objects that have been converted to a normal object + if (!(error instanceof Error) && (typeof error === "object")) { + const newError = new Error(error.message); + for (key of Object.keys(error || {})) { + value = error[key]; + newError[key] = value; + } + error = newError; + } + // filter paths from the message to avoid duplicate errors in sentry + // (e.g. errors from `fs` methods which have a path attribute) + try { + if (error.path) { error.message = error.message.replace(` '${error.path}'`, ''); } + } catch (error1) {} + // send the error to sentry + try { + this.raven.captureException(error, {tags, extra, level}); + // put a flag on the errors to avoid reporting them multiple times + return (() => { + const result = []; + for (key in attributes) { + value = attributes[key]; + if (value instanceof Error) { result.push(value.reportedToSentry = true); } else { + result.push(undefined); + } + } + return result; + })(); + } catch (error2) { + return; + } + } + } + + debug() { + return this.logger.debug.apply(this.logger, arguments); + } + + info(){ + return this.logger.info.apply(this.logger, arguments); + } + + log(){ + return this.logger.info.apply(this.logger, arguments); + } + + error(attributes, message, ...args){ + this.logger.error(attributes, message, ...Array.from(args)); + if (this.raven != null) { + const MAX_ERRORS = 5; // maximum number of errors in 1 minute + const now = new Date(); + // have we recently reported an error? + const recentSentryReport = (now - this.lastErrorTimeStamp) < (60 * 1000); + // if so, increment the error count + if (recentSentryReport) { + this.lastErrorCount++; + } else { + this.lastErrorCount = 0; + this.lastErrorTimeStamp = now; + } + // only report 5 errors every minute to avoid overload + if (this.lastErrorCount < MAX_ERRORS) { + // add a note if the rate limit has been hit + const note = (this.lastErrorCount+1) === MAX_ERRORS ? "(rate limited)" : ""; + // report the exception + return this.captureException(attributes, message, `error${note}`); + } + } + } + + err() { + return this.error.apply(this, arguments); + } + + warn(){ + return this.logger.warn.apply(this.logger, arguments); + } + + fatal(attributes, message, callback) { + if (callback == null) { callback = function() {}; } + this.logger.fatal(attributes, message); + if (this.raven != null) { + var cb = function(e) { // call the callback once after 'logged' or 'error' event + callback(); + return cb = function() {}; + }; + this.captureException(attributes, message, "fatal"); + this.raven.once('logged', cb); + return this.raven.once('error', cb); + } else { + return callback(); + } + } +} + +let defaultLogger +// initialize default logger if not already initialized +if (!global.__logger_sharelatex__default_logger__) { + global.__logger_sharelatex__default_logger__ = defaultLogger = new Logger("default-sharelatex") +} +else { + defaultLogger = global.__logger_sharelatex__default_logger__ +} + +// support old interface for creating new Logger instances +Logger.initialize = function initialize(name) { + return new Logger(name) +} +// add a static method for each log method that will use the default logger +for (const logMethod of LOG_METHODS) { + Logger[logMethod] = function () { + return defaultLogger[logMethod].apply(defaultLogger, arguments) + } +} +// return default logger +Logger.defaultLogger = function defaultLogger () { + return defaultLogger +} + +module.exports = Logger diff --git a/libraries/logger/package-lock.json b/libraries/logger/package-lock.json new file mode 100644 index 0000000000..2a3b42cf20 --- /dev/null +++ b/libraries/logger/package-lock.json @@ -0,0 +1,826 @@ +{ + "name": "logger-sharelatex", + "version": "1.6.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@sinonjs/commons": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", + "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.2.0.tgz", + "integrity": "sha512-j5F1rScewLtx6pbTK0UAjA3jJj4RYiSKOix53YWv+Jzy/AZ69qHxUpU8fwVLjyKbEEud9QrLpv6Ggs7WqTimYw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "bunyan": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.5.1.tgz", + "integrity": "sha1-X259RMQ7lS9WsPQTCeOrEjkbTi0=", + "requires": { + "dtrace-provider": "~0.6", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "dtrace-provider": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.6.0.tgz", + "integrity": "sha1-CweNVReTfYcxAUUtkUZzdVe3XlE=", + "optional": true, + "requires": { + "nan": "^2.0.8" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "optional": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lolex": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz", + "integrity": "sha512-zFo5MgCJ0rZ7gQg69S4pqBsLURbFw11X68C18OcJjJQbqaXm2NoTrGl1IMM3TIz0/BnN1tIs2tzmmqvCsOMMjw==", + "dev": true + }, + "lsmod": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", + "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==" + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "requires": { + "mime-db": "~1.38.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "optional": true, + "requires": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + } + }, + "nan": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", + "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", + "optional": true + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "optional": true + }, + "nise": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz", + "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "raven": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", + "integrity": "sha1-lJwTTbAooZC3u/j3kKrlQbfAIL0=", + "requires": { + "cookie": "0.3.1", + "json-stringify-safe": "5.0.1", + "lsmod": "1.0.0", + "stack-trace": "0.0.9", + "uuid": "3.0.0" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", + "dev": true + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "optional": true, + "requires": { + "glob": "^6.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sandboxed-module": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-2.0.3.tgz", + "integrity": "sha1-x+VFkzm7y6KMUwPusz9ug4e/upY=", + "dev": true, + "requires": { + "require-like": "0.1.2", + "stack-trace": "0.0.9" + } + }, + "sinon": { + "version": "7.2.7", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.7.tgz", + "integrity": "sha512-rlrre9F80pIQr3M36gOdoCEWzFAMDgHYD8+tocqOw+Zw9OZ8F84a80Ds69eZfcjnzDqqG88ulFld0oin/6rG/g==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.3.1", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.2.0", + "diff": "^3.5.0", + "lolex": "^3.1.0", + "nise": "^1.4.10", + "supports-color": "^5.5.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "sinon-chai": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.3.0.tgz", + "integrity": "sha512-r2JhDY7gbbmh5z3Q62pNbrjxZdOAjpsqW/8yxAZRSqLZqowmfGZPGUZPFf3UX36NLis0cv8VEM5IJh9HgkSOAA==", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", + "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/libraries/logger/package.json b/libraries/logger/package.json index 96d8d7dd8a..cd6b0ac64e 100644 --- a/libraries/logger/package.json +++ b/libraries/logger/package.json @@ -8,18 +8,17 @@ }, "version": "1.6.0", "scripts": { - "test": "mocha --require coffee-script/register test/**/*.coffee" + "test": "mocha test/**/*.js" }, "dependencies": { "bunyan": "1.5.1", - "coffee-script": "1.12.4", "raven": "^1.1.3", "request": "^2.88.0" }, "devDependencies": { "chai": "^4.2.0", "mocha": "^5.2.0", - "sandboxed-module": "0.2.0", + "sandboxed-module": "2.0.3", "sinon": "^7.2.3", "sinon-chai": "^3.3.0" } diff --git a/libraries/logger/test/unit/loggingManagerTests.js b/libraries/logger/test/unit/loggingManagerTests.js new file mode 100644 index 0000000000..a808ac5f2c --- /dev/null +++ b/libraries/logger/test/unit/loggingManagerTests.js @@ -0,0 +1,302 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require("sandboxed-module"); +const chai = require("chai"); +const path = require("path"); +const sinon = require("sinon"); +const sinonChai = require("sinon-chai"); + +chai.use(sinonChai); +chai.should(); + +const modulePath = path.join(__dirname, '../../logging-manager.js'); + +describe('LoggingManager', function() { + + beforeEach(function() { + this.start = Date.now(); + this.clock = sinon.useFakeTimers(this.start); + this.captureException = sinon.stub(); + this.mockBunyanLogger = { + debug: sinon.stub(), + error: sinon.stub(), + fatal: sinon.stub(), + info: sinon.stub(), + level: sinon.stub(), + warn: sinon.stub() + }; + this.mockRavenClient = { + captureException: this.captureException, + once: sinon.stub().yields() + }; + this.LoggingManager = SandboxedModule.require(modulePath, { + globals: { console }, + requires: { + bunyan: (this.Bunyan = {createLogger: sinon.stub().returns(this.mockBunyanLogger)}), + raven: (this.Raven = {Client: sinon.stub().returns(this.mockRavenClient)}), + request: (this.Request = sinon.stub()), + }, + }); + this.loggerName = "test"; + this.logger = this.LoggingManager.initialize(this.loggerName); + this.logger.initializeErrorReporting("test_dsn"); + }); + + afterEach(function() { + this.clock.restore(); + }); + + describe('initialize', function() { + beforeEach(function() { + this.checkLogLevelStub = sinon.stub(this.LoggingManager.prototype, "checkLogLevel"); + this.Bunyan.createLogger.reset(); + }); + + afterEach(function () { + this.checkLogLevelStub.restore() + }) + + describe("not in production", function() { + beforeEach(function() { + this.logger = this.LoggingManager.initialize(this.loggerName) + }); + + it('should default to log level debug', function() { + this.Bunyan.createLogger.should.have.been.calledWithMatch({level: "debug"}); + }); + + it('should not run checkLogLevel', function() { + this.checkLogLevelStub.should.not.have.been.called; + }); + }); + + describe("in production", function() { + beforeEach(function() { + process.env.NODE_ENV = 'production'; + this.logger = this.LoggingManager.initialize(this.loggerName) + }); + + afterEach(() => delete process.env.NODE_ENV); + + it('should default to log level warn', function() { + this.Bunyan.createLogger.should.have.been.calledWithMatch({level: "warn"}); + }); + + it('should run checkLogLevel', function() { + this.checkLogLevelStub.should.have.been.calledOnce; + }); + + describe('after 1 minute', () => + it('should run checkLogLevel again', function() { + this.clock.tick(61*1000); + this.checkLogLevelStub.should.have.been.calledTwice; + }) + ); + + describe('after 2 minutes', () => + it('should run checkLogLevel again', function() { + this.clock.tick(121*1000); + this.checkLogLevelStub.should.have.been.calledThrice; + }) + ); + }); + + describe("when LOG_LEVEL set in env", function() { + beforeEach(function() { + process.env.LOG_LEVEL = "trace"; + this.LoggingManager.initialize(); + }); + + afterEach(() => delete process.env.LOG_LEVEL); + + it("should use custom log level", function() { + this.Bunyan.createLogger.should.have.been.calledWithMatch({level: "trace"}); + }); + }); + }); + + describe('bunyan logging', function() { + beforeEach(function() { + this.logArgs = [ {foo: "bar"}, "foo", "bar" ];}); + + it('should log debug', function() { + this.logger.debug(this.logArgs); + this.mockBunyanLogger.debug.should.have.been.calledWith(this.logArgs); + }); + + it('should log error', function() { + this.logger.error(this.logArgs); + this.mockBunyanLogger.error.should.have.been.calledWith(this.logArgs); + }); + + it('should log fatal', function() { + this.logger.fatal(this.logArgs); + this.mockBunyanLogger.fatal.should.have.been.calledWith(this.logArgs); + }); + + it('should log info', function() { + this.logger.info(this.logArgs); + this.mockBunyanLogger.info.should.have.been.calledWith(this.logArgs); + }); + + it('should log warn', function() { + this.logger.warn(this.logArgs); + this.mockBunyanLogger.warn.should.have.been.calledWith(this.logArgs); + }); + + it('should log err', function() { + this.logger.err(this.logArgs); + this.mockBunyanLogger.error.should.have.been.calledWith(this.logArgs); + }); + + it('should log log', function() { + this.logger.log(this.logArgs); + this.mockBunyanLogger.info.should.have.been.calledWith(this.logArgs); + }); + }); + + describe('logger.error', function() { + it('should report a single error to sentry', function() { + this.logger.error({foo:'bar'}, "message"); + this.captureException.called.should.equal(true); + }); + + it('should report the same error to sentry only once', function() { + const error1 = new Error('this is the error'); + this.logger.error({foo: error1}, "first message"); + this.logger.error({bar: error1}, "second message"); + this.captureException.callCount.should.equal(1); + }); + + it('should report two different errors to sentry individually', function() { + const error1 = new Error('this is the error'); + const error2 = new Error('this is the error'); + this.logger.error({foo: error1}, "first message"); + this.logger.error({bar: error2}, "second message"); + this.captureException.callCount.should.equal(2); + }); + + it('should remove the path from fs errors', function() { + const fsError = new Error("Error: ENOENT: no such file or directory, stat '/tmp/3279b8d0-da10-11e8-8255-efd98985942b'"); + fsError.path = "/tmp/3279b8d0-da10-11e8-8255-efd98985942b"; + this.logger.error({err: fsError}, "message"); + this.captureException.calledWith(sinon.match.has('message', 'Error: ENOENT: no such file or directory, stat')).should.equal(true); + }); + + it('for multiple errors should only report a maximum of 5 errors to sentry', function() { + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.captureException.callCount.should.equal(5); + }); + + it('for multiple errors with a minute delay should report 10 errors to sentry', function() { + // the first five errors should be reported to sentry + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + // the following errors should not be reported + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + // allow a minute to pass + this.clock.tick(this.start+ (61*1000)); + // after a minute the next five errors should be reported to sentry + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + // the following errors should not be reported to sentry + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.logger.error({foo:'bar'}, "message"); + this.captureException.callCount.should.equal(10); + }); + }); + + describe('checkLogLevel', function() { + it('should request log level override from google meta data service', function() { + this.logger.checkLogLevel(); + const options = { + headers: { + "Metadata-Flavor": "Google" + }, + uri: `http://metadata.google.internal/computeMetadata/v1/project/attributes/${this.loggerName}-setLogLevelEndTime` + }; + this.Request.should.have.been.calledWithMatch(options); + }); + + describe('when request has error', function() { + beforeEach(function() { + this.Request.yields("error"); + this.logger.checkLogLevel(); + }); + + it("should only set default level", function() { + this.mockBunyanLogger.level.should.have.been.calledOnce.and.calledWith('debug'); + }); + }); + + describe('when statusCode is not 200', function() { + beforeEach(function() { + this.Request.yields(null, {statusCode: 404}); + this.logger.checkLogLevel(); + }); + + it("should only set default level", function() { + this.mockBunyanLogger.level.should.have.been.calledOnce.and.calledWith('debug'); + }); + }); + + describe('when time value returned that is less than current time', function() { + beforeEach(function() { + this.Request.yields(null, {statusCode: 200}, '1'); + this.logger.checkLogLevel(); + }); + + it("should only set default level", function() { + this.mockBunyanLogger.level.should.have.been.calledOnce.and.calledWith('debug'); + }); + }); + + describe('when time value returned that is less than current time', function() { + describe('when level is already set', function() { + beforeEach(function() { + this.mockBunyanLogger.level.returns(10); + this.Request.yields(null, {statusCode: 200}, this.start + 1000); + this.logger.checkLogLevel(); + }); + + it("should set trace level", function() { + this.mockBunyanLogger.level.should.have.been.calledOnce.and.calledWith('trace'); + }); + }); + + describe('when level is not already set', function() { + beforeEach(function() { + this.mockBunyanLogger.level.returns(20); + this.Request.yields(null, {statusCode: 200}, this.start + 1000); + this.logger.checkLogLevel(); + }); + + it("should set trace level", function() { + this.mockBunyanLogger.level.should.have.been.calledOnce.and.calledWith('trace'); + }); + }); + }); + }); +});