Skip to content

Commit

Permalink
feat(logger): introduce log key reordering functionality (#2736)
Browse files Browse the repository at this point in the history
  • Loading branch information
arnabrahman authored Sep 9, 2024
1 parent ad323ae commit 9677258
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 33 deletions.
19 changes: 19 additions & 0 deletions docs/core/logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,25 @@ We prioritise log level settings in this order:

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.

### Reordering log keys position

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.

!!! note
This feature is available only in the default log formatter and not with custom log formatters.

=== "reorderLogKeys.ts"

```typescript hl_lines="5 10"
--8<-- "examples/snippets/logger/reorderLogKeys.ts"
```

=== "reorderLogKeysOutput.json"

```json hl_lines="2-3"
--8<-- "examples/snippets/logger/reorderLogKeysOutput.json"
```

### Setting timestamp to custom Timezone

By default, Logger emits records with the default Lambda timestamp in **UTC**, i.e. `2016-06-20T12:08:10.000Z`
Expand Down
12 changes: 12 additions & 0 deletions examples/snippets/logger/reorderLogKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Logger } from '@aws-lambda-powertools/logger';

const logger = new Logger({
serviceName: 'serverlessAirline',
logRecordOrder: ['timestamp', 'additionalKey'],
});

export const handler = async (): Promise<void> => {
logger.info('Hello, World!', {
additionalKey: 'additionalValue',
});
};
9 changes: 9 additions & 0 deletions examples/snippets/logger/reorderLogKeysOutput.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"timestamp": "2024-09-03T02:59:06.603Z",
"additionalKey": "additionalValue",
"level": "INFO",
"message": "Hello, World!",
"sampling_rate": 0,
"service": "serverlessAirline",
"xray_trace_id": "1-66d67b7a-79bc7b2346b32af01b437cf8"
}
19 changes: 14 additions & 5 deletions packages/logger/src/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type {
LogFunction,
LogItemExtraInput,
LogItemMessage,
LogRecordOrder,
LoggerInterface,
PowertoolsLogData,
} from './types/Logger.js';
Expand Down Expand Up @@ -1077,15 +1078,22 @@ class Logger extends Utility implements LoggerInterface {

/**
* Set the log formatter instance, in charge of giving a custom format
* to the structured logs
* to the structured logs, and optionally the ordering for keys within logs.
*
* @private
* @param {LogFormatterInterface} logFormatter - The log formatt er
* @param {LogFormatterInterface} logFormatter - The log formatter
* @param {LogRecordOrder} logRecordOrder - Optional list of keys to specify order in logs
*/
private setLogFormatter(logFormatter?: LogFormatterInterface): void {
private setLogFormatter(
logFormatter?: LogFormatterInterface,
logRecordOrder?: LogRecordOrder
): void {
this.logFormatter =
logFormatter ??
new PowertoolsLogFormatter({ envVarsService: this.getEnvVarsService() });
new PowertoolsLogFormatter({
envVarsService: this.getEnvVarsService(),
logRecordOrder,
});
}

/**
Expand Down Expand Up @@ -1119,6 +1127,7 @@ class Logger extends Utility implements LoggerInterface {
persistentLogAttributes, // deprecated in favor of persistentKeys
environment,
jsonReplacerFn,
logRecordOrder,
} = options;

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

// configurations that affect how logs are printed
this.setLogFormatter(logFormatter);
this.setLogFormatter(logFormatter, logRecordOrder);
this.setConsole();
this.setLogIndentation();
this.#jsonReplacerFn = jsonReplacerFn;
Expand Down
60 changes: 56 additions & 4 deletions packages/logger/src/formatter/PowertoolsLogFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { LogAttributes, PowertoolsLog } from '../types/Log.js';
import type { UnformattedAttributes } from '../types/Logger.js';
import type {
LogAttributes,
PowerToolsLogFormatterOptions,
PowertoolsLog,
} from '../types/Log.js';
import type { LogRecordOrder, UnformattedAttributes } from '../types/Logger.js';
import { LogFormatter } from './LogFormatter.js';
import { LogItem } from './LogItem.js';

Expand All @@ -11,6 +15,17 @@ import { LogItem } from './LogItem.js';
* @extends {LogFormatter}
*/
class PowertoolsLogFormatter extends LogFormatter {
/**
* An array of keys that defines the order of the log record.
*/
#logRecordOrder?: LogRecordOrder;

public constructor(options?: PowerToolsLogFormatterOptions) {
super(options);

this.#logRecordOrder = options?.logRecordOrder;
}

/**
* It formats key-value pairs of log attributes.
*
Expand All @@ -34,8 +49,45 @@ class PowertoolsLogFormatter extends LogFormatter {
timestamp: this.formatTimestamp(attributes.timestamp),
xray_trace_id: attributes.xRayTraceId,
};
const powertoolsLogItem = new LogItem({ attributes: baseAttributes });
powertoolsLogItem.addAttributes(additionalLogAttributes);

// If logRecordOrder is not set, return the log item with the attributes in the order they were added
if (this.#logRecordOrder === undefined) {
return new LogItem({ attributes: baseAttributes }).addAttributes(
additionalLogAttributes
);
}

const orderedAttributes = {} as PowertoolsLog;

// If logRecordOrder is set, order the attributes in the log item
for (const key of this.#logRecordOrder) {
if (key in baseAttributes && !(key in orderedAttributes)) {
orderedAttributes[key] = baseAttributes[key];
} else if (
key in additionalLogAttributes &&
!(key in orderedAttributes)
) {
orderedAttributes[key] = additionalLogAttributes[key];
}
}

// Add remaining attributes from baseAttributes
for (const key in baseAttributes) {
if (!(key in orderedAttributes)) {
orderedAttributes[key] = baseAttributes[key];
}
}

// Add remaining attributes from additionalLogAttributes
for (const key in additionalLogAttributes) {
if (!(key in orderedAttributes)) {
orderedAttributes[key] = additionalLogAttributes[key];
}
}

const powertoolsLogItem = new LogItem({
attributes: orderedAttributes,
});

return powertoolsLogItem;
}
Expand Down
17 changes: 16 additions & 1 deletion packages/logger/src/types/Log.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { EnvironmentVariablesService } from '../config/EnvironmentVariablesService.js';
import type { LogLevel as LogLevelList } from '../constants.js';
import type { LogItem } from '../formatter/LogItem.js';
import type { UnformattedAttributes } from './Logger.js';
import type { LogRecordOrder, UnformattedAttributes } from './Logger.js';

type LogLevel =
| (typeof LogLevelList)[keyof typeof LogLevelList]
Expand Down Expand Up @@ -127,6 +127,20 @@ type LogFormatterOptions = {
envVarsService?: EnvironmentVariablesService;
};

/**
* Options for the `PowertoolsLogFormatter`.
*
* @type {Object} PowertoolsLogFormatterOptions
* @extends {LogFormatterOptions}
* @property {LogRecordOrder} [logRecordOrder] - Optional list of keys to specify order in logs
*/
type PowerToolsLogFormatterOptions = LogFormatterOptions & {
/**
* An array of keys that defines the order of the log record.
*/
logRecordOrder?: LogRecordOrder;
};

/**
* @interface
*/
Expand Down Expand Up @@ -175,4 +189,5 @@ export type {
LogItemInterface,
LogFormatterOptions,
LogFormatterInterface,
PowerToolsLogFormatterOptions,
};
43 changes: 40 additions & 3 deletions packages/logger/src/types/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,13 @@ type CustomJsonReplacerFn = (key: string, value: unknown) => unknown;
* @property {LogLevel} [logLevel] - The log level.
* @property {string} [serviceName] - The service name.
* @property {number} [sampleRateValue] - The sample rate value.
* @property {LogFormatterInterface} [logFormatter] - The custom log formatter.
* @property {ConfigServiceInterface} [customConfigService] - The custom config service.
* @property {Environment} [environment] - The environment.
*/
type BaseConstructorOptions = {
logLevel?: LogLevel;
serviceName?: string;
sampleRateValue?: number;
logFormatter?: LogFormatterInterface;
customConfigService?: ConfigServiceInterface;
environment?: Environment;
/**
Expand Down Expand Up @@ -115,6 +113,40 @@ type DeprecatedOption = {
persistentKeys?: never;
};

/**
* Options for the `logFormatter` constructor option.
*
* @type {Object} LogFormatterOption
* @property {LogFormatterInterface} [logFormatter] - The custom log formatter.
*/
type LogFormatterOption = {
/**
* The custom log formatter.
*/
logFormatter?: LogFormatterInterface;
/**
* Optional list of keys to specify order in logs
*/
logRecordOrder?: never;
};

/**
* Options for the `logRecordOrder` constructor option.
*
* @type {Object} LogRecordOrderOption
* @property {LogRecordOrder} [logRecordOrder] - The log record order.
*/
type LogRecordOrderOption = {
/**
* Optional list of keys to specify order in logs
*/
logRecordOrder?: LogRecordOrder;
/**
* The custom log formatter.
*/
logFormatter?: never;
};

/**
* Options for the Logger class constructor.
*
Expand All @@ -126,9 +158,11 @@ type DeprecatedOption = {
* @property {ConfigServiceInterface} [customConfigService] - The custom config service.
* @property {Environment} [environment] - The environment.
* @property {LogAttributes} [persistentKeys] - Keys that will be added in all log items.
* @property {LogRecordOrder} [logRecordOrder] - The log record order.
*/
type ConstructorOptions = BaseConstructorOptions &
(PersistentKeysOption | DeprecatedOption);
(PersistentKeysOption | DeprecatedOption) &
(LogFormatterOption | LogRecordOrderOption);

type LambdaFunctionContext = Pick<
Context,
Expand Down Expand Up @@ -157,6 +191,8 @@ type UnformattedAttributes = PowertoolsLogData & {
message: string;
};

type LogRecordOrder = Array<keyof UnformattedAttributes | keyof LogAttributes>;

type LogItemMessage = string | LogAttributesWithMessage;
type LogItemExtraInput = [Error | string] | LogAttributes[];

Expand Down Expand Up @@ -197,4 +233,5 @@ export type {
ConstructorOptions,
InjectLambdaContextOptions,
CustomJsonReplacerFn,
LogRecordOrder,
};
Loading

0 comments on commit 9677258

Please sign in to comment.