From f5ff8aa05a3c6d44f71b2db8e2ba5708ba677301 Mon Sep 17 00:00:00 2001 From: Stacey Van Herk Date: Thu, 11 Mar 2021 16:47:42 -0500 Subject: [PATCH] feat: US124288 - Add Ability to Throttle Logs (#7) * Add ability to throttle logs in the client, only sending the same log/error once per minute * Remove semantic-release from package.json * Add tests --- CODEOWNERS | 2 +- index.js | 4 +- logging.js | 43 +++- package.json | 20 +- test/logging.test.js | 577 ++++++++++++++++++++++++++++++++++--------- 5 files changed, 501 insertions(+), 145 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index aaf4e92..fb15eca 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @dlockhart +* @dlockhart @svanherk diff --git a/index.js b/index.js index 1869df4..bea0a9a 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,6 @@ const batchSize = 500; const batchTime = 5000; const serverLogger = new ServerLogger(batchSize, batchTime); -export function createClient(appId) { - return new LoggingClient(appId, serverLogger); +export function createClient(appId, opts) { + return new LoggingClient(appId, serverLogger, opts); } diff --git a/logging.js b/logging.js index c76e4ad..40ab636 100644 --- a/logging.js +++ b/logging.js @@ -2,6 +2,7 @@ const _isFiniteNumber = (val) => val !== null && isFinite(val) && !isNaN(val); const dataLoggingEndpointAttribute = 'data-logging-endpoint'; +const defaultThrottleRateMs = 60000; export class ServerLogger { @@ -157,9 +158,12 @@ export class LogBuilder { } export class LoggingClient { - constructor(appId, logger) { + constructor(appId, logger, opts) { this._appId = appId; this._logger = logger; + this._shouldThrottle = opts ? !!opts.shouldThrottle : false; + + this._uniqueLogs = new Map(); } log(developerMessage) { @@ -170,8 +174,11 @@ export class LoggingClient { const logs = developerMessages.map(developerMessage => new LogBuilder(this._appId) .withMessage(developerMessage) .withLocation() - .build()); - this._logger.logBatch(logs); + .build() + ).filter(this._throttle.bind(this)); + if (logs.length > 0) { + this._logger.logBatch(logs); + } } error(error, developerMessage) { @@ -183,8 +190,11 @@ export class LoggingClient { .withError(error) .withMessage(developerMessage) .withLocation() - .build()); - this._logger.logBatch(logs); + .build() + ).filter(this._throttle.bind(this)); + if (logs.length > 0) { + this._logger.logBatch(logs); + } } legacyError(message, source, lineno, colno, error, developerMessage) { @@ -197,7 +207,26 @@ export class LoggingClient { .withError(error) .withMessage(developerMessage) .withLocation() - .build()); - this._logger.logBatch(logs); + .build() + ).filter(this._throttle.bind(this)); + if (logs.length > 0) { + this._logger.logBatch(logs); + } + } + + _throttle(log) { + if (!this._shouldThrottle) { + return true; + } + + const now = new Date().getTime(); + const key = JSON.stringify(log); + const lastLogged = this._uniqueLogs.get(key); + if (lastLogged === undefined || now - lastLogged >= defaultThrottleRateMs) { + this._uniqueLogs.set(key, now); + return true; + } else { + return false; + } } } diff --git a/package.json b/package.json index 597b442..514a8c1 100644 --- a/package.json +++ b/package.json @@ -31,25 +31,7 @@ "eslint-plugin-sort-class-members": "^1", "eslint": "^6", "karma-sauce-launcher": "^2", - "semantic-release": "^17.1.1", "sinon": "^9.0.3" }, - "dependencies": {}, - "release": { - "plugins": [ - "@semantic-release/commit-analyzer", - "@semantic-release/github", - "@semantic-release/npm", - "@semantic-release/release-notes-generator", - [ - "@semantic-release/git", - { - "assets": [ - "package.json" - ], - "message": "chore(release): ${nextRelease.version} [skip ci]" - } - ] - ] - } + "dependencies": {} } diff --git a/test/logging.test.js b/test/logging.test.js index 526c188..b87f7db 100644 --- a/test/logging.test.js +++ b/test/logging.test.js @@ -2,6 +2,9 @@ import { aTimeout, expect } from '@open-wc/testing'; import { LogBuilder, LoggingClient, ServerLogger } from '../logging.js'; import sinon from 'sinon'; +const defaultThrottleRateMs = 60000; +let clock; + describe('logging', () => { describe('LogBuilder', () => { @@ -108,87 +111,367 @@ describe('logging', () => { describe('LoggingClient', () => { - describe('log', () => { + describe('Throttling Off', () => { + + describe('log', () => { + + it('should log message', (done) => { + + const mockLogger = { + logBatch: (logs) => { + expect(logs.length).to.equal(1); + + const log = logs[0]; + expect(log.appId).to.equal('my-app-id'); + expect(log.message).to.equal('this is my message I want to log'); + + expect(client._uniqueLogs).to.be.empty; + done(); + } + }; + const client = new LoggingClient('my-app-id', mockLogger); + client.log('this is my message I want to log'); + }); + + it('should log message batch', (done) => { - it('should log message', (done) => { + const messages = ['this is my message I want to log', 'second message', 'third message']; + const mockLogger = { + logBatch: (logs) => { + expect(logs.length).to.equal(messages.length); - const mockLogger = { - logBatch: (logs) => { - expect(logs.length).to.equal(1); + for (let i = 0; i < messages.length; i += 1) { + const log = logs[i]; + const message = messages[i]; + expect(log.appId).to.equal('my-app-id'); + expect(log.message).to.equal(message); + } - const log = logs[0]; - expect(log.appId).to.equal('my-app-id'); - expect(log.message).to.equal('this is my message I want to log'); + expect(client._uniqueLogs).to.be.empty; + done(); + } + }; + const client = new LoggingClient('my-app-id', mockLogger); + client.logBatch(messages); + }); - done(); - } - }; - const client = new LoggingClient('my-app-id', mockLogger); - client.log('this is my message I want to log'); }); - it('should log message batch', (done) => { + describe('error', () => { - const messages = ['this is my message I want to log', 'second message', 'third message']; - const mockLogger = { - logBatch: (logs) => { - expect(logs.length).to.equal(messages.length); + it('should log error', (done) => { - for (let i = 0; i < messages.length; i += 1) { - const log = logs[i]; - const message = messages[i]; + const error = new Error('An error occurred'); + const message = 'My custom message to go along with it'; + const mockLogger = { + logBatch: (logs) => { + expect(logs.length).to.equal(1); + + const log = logs[0]; expect(log.appId).to.equal('my-app-id'); expect(log.message).to.equal(message); + expect(log.error.name).to.equal(error.name); + expect(log.error.message).to.equal(error.message); + expect(log.error.fileName).to.equal(error.fileName); + expect(log.error.description).to.equal(error.description); + expect(log.error.number).to.equal(error.number); + expect(log.error.lineNumber).to.equal(error.lineNumber); + expect(log.error.columnNumber).to.equal(error.columnNumber); + expect(log.error.stack).to.equal(error.stack); + + expect(client._uniqueLogs).to.be.empty; + done(); } - done(); - } - }; - const client = new LoggingClient('my-app-id', mockLogger); - client.logBatch(messages); + }; + const client = new LoggingClient('my-app-id', mockLogger); + client.error(error, message); + }); + + it('should log error batch', (done) => { + + const errors = [ + { error: new Error('First error occurred'), developerMessage: 'My first message' }, + { error: new Error('Second error occurred'), developerMessage: 'My second message' }, + { error: new Error('Third error occurred'), developerMessage: 'My third message' } + ]; + const mockLogger = { + logBatch: (logs) => { + expect(logs.length).to.equal(errors.length); + + for (let i = 0; i < errors.length; i += 1) { + const log = logs[i]; + const { error, developerMessage } = errors[i]; + expect(log.appId).to.equal('my-app-id'); + expect(log.message).to.equal(developerMessage); + expect(log.error.name).to.equal(error.name); + expect(log.error.message).to.equal(error.message); + expect(log.error.fileName).to.equal(error.fileName); + expect(log.error.description).to.equal(error.description); + expect(log.error.number).to.equal(error.number); + expect(log.error.lineNumber).to.equal(error.lineNumber); + expect(log.error.columnNumber).to.equal(error.columnNumber); + expect(log.error.stack).to.equal(error.stack); + } + + expect(client._uniqueLogs).to.be.empty; + done(); + } + }; + const client = new LoggingClient('my-app-id', mockLogger); + client.errorBatch(errors); + }); + }); + describe('legacy error', () => { + + it('should log legacy error', (done) => { + + const message = 'The error message'; + const source = 'logging.js'; + const lineno = 102; + const colno = 23; + const error = new Error('An error occurred'); + const developerMessage = 'My custom message to go along with it'; + + const mockLogger = { + logBatch: (logs) => { + expect(logs.length).to.equal(1); + + const log = logs[0]; + expect(log.appId).to.equal('my-app-id'); + expect(log.message).to.equal(developerMessage); + expect(log.legacyError.message).to.equal(message); + expect(log.legacyError.source).to.equal(source); + expect(log.legacyError.lineno).to.equal(lineno); + expect(log.legacyError.colno).to.equal(colno); + expect(log.error.name).to.equal(error.name); + expect(log.error.message).to.equal(error.message); + expect(log.error.fileName).to.equal(error.fileName); + expect(log.error.description).to.equal(error.description); + expect(log.error.number).to.equal(error.number); + expect(log.error.lineNumber).to.equal(error.lineNumber); + expect(log.error.columnNumber).to.equal(error.columnNumber); + expect(log.error.stack).to.equal(error.stack); + + expect(client._uniqueLogs).to.be.empty; + done(); + } + }; + const client = new LoggingClient('my-app-id', mockLogger); + client.legacyError(message, source, lineno, colno, error, developerMessage); + }); + + it('should log legacy error batch', (done) => { + + const legacyErrors = [ + { message: 'First error message', source: 'logging.js', lineno: 102, colno: 23, error: new Error('First error occurred'), developerMessage: 'My first message' }, + { message: 'Second error message', source: 'logging.js', lineno: 45, colno: 12, error: new Error('Second error occurred'), developerMessage: 'My second message' }, + { message: 'Third error message', source: 'logging.js', lineno: 2, colno: 19, error: new Error('Third error occurred'), developerMessage: 'My third message' }, + ]; + const mockLogger = { + logBatch: (logs) => { + expect(logs.length).to.equal(legacyErrors.length); + + for (let i = 0; i < legacyErrors.length; i += 1) { + const log = logs[i]; + const { message, source, lineno, colno, error, developerMessage } = legacyErrors[i]; + expect(log.appId).to.equal('my-app-id'); + expect(log.message).to.equal(developerMessage); + expect(log.legacyError.message).to.equal(message); + expect(log.legacyError.source).to.equal(source); + expect(log.legacyError.lineno).to.equal(lineno); + expect(log.legacyError.colno).to.equal(colno); + expect(log.error.name).to.equal(error.name); + expect(log.error.message).to.equal(error.message); + expect(log.error.fileName).to.equal(error.fileName); + expect(log.error.description).to.equal(error.description); + expect(log.error.number).to.equal(error.number); + expect(log.error.lineNumber).to.equal(error.lineNumber); + expect(log.error.columnNumber).to.equal(error.columnNumber); + expect(log.error.stack).to.equal(error.stack); + } + + expect(client._uniqueLogs).to.be.empty; + done(); + } + }; + const client = new LoggingClient('my-app-id', mockLogger); + client.legacyErrorBatch(legacyErrors); + }); + + }); }); - describe('error', () => { + describe('Throttling On', () => { + + beforeEach(() => { + clock = sinon.useFakeTimers(); + }); + afterEach(() => { + clock.restore(); + }); + + describe('log', () => { + + it('should log new message', (done) => { + const mockLogger = { + logBatch: (logs) => { + expect(logs.length).to.equal(1); + + const log = logs[0]; + expect(log.appId).to.equal('my-app-id'); + expect(log.message).to.equal('this is my message I want to log'); + + expect(client._uniqueLogs.size).to.equal(1); + done(); + } + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.log('this is my message I want to log'); + }); + + it('should log identical message only once per throttle rate', () => { + const stub = sinon.stub(); + const mockLogger = { + logBatch: stub + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.log('this is my message I want to log'); + client.log('this is my message I want to log'); // Should not be logged + + expect(client._uniqueLogs.size).to.equal(1); + expect(stub.calledOnce).to.be.true; + }); + + it('should log identical message again after throttle rate has passed', () => { + let messageKey, timeValue; + const stub = sinon.stub(); + const mockLogger = { + logBatch: stub + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.log('this is my message I want to log'); + expect(client._uniqueLogs.size).to.equal(1); + client._uniqueLogs.forEach((value, key) => { + messageKey = key; + timeValue = value; + }); + + clock.tick(defaultThrottleRateMs); + client.log('this is my message I want to log'); + + expect(client._uniqueLogs.size).to.equal(1); + const newTime = client._uniqueLogs.get(messageKey); + expect(newTime).to.be.at.least(timeValue + defaultThrottleRateMs); + expect(stub.calledTwice).to.be.true; + }); + + it('should throttle log message batch', () => { + const messages = ['this is my message I want to log', 'second message', 'this is my message I want to log']; + const checkLogs = function(logs) { + expect(logs.length).to.equal(messages.length - 1); // should not log third message + + for (let i = 0; i < logs.length; i += 1) { + const log = logs[i]; + const message = messages[i]; + expect(log.appId).to.equal('my-app-id'); + expect(log.message).to.equal(message); + } + }; + const spy = sinon.spy(checkLogs); + const mockLogger = { + logBatch: spy + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.logBatch(messages); + client.logBatch(messages); // None of these should be logged + + expect(client._uniqueLogs.size).to.equal(2); + expect(spy.calledOnce).to.be.true; + }); - it('should log error', (done) => { - - const error = new Error('An error occurred'); - const message = 'My custom message to go along with it'; - const mockLogger = { - logBatch: (logs) => { - expect(logs.length).to.equal(1); - - const log = logs[0]; - expect(log.appId).to.equal('my-app-id'); - expect(log.message).to.equal(message); - expect(log.error.name).to.equal(error.name); - expect(log.error.message).to.equal(error.message); - expect(log.error.fileName).to.equal(error.fileName); - expect(log.error.description).to.equal(error.description); - expect(log.error.number).to.equal(error.number); - expect(log.error.lineNumber).to.equal(error.lineNumber); - expect(log.error.columnNumber).to.equal(error.columnNumber); - expect(log.error.stack).to.equal(error.stack); - done(); - } - }; - const client = new LoggingClient('my-app-id', mockLogger); - client.error(error, message); }); - it('should log error batch', (done) => { + describe('error', () => { + + it('should log new error', (done) => { + const error = new Error('An error occurred'); + const message = 'My custom message to go along with it'; + const mockLogger = { + logBatch: (logs) => { + expect(logs.length).to.equal(1); + + const log = logs[0]; + expect(log.appId).to.equal('my-app-id'); + expect(log.message).to.equal(message); + expect(log.error.name).to.equal(error.name); + expect(log.error.message).to.equal(error.message); + expect(log.error.fileName).to.equal(error.fileName); + expect(log.error.description).to.equal(error.description); + expect(log.error.number).to.equal(error.number); + expect(log.error.lineNumber).to.equal(error.lineNumber); + expect(log.error.columnNumber).to.equal(error.columnNumber); + expect(log.error.stack).to.equal(error.stack); - const errors = [ - { error: new Error('First error occurred'), developerMessage: 'My first message' }, - { error: new Error('Second error occurred'), developerMessage: 'My second message' }, - { error: new Error('Third error occurred'), developerMessage: 'My third message' } - ]; - const mockLogger = { - logBatch: (logs) => { - expect(logs.length).to.equal(errors.length); + expect(client._uniqueLogs.size).to.equal(1); + done(); + } + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.error(error, message); + }); - for (let i = 0; i < errors.length; i += 1) { + it('should log identical error only once per throttle rate', () => { + const error = new Error('An error occurred'); + const message = 'My custom message to go along with it'; + const stub = sinon.stub(); + const mockLogger = { + logBatch: stub + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.error(error, message); + client.error(error, message); // Should not be logged + + expect(client._uniqueLogs.size).to.equal(1); + expect(stub.calledOnce).to.be.true; + }); + + it('should log identical error again after throttle rate has passed', () => { + let messageKey, timeValue; + const error = new Error('An error occurred'); + const message = 'My custom message to go along with it'; + const stub = sinon.stub(); + const mockLogger = { + logBatch: stub + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.error(error, message); + expect(client._uniqueLogs.size).to.equal(1); + client._uniqueLogs.forEach((value, key) => { + messageKey = key; + timeValue = value; + }); + + clock.tick(defaultThrottleRateMs); + client.error(error, message); + + expect(client._uniqueLogs.size).to.equal(1); + const newTime = client._uniqueLogs.get(messageKey); + expect(newTime).to.be.at.least(timeValue + defaultThrottleRateMs); + expect(stub.calledTwice).to.be.true; + }); + + it('should throttle error batch', () => { + const errors = [ + { error: new Error('First error occurred'), developerMessage: 'My first message' }, + { error: new Error('Second error occurred'), developerMessage: 'My second message' } + ]; + errors.push(errors[0]); + const checkLogs = function(logs) { + expect(logs.length).to.equal(errors.length - 1); // should not log third error + + for (let i = 0; i < logs.length; i += 1) { const log = logs[i]; const { error, developerMessage } = errors[i]; expect(log.appId).to.equal('my-app-id'); @@ -202,64 +485,119 @@ describe('logging', () => { expect(log.error.columnNumber).to.equal(error.columnNumber); expect(log.error.stack).to.equal(error.stack); } - done(); - } - }; - const client = new LoggingClient('my-app-id', mockLogger); - client.errorBatch(errors); + }; + const spy = sinon.spy(checkLogs); + const mockLogger = { + logBatch: spy + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.errorBatch(errors); + client.errorBatch(errors); // None of these should be logged + + expect(client._uniqueLogs.size).to.equal(2); + expect(spy.calledOnce).to.be.true; + }); + }); - }); + describe('legacy error', () => { - describe('legacy error', () => { + it('should log new legacy error', (done) => { + const message = 'The error message'; + const source = 'logging.js'; + const lineno = 102; + const colno = 23; + const error = new Error('An error occurred'); + const developerMessage = 'My custom message to go along with it'; - it('should log legacy error', (done) => { + const mockLogger = { + logBatch: (logs) => { + expect(logs.length).to.equal(1); - const message = 'The error message'; - const source = 'logging.js'; - const lineno = 102; - const colno = 23; - const error = new Error('An error occurred'); - const developerMessage = 'My custom message to go along with it'; - - const mockLogger = { - logBatch: (logs) => { - expect(logs.length).to.equal(1); - - const log = logs[0]; - expect(log.appId).to.equal('my-app-id'); - expect(log.message).to.equal(developerMessage); - expect(log.legacyError.message).to.equal(message); - expect(log.legacyError.source).to.equal(source); - expect(log.legacyError.lineno).to.equal(lineno); - expect(log.legacyError.colno).to.equal(colno); - expect(log.error.name).to.equal(error.name); - expect(log.error.message).to.equal(error.message); - expect(log.error.fileName).to.equal(error.fileName); - expect(log.error.description).to.equal(error.description); - expect(log.error.number).to.equal(error.number); - expect(log.error.lineNumber).to.equal(error.lineNumber); - expect(log.error.columnNumber).to.equal(error.columnNumber); - expect(log.error.stack).to.equal(error.stack); - done(); - } - }; - const client = new LoggingClient('my-app-id', mockLogger); - client.legacyError(message, source, lineno, colno, error, developerMessage); - }); + const log = logs[0]; + expect(log.appId).to.equal('my-app-id'); + expect(log.message).to.equal(developerMessage); + expect(log.legacyError.message).to.equal(message); + expect(log.legacyError.source).to.equal(source); + expect(log.legacyError.lineno).to.equal(lineno); + expect(log.legacyError.colno).to.equal(colno); + expect(log.error.name).to.equal(error.name); + expect(log.error.message).to.equal(error.message); + expect(log.error.fileName).to.equal(error.fileName); + expect(log.error.description).to.equal(error.description); + expect(log.error.number).to.equal(error.number); + expect(log.error.lineNumber).to.equal(error.lineNumber); + expect(log.error.columnNumber).to.equal(error.columnNumber); + expect(log.error.stack).to.equal(error.stack); - it('should log legacy error batch', (done) => { + expect(client._uniqueLogs.size).to.equal(1); + done(); + } + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.legacyError(message, source, lineno, colno, error, developerMessage); + }); - const legacyErrors = [ - { message: 'First error message', source: 'logging.js', lineno: 102, colno: 23, error: new Error('First error occurred'), developerMessage: 'My first message' }, - { message: 'Second error message', source: 'logging.js', lineno: 45, colno: 12, error: new Error('Second error occurred'), developerMessage: 'My second message' }, - { message: 'Third error message', source: 'logging.js', lineno: 2, colno: 19, error: new Error('Third error occurred'), developerMessage: 'My third message' }, - ]; - const mockLogger = { - logBatch: (logs) => { - expect(logs.length).to.equal(legacyErrors.length); + it('should log identical legacy error only once per throttle rate', () => { + const message = 'The error message'; + const source = 'logging.js'; + const lineno = 102; + const colno = 23; + const error = new Error('An error occurred'); + const developerMessage = 'My custom message to go along with it'; + + const stub = sinon.stub(); + const mockLogger = { + logBatch: stub + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.legacyError(message, source, lineno, colno, error, developerMessage); + client.legacyError(message, source, lineno, colno, error, developerMessage); // Should not be logged + + expect(client._uniqueLogs.size).to.equal(1); + expect(stub.calledOnce).to.be.true; + }); - for (let i = 0; i < legacyErrors.length; i += 1) { + it('should log identical legacy error again after throttle rate has passed', () => { + let messageKey, timeValue; + const message = 'The error message'; + const source = 'logging.js'; + const lineno = 102; + const colno = 23; + const error = new Error('An error occurred'); + const developerMessage = 'My custom message to go along with it'; + + const stub = sinon.stub(); + const mockLogger = { + logBatch: stub + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.legacyError(message, source, lineno, colno, error, developerMessage); + expect(client._uniqueLogs.size).to.equal(1); + client._uniqueLogs.forEach((value, key) => { + messageKey = key; + timeValue = value; + }); + + clock.tick(defaultThrottleRateMs); + client.legacyError(message, source, lineno, colno, error, developerMessage); + + expect(client._uniqueLogs.size).to.equal(1); + const newTime = client._uniqueLogs.get(messageKey); + expect(newTime).to.be.at.least(timeValue + defaultThrottleRateMs); + expect(stub.calledTwice).to.be.true; + }); + + it('should throttle legacy error batch', () => { + const legacyErrors = [ + { message: 'First error message', source: 'logging.js', lineno: 102, colno: 23, error: new Error('First error occurred'), developerMessage: 'My first message' }, + { message: 'Second error message', source: 'logging.js', lineno: 45, colno: 12, error: new Error('Second error occurred'), developerMessage: 'My second message' } + ]; + legacyErrors.push(legacyErrors[0]); + const checkLogs = function(logs) { + expect(logs.length).to.equal(legacyErrors.length - 1); // should not log third legacy error + + for (let i = 0; i < logs.length; i += 1) { const log = logs[i]; const { message, source, lineno, colno, error, developerMessage } = legacyErrors[i]; expect(log.appId).to.equal('my-app-id'); @@ -277,11 +615,18 @@ describe('logging', () => { expect(log.error.columnNumber).to.equal(error.columnNumber); expect(log.error.stack).to.equal(error.stack); } - done(); - } - }; - const client = new LoggingClient('my-app-id', mockLogger); - client.legacyErrorBatch(legacyErrors); + }; + const spy = sinon.spy(checkLogs); + const mockLogger = { + logBatch: spy + }; + const client = new LoggingClient('my-app-id', mockLogger, { shouldThrottle: true }); + client.legacyErrorBatch(legacyErrors); + client.legacyErrorBatch(legacyErrors); // None of these should be logged + + expect(client._uniqueLogs.size).to.equal(2); + expect(spy.calledOnce).to.be.true; + }); }); });