From 5113fa3d52f00bb6bd935aaa028a892f1be24740 Mon Sep 17 00:00:00 2001 From: Jay McDoniel Date: Tue, 17 Oct 2023 13:22:47 -0700 Subject: [PATCH] feat: create a mixin property for open telemtry and metadata support --- .changeset/selfish-garlics-lick.md | 5 ++ .../docs/src/components/Header/OgmaLogo.astro | 2 +- .../components/RightSidebar/MoreMenu.astro | 8 +++ apps/docs/src/config.ts | 12 ++--- apps/docs/src/pages/en/logger.md | 5 ++ packages/logger/src/index.ts | 2 +- .../logger/src/interfaces/ogma-options.ts | 11 +++- packages/logger/src/logger/ogma.ts | 25 +++++++--- packages/logger/test/ogma.spec.ts | 50 +++++++++++++++++++ 9 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 .changeset/selfish-garlics-lick.md diff --git a/.changeset/selfish-garlics-lick.md b/.changeset/selfish-garlics-lick.md new file mode 100644 index 000000000..fc5a65209 --- /dev/null +++ b/.changeset/selfish-garlics-lick.md @@ -0,0 +1,5 @@ +--- +'@ogma/logger': minor +--- + +Create a new `mixin` option for open telemetry and dynamic metadata support diff --git a/apps/docs/src/components/Header/OgmaLogo.astro b/apps/docs/src/components/Header/OgmaLogo.astro index c6ca38a05..268a97676 100644 --- a/apps/docs/src/components/Header/OgmaLogo.astro +++ b/apps/docs/src/components/Header/OgmaLogo.astro @@ -5,4 +5,4 @@ type Props = { const { size } = Astro.props as Props; --- -Ogma Logo +Ogma Logo diff --git a/apps/docs/src/components/RightSidebar/MoreMenu.astro b/apps/docs/src/components/RightSidebar/MoreMenu.astro index d7eb2269c..77d690c79 100644 --- a/apps/docs/src/components/RightSidebar/MoreMenu.astro +++ b/apps/docs/src/components/RightSidebar/MoreMenu.astro @@ -67,6 +67,14 @@ const showMoreSection = CONFIG.COMMUNITY_INVITE_URL; )} +
diff --git a/apps/docs/src/config.ts b/apps/docs/src/config.ts index 7bf71d4ed..443cd4e94 100644 --- a/apps/docs/src/config.ts +++ b/apps/docs/src/config.ts @@ -6,10 +6,8 @@ export const SITE = { export const OPEN_GRAPH = { image: { - src: 'https://github.com/withastro/astro/blob/main/assets/social/banner-minimal.png?raw=true', - alt: - 'astro logo on a starry expanse of space,' + - ' with a purple saturn-like planet floating in the right foreground', + src: 'https://ogma.jaymcdoniel.dev/logo.svg', + alt: 'ogma logo, a blue book with a purple "O" on the cover', }, twitter: 'jmcdo29', }; @@ -30,11 +28,13 @@ export const KNOWN_LANGUAGES = { } as const; export const KNOWN_LANGUAGE_CODES = Object.values(KNOWN_LANGUAGES); -export const GITHUB_EDIT_URL = `https://github.com/jmcdo29/ogma/tree/main/apps/docs`; +export const GITHUB_REPO_URL = 'https://github.com/jmcdo29/ogma'; + +export const GITHUB_EDIT_URL = `${GITHUB_REPO_URL}/tree/main/apps/docs`; export const COMMUNITY_INVITE_URL = `https://discord.gg/7cJqcFncAX`; -export const GITHUB_DISCUSSIONS_URL = `https://github.com/jmcdo29/ogma/discussions`; +export const GITHUB_DISCUSSIONS_URL = `${GITHUB_REPO_URL}/discussions`; // See "Algolia" section of the README for more information. export const ALGOLIA = { diff --git a/apps/docs/src/pages/en/logger.md b/apps/docs/src/pages/en/logger.md index 5a1cf2e58..28a057942 100644 --- a/apps/docs/src/pages/en/logger.md +++ b/apps/docs/src/pages/en/logger.md @@ -93,6 +93,7 @@ This option is available in `@ogma/logger@^2.5.0` | logApplication | boolean | An optional property you can set if you don't want to the the application name. | | logHostname | boolean | An optional property you can set if you don't want to log the hostname of the machine you're on. | | each | boolean | An optional property that determines if array values should be printed on separate lines by default or not | +| mixin | function | A utility to add dynamic data to each log. Takes in the log level and the ogma instance and returns an object | :::note @@ -132,6 +133,10 @@ As of version 2, it is suggested to use the separate [`@ogma/styler`](/en/styler Using the non-JSON mode, color is attempted to be applied by default. This is determined by checking the current environment (if there is a global `process` variable) and if there is, what `stdout.getColorDepth()` returns. If a custom stream is passed instead, a `getColorDepth` method can be added to the stream object which should return a 1, 4, 8, or 24. If no `getColorDepth()` is present, but the `color` option is true, Ogma will set the method to return `4` for you. If you want to disable colors completely, you can either set `color` to be `false` or you can set the `NO_COLOR` environment variable. +### Using a Mixin + +There may be times, like with [OpenTelemetry](https://opentelemetry.io) or with adding a correlation id, that you need to add dynamic values to each log. Rather than making a new wrapper around the Ogma instance, Ogma provides an optional `mixin` property that can be used to add extra data to each log without being in `verbose` mode. This property is a function, that takes in the current log level and the Ogma instance, and should return an object of any shape. + ## Example of what the logs look like I said the logs were beautiful, and to me they absolutely are. Each log is matched with a timestamp in ISO format, the log level, a pipe character, and then the message, for easier searching if needed. If a JSON is passed to Ogma, a new line will separate the JSON and the original log line, but the timestamp and level will not be duplicated. Ogma also will print `'[Function]'` if a function is found or `'[Circular]'` is a circular reference is found. diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index aebb745ed..80948516a 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -1,2 +1,2 @@ -export { OgmaOptions, OgmaPrintOptions } from './interfaces'; +export { OgmaDefaults, OgmaOptions, OgmaPrintOptions } from './interfaces'; export * from './logger'; diff --git a/packages/logger/src/interfaces/ogma-options.ts b/packages/logger/src/interfaces/ogma-options.ts index 2b924d3d7..a734f3f82 100644 --- a/packages/logger/src/interfaces/ogma-options.ts +++ b/packages/logger/src/interfaces/ogma-options.ts @@ -1,5 +1,6 @@ import { LogLevel, OgmaStream, OgmaWritableLevel } from '@ogma/common'; +import type { Ogma } from '../logger/ogma'; import { initializeStreamOnNodeJs } from '../utils/sonic-boom'; export interface OgmaOptions { @@ -105,10 +106,18 @@ export interface OgmaOptions { */ masks?: string[]; /** - * Log each member of an array out on a new line. This is a global option tthat can be overridden per call as + * Log each member of an array out on a new line. This is a global option that can be overridden per call as * desired. */ each: boolean; + /** + * A utility method to add dynamic metadata to each log. + * This method will be called __per log__ and will change regular strings + * to JSON objects in the non-json mode. This method is mostly to be used + * to accommodate OpenTelemetry, but feel free to use it for other use cases + * and ask for enhancements as necessary. + */ + mixin?: (logLevel: LogLevel, ogma: Ogma) => Record; } const stream = process diff --git a/packages/logger/src/logger/ogma.ts b/packages/logger/src/logger/ogma.ts index baae3c7a9..1de6e9f5f 100644 --- a/packages/logger/src/logger/ogma.ts +++ b/packages/logger/src/logger/ogma.ts @@ -117,22 +117,33 @@ export class Ogma { } private printMessage( - message: any, + message: unknown, logLevel: LogLevel, formattedLevel: string, options?: OgmaPrintOptions, ): void { - const ogmaPrintOptions = options || {}; - + const ogmaPrintOptions = options ?? {}; + let mixin: Record; if (logLevel < LogLevel[this.options.logLevel]) { return; } + if (this.options.mixin) { + mixin = this.options.mixin(logLevel, this); + } + let logString = ''; if (this.options.json) { - logString = this.formatJSON(message, logLevel, ogmaPrintOptions); + logString = this.formatJSON(message, logLevel, { ...ogmaPrintOptions, ...(mixin ?? {}) }); } else { + if (mixin) { + if (typeof message === 'object') { + message = { ...message, ...mixin }; + } else { + message = { message, ...mixin }; + } + } logString = this.formatStream(message, formattedLevel, ogmaPrintOptions); } @@ -143,7 +154,7 @@ export class Ogma { application: _application, correlationId: _correlationId, ...meta - } = options; + } = ogmaPrintOptions; const verboseLogString = this.formatStream(meta, formattedLevel, ogmaPrintOptions); this.options.stream.write(`${verboseLogString}\n`); @@ -202,7 +213,7 @@ export class Ogma { } private formatJSON( - message: any, + message: unknown, level: LogLevel, { application = this.application, @@ -296,7 +307,7 @@ export class Ogma { } private formatStream( - message: any, + message: unknown, formattedLevel: string, { application = '', correlationId = '', context = '', each = this.each }: OgmaPrintOptions, ): string { diff --git a/packages/logger/test/ogma.spec.ts b/packages/logger/test/ogma.spec.ts index 27d1a7f81..81bea5e25 100644 --- a/packages/logger/test/ogma.spec.ts +++ b/packages/logger/test/ogma.spec.ts @@ -468,5 +468,55 @@ OgmaSuite('It should not explosively print array values', ({ writeSpy, ogmaFacto ogma.log(messages, { each: true }); is(writeSpy.calls.size, 1, 'There should only be one calls'); }); +OgmaSuite( + 'The mixin should be called if for every log if it exists', + ({ writeSpy, ogmaFactory }) => { + let val = 0; + const ogma = ogmaFactory({ + mixin: () => ({ hello: `mixin ${++val}` }), + logLevel: 'DEBUG', + }); + ogma.log('hello'); + const expectCallToPass = (callNum: number): void => { + const expectedAddition = `"hello": "mixin ${callNum + 1}"`; + const callString = writeSpy.getCall(callNum).args[0].toString(); + match( + callString, + expectedAddition, + `Expected "${callString}" to include string ${expectedAddition}`, + ); + }; + expectCallToPass(0); + ogma.debug({ with: 'an object' }); + expectCallToPass(1); + ogma.warn({ message: 'using the message key' }); + expectCallToPass(2); + writeSpy.calls.forEach((c) => console.log(c.args.join(' '))); + }, +); +OgmaSuite('The mixin should be called if for every log JSON-mode', ({ writeSpy, ogmaFactory }) => { + let val = 0; + const ogma = ogmaFactory({ + mixin: () => ({ hello: `mixin ${++val}` }), + logLevel: 'DEBUG', + json: true, + }); + ogma.log('hello'); + const expectCallToPass = (callNum: number): void => { + const expectedAddition = `"hello":"mixin ${callNum + 1}"`; + const callString = writeSpy.getCall(callNum).args[0].toString(); + match( + callString, + expectedAddition, + `Expected "${callString}" to include string ${expectedAddition}`, + ); + }; + expectCallToPass(0); + ogma.debug({ with: 'an object' }); + expectCallToPass(1); + ogma.warn({ message: 'using the message key' }); + expectCallToPass(2); + writeSpy.calls.forEach((c) => console.log(c.args.join(' '))); +}); OgmaSuite.run();