From bbae43f53011c4f2fa6ea8b7b9d01616b8fe254c Mon Sep 17 00:00:00 2001 From: Vishnu Onteddu Date: Mon, 23 Mar 2020 11:39:09 -0700 Subject: [PATCH 1/4] Added logic to support custom timestamp --- src/index.spec.ts | 24 +++++++++++++++++++++++- src/index.ts | 17 +++++++++++++++++ src/metrics/listener.spec.ts | 21 +++++++++++++++++++++ src/metrics/listener.ts | 10 +++++++--- src/metrics/metric-log.spec.ts | 5 ++--- src/metrics/metric-log.ts | 8 ++++---- 6 files changed, 74 insertions(+), 11 deletions(-) diff --git a/src/index.spec.ts b/src/index.spec.ts index bf0940ff..943049f0 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -2,7 +2,7 @@ import http from "http"; import nock from "nock"; import { Context, Handler } from "aws-lambda"; -import { datadog, getTraceHeaders, sendDistributionMetric, TraceHeaders } from "./index"; +import { datadog, getTraceHeaders, sendDistributionMetric, TraceHeaders, sendDistributionMetricWithDate } from "./index"; import { incrementErrorsMetric, incrementInvocationsMetric } from "./metrics/enhanced-metrics"; import { MetricsListener } from "./metrics/listener"; import { LogLevel, setLogLevel } from "./utils"; @@ -163,6 +163,28 @@ describe("datadog", () => { expect(nock.isDone()).toBeTruthy(); }); + it("reads site keys from the environment using custom timestamp", async () => { + const site = "datadoghq.com"; + const siteEnvVar = "DD_SITE"; + const apiKey = "12345"; + process.env[siteEnvVar] = site; + + nock("https://api.datadoghq.com") + .post(`/api/v1/distribution_points?api_key=${apiKey}`, (request: any) => request.series[0].metric === "my-dist") + .reply(200, {}); + + const wrapped = datadog( + async () => { + sendDistributionMetricWithDate("my-dist", 100, new Date(), "first-tag", "second-tag"); + return ""; + }, + { apiKey, forceWrap: true }, + ); + await wrapped({}, {} as any, () => {}); + + expect(nock.isDone()).toBeTruthy(); + }); + it("makes the current trace headers available", async () => { let traceHeaders: Partial = {}; const event = { diff --git a/src/index.ts b/src/index.ts index 60272647..e8d032ef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -117,6 +117,23 @@ export function datadog( return wrappedFunc; } +/** + * Sends a Distribution metric asynchronously to the Datadog API. + * @param name The name of the metric to send. + * @param value The value of the metric + * @param metricTime The timesamp associated with this metric data point. + * @param tags The tags associated with the metric. Should be of the format "tag:value". + */ +export function sendDistributionMetricWithDate(name: string, value: number, metricTime: Date, ...tags: string[]) { + tags = [...tags, getRuntimeTag()]; + + if (currentMetricsListener !== undefined) { + currentMetricsListener.sendDistributionMetricWithDate(name, value, metricTime, ...tags); + } else { + logError("handler not initialized"); + } +} + /** * Sends a Distribution metric asynchronously to the Datadog API. * @param name The name of the metric to send. diff --git a/src/metrics/listener.spec.ts b/src/metrics/listener.spec.ts index 20d86f12..52d86b44 100644 --- a/src/metrics/listener.spec.ts +++ b/src/metrics/listener.spec.ts @@ -100,4 +100,25 @@ describe("MetricsListener", () => { expect(spy).toHaveBeenCalledWith(`{"e":1487076708,"m":"my-metric","t":["tag:a","tag:b"],"v":10}\n`); }); + + it("logs metrics when logForwarding is enabled with custom timestamp", async () => { + const spy = jest.spyOn(process.stdout, "write"); + // jest.spyOn(Date, "now").mockImplementation(() => 1487076708000); + const kms = new MockKMS("kms-api-key-decrypted"); + const listener = new MetricsListener(kms as any, { + apiKey: "api-key", + apiKeyKMS: "kms-api-key-encrypted", + enhancedMetrics: false, + logForwarding: true, + shouldRetryMetrics: false, + siteURL, + }); + // jest.useFakeTimers(); + + listener.onStartInvocation({}); + listener.sendDistributionMetricWithDate("my-metric", 10, new Date(1584983836 * 1000), "tag:a", "tag:b"); + await listener.onCompleteInvocation(); + + expect(spy).toHaveBeenCalledWith(`{"e":1584983836,"m":"my-metric","t":["tag:a","tag:b"],"v":10}\n`); + }); }); diff --git a/src/metrics/listener.ts b/src/metrics/listener.ts index 41bba1dd..31f84a59 100644 --- a/src/metrics/listener.ts +++ b/src/metrics/listener.ts @@ -83,12 +83,12 @@ export class MetricsListener { this.currentProcessor = undefined; } - public sendDistributionMetric(name: string, value: number, ...tags: string[]) { + public sendDistributionMetricWithDate(name: string, value: number, metricTime: Date, ...tags: string[]) { if (this.config.logForwarding) { - writeMetricToStdout(name, value, tags); + writeMetricToStdout(name, value, metricTime, tags); return; } - const dist = new Distribution(name, [{ timestamp: new Date(), value }], ...tags); + const dist = new Distribution(name, [{ timestamp: metricTime, value }], ...tags); if (this.currentProcessor !== undefined) { this.currentProcessor.then((processor) => { @@ -99,6 +99,10 @@ export class MetricsListener { } } + public sendDistributionMetric(name: string, value: number, ...tags: string[]) { + this.sendDistributionMetricWithDate(name, value, new Date(Date.now()), ...tags); + } + private async createProcessor(config: MetricsConfig, apiKey: Promise) { const key = await apiKey; const url = `https://api.${config.siteURL}`; diff --git a/src/metrics/metric-log.spec.ts b/src/metrics/metric-log.spec.ts index d841ab8f..cbf92e2f 100644 --- a/src/metrics/metric-log.spec.ts +++ b/src/metrics/metric-log.spec.ts @@ -1,14 +1,13 @@ import { buildMetricLog } from "./metric-log"; describe("buildMetricLog", () => { - jest.spyOn(Date, "now").mockImplementation(() => 1487076708123); it("handles empty tag list", () => { - expect(buildMetricLog("my.test.metric", 1337, [])).toStrictEqual( + expect(buildMetricLog("my.test.metric", 1337, new Date(1487076708123), [])).toStrictEqual( '{"e":1487076708.123,"m":"my.test.metric","t":[],"v":1337}\n', ); }); it("writes timestamp in Unix seconds", () => { - expect(buildMetricLog("my.test.metric", 1337, ["region:us", "account:dev", "team:serverless"])).toStrictEqual( + expect(buildMetricLog("my.test.metric", 1337, new Date(1487076708123), ["region:us", "account:dev", "team:serverless"])).toStrictEqual( '{"e":1487076708.123,"m":"my.test.metric","t":["region:us","account:dev","team:serverless"],"v":1337}\n', ); }); diff --git a/src/metrics/metric-log.ts b/src/metrics/metric-log.ts index 3f08f9da..1701d6dc 100644 --- a/src/metrics/metric-log.ts +++ b/src/metrics/metric-log.ts @@ -1,8 +1,8 @@ // Builds the string representation of the metric that will be written to logs -export function buildMetricLog(name: string, value: number, tags: string[]) { +export function buildMetricLog(name: string, value: number, metricTime: Date, tags: string[]) { return `${JSON.stringify({ // Date.now() returns Unix time in milliseconds, we convert to seconds for DD API submission - e: Date.now() / 1000, + e: metricTime.getTime() / 1000, m: name, t: tags, v: value, @@ -15,8 +15,8 @@ export function buildMetricLog(name: string, value: number, tags: string[]) { * @param value Metric datapoint's value * @param tags Tags to apply to the metric */ -export function writeMetricToStdout(name: string, value: number, tags: string[]) { +export function writeMetricToStdout(name: string, value: number, metricTime: Date, tags: string[]) { // We use process.stdout.write, because console.log will prepend metadata to the start // of the log that log forwarder doesn't know how to read. - process.stdout.write(buildMetricLog(name, value, tags)); + process.stdout.write(buildMetricLog(name, value, metricTime, tags)); } From 76c17b2767350afb096a880a207e3b756a9ad147 Mon Sep 17 00:00:00 2001 From: Vishnu Onteddu Date: Mon, 23 Mar 2020 12:14:00 -0700 Subject: [PATCH 2/4] Fixed compile error using metric-log writeMetricToStdout --- src/metrics/enhanced-metrics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/metrics/enhanced-metrics.ts b/src/metrics/enhanced-metrics.ts index 12103558..6f2a4837 100644 --- a/src/metrics/enhanced-metrics.ts +++ b/src/metrics/enhanced-metrics.ts @@ -65,7 +65,7 @@ export function getEnhancedMetricTags(context: Context): string[] { */ function incrementEnhancedMetric(metricName: string, context: Context) { // Always write enhanced metrics to standard out - writeMetricToStdout(`${ENHANCED_LAMBDA_METRICS_NAMESPACE}.${metricName}`, 1, getEnhancedMetricTags(context)); + writeMetricToStdout(`${ENHANCED_LAMBDA_METRICS_NAMESPACE}.${metricName}`, 1, new Date(), getEnhancedMetricTags(context)); } export function incrementInvocationsMetric(context: Context): void { From 448ef4dc0288b1285a0bef1dcbc89a7fa0a7417a Mon Sep 17 00:00:00 2001 From: Vishnu Onteddu Date: Mon, 23 Mar 2020 12:50:10 -0700 Subject: [PATCH 3/4] Resolved code style issues using prettier vscode extension --- src/index.spec.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/index.spec.ts b/src/index.spec.ts index 943049f0..74350a0c 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -2,7 +2,13 @@ import http from "http"; import nock from "nock"; import { Context, Handler } from "aws-lambda"; -import { datadog, getTraceHeaders, sendDistributionMetric, TraceHeaders, sendDistributionMetricWithDate } from "./index"; +import { + datadog, + getTraceHeaders, + sendDistributionMetric, + TraceHeaders, + sendDistributionMetricWithDate, +} from "./index"; import { incrementErrorsMetric, incrementInvocationsMetric } from "./metrics/enhanced-metrics"; import { MetricsListener } from "./metrics/listener"; import { LogLevel, setLogLevel } from "./utils"; @@ -317,4 +323,4 @@ describe("datadog", () => { expect(mockedIncrementInvocations).toBeCalledTimes(0); expect(mockedIncrementErrors).toBeCalledTimes(0); }); -}); +}); \ No newline at end of file From 3d5bc9fe13c638cf7a3e73cf14f37ee59f9d7252 Mon Sep 17 00:00:00 2001 From: Vishnu Onteddu Date: Mon, 23 Mar 2020 12:54:15 -0700 Subject: [PATCH 4/4] Resolved code style issues --- src/index.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.spec.ts b/src/index.spec.ts index 74350a0c..0f9f02cf 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -323,4 +323,4 @@ describe("datadog", () => { expect(mockedIncrementInvocations).toBeCalledTimes(0); expect(mockedIncrementErrors).toBeCalledTimes(0); }); -}); \ No newline at end of file +});