From d066d140b94efc8ee8e6a4b4f4e577f7566b65a7 Mon Sep 17 00:00:00 2001 From: kawaaaas Date: Fri, 9 Jan 2026 02:08:43 +0900 Subject: [PATCH 1/3] feat: add tenantId to logger default properties --- packages/logger/src/Logger.ts | 1 + .../src/formatter/PowertoolsLogFormatter.ts | 1 + packages/logger/src/types/logKeys.ts | 7 ++++ packages/logger/tests/unit/formatters.test.ts | 7 ++++ .../tests/unit/injectLambdaContext.test.ts | 34 +++++++++++++++++++ packages/testing/src/context.ts | 1 + 6 files changed, 51 insertions(+) diff --git a/packages/logger/src/Logger.ts b/packages/logger/src/Logger.ts index 6a73b7e4d6..4efd483963 100644 --- a/packages/logger/src/Logger.ts +++ b/packages/logger/src/Logger.ts @@ -282,6 +282,7 @@ class Logger extends Utility implements LoggerInterface { memoryLimitInMB: context.memoryLimitInMB, functionName: context.functionName, functionVersion: context.functionVersion, + tenantId: context.tenantId, }, }); } diff --git a/packages/logger/src/formatter/PowertoolsLogFormatter.ts b/packages/logger/src/formatter/PowertoolsLogFormatter.ts index e76657c0d6..195d6fa43e 100644 --- a/packages/logger/src/formatter/PowertoolsLogFormatter.ts +++ b/packages/logger/src/formatter/PowertoolsLogFormatter.ts @@ -55,6 +55,7 @@ class PowertoolsLogFormatter extends LogFormatter { function_request_id: attributes.lambdaContext?.awsRequestId, sampling_rate: attributes.sampleRateValue, xray_trace_id: attributes.xRayTraceId, + tenant_id: attributes.lambdaContext?.tenantId, }; // If logRecordOrder is not set, return the log item with the attributes in the order they were added diff --git a/packages/logger/src/types/logKeys.ts b/packages/logger/src/types/logKeys.ts index 8de523dcb2..72dc309ff8 100644 --- a/packages/logger/src/types/logKeys.ts +++ b/packages/logger/src/types/logKeys.ts @@ -140,6 +140,12 @@ type PowertoolsLambdaContextKeys = { * @example "899856cb-83d1-40d7-8611-9e78f15f32f4" */ function_request_id: string; + /** + * The tenant ID from AWS Lambda Tenant Isolation feature. + * + * @example "cff02b3a-0e12-4be2-b3e0-758b49c4cd9b" + */ + tenant_id?: string; }; /** @@ -159,6 +165,7 @@ type LambdaFunctionContext = Pick< | 'functionVersion' | 'invokedFunctionArn' | 'awsRequestId' + | 'tenantId' > & { coldStart: boolean; }; diff --git a/packages/logger/tests/unit/formatters.test.ts b/packages/logger/tests/unit/formatters.test.ts index 0f335d0d5b..c4e681e08d 100644 --- a/packages/logger/tests/unit/formatters.test.ts +++ b/packages/logger/tests/unit/formatters.test.ts @@ -58,6 +58,7 @@ const unformattedAttributes: UnformattedAttributes = { invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:Example', awsRequestId: 'abcdefg123456789', + tenantId: 'cff02b3a-0e12-4be2-b3e0-758b49c4cd9b', }, }; @@ -155,6 +156,7 @@ describe('Formatters', () => { service: 'hello-world', timestamp: '2016-06-20T12:08:10.000Z', xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793', + tenant_id: undefined, }); expect(value).toBeInstanceOf(LogItem); }); @@ -184,6 +186,7 @@ describe('Formatters', () => { service: 'hello-world', timestamp: '2016-06-20T12:08:10.000Z', xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793', + tenant_id: 'cff02b3a-0e12-4be2-b3e0-758b49c4cd9b', }); }); @@ -215,6 +218,7 @@ describe('Formatters', () => { function_request_id: 'abcdefg123456789', sampling_rate: 0.25, xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793', + tenant_id: 'cff02b3a-0e12-4be2-b3e0-758b49c4cd9b', }); }); @@ -256,6 +260,7 @@ describe('Formatters', () => { function_request_id: 'abcdefg123456789', sampling_rate: 0.25, xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793', + tenant_id: 'cff02b3a-0e12-4be2-b3e0-758b49c4cd9b', another_key: 'another_value', }); }); @@ -297,6 +302,7 @@ describe('Formatters', () => { function_request_id: 'abcdefg123456789', sampling_rate: 0.25, xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793', + tenant_id: 'cff02b3a-0e12-4be2-b3e0-758b49c4cd9b', }); }); @@ -328,6 +334,7 @@ describe('Formatters', () => { function_request_id: 'abcdefg123456789', sampling_rate: 0.25, xray_trace_id: '1-5759e988-bd862e3fe1be46a994272793', + tenant_id: 'cff02b3a-0e12-4be2-b3e0-758b49c4cd9b', additional_key: 'additional_value', }); }); diff --git a/packages/logger/tests/unit/injectLambdaContext.test.ts b/packages/logger/tests/unit/injectLambdaContext.test.ts index 4bc6bcec53..68ecfd3d20 100644 --- a/packages/logger/tests/unit/injectLambdaContext.test.ts +++ b/packages/logger/tests/unit/injectLambdaContext.test.ts @@ -16,6 +16,7 @@ const getContextLogEntries = (overrides?: Record) => ({ function_memory_size: context.memoryLimitInMB, function_name: context.functionName, function_request_id: context.awsRequestId, + tenant_id: context.tenantId, cold_start: true, ...overrides, }); @@ -76,6 +77,39 @@ describe('Inject Lambda Context', () => { ); }); + it('does not include tenant_id when context does not have tenantId', () => { + // Prepare + const logger = new Logger(); + const contextWithoutTenantId = { + ...context, + tenantId: undefined, + }; + + // Act + logger.addContext(contextWithoutTenantId); + logger.info('Hello, world!'); + + // Assess + expect(console.info).toHaveBeenCalledTimes(1); + expect(console.info).toHaveLoggedNth( + 1, + expect.objectContaining({ + message: 'Hello, world!', + function_arn: context.invokedFunctionArn, + function_memory_size: context.memoryLimitInMB, + function_name: context.functionName, + function_request_id: context.awsRequestId, + cold_start: true, + }) + ); + expect(console.info).not.toHaveLoggedNth( + 1, + expect.objectContaining({ + tenant_id: expect.anything(), + }) + ); + }); + it('adds the context to log messages when the feature is enabled in the Middy.js middleware', async () => { // Prepare const logger = new Logger(); diff --git a/packages/testing/src/context.ts b/packages/testing/src/context.ts index 59ebe833d8..2d7a9161cf 100644 --- a/packages/testing/src/context.ts +++ b/packages/testing/src/context.ts @@ -10,6 +10,7 @@ export default { invokedFunctionArn: 'arn:aws:lambda:eu-west-1:123456789012:function:foo-bar-function', awsRequestId: 'c6af9ac6-7b61-11e6-9a41-93e812345678', + tenantId: 'cff02b3a-0e12-4be2-b3e0-758b49c4cd9b', getRemainingTimeInMillis: () => 1234, done: () => console.log('Done!'), fail: () => console.log('Failed!'), From 75215a0858d2396a9e76d49a5c050b4060973c1e Mon Sep 17 00:00:00 2001 From: kawaaaas Date: Fri, 9 Jan 2026 10:08:27 +0900 Subject: [PATCH 2/3] feat(logger): add Middy middleware test for tenantId handling --- .../tests/unit/injectLambdaContext.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/logger/tests/unit/injectLambdaContext.test.ts b/packages/logger/tests/unit/injectLambdaContext.test.ts index 68ecfd3d20..2b6d9d7fba 100644 --- a/packages/logger/tests/unit/injectLambdaContext.test.ts +++ b/packages/logger/tests/unit/injectLambdaContext.test.ts @@ -131,6 +131,30 @@ describe('Inject Lambda Context', () => { ); }); + it('does not include tenant_id when using Middy.js middleware without tenantId in context', async () => { + // Prepare + const logger = new Logger(); + const contextWithoutTenantId = { + ...context, + tenantId: undefined, + }; + const handler = middy(() => { + logger.info('Hello, world!'); + }).use(injectLambdaContext(logger)); + + // Act + await handler(event, contextWithoutTenantId); + + // Assess + expect(console.info).toHaveBeenCalledTimes(1); + expect(console.info).not.toHaveLoggedNth( + 1, + expect.objectContaining({ + tenant_id: expect.anything(), + }) + ); + }); + it('adds the context to the messages of each logger instance', async () => { // Prepare const logger1 = new Logger({ serviceName: 'parent' }); From 29ff154bd8d68a1da0c570c45ad5bc2fd1a38388 Mon Sep 17 00:00:00 2001 From: kawaaaas Date: Fri, 9 Jan 2026 10:08:59 +0900 Subject: [PATCH 3/3] feat(logger): add tenantId for context test --- packages/logger/tests/unit/logEvent.test.ts | 2 ++ packages/testing/src/TestInvocationLogs.ts | 6 +++--- .../testing/tests/unit/TestInvocationLogs.test.ts | 11 ++++++----- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/logger/tests/unit/logEvent.test.ts b/packages/logger/tests/unit/logEvent.test.ts index e0388ebeac..5e45ee491f 100644 --- a/packages/logger/tests/unit/logEvent.test.ts +++ b/packages/logger/tests/unit/logEvent.test.ts @@ -77,6 +77,7 @@ describe('Log event', () => { function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', + tenant_id: 'cff02b3a-0e12-4be2-b3e0-758b49c4cd9b' }) ); }); @@ -107,6 +108,7 @@ describe('Log event', () => { function_memory_size: '128', function_name: 'foo-bar-function', function_request_id: 'c6af9ac6-7b61-11e6-9a41-93e812345678', + tenant_id: 'cff02b3a-0e12-4be2-b3e0-758b49c4cd9b' }) ); }); diff --git a/packages/testing/src/TestInvocationLogs.ts b/packages/testing/src/TestInvocationLogs.ts index faba42b21d..0f864fc7f7 100644 --- a/packages/testing/src/TestInvocationLogs.ts +++ b/packages/testing/src/TestInvocationLogs.ts @@ -19,9 +19,9 @@ class TestInvocationLogs { * The first element is START, and the last two elements are END, and REPORT. * [ * 'START RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678 Version: $LATEST', - * '{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is an INFO log with some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"}', - * '{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is an INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"}', - * '{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"ERROR","message":"There was an error","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","error":{"name":"Error","location":"/var/task/index.js:2778","message":"you cannot prevent this","stack":"Error: you cannot prevent this\\n at testFunction (/var/task/index.js:2778:11)\\n at runRequest (/var/task/index.js:2314:36)"}}', + * '{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","tenant_id":"cff02b3a-0e12-4be2-b3e0-758b49c4cd9b","level":"INFO","message":"This is an INFO log with some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"}', + * '{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","tenant_id":"cff02b3a-0e12-4be2-b3e0-758b49c4cd9b","level":"INFO","message":"This is an INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"}', + * '{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","tenant_id":"cff02b3a-0e12-4be2-b3e0-758b49c4cd9b","level":"ERROR","message":"There was an error","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","error":{"name":"Error","location":"/var/task/index.js:2778","message":"you cannot prevent this","stack":"Error: you cannot prevent this\\n at testFunction (/var/task/index.js:2778:11)\\n at runRequest (/var/task/index.js:2314:36)"}}', * 'END RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678', * 'REPORT RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678\tDuration: 2.16 ms\tBilled Duration: 3 ms\tMemory Size: 128 MB\tMax Memory Used: 57 MB\t', * ] diff --git a/packages/testing/tests/unit/TestInvocationLogs.test.ts b/packages/testing/tests/unit/TestInvocationLogs.test.ts index de13cfae72..a08df997f0 100644 --- a/packages/testing/tests/unit/TestInvocationLogs.test.ts +++ b/packages/testing/tests/unit/TestInvocationLogs.test.ts @@ -2,10 +2,10 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { TestInvocationLogs } from '../../src/TestInvocationLogs.js'; const exampleLogs = `START RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678 Version: $LATEST -{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"DEBUG","message":"This is a DEBUG log but contains the word INFO some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"} -{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is an INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"} -{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"INFO","message":"This is a second INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"} -{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"ERROR","message":"There was an error","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","error":{"name":"Error","location":"/var/task/index.js:2778","message":"you cannot prevent this","stack":"Error: you cannot prevent this\\n at testFunction (/var/task/index.js:2778:11)\\n at runRequest (/var/task/index.js:2314:36)"}} +{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","tenant_id":"cff02b3a-0e12-4be2-b3e0-758b49c4cd9b","level":"DEBUG","message":"This is a DEBUG log but contains the word INFO some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"} +{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","tenant_id":"cff02b3a-0e12-4be2-b3e0-758b49c4cd9b","level":"INFO","message":"This is an INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"} +{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","tenant_id":"cff02b3a-0e12-4be2-b3e0-758b49c4cd9b","level":"INFO","message":"This is a second INFO log with some context","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","additionalKey":"additionalValue"} +{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","tenant_id":"cff02b3a-0e12-4be2-b3e0-758b49c4cd9b","level":"ERROR","message":"There was an error","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works","error":{"name":"Error","location":"/var/task/index.js:2778","message":"you cannot prevent this","stack":"Error: you cannot prevent this\\n at testFunction (/var/task/index.js:2778:11)\\n at runRequest (/var/task/index.js:2314:36)"}} END RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678 REPORT RequestId: c6af9ac6-7b61-11e6-9a41-93e812345678\tDuration: 2.16 ms\tBilled Duration: 3 ms\tMemory Size: 128 MB\tMax Memory Used: 57 MB\t`; @@ -121,7 +121,7 @@ describe('getFunctionLogs()', () => { describe('parseFunctionLog()', () => { it('returns an object with the correct values based on the given log', () => { const rawLogStr = - '{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","level":"DEBUG","message":"This is a DEBUG log but contains the word INFO some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"}'; + '{"cold_start":true,"function_arn":"arn:aws:lambda:eu-west-1:561912387782:function:loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_memory_size":128,"function_name":"loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c","function_request_id":"7f586697-238a-4c3b-9250-a5f057c1119c","tenant_id":"cff02b3a-0e12-4be2-b3e0-758b49c4cd9b","level":"DEBUG","message":"This is a DEBUG log but contains the word INFO some context and persistent key","service":"logger-e2e-testing","timestamp":"2022-01-27T16:04:39.323Z","persistentKey":"works"}'; const logObj = TestInvocationLogs.parseFunctionLog(rawLogStr); expect(logObj).toStrictEqual({ @@ -132,6 +132,7 @@ describe('parseFunctionLog()', () => { function_name: 'loggerMiddyStandardFeatures-c555a2ec-1121-4586-9c04-185ab36ea34c', function_request_id: '7f586697-238a-4c3b-9250-a5f057c1119c', + tenant_id: 'cff02b3a-0e12-4be2-b3e0-758b49c4cd9b', level: 'DEBUG', message: 'This is a DEBUG log but contains the word INFO some context and persistent key',