Skip to content

Commit 9677258

Browse files
authored
feat(logger): introduce log key reordering functionality (#2736)
1 parent ad323ae commit 9677258

File tree

9 files changed

+370
-33
lines changed

9 files changed

+370
-33
lines changed

Diff for: docs/core/logger.md

+19
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,25 @@ We prioritise log level settings in this order:
542542

543543
In the event you have set a log level in Powertools to a level that is lower than the ACL setting, we will output a warning log message informing you that your messages will be discarded by Lambda.
544544

545+
### Reordering log keys position
546+
547+
You can change the order of [standard Logger keys](#standard-structured-keys) or any keys that will be appended later at runtime via the `logRecordOrder` parameter.
548+
549+
!!! note
550+
This feature is available only in the default log formatter and not with custom log formatters.
551+
552+
=== "reorderLogKeys.ts"
553+
554+
```typescript hl_lines="5 10"
555+
--8<-- "examples/snippets/logger/reorderLogKeys.ts"
556+
```
557+
558+
=== "reorderLogKeysOutput.json"
559+
560+
```json hl_lines="2-3"
561+
--8<-- "examples/snippets/logger/reorderLogKeysOutput.json"
562+
```
563+
545564
### Setting timestamp to custom Timezone
546565

547566
By default, Logger emits records with the default Lambda timestamp in **UTC**, i.e. `2016-06-20T12:08:10.000Z`

Diff for: examples/snippets/logger/reorderLogKeys.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Logger } from '@aws-lambda-powertools/logger';
2+
3+
const logger = new Logger({
4+
serviceName: 'serverlessAirline',
5+
logRecordOrder: ['timestamp', 'additionalKey'],
6+
});
7+
8+
export const handler = async (): Promise<void> => {
9+
logger.info('Hello, World!', {
10+
additionalKey: 'additionalValue',
11+
});
12+
};

Diff for: examples/snippets/logger/reorderLogKeysOutput.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"timestamp": "2024-09-03T02:59:06.603Z",
3+
"additionalKey": "additionalValue",
4+
"level": "INFO",
5+
"message": "Hello, World!",
6+
"sampling_rate": 0,
7+
"service": "serverlessAirline",
8+
"xray_trace_id": "1-66d67b7a-79bc7b2346b32af01b437cf8"
9+
}

Diff for: packages/logger/src/Logger.ts

