Skip to content

docs(logger): add notice about mutating attributes in log formatter #3604

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions docs/core/logger.md
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ If you prefer to log in a specific timezone, you can configure it by setting the

### Using multiple Logger instances across your code

The `createChild` method allows you to create a child instance of the Logger, which inherits all of the attributes from its parent. You have the option to override any of the settings and attributes from the parent logger, including [its settings](#utility-settings), any [extra keys](#appending-additional-keys), and [the log formatter](#custom-log-formatter-bring-your-own-formatter).
The `createChild` method allows you to create a child instance of the Logger, which inherits all of the attributes from its parent. You have the option to override any of the settings and attributes from the parent logger, including [its settings](#utility-settings), any [extra keys](#appending-additional-keys), and [the log formatter](#custom-log-formatter).

Once a child logger is created, the logger and its parent will act as separate instances of the Logger class, and as such any change to one won't be applied to the other.

Expand Down Expand Up @@ -754,26 +754,24 @@ Sampling decision happens at the Logger initialization. This means sampling may
}
```

### Custom Log formatter (Bring Your Own Formatter)
### Custom Log formatter

You can customize the structure (keys and values) of your log items by passing a custom log formatter, an object that implements the `LogFormatter` abstract class.
You can customize the structure (keys and values) of your logs by passing a custom log formatter, a class that implements the `LogFormatter` interface, to the `Logger` constructor.

When working with custom log formatters, you take full control over the structure of your logs. This allows you to optionally drop or transform keys, add new ones, or change the format to suit your company's logging standards or use Logger with a third-party logging service.

=== "handler.ts"

```typescript hl_lines="2 6"
--8<-- "examples/snippets/logger/bringYourOwnFormatterHandler.ts"
```

This is how the `MyCompanyLogFormatter` (dummy name) would look like:

=== "utils/formatters/MyCompanyLogFormatter.ts"

```typescript
--8<-- "examples/snippets/logger/bringYourOwnFormatterClass.ts"
```

This is how the printed log would look:

=== "Example CloudWatch Logs excerpt"

```json
Expand Down Expand Up @@ -804,8 +802,7 @@ This is how the printed log would look:
}
```

!!! tip "Custom Log formatter and Child loggers"
It is not necessary to pass the `LogFormatter` each time a [child logger](#using-multiple-logger-instances-across-your-code) is created. The parent's LogFormatter will be inherited by the child logger.
Note that when implementing this method, you should avoid mutating the `attributes` and `additionalLogAttributes` objects directly. Instead, create a new object with the desired structure and return it. If mutation is necessary, you can create a [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) of the object to avoid side effects.

### Bring your own JSON serializer

Expand Down
18 changes: 14 additions & 4 deletions packages/logger/src/formatter/LogFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ abstract class LogFormatter {
/**
* Format key-value pairs of log attributes.
*
* You should implement this method in a subclass to define the structure of the log item.
* You should implement this method in a subclass to define the structure of the log item
* and instantiate a new {@link LogItem} object with the formatted attributes.
*
* Note that when implementing this method, you should avoid mutating the `attributes` and
* `additionalLogAttributes` objects directly. Instead, create a new object with the desired
* structure and return it.
*
* If mutation is necessary, you can create a `structuredClone` of the object to avoid side effects.
*
* @example
* ```typescript
Expand All @@ -40,7 +47,7 @@ abstract class LogFormatter {
* attributes: UnformattedAttributes,
* additionalLogAttributes: LogAttributes
* ): LogItem {
* const baseAttributes: MyCompanyLog = {
* const baseAttributes = {
* message: attributes.message,
* service: attributes.serviceName,
* environment: attributes.environment,
Expand Down Expand Up @@ -116,8 +123,11 @@ abstract class LogFormatter {
: error.cause,
};
for (const key in error) {
if (typeof key === 'string' && !['name', 'message', 'stack', 'cause'].includes(key)) {
formattedError[key] = (errorAttributes as Record<string, unknown>)[key];
if (
typeof key === 'string' &&
!['name', 'message', 'stack', 'cause'].includes(key)
) {
formattedError[key] = (errorAttributes as Record<string, unknown>)[key];
}
}

Expand Down
Loading