Repository: awslabs/aws-embedded-metrics-node (ts)

Issue:
Application crashes when there's no connection to CloudwatchAgent
When running in a test environment (Which is similar to a setup on EC2 with a CloudwatchAgent) the whole node.js application crashes when it's unable to push metrics to the Agent and I'm unable to catch the error.

**Steps to reproduce**
- Setup an environment similar to EC2 setup
- Do **not** start a CloudWatchAgent
- Try to push some metrics to the Agent (which of course fails)

**Expected behavior**
- Library emits an error which can be caught to handle it appropriately OR handle the error with a log statement (Both are fine with me, but maybe in general it's better to let the caller handle the error)

**Actual behavior**
- You get a UnhandledPromiseRejectionWarning: Error: connect ECONNREFUSED 0.0.0.0:25888
- The moment the Agent starts you will see a Error: read ECONNRESET and the application closes

**Root Cause**
I tried to narrow down the problem and it seems that https://github.com/awslabs/aws-embedded-metrics-node/blob/2ec8a84b7cc6081ff368aff501101a710367c6bb/src/logger/MetricsLogger.ts#L50 is missing an await. At least if I add an await here the exception is caught and the Log (https://github.com/awslabs/aws-embedded-metrics-node/blob/master/src/logger/MetricScope.ts#L34) is emitted.

**System Overview**
- Running the CloudWatchAgent in a docker container
- Node v12.16.1
- aws-embedded-metrics-node 2.0.3

Pull request:
Fix #81 (crash when no connection to agent)
*Issue #81*

*Description of changes:*
- Added 'await' in the MetricsLogger
- Added a test for checking the behavior (I added a describe block around the existing tests and added a second describe to setup a TestSink where the accept function rejects. So it looks like a large change on the diff but most lines only got 2 extra spaces because they became part of the first describe block)

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.


Patch:
diff --git a/src/logger/MetricsLogger.ts b/src/logger/MetricsLogger.ts
index 0af57e5..cc5fd33 100644
--- a/src/logger/MetricsLogger.ts
+++ b/src/logger/MetricsLogger.ts
@@ -47,7 +47,7 @@ export class MetricsLogger {
     const sink = environment.getSink();
 
     // accept and reset the context
-    sink.accept(this.context);
+    await sink.accept(this.context);
     this.context = this.context.createCopyWithContext();
   }
 


Test patch:
diff --git a/src/logger/__tests__/MetricsLogger.test.ts b/src/logger/__tests__/MetricsLogger.test.ts
index 698a50e..a00d0d7 100644
--- a/src/logger/__tests__/MetricsLogger.test.ts
+++ b/src/logger/__tests__/MetricsLogger.test.ts
@@ -8,7 +8,7 @@ import { ISink } from '../../sinks/Sink';
 import { MetricsContext } from '../MetricsContext';
 import { MetricsLogger } from '../MetricsLogger';
 
-const createSink = () => new TestSink();
+const createSink = (forceAcceptRejects = false) => new TestSink(forceAcceptRejects);
 const createEnvironment = (sink: ISink) => {
   return {
     probe: () => Promise.resolve(true),
@@ -25,362 +25,377 @@ let sink: TestSink;
 let environment: IEnvironment;
 let logger: MetricsLogger;
 
-beforeEach(() => {
-  sink = createSink();
-  environment = createEnvironment(sink);
-  logger = createLogger(() => Promise.resolve(environment));
-});
-
-test('can set property', async () => {
-  // arrange
-  const expectedKey = faker.random.word();
-  const expectedValue = faker.random.word();
-
-  // act
-  logger.setProperty(expectedKey, expectedValue);
-  await logger.flush();
-
-  // assert
-  expect(sink.events).toHaveLength(1);
-  const actualValue = sink.events[0].properties[expectedKey];
-  expect(actualValue).toBeTruthy();
-  expect(actualValue).toBe(expectedValue);
-});
-
-test('can put metric', async () => {
-  // arrange
-  const expectedKey = faker.random.word();
-  const expectedValue = faker.random.number();
-
-  // act
-  logger.putMetric(expectedKey, expectedValue);
-  await logger.flush();
-
-  // assert
-  expect(sink.events).toHaveLength(1);
-  const actualMetric = sink.events[0].metrics.get(expectedKey);
-  expect(actualMetric).toBeTruthy();
-  expect(actualMetric!.values).toStrictEqual([expectedValue]);
-  expect(actualMetric!.unit).toBe('None');
-});
-
-test('put metric appends metrics using same key', async () => {
-  // arrange
-  const expectedKey = faker.random.word();
-  const expectedValues = [faker.random.number(), faker.random.number()];
-
-  // act
-  logger.putMetric(expectedKey, expectedValues[0]);
-  logger.putMetric(expectedKey, expectedValues[1]);
-  await logger.flush();
-
-  // assert
-  expect(sink.events).toHaveLength(1);
-  const actualMetric = sink.events[0].metrics.get(expectedKey);
-  expect(actualMetric).toBeTruthy();
-  expect(actualMetric!.values).toStrictEqual(expectedValues);
-  expect(actualMetric!.unit).toBe('None');
-});
-
-test('can put metric with enum unit', async () => {
-  // arrange
-  const expectedKey = faker.random.word();
-  const expectedValue = faker.random.number();
-  const expectedUnit = Unit.Bits;
-
-  // act
-  logger.putMetric(expectedKey, expectedValue, expectedUnit);
-  await logger.flush();
-
-  // assert
-  expect(sink.events).toHaveLength(1);
-  const actualMetric = sink.events[0].metrics.get(expectedKey);
-  expect(actualMetric).toBeTruthy();
-  expect(actualMetric!.unit).toBe('Bits');
-});
-
-test('can put metric with string unit', async () => {
-  // arrange
-  const expectedKey = faker.random.word();
-  const expectedValue = faker.random.number();
-  const expectedUnit = 'Bits/Second';
-
-  // act
-  logger.putMetric(expectedKey, expectedValue, expectedUnit);
-  await logger.flush();
-
-  // assert
-  expect(sink.events).toHaveLength(1);
-  const actualMetric = sink.events[0].metrics.get(expectedKey);
-  expect(actualMetric).toBeTruthy();
-  expect(actualMetric!.unit).toBe(expectedUnit);
-});
-
-test('can put dimension', async () => {
-  // arrange
-  const expectedKey = faker.random.word();
-  const expectedValue = faker.random.word();
-  const dimensions: Record<string, string> = {};
-  dimensions[expectedKey] = expectedValue;
-
-  // act
-  logger.putDimensions(dimensions);
-  await logger.flush();
-
-  // assert
-  expect(sink.events).toHaveLength(1);
-  const dimensionSets = sink.events[0].getDimensions();
-  expect(dimensionSets).toHaveLength(1);
-  const dimension = dimensionSets[0];
-  const actualValue = dimension[expectedKey];
-  expect(actualValue).toBe(expectedValue);
-});
-
-test('setDimensions overwrites default dimensions', async () => {
-  // arrange
-  const context = MetricsContext.empty();
-  context.setDefaultDimensions({ Foo: 'Bar' });
-
-  const sink = createSink();
-  const env = createEnvironment(sink);
-  const logger = new MetricsLogger(() => Promise.resolve(env), context);
-
-  const expectedKey = faker.random.word();
-  const expectedValue = faker.random.word();
-  const dimensions: Record<string, string> = {};
-  dimensions[expectedKey] = expectedValue;
-
-  // act
-  logger.setDimensions(dimensions);
-  await logger.flush();
-
-  // assert
-  expect(sink.events).toHaveLength(1);
-  const dimensionSets = sink.events[0].getDimensions();
-  expect(dimensionSets).toHaveLength(1);
-  const actualDimensions = dimensionSets[0];
-  expect(Object.keys(actualDimensions).length).toBe(1);
-  const actualValue = actualDimensions[expectedKey];
-  expect(actualValue).toBe(expectedValue);
-});
-
-test('setDimensions overwrites previous dimensions', async () => {
-  // arrange
-  const expectedKey = faker.random.word();
-  const expectedValue = faker.random.word();
-  const dimensions: Record<string, string> = {};
-  dimensions[expectedKey] = expectedValue;
-
-  // act
-  logger.putDimensions({ Foo: 'Bar' });
-  logger.setDimensions(dimensions);
-  await logger.flush();
-
-  // assert
-  expect(sink.events).toHaveLength(1);
-  const dimensionSets = sink.events[0].getDimensions();
-  expect(dimensionSets).toHaveLength(1);
-  const dimension = dimensionSets[0];
-  const actualValue = dimension[expectedKey];
-  expect(actualValue).toBe(expectedValue);
-});
-
-test('can set namespace', async () => {
-  // arrange
-  const expectedValue = faker.random.word();
-
-  // act
-  logger.setNamespace(expectedValue);
-  await logger.flush();
-
-  // assert
-  expect(sink.events).toHaveLength(1);
-  const actualValue = sink.events[0].namespace;
-  expect(actualValue).toBe(expectedValue);
-});
-
-test('defaults timestamp to now', async () => {
-  // arrange
-  const before = new Date();
-  // recreate logger to regenerate meta.Timestamp set to now
-  sink = createSink();
-  environment = createEnvironment(sink);
-  logger = createLogger(() => Promise.resolve(environment));
-
-  // act
-  logger.putMetric(faker.random.word(), faker.random.number());
-  await logger.flush();
-
-  //assert
-  const after = new Date();
-  const lastEvent = sink.events.slice(-1)[0];
-  expectTimestampWithin(lastEvent, [before, after]);
-});
-
-test('can set timestamp', async () => {
-  // arrange
-  const timestamp = faker.date.recent();
-  logger.setTimestamp(timestamp)
-
-  // act
-  logger.putMetric(faker.random.word(), faker.random.number());
-  await logger.flush();
-
-  //assert
-  expect(sink.events.length).toEqual(1);
-  expect(sink.events[0].meta.Timestamp).toEqual(timestamp.getTime());
-});
-
-test('flush() preserves timestamp if set explicitly', async () => {
-  // arrange
-  const timestamp = faker.date.recent();
-  logger.setTimestamp(timestamp)
-
-  // act
-  logger.putMetric(faker.random.word(), faker.random.number());
-  await logger.flush();
-  logger.putMetric(faker.random.word(), faker.random.number());
-  await logger.flush();
-
-  //assert
-  expect(sink.events.length).toEqual(2);
-  expect(sink.events[1].meta.Timestamp).toEqual(timestamp.getTime());
-});
-
-test('flush() resets timestamp to now if not set explicitly', async () => {
-  // arrange
-  const before = new Date();
-  // recreate logger to regenerate meta.Timestamp set to now
-  sink = createSink();
-  environment = createEnvironment(sink);
-  logger = createLogger(() => Promise.resolve(environment));
-  // act
-  logger.putMetric(faker.random.word(), faker.random.number());
-  await logger.flush();
-  const afterFirstFlush = new Date();
-  logger.putMetric(faker.random.word(), faker.random.number());
-  await logger.flush();
-  const afterSecondFlush = new Date();
-
-  //assert
-  expect(sink.events.length).toEqual(2);
-
-  expectTimestampWithin(sink.events[0], [before, afterFirstFlush]);
-  expectTimestampWithin(sink.events[1], [afterFirstFlush, afterSecondFlush]);
-});
-
-test('flush() uses configured serviceName for default dimension if provided', async () => {
-  // arrange
-  const expected = faker.random.word();
-  Configuration.serviceName = expected;
-
-  // act
-  await logger.flush();
-
-  // assert
-  expectDimension('ServiceName', expected);
-});
-
-test('flush() uses environment serviceName for default dimension if not configured', async () => {
-  // arrange
-  const expected = faker.random.word();
-  Configuration.serviceName = undefined;
-  environment.getName = () => expected;
-
-  // act
-  await logger.flush();
-
-  // assert
-  expectDimension('ServiceName', expected);
-});
-
-test('flush() uses configured serviceType for default dimension if provided', async () => {
-  // arrange
-  const expected = faker.random.word();
-  Configuration.serviceType = expected;
-
-  // act
-  await logger.flush();
-
-  // assert
-  expectDimension('ServiceType', expected);
-});
-
-test('flush() uses environment serviceType for default dimension if not configured', async () => {
-  // arrange
-  const expected = faker.random.word();
-  Configuration.serviceType = undefined;
-  environment.getType = () => expected;
-
-  // act
-  await logger.flush();
-
-  // assert
-  expectDimension('ServiceType', expected);
-});
-
-test('flush() delegates context configuration to the environment by calling configureContext()', async () => {
-  // arrange
-  const expected = faker.random.word();
-  Configuration.serviceType = expected;
-
-  // act
-  await logger.flush();
-
-  // assert
-  expect(environment.configureContext).toBeCalled();
-});
+describe('successful', () => {
+  beforeEach(() => {
+    sink = createSink();
+    environment = createEnvironment(sink);
+    logger = createLogger(() => Promise.resolve(environment));
+  });
+
+  test('can set property', async () => {
+    // arrange
+    const expectedKey = faker.random.word();
+    const expectedValue = faker.random.word();
+
+    // act
+    logger.setProperty(expectedKey, expectedValue);
+    await logger.flush();
+
+    // assert
+    expect(sink.events).toHaveLength(1);
+    const actualValue = sink.events[0].properties[expectedKey];
+    expect(actualValue).toBeTruthy();
+    expect(actualValue).toBe(expectedValue);
+  });
+
+  test('can put metric', async () => {
+    // arrange
+    const expectedKey = faker.random.word();
+    const expectedValue = faker.random.number();
+
+    // act
+    logger.putMetric(expectedKey, expectedValue);
+    await logger.flush();
+
+    // assert
+    expect(sink.events).toHaveLength(1);
+    const actualMetric = sink.events[0].metrics.get(expectedKey);
+    expect(actualMetric).toBeTruthy();
+    expect(actualMetric!.values).toStrictEqual([expectedValue]);
+    expect(actualMetric!.unit).toBe('None');
+  });
+
+  test('put metric appends metrics using same key', async () => {
+    // arrange
+    const expectedKey = faker.random.word();
+    const expectedValues = [faker.random.number(), faker.random.number()];
+
+    // act
+    logger.putMetric(expectedKey, expectedValues[0]);
+    logger.putMetric(expectedKey, expectedValues[1]);
+    await logger.flush();
+
+    // assert
+    expect(sink.events).toHaveLength(1);
+    const actualMetric = sink.events[0].metrics.get(expectedKey);
+    expect(actualMetric).toBeTruthy();
+    expect(actualMetric!.values).toStrictEqual(expectedValues);
+    expect(actualMetric!.unit).toBe('None');
+  });
+
+  test('can put metric with enum unit', async () => {
+    // arrange
+    const expectedKey = faker.random.word();
+    const expectedValue = faker.random.number();
+    const expectedUnit = Unit.Bits;
+
+    // act
+    logger.putMetric(expectedKey, expectedValue, expectedUnit);
+    await logger.flush();
+
+    // assert
+    expect(sink.events).toHaveLength(1);
+    const actualMetric = sink.events[0].metrics.get(expectedKey);
+    expect(actualMetric).toBeTruthy();
+    expect(actualMetric!.unit).toBe('Bits');
+  });
+
+  test('can put metric with string unit', async () => {
+    // arrange
+    const expectedKey = faker.random.word();
+    const expectedValue = faker.random.number();
+    const expectedUnit = 'Bits/Second';
+
+    // act
+    logger.putMetric(expectedKey, expectedValue, expectedUnit);
+    await logger.flush();
+
+    // assert
+    expect(sink.events).toHaveLength(1);
+    const actualMetric = sink.events[0].metrics.get(expectedKey);
+    expect(actualMetric).toBeTruthy();
+    expect(actualMetric!.unit).toBe(expectedUnit);
+  });
+
+  test('can put dimension', async () => {
+    // arrange
+    const expectedKey = faker.random.word();
+    const expectedValue = faker.random.word();
+    const dimensions: Record<string, string> = {};
+    dimensions[expectedKey] = expectedValue;
+
+    // act
+    logger.putDimensions(dimensions);
+    await logger.flush();
+
+    // assert
+    expect(sink.events).toHaveLength(1);
+    const dimensionSets = sink.events[0].getDimensions();
+    expect(dimensionSets).toHaveLength(1);
+    const dimension = dimensionSets[0];
+    const actualValue = dimension[expectedKey];
+    expect(actualValue).toBe(expectedValue);
+  });
+
+  test('setDimensions overwrites default dimensions', async () => {
+    // arrange
+    const context = MetricsContext.empty();
+    context.setDefaultDimensions({ Foo: 'Bar' });
+
+    const sink = createSink();
+    const env = createEnvironment(sink);
+    const logger = new MetricsLogger(() => Promise.resolve(env), context);
+
+    const expectedKey = faker.random.word();
+    const expectedValue = faker.random.word();
+    const dimensions: Record<string, string> = {};
+    dimensions[expectedKey] = expectedValue;
+
+    // act
+    logger.setDimensions(dimensions);
+    await logger.flush();
+
+    // assert
+    expect(sink.events).toHaveLength(1);
+    const dimensionSets = sink.events[0].getDimensions();
+    expect(dimensionSets).toHaveLength(1);
+    const actualDimensions = dimensionSets[0];
+    expect(Object.keys(actualDimensions).length).toBe(1);
+    const actualValue = actualDimensions[expectedKey];
+    expect(actualValue).toBe(expectedValue);
+  });
+
+  test('setDimensions overwrites previous dimensions', async () => {
+    // arrange
+    const expectedKey = faker.random.word();
+    const expectedValue = faker.random.word();
+    const dimensions: Record<string, string> = {};
+    dimensions[expectedKey] = expectedValue;
+
+    // act
+    logger.putDimensions({ Foo: 'Bar' });
+    logger.setDimensions(dimensions);
+    await logger.flush();
+
+    // assert
+    expect(sink.events).toHaveLength(1);
+    const dimensionSets = sink.events[0].getDimensions();
+    expect(dimensionSets).toHaveLength(1);
+    const dimension = dimensionSets[0];
+    const actualValue = dimension[expectedKey];
+    expect(actualValue).toBe(expectedValue);
+  });
+
+  test('can set namespace', async () => {
+    // arrange
+    const expectedValue = faker.random.word();
+
+    // act
+    logger.setNamespace(expectedValue);
+    await logger.flush();
+
+    // assert
+    expect(sink.events).toHaveLength(1);
+    const actualValue = sink.events[0].namespace;
+    expect(actualValue).toBe(expectedValue);
+  });
+
+  test('defaults timestamp to now', async () => {
+    // arrange
+    const before = new Date();
+    // recreate logger to regenerate meta.Timestamp set to now
+    sink = createSink();
+    environment = createEnvironment(sink);
+    logger = createLogger(() => Promise.resolve(environment));
+
+    // act
+    logger.putMetric(faker.random.word(), faker.random.number());
+    await logger.flush();
+
+    //assert
+    const after = new Date();
+    const lastEvent = sink.events.slice(-1)[0];
+    expectTimestampWithin(lastEvent, [before, after]);
+  });
+
+  test('can set timestamp', async () => {
+    // arrange
+    const timestamp = faker.date.recent();
+    logger.setTimestamp(timestamp)
+
+    // act
+    logger.putMetric(faker.random.word(), faker.random.number());
+    await logger.flush();
+
+    //assert
+    expect(sink.events.length).toEqual(1);
+    expect(sink.events[0].meta.Timestamp).toEqual(timestamp.getTime());
+  });
+
+  test('flush() preserves timestamp if set explicitly', async () => {
+    // arrange
+    const timestamp = faker.date.recent();
+    logger.setTimestamp(timestamp)
+
+    // act
+    logger.putMetric(faker.random.word(), faker.random.number());
+    await logger.flush();
+    logger.putMetric(faker.random.word(), faker.random.number());
+    await logger.flush();
+
+    //assert
+    expect(sink.events.length).toEqual(2);
+    expect(sink.events[1].meta.Timestamp).toEqual(timestamp.getTime());
+  });
+
+  test('flush() resets timestamp to now if not set explicitly', async () => {
+    // arrange
+    const before = new Date();
+    // recreate logger to regenerate meta.Timestamp set to now
+    sink = createSink();
+    environment = createEnvironment(sink);
+    logger = createLogger(() => Promise.resolve(environment));
+    // act
+    logger.putMetric(faker.random.word(), faker.random.number());
+    await logger.flush();
+    const afterFirstFlush = new Date();
+    logger.putMetric(faker.random.word(), faker.random.number());
+    await logger.flush();
+    const afterSecondFlush = new Date();
+
+    //assert
+    expect(sink.events.length).toEqual(2);
+
+    expectTimestampWithin(sink.events[0], [before, afterFirstFlush]);
+    expectTimestampWithin(sink.events[1], [afterFirstFlush, afterSecondFlush]);
+  });
+
+  test('flush() uses configured serviceName for default dimension if provided', async () => {
+    // arrange
+    const expected = faker.random.word();
+    Configuration.serviceName = expected;
+
+    // act
+    await logger.flush();
+
+    // assert
+    expectDimension('ServiceName', expected);
+  });
+
+  test('flush() uses environment serviceName for default dimension if not configured', async () => {
+    // arrange
+    const expected = faker.random.word();
+    Configuration.serviceName = undefined;
+    environment.getName = () => expected;
+
+    // act
+    await logger.flush();
+
+    // assert
+    expectDimension('ServiceName', expected);
+  });
+
+  test('flush() uses configured serviceType for default dimension if provided', async () => {
+    // arrange
+    const expected = faker.random.word();
+    Configuration.serviceType = expected;
+
+    // act
+    await logger.flush();
+
+    // assert
+    expectDimension('ServiceType', expected);
+  });
+
+  test('flush() uses environment serviceType for default dimension if not configured', async () => {
+    // arrange
+    const expected = faker.random.word();
+    Configuration.serviceType = undefined;
+    environment.getType = () => expected;
+
+    // act
+    await logger.flush();
+
+    // assert
+    expectDimension('ServiceType', expected);
+  });
+
+  test('flush() delegates context configuration to the environment by calling configureContext()', async () => {
+    // arrange
+    const expected = faker.random.word();
+    Configuration.serviceType = expected;
+
+    // act
+    await logger.flush();
+
+    // assert
+    expect(environment.configureContext).toBeCalled();
+  });
+
+  test('context is preserved across flush() calls', async () => {
+    // arrange
+    const expectedNamespace = 'Namespace';
+    const metricKey = 'Metric';
+    const expectedDimensionKey = 'Dim';
+    const expectedPropertyKey = 'Prop';
+    const expectedValues = 'Value';
+    const expectedTimestamp = faker.date.recent();
+
+    const dimensions: Record<string, string> = {};
+    dimensions[expectedDimensionKey] = expectedValues;
+
+    logger.setNamespace(expectedNamespace);
+    logger.setProperty(expectedPropertyKey, expectedValues);
+    logger.setDimensions(dimensions);
+    logger.setTimestamp(expectedTimestamp);
+
+    // act
+    logger.putMetric(metricKey, 0);
+    await logger.flush();
+
+    logger.putMetric(metricKey, 1);
+    await logger.flush();
+
+    // assert
+    expect(sink.events).toHaveLength(2);
+    for (let i = 0; i < sink.events.length; i++) {
+      const evt = sink.events[i];
+      // namespace, properties, dimensions, timestamp should survive flushes
+      expect(evt.namespace).toBe(expectedNamespace);
+      expect(evt.getDimensions()[0][expectedDimensionKey]).toBe(expectedValues);
+      expect(evt.properties[expectedPropertyKey]).toBe(expectedValues);
+      expect(evt.meta.Timestamp).toEqual(expectedTimestamp.getTime());
+      // metric values should not survive flushes
+      // @ts-ignore
+      expect(evt.metrics.get(metricKey).values).toStrictEqual([i]);
+    }
+  });
+
+  const expectDimension = (key: string, value: string) => {
+    expect(sink.events).toHaveLength(1);
+    const dimensionSets = sink.events[0].getDimensions();
+
+    expect(dimensionSets).toHaveLength(1);
+    const dimension = dimensionSets[0];
+    const actualValue = dimension[key];
+    expect(actualValue).toBe(value);
+  };
 
-test('context is preserved across flush() calls', async () => {
-  // arrange
-  const expectedNamespace = 'Namespace';
-  const metricKey = 'Metric';
-  const expectedDimensionKey = 'Dim';
-  const expectedPropertyKey = 'Prop';
-  const expectedValues = 'Value';
-  const expectedTimestamp = faker.date.recent();
-
-  const dimensions: Record<string, string> = {};
-  dimensions[expectedDimensionKey] = expectedValues;
-
-  logger.setNamespace(expectedNamespace);
-  logger.setProperty(expectedPropertyKey, expectedValues);
-  logger.setDimensions(dimensions);
-  logger.setTimestamp(expectedTimestamp);
-
-  // act
-  logger.putMetric(metricKey, 0);
-  await logger.flush();
-
-  logger.putMetric(metricKey, 1);
-  await logger.flush();
-
-  // assert
-  expect(sink.events).toHaveLength(2);
-  for (let i = 0; i < sink.events.length; i++) {
-    const evt = sink.events[i];
-    // namespace, properties, dimensions, timestamp should survive flushes
-    expect(evt.namespace).toBe(expectedNamespace);
-    expect(evt.getDimensions()[0][expectedDimensionKey]).toBe(expectedValues);
-    expect(evt.properties[expectedPropertyKey]).toBe(expectedValues);
-    expect(evt.meta.Timestamp).toEqual(expectedTimestamp.getTime());
-    // metric values should not survive flushes
-    // @ts-ignore
-    expect(evt.metrics.get(metricKey).values).toStrictEqual([i]);
+  const expectTimestampWithin = (context: MetricsContext, range: [Date, Date]) => {
+    expect(context.meta.Timestamp).toBeGreaterThanOrEqual(range[0].getTime());
+    expect(context.meta.Timestamp).toBeLessThanOrEqual(range[1].getTime());
   }
 });
 
-const expectDimension = (key: string, value: string) => {
-  expect(sink.events).toHaveLength(1);
-  const dimensionSets = sink.events[0].getDimensions();
-
-  expect(dimensionSets).toHaveLength(1);
-  const dimension = dimensionSets[0];
-  const actualValue = dimension[key];
-  expect(actualValue).toBe(value);
-};
-
-const expectTimestampWithin = (context: MetricsContext, range: [Date, Date]) => {
-  expect(context.meta.Timestamp).toBeGreaterThanOrEqual(range[0].getTime());
-  expect(context.meta.Timestamp).toBeLessThanOrEqual(range[1].getTime());
-}
+describe('failure', () => {
+  beforeEach(() => {
+    sink = createSink(true);
+    environment = createEnvironment(sink);
+    logger = createLogger(() => Promise.resolve(environment));
+  });
+
+  test('flush should reject on sink error', async () => {
+    // assert
+    await expect(logger.flush()).rejects.toBeUndefined();
+  });
+});
\ No newline at end of file
diff --git a/test/utils/TestSink.ts b/test/utils/TestSink.ts
index d71ac4c..503cc8e 100644
--- a/test/utils/TestSink.ts
+++ b/test/utils/TestSink.ts
@@ -25,8 +25,14 @@ export class TestSink implements ISink {
 
   public events: MetricsContext[] = [];
 
+  public forceAcceptRejects: boolean;
+
+  constructor(forceAcceptRejects: boolean) {
+    this.forceAcceptRejects = forceAcceptRejects;
+  }
+
   public accept(context: MetricsContext): Promise<void> {
     this.events.push(context);
-    return Promise.resolve();
+    return this.forceAcceptRejects ? Promise.reject(): Promise.resolve();
   }
 }