@remit/logger-lambda 0.0.12 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/logger-lambda",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -28,7 +28,9 @@ process.stdout.write = ((
28
28
  return (originalWrite as (...args: unknown[]) => boolean)(chunk, ...rest);
29
29
  }) as typeof process.stdout.write;
30
30
 
31
- const { createLogger, logger, withLogContext } = await import("./logger.js");
31
+ const { createLogger, logger, withLogContext, withTelemetry } = await import(
32
+ "./logger.js"
33
+ );
32
34
 
33
35
  const parse = (): Line[] =>
34
36
  written
@@ -353,3 +355,76 @@ describe("withLogContext", () => {
353
355
  assert.equal(repeats.length, 1);
354
356
  });
355
357
  });
358
+
359
+ describe("withTelemetry", () => {
360
+ const context = {
361
+ awsRequestId: "req-1",
362
+ functionName: "test-function",
363
+ } as unknown as Parameters<Parameters<typeof withTelemetry>[0]>[1];
364
+
365
+ it("puts its own start line inside the scope", async () => {
366
+ const lines = await captureAsync(async () => {
367
+ await withTelemetry(async () => "ok")({}, context);
368
+ });
369
+ const started = lines.find(
370
+ (line) => line.msg === "Lambda invocation started",
371
+ );
372
+ assert.ok(started, "expected the start line");
373
+ assert.equal(started.requestId, "req-1");
374
+ });
375
+
376
+ // The line an operator correlates from is written after the handler has
377
+ // unwound, which is exactly what a scope opened inside the handler misses.
378
+ it("puts its own failure line inside the scope", async () => {
379
+ const lines = await captureAsync(async () => {
380
+ await assert.rejects(
381
+ withTelemetry(async () => {
382
+ throw new Error("boom");
383
+ })({}, context),
384
+ );
385
+ });
386
+ const failed = lines.find(
387
+ (line) => line.msg === "Lambda invocation failed",
388
+ );
389
+ assert.ok(failed, "expected the failure line");
390
+ assert.equal(failed.requestId, "req-1");
391
+ });
392
+
393
+ it("carries the same requestId onto the handler's own lines", async () => {
394
+ const lines = await captureAsync(async () => {
395
+ await withTelemetry(async () => {
396
+ logger.warn("from the handler");
397
+ return "ok";
398
+ })({}, context);
399
+ });
400
+ assert.deepEqual(
401
+ [...new Set(lines.map((line) => line.requestId))],
402
+ ["req-1"],
403
+ );
404
+ });
405
+
406
+ it("keeps concurrent invocations apart", async () => {
407
+ const invoke = (id: string, delayMs: number) =>
408
+ withTelemetry(async () => {
409
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
410
+ logger.warn(`handled ${id}`);
411
+ })({}, {
412
+ awsRequestId: id,
413
+ functionName: "test-function",
414
+ } as unknown as typeof context);
415
+
416
+ const lines = await captureAsync(async () => {
417
+ await Promise.all([invoke("slow", 10), invoke("fast", 1)]);
418
+ });
419
+
420
+ assert.deepEqual(
421
+ lines
422
+ .filter((line) => String(line.msg).startsWith("handled "))
423
+ .map((line) => [line.msg, line.requestId]),
424
+ [
425
+ ["handled fast", "fast"],
426
+ ["handled slow", "slow"],
427
+ ],
428
+ );
429
+ });
430
+ });
package/src/logger.ts CHANGED
@@ -159,32 +159,38 @@ export const logger: Logger = createAdapter(root, {});
159
159
 
160
160
  export const createLogger = (): Logger => createAdapter(root, {});
161
161
 
162
+ // The scope opens around the wrapper's own two lines, not just the handler:
163
+ // "Lambda invocation failed" is the line an operator correlates from, and it is
164
+ // written after the handler has already unwound, so a scope opened inside the
165
+ // handler no longer covers it. Everything the invocation writes — the wrapper's
166
+ // lines and the handler's — carries the same `requestId`.
162
167
  export const withTelemetry = <TEvent, TResult>(
163
168
  handler: (event: TEvent, context: Context) => Promise<TResult>,
164
169
  ): ((event: TEvent, context: Context) => Promise<TResult>) => {
165
- return async (event: TEvent, context: Context): Promise<TResult> => {
166
- logger.debug("Lambda invocation started", {
167
- functionName: context.functionName,
168
- });
169
-
170
- const start = Date.now();
171
-
172
- try {
173
- const result = await handler(event, context);
174
- recordHandlerOutcome({
175
- handler: context.functionName,
176
- outcome: "success",
177
- durationMs: Date.now() - start,
178
- });
179
- return result;
180
- } catch (err) {
181
- recordHandlerOutcome({
182
- handler: context.functionName,
183
- outcome: "failure",
184
- durationMs: Date.now() - start,
170
+ return async (event: TEvent, context: Context): Promise<TResult> =>
171
+ withLogContext({ requestId: context.awsRequestId }, async () => {
172
+ logger.debug("Lambda invocation started", {
173
+ functionName: context.functionName,
185
174
  });
186
- logger.error("Lambda invocation failed", { error: err });
187
- throw err;
188
- }
189
- };
175
+
176
+ const start = Date.now();
177
+
178
+ try {
179
+ const result = await handler(event, context);
180
+ recordHandlerOutcome({
181
+ handler: context.functionName,
182
+ outcome: "success",
183
+ durationMs: Date.now() - start,
184
+ });
185
+ return result;
186
+ } catch (err) {
187
+ recordHandlerOutcome({
188
+ handler: context.functionName,
189
+ outcome: "failure",
190
+ durationMs: Date.now() - start,
191
+ });
192
+ logger.error("Lambda invocation failed", { error: err });
193
+ throw err;
194
+ }
195
+ });
190
196
  };