Skip to content

Commit c91fa34

Browse files
Merge pull request #33 from DataDog/darcy.rayner/fix-timeout-no-return
Darcy.rayner/fix timeout no return
2 parents 021acfd + 2aaa32f commit c91fa34

File tree

5 files changed

+83
-11
lines changed

5 files changed

+83
-11
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "datadog-lambda-js",
3-
"version": "0.7.0",
3+
"version": "0.8.0",
44
"description": "Lambda client library that supports hybrid tracing in node js",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/metrics/listener.spec.ts

+26
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { AWSError, KMS, Request } from "aws-sdk";
22
import nock from "nock";
33

44
import { LogLevel, setLogLevel } from "../utils";
5+
56
import { MetricsListener } from "./listener";
67

78
const siteURL = "example.com";
@@ -17,6 +18,10 @@ class MockKMS {
1718
setLogLevel(LogLevel.NONE);
1819

1920
describe("MetricsListener", () => {
21+
afterEach(() => {
22+
jest.restoreAllMocks();
23+
});
24+
2025
it("uses unencrypted api key by default", async () => {
2126
nock("https://api.example.com")
2227
.post("/api/v1/distribution_points?api_key=api-key")
@@ -74,4 +79,25 @@ describe("MetricsListener", () => {
7479
listener.sendDistributionMetric("my-metric", 10, "tag:a", "tag:b");
7580
await expect(listener.onCompleteInvocation()).resolves.toEqual(undefined);
7681
});
82+
83+
it("logs metrics when logForwarding is enabled", async () => {
84+
const spy = jest.spyOn(process.stdout, "write");
85+
jest.spyOn(Date, "now").mockImplementation(() => 1487076708000);
86+
const kms = new MockKMS("kms-api-key-decrypted");
87+
const listener = new MetricsListener(kms as any, {
88+
apiKey: "api-key",
89+
apiKeyKMS: "kms-api-key-encrypted",
90+
enhancedMetrics: false,
91+
logForwarding: true,
92+
shouldRetryMetrics: false,
93+
siteURL,
94+
});
95+
jest.useFakeTimers();
96+
97+
listener.onStartInvocation({});
98+
listener.sendDistributionMetric("my-metric", 10, "tag:a", "tag:b");
99+
await listener.onCompleteInvocation();
100+
101+
expect(spy).toHaveBeenCalledWith(`{"e":1487076708000,"m":"my-metric","t":["tag:a","tag:b"],"v":10}\n`);
102+
});
77103
});

src/metrics/listener.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { promisify } from "util";
22

3-
import { logError } from "../utils";
3+
import { logDebug, logError } from "../utils";
44
import { APIClient } from "./api";
55
import { KMSService } from "./kms-service";
66
import { Distribution } from "./model";
@@ -55,6 +55,9 @@ export class MetricsListener {
5555
}
5656

5757
public onStartInvocation(_: any) {
58+
if (this.config.logForwarding) {
59+
return;
60+
}
5861
this.currentProcessor = this.createProcessor(this.config, this.apiKey);
5962
}
6063

@@ -124,8 +127,13 @@ export class MetricsListener {
124127
} catch (error) {
125128
logError("couldn't decrypt kms api key", { innerError: error });
126129
}
127-
} else {
128-
logError("api key not configured");
130+
} else if (!config.logForwarding) {
131+
const errorMessage = "api key not configured, see https://dtdg.co/sls-node-metrics";
132+
if (config.logForwarding) {
133+
logDebug(errorMessage);
134+
} else {
135+
logError(errorMessage);
136+
}
129137
}
130138
return "";
131139
}

src/utils/handler.spec.ts

+38
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,42 @@ describe("wrap", () => {
187187
expect(calledComplete).toBeTruthy();
188188
expect(calledOriginalHandler).toBeFalsy();
189189
});
190+
191+
it("returns the first result to complete between the callback and the handler promise", async () => {
192+
const handler: Handler = async (event, context, callback) => {
193+
callback(null, { statusCode: 204, body: "The callback response" });
194+
return { statusCode: 200, body: "The promise response" };
195+
};
196+
197+
let calledOriginalHandler = false;
198+
199+
const wrappedHandler = wrap(handler, () => {}, async () => {});
200+
201+
const result = await wrappedHandler({}, mockContext, () => {
202+
calledOriginalHandler = true;
203+
});
204+
205+
expect(result).toEqual({ statusCode: 204, body: "The callback response" });
206+
expect(calledOriginalHandler).toBeFalsy();
207+
});
208+
209+
it("doesn't complete using non-promise return values", async () => {
210+
const handler: Handler = (event, context, callback) => {
211+
setTimeout(() => {
212+
callback(null, { statusCode: 204, body: "The callback response" });
213+
}, 10);
214+
return ({ statusCode: 200, body: "The promise response" } as unknown) as void;
215+
};
216+
217+
let calledOriginalHandler = false;
218+
219+
const wrappedHandler = wrap(handler, () => {}, async () => {});
220+
221+
const result = await wrappedHandler({}, mockContext, () => {
222+
calledOriginalHandler = true;
223+
});
224+
225+
expect(result).toEqual({ statusCode: 204, body: "The callback response" });
226+
expect(calledOriginalHandler).toBeFalsy();
227+
});
190228
});

src/utils/handler.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@ export function wrap<TEvent, TResult>(
2323
logError("Pre-lambda hook threw error", { innerError: error });
2424
}
2525
let result: TResult;
26-
// Need to disable linter rule to explicitly assign the variable, otherwise TS
27-
// won't reccognize that the var may be assigned in the catch block
28-
// tslint:disable-next-line: no-unnecessary-initializer
29-
let handlerError: Error | undefined = undefined;
26+
27+
let handlerError: Error | undefined;
3028

3129
try {
3230
const wrappedHandler = onWrap !== undefined ? onWrap(promHandler) : promHandler;
@@ -69,9 +67,11 @@ export function promisifiedHandler<TEvent, TResult>(handler: Handler<TEvent, TRe
6967
};
7068
});
7169

72-
let promise = handler(event, context, modifiedCallback) as Promise<TResult> | undefined;
73-
if (promise === undefined) {
74-
promise = callbackProm;
70+
const asyncProm = handler(event, context, modifiedCallback) as Promise<TResult> | undefined;
71+
let promise: Promise<TResult> = callbackProm;
72+
if (asyncProm !== undefined && typeof asyncProm.then === "function") {
73+
// Mimics behaviour of lambda runtime, the first method of returning a result always wins.
74+
promise = Promise.race([callbackProm, asyncProm]);
7575
}
7676
return promise;
7777
};

0 commit comments

Comments
 (0)