+14-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
LogFunction,
2424
LogItemExtraInput,
2525
LogItemMessage,
26+
LogRecordOrder,
2627
LoggerInterface,
2728
PowertoolsLogData,
2829
} from './types/Logger.js';
@@ -1077,15 +1078,22 @@ class Logger extends Utility implements LoggerInterface {
10771078

10781079
/**
10791080
* Set the log formatter instance, in charge of giving a custom format
1080-
* to the structured logs
1081+
* to the structured logs, and optionally the ordering for keys within logs.
10811082
*
10821083
* @private
1083-
* @param {LogFormatterInterface} logFormatter - The log formatt er
1084+
* @param {LogFormatterInterface} logFormatter - The log formatter
1085+
* @param {LogRecordOrder} logRecordOrder - Optional list of keys to specify order in logs
10841086
*/
1085-
private setLogFormatter(logFormatter?: LogFormatterInterface): void {
1087+
private setLogFormatter(
1088+
logFormatter?: LogFormatterInterface,
1089+
logRecordOrder?: LogRecordOrder
1090+
): void {
10861091
this.logFormatter =
10871092
logFormatter ??
1088-
new PowertoolsLogFormatter({ envVarsService: this.getEnvVarsService() });
1093+
new PowertoolsLogFormatter({
1094+
envVarsService: this.getEnvVarsService(),
1095+
logRecordOrder,
1096+
});
10891097
}
10901098

10911099
/**
@@ -1119,6 +1127,7 @@ class Logger extends Utility implements LoggerInterface {
11191127
persistentLogAttributes, // deprecated in favor of persistentKeys
11201128
environment,
11211129
jsonReplacerFn,
1130+
logRecordOrder,
11221131
} = options;
11231132

11241133
if (persistentLogAttributes && persistentKeys) {
@@ -1140,7 +1149,7 @@ class Logger extends Utility implements LoggerInterface {
11401149
this.setInitialSampleRate(sampleRateValue);
11411150

11421151
// configurations that affect how logs are printed
1143-
this.setLogFormatter(logFormatter);
1152+
this.setLogFormatter(logFormatter, logRecordOrder);
11441153
this.setConsole();
11451154
this.setLogIndentation();
11461155
this.#jsonReplacerFn = jsonReplacerFn;

Diff for: packages/logger/src/formatter/PowertoolsLogFormatter.ts

+56-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import type { LogAttributes, PowertoolsLog } from '../types/Log.js';
2-
import type { UnformattedAttributes } from '../types/Logger.js';
1+
import type {
2+
LogAttributes,
3+
PowerToolsLogFormatterOptions,
4+
PowertoolsLog,
5+
} from '../types/Log.js';
6+
import type { LogRecordOrder, UnformattedAttributes } from '../types/Logger.js';
37
import { LogFormatter } from './LogFormatter.js';
48
import { LogItem } from './LogItem.js';
59

@@ -11,6 +15,17 @@ import { LogItem } from './LogItem.js';
1115
* @extends {LogFormatter}
1216
*/
1317
class PowertoolsLogFormatter extends LogFormatter {
18+
/**
19+
* An array of keys that defines the order of the log record.
20+
*/
21+
#logRecordOrder?: LogRecordOrder;
22+
23+
public constructor(options?: PowerToolsLogFormatterOptions) {
24+
super(options);
25+
26+
this.#logRecordOrder = options?.logRecordOrder;
27+
}
28+
1429
/**
1530
* It formats key-value pairs of log attributes.
1631
*
@@ -34,8 +49,45 @@ class PowertoolsLogFormatter extends LogFormatter {
3449
timestamp: this.formatTimestamp(attributes.timestamp),
3550
xray_trace_id: attributes.xRayTraceId,
3651
};
37-
const powertoolsLogItem = new LogItem({ attributes: baseAttributes });
38-
powertoolsLogItem.addAttributes(additionalLogAttributes);
52+
53+
// If logRecordOrder is not set, return the log item with the attributes in the order they were added
54+
if (this.#logRecordOrder === undefined) {
55+
return new LogItem({ attributes: baseAttributes }).addAttributes(
56+
additionalLogAttributes
57+
);
58+
}
59+
60+
const orderedAttributes = {} as PowertoolsLog;
61+
62+
// If logRecordOrder is set, order the attributes in the log item
63+
for (const key of this.#logRecordOrder) {
64+
if (key in baseAttributes && !(key in orderedAttributes)) {
65+
orderedAttributes[key] = baseAttributes[key];
66+
} else if (
67+
key in additionalLogAttributes &&
68+
!(key in orderedAttributes)
69+
) {
70+
orderedAttributes[key] = additionalLogAttributes[key];
71+
}
72+
}
73+
74+
// Add remaining attributes from baseAttributes
75+
for (const key in baseAttributes) {
76+
if (!(key in orderedAttributes)) {
77+
orderedAttributes[key] = baseAttributes[key];
78+
}
79+
}
80+
81+
// Add remaining attributes from additionalLogAttributes
82+
for (const key in additionalLogAttributes) {
83+
if (!(key in orderedAttributes)) {
84+
orderedAttributes[key] = additionalLogAttributes[key];
85+
}
86+
}
87+
88+
const powertoolsLogItem = new LogItem({
89+
attributes: orderedAttributes,
90+
});
3991

4092
return powertoolsLogItem;
4193
}

Diff for: packages/logger/src/types/Log.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { EnvironmentVariablesService } from '../config/EnvironmentVariablesService.js';
22
import type { LogLevel as LogLevelList } from '../constants.js';
33
import type { LogItem } from '../formatter/LogItem.js';
4-
import type { UnformattedAttributes } from './Logger.js';
4+
import type { LogRecordOrder, UnformattedAttributes } from './Logger.js';
55

66
type LogLevel =
77
| (typeof LogLevelList)[keyof typeof LogLevelList]
@@ -127,6 +127,20 @@ type LogFormatterOptions = {
127127
envVarsService?: EnvironmentVariablesService;
128128
};
129129

130+
/**
131+
* Options for the `PowertoolsLogFormatter`.
132+
*
133+
* @type {Object} PowertoolsLogFormatterOptions
134+
* @extends {LogFormatterOptions}
135+
* @property {LogRecordOrder} [logRecordOrder] - Optional list of keys to specify order in logs
136+
*/
137+
type PowerToolsLogFormatterOptions = LogFormatterOptions & {
138+
/**
139+
* An array of keys that defines the order of the log record.
140+
*/
141+
logRecordOrder?: LogRecordOrder;
142+
};
143+
130144
/**
131145
* @interface
132146
*/
@@ -175,4 +189,5 @@ export type {
175189
LogItemInterface,
176190
LogFormatterOptions,
177191
LogFormatterInterface,
192+
PowerToolsLogFormatterOptions,
178193
};

Diff for: packages/logger/src/types/Logger.ts

+40-3
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,13 @@ type CustomJsonReplacerFn = (key: string, value: unknown) => unknown;
6565
* @property {LogLevel} [logLevel] - The log level.
6666
* @property {string} [serviceName] - The service name.
6767
* @property {number} [sampleRateValue] - The sample rate value.
68-
* @property {LogFormatterInterface} [logFormatter] - The custom log formatter.
6968
* @property {ConfigServiceInterface} [customConfigService] - The custom config service.
7069
* @property {Environment} [environment] - The environment.
7170
*/
7271
type BaseConstructorOptions = {
7372
logLevel?: LogLevel;
7473
serviceName?: string;
7574
sampleRateValue?: number;
76-
logFormatter?: LogFormatterInterface;
7775
customConfigService?: ConfigServiceInterface;
7876
environment?: Environment;
7977
/**
@@ -115,6 +113,40 @@ type DeprecatedOption = {
115113
persistentKeys?: never;
116114
};
117115

116+
/**
117+
* Options for the `logFormatter` constructor option.
118+
*
119+
* @type {Object} LogFormatterOption
120+
* @property {LogFormatterInterface} [logFormatter] - The custom log formatter.
121+
*/
122+
type LogFormatterOption = {
123+
/**
124+
* The custom log formatter.
125+
*/
126+
logFormatter?: LogFormatterInterface;
127+
/**
128+
* Optional list of keys to specify order in logs
129+
*/
130+
logRecordOrder?: never;
131+
};
132+
133+
/**
134+
* Options for the `logRecordOrder` constructor option.
135+
*
136+
* @type {Object} LogRecordOrderOption
137+
* @property {LogRecordOrder} [logRecordOrder] - The log record order.
138+
*/
139+
type LogRecordOrderOption = {
140+
/**
141+
* Optional list of keys to specify order in logs
142+
*/
143+
logRecordOrder?: LogRecordOrder;
144+
/**
145+
* The custom log formatter.
146+
*/
147+
logFormatter?: never;
148+
};
149+
118150
/**
119151
* Options for the Logger class constructor.
120152
*
@@ -126,9 +158,11 @@ type DeprecatedOption = {
126158
* @property {ConfigServiceInterface} [customConfigService] - The custom config service.
127159
* @property {Environment} [environment] - The environment.
128160
* @property {LogAttributes} [persistentKeys] - Keys that will be added in all log items.
161+
* @property {LogRecordOrder} [logRecordOrder] - The log record order.
129162
*/
130163
type ConstructorOptions = BaseConstructorOptions &
131-
(PersistentKeysOption | DeprecatedOption);
164+
(PersistentKeysOption | DeprecatedOption) &
165+
(LogFormatterOption | LogRecordOrderOption);
132166

133167
type LambdaFunctionContext = Pick<
134168
Context,
@@ -157,6 +191,8 @@ type UnformattedAttributes = PowertoolsLogData & {
157191
message: string;
158192
};
159193

194+
type LogRecordOrder = Array<keyof UnformattedAttributes | keyof LogAttributes>;
195+
160196
type LogItemMessage = string | LogAttributesWithMessage;
161197
type LogItemExtraInput = [Error | string] | LogAttributes[];
162198

@@ -197,4 +233,5 @@ export type {
197233
ConstructorOptions,
198234
InjectLambdaContextOptions,
199235
CustomJsonReplacerFn,
236+
LogRecordOrder,
200237
};

0 commit comments

Comments
 (0)