Skip to content

Commit

Permalink
feat: add onMetadataCalled plugin callback
Browse files Browse the repository at this point in the history
  • Loading branch information
theogravity committed May 8, 2024
1 parent a6fb176 commit 74756da
Show file tree
Hide file tree
Showing 8 changed files with 429 additions and 56 deletions.
7 changes: 7 additions & 0 deletions .changeset/polite-lions-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"loglayer": minor
---

Add `onMetadataCalled()` plugin callback to hook into `withMetadata()` and `metadataOnly()` calls.

See the README section on `intercept metadata calls` for usage details.
77 changes: 69 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ logLayer

- [Installation](#installation)
- [Example installations](#example-installations)
- [`console`](#console)
- [`pino`](#pino)
- [`bunyan`](#bunyan)
- [`consola`](#consola)
- [`console`](#console)
- [`winston`](#winston)
- [`roarr`](#roarr)
- [`electron-log`](#electron-log)
- [`log4js-node`](#log4js-node)
- [`pino`](#pino)
- [`roarr`](#roarr)
- [`signale`](#signale)
- [`winston`](#winston)
- [`consola`](#consola)
- [Example integration](#example-integration)
- [API](#api)
- [Constructor](#constructor)
Expand Down Expand Up @@ -97,6 +97,7 @@ logLayer
- [Callbacks](#callbacks)
- [Modify / create object data before being sent to the logging library](#modify--create-object-data-before-being-sent-to-the-logging-library)
- [Conditionally send or not send an entry to the logging library](#conditionally-send-or-not-send-an-entry-to-the-logging-library)
- [Intercept metadata calls](#intercept-metadata-calls)
- [Mocking for tests](#mocking-for-tests)
- [Running tests](#running-tests)

Expand Down Expand Up @@ -850,6 +851,7 @@ export interface LogLayerPlugin {
disabled?: boolean;
onBeforeDataOut?(params: PluginBeforeDataOutParams): Record<string, any> | null | undefined;
shouldSendToLogger?(params: PluginShouldSendToLoggerParams): boolean;
onMetadataCalled?(metadata: Record<string, any>): Record<string, any> | null | undefined;
}
```

Expand Down Expand Up @@ -903,9 +905,7 @@ export interface PluginBeforeDataOutParams {

`onBeforeDataOut(params: PluginBeforeDataOutParams) => Record<string, any> | null | undefined`

The callback `onBeforeDataOut` can be used to modify the data object
that contains the context / metadata / error data or create a custom object
before it is sent out to the logging library.
The callback `onBeforeDataOut` can be used to modify the data object that contains the context / metadata / error data or create a custom object before it is sent out to the logging library.

Return `null` or `undefined` to not modify the data object.

Expand Down Expand Up @@ -1007,6 +1007,67 @@ const log = new LogLayer({
log.info('do not send out')
```

##### Intercept metadata calls

`onMetadataCalled(metadata: Record<string, any>) => Record<string, any> | null | undefined`

The callback `onMetadataCalled` is called when `withMetadata()` or `metadataOnly()` is called with the input being a shallow clone of the metadata from `withMetadata()` / `metadataOnly()`.

One use-case would be for situations where you may want to redact sensitive information from the metadata before it is sent to the logging library and defining the `onBeforeDataOut` plugin callback is too much of a hassle.

- Return the (un)modified metadata object to be sent to the logging library.
- Return `null` or `undefined` to prevent the metadata from being sent to the logging library.
- In multiple plugins, the metadata object will be updated with the results of the previous plugin if a result was returned from it.
* If in the sequence, one of the `onMetadataCalled` callbacks returns `null` or `undefined`, the metadata object will be omitted from the log entry.

```typescript
import {
LoggerType,
LogLayer,
PluginOnMetadataCalledFn,
} from 'loglayer'

const onMetadataCalled: PluginOnMetadataCalledFn = (metadata: Record<string, any>) => {
// Modify the metadata object
metadata.modified = true

return metadata
}

const log = new LogLayer({
...
plugins: [{
onMetadataCalled,
}]
})

// Metadata will now include the modified field in the output
log.withMetadata({ some: 'data' }).info('modified metadata')
```

```typescript
import {
LoggerType,
LogLayer,
PluginOnMetadataCalledFn,
} from 'loglayer'

const onMetadataCalled: PluginOnMetadataCalledFn = (metadata: Record<string, any>) => {
// Return null to prevent the metadata from being sent to the logging library
return null
}

const log = new LogLayer({
...
plugins: [{
onMetadataCalled,
}]
})

// Metadata will be completely omitted from the log print
log.withMetadata({ some: 'data' }).info('no metadata included')
```

## Mocking for tests

Rather than having to define your own mocks for `loglayer`, we have a mock class you can use for your tests:
Expand Down
34 changes: 32 additions & 2 deletions src/LogBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import type { LogLayer } from "./LogLayer";
import type { ErrorDataType, ILogBuilder, LoggerLibrary, MessageDataType } from "./types";
import type { PluginManager } from "./plugins/PluginManager";
import {
type ErrorDataType,
type ILogBuilder,
type LoggerLibrary,
type MessageDataType,
PluginCallbackType,
} from "./types";
import { LogLevel } from "./types";

/**
Expand All @@ -13,21 +20,44 @@ export class LogBuilder<ExternalLogger extends LoggerLibrary = LoggerLibrary, Er
private metadata: Record<string, any>;
private structuredLogger: LogLayer<ExternalLogger, ErrorType>;
private hasMetadata: boolean;
private pluginManager: PluginManager;

constructor(structuredLogger: LogLayer<ExternalLogger, ErrorType>) {
this.err = null;
this.metadata = {};
this.structuredLogger = structuredLogger;
this.hasMetadata = false;
this.pluginManager = structuredLogger["pluginManager"];
}

/**
* Adds metadata to the current log entry
*/
withMetadata(metadata: Record<string, any>) {
const {
pluginManager,
structuredLogger: {
_config: { consoleDebug },
},
} = this;

let data: Record<string, any> | null = metadata;

if (pluginManager.hasPlugins(PluginCallbackType.onMetadataCalled)) {
data = pluginManager.runOnMetadataCalled(metadata);

if (!data) {
if (consoleDebug) {
console.debug("[LogLayer] Metadata was dropped due to plugin returning falsy value.");
}

return this;
}
}

this.metadata = {
...this.metadata,
...metadata,
...data,
};

this.hasMetadata = true;
Expand Down
20 changes: 18 additions & 2 deletions src/LogLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,29 @@ export class LogLayer<ExternalLogger extends LoggerLibrary = LoggerLibrary, Erro
* Logs only metadata without a log message
*/
metadataOnly(metadata: Record<string, any>, logLevel: LogLevel = LogLevel.info) {
if (this._config.muteMetadata) {
const { muteMetadata, consoleDebug } = this._config;

if (muteMetadata) {
return;
}

let data: Record<string, any> | null = metadata;

if (this.pluginManager.hasPlugins(PluginCallbackType.onMetadataCalled)) {
data = this.pluginManager.runOnMetadataCalled(metadata);

if (!data) {
if (consoleDebug) {
console.debug("[LogLayer] Metadata was dropped due to plugin returning falsy value.");
}

return;
}
}

const config: FormatLogParams = {
logLevel,
data: metadata,
data,
};

if (this.loggerType === LoggerType.ROARR) {
Expand Down
Loading

0 comments on commit 74756da

Please sign in to comment.