Closed
Description
Use case
In my use case there is a compliance that enforces the developer to put some basic attributes in the root level of a log json. All the other additional data like a error or other data to identify the reason or context of that log should be placed under a attribute "data".
Since the formatAttributes
function of a Formatter only gets the unformattedBaseAttributes
it is not possible to sort or format the other attributes.
Solution/User Experience
I would like to pass any attribute to the logs and enforce a nice ordering of the log in the formatter:
export class MyLogFormatter extends LogFormatter {
public formatAttributes(attributes: UnformattedAttributes): LogAttributes {
const {
environment,
serviceName,
sampleRateValue,
lambdaContext,
xRayTraceId,
awsRegion,
logLevel,
timestamp,
message,
...data
} = attributes;
return {
id: randomUUID(),
timestamp: timestamp.getTime(),
logLevel: logLevel,
serviceName: serviceName,
message: message,
xrayTraceId: xRayTraceId,
requestId: lambdaContext?.awsRequestId,
data,
};
}
}
export const logger = new Logger({
logFormatter: new MyLogFormatter(),
});
logger.error('This is my message', { error: new Error('something bad happened'), contextInfo: 1234 })
which should then result in a object like this:
{
"id": "...",
"timestamp": "...",
"logLevel": "error",
"serviceName": "...",
"message": "This is my message",
"xrayTraceId": "...",
"requestId": "...",
"data": {
"error": { "name":"...", "message": "..."},
"contextInfo": 1234
}
Alternative solutions
My current workaround is to extend the Logger Class and do the sorting in the debug,info,warn and error method:
interface LogAttributesWithMessageAndData extends LogAttributesWithMessage {
data?: LogAttributes;
}
export class MyLogger extends Logger {
public override debug(input: LogItemMessage, ...extraInput: LogItemExtraInput): void {
super.debug(this.convertLogAttributes(input, extraInput));
}
public override info(input: LogItemMessage, ...extraInput: LogItemExtraInput): void {
super.info(this.convertLogAttributes(input, extraInput));
}
public override warn(input: LogItemMessage, ...extraInput: LogItemExtraInput): void {
super.warn(this.convertLogAttributes(input, extraInput));
}
public override error(input: LogItemMessage, ...extraInput: LogItemExtraInput): void {
super.error(this.convertLogAttributes(input, extraInput));
}
private convertLogAttributes(input: LogItemMessage, extraInput: LogItemExtraInput): LogAttributesWithMessage {
const attributes: LogAttributesWithMessageAndData = {
message: '',
data: {},
};
if (typeof input === 'string') {
attributes.message = input;
} else {
const { message, ...data } = input;
attributes.message = message;
attributes.data = data;
}
extraInput.forEach((item) => {
const dataAttributes: LogAttributes =
item instanceof Error ? { error: item } : typeof item === 'string' ? { extra: item } : item;
merge(attributes.data, dataAttributes);
});
if (attributes.data && !Object.keys(attributes.data).length) {
delete attributes.data;
}
return attributes;
}
}
export const logger = new MyLogger({
logFormatter: new MyLogFormatter(),
});
logger.error('This is my message', { error: new Error('something bad happened'), contextInfo: 1234 })
Acknowledgment
- This feature request meets Lambda Powertools Tenets
- Should this be considered in other Lambda Powertools languages? i.e. Python, Java
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
Shipped