Skip to content

Feature request: allow custom formatter to formatAttributes on all attributes (not only the base attributes) #1261

Closed
@ghdoergeloh

Description

@ghdoergeloh

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

Metadata

Metadata

Assignees

Labels

confirmedThe scope is clear, ready for implementationfeature-requestThis item refers to a feature request for an existing or new utilityloggerThis item relates to the Logger Utility

Type

No type

Projects

Status

Shipped

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions