Skip to content

Commit

Permalink
Allow setting timestamp of metrics. (#69 by @Grundlefleck)
Browse files Browse the repository at this point in the history
Allow setting timestamp of metrics.
  • Loading branch information
jaredcnance authored Nov 25, 2020
2 parents 65927e5 + 62f2559 commit 2ec8a84
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 4 deletions.
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,29 @@ Requirements:

Examples:

```py
```js
setNamespace("MyApplication");
```

- **setTimestamp**(Date | number timestamp)

Sets the CloudWatch [timestamp](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#about_timestamp) that extracted metrics are associated with. If not set a default value of `new Date()` will be used.

If set for a given `MetricsLogger`, timestamp will be preserved across calls to flush().

Requirements:
* Date or Unix epoch millis, up to two weeks in the past and up to two hours in the future, as enforced by [CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#about_timestamp).

Examples:

```js
setTimestamp(new Date())
setTimestamp(new Date().getTime())
```

- **flush**()

Flushes the current MetricsContext to the configured sink and resets all properties, dimensions and metric values. The namespace and default dimensions will be preserved across flushes.
Flushes the current MetricsContext to the configured sink and resets all properties, dimensions and metric values. The namespace and default dimensions will be preserved across flushes. Timestamp will be preserved if set explicitly via `setTimestamp()`.

## Configuration

Expand Down
21 changes: 20 additions & 1 deletion src/logger/MetricsContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class MetricsContext {
private dimensions: Array<Record<string, string>>;
private defaultDimensions: Record<string, string>;
private shouldUseDefaultDimensions = true;
private timestamp: Date | number | undefined;

/**
* Constructor used to create child instances.
Expand All @@ -56,14 +57,26 @@ export class MetricsContext {
properties?: IProperties,
dimensions?: Array<Record<string, string>>,
defaultDimensions?: Record<string, string>,
timestamp?: Date | number,
) {
this.namespace = namespace || Configuration.namespace
this.properties = properties || {};
this.dimensions = dimensions || [];
this.meta.Timestamp = new Date().getTime();
this.timestamp = timestamp;
this.meta.Timestamp = MetricsContext.resolveMetaTimestamp(timestamp);
this.defaultDimensions = defaultDimensions || {};
}

private static resolveMetaTimestamp(timestamp?: Date | number): number {
if (timestamp instanceof Date) {
return timestamp.getTime()
} else if (timestamp) {
return timestamp;
} else {
return new Date().getTime();
}
}

public setNamespace(value: string): void {
this.namespace = value;
}
Expand All @@ -72,6 +85,11 @@ export class MetricsContext {
this.properties[key] = value;
}

public setTimestamp(timestamp: Date | number) {
this.timestamp = timestamp;
this.meta.Timestamp = MetricsContext.resolveMetaTimestamp(timestamp);
}

/**
* Sets default dimensions for the Context.
* A dimension set will be created with just the default dimensions
Expand Down Expand Up @@ -173,6 +191,7 @@ export class MetricsContext {
Object.assign({}, this.properties),
Object.assign([], this.dimensions),
this.defaultDimensions,
this.timestamp
);
}
}
15 changes: 15 additions & 0 deletions src/logger/MetricsLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,21 @@ export class MetricsLogger {
return this;
}

/**
* Set the timestamp of metrics emitted in this context.
*
* If not set, the timestamp will default to new Date() at the point
* the context is constructed.
*
* If set, timestamp will preserved across calls to flush().
*
* @param timestamp
*/
public setTimestamp(timestamp: Date | number): MetricsLogger {
this.context.setTimestamp(timestamp);
return this;
}

/**
* Creates a new logger using the same contextual data as
* the previous logger. This allows you to flush the instances
Expand Down
80 changes: 79 additions & 1 deletion src/logger/__tests__/MetricsLogger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,76 @@ test('can set namespace', async () => {
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();
Expand Down Expand Up @@ -268,13 +338,15 @@ test('context is preserved across flush() calls', async () => {
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);
Expand All @@ -287,10 +359,11 @@ test('context is preserved across flush() calls', async () => {
expect(sink.events).toHaveLength(2);
for (let i = 0; i < sink.events.length; i++) {
const evt = sink.events[i];
// namespace, properties, dimensions should survive flushes
// 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]);
Expand All @@ -306,3 +379,8 @@ const expectDimension = (key: string, value: string) => {
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());
}

0 comments on commit 2ec8a84

Please sign in to comment.