-
-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: separate built-in formatters into separate files
- Loading branch information
Showing
7 changed files
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import 'expect-more-jest'; | ||
import stringify from 'fast-safe-stringify'; | ||
import LogMessage from '../LogMessage'; | ||
import fullFormatter from './full'; | ||
|
||
const defaultOpts = { | ||
meta: {}, | ||
dynamicMeta: null, | ||
tags: [], | ||
levelKey: '_logLevel', | ||
messageKey: 'msg', | ||
tagsKey: '_tags', | ||
replacer: null | ||
}; | ||
|
||
describe('formatters/full', () => { | ||
it('should export a closure function', () => { | ||
expect(typeof fullFormatter).toBe('function'); | ||
}); | ||
|
||
it('should return a formatter function', () => { | ||
expect(typeof fullFormatter()).toBe('function'); | ||
}); | ||
|
||
it('should overrride configuration', () => { | ||
const formatter = fullFormatter({ | ||
includeTimestamp: false, | ||
includeTags: false, | ||
includeMeta: false, | ||
separator: '\t', | ||
inspectOptions: { | ||
colors: false, | ||
maxArrayLength: 25 | ||
} | ||
}); | ||
|
||
const cfg = formatter._cfg!; | ||
|
||
expect(cfg.includeTimestamp).toBe(false); | ||
expect(cfg.includeTags).toBe(false); | ||
expect(cfg.includeMeta).toBe(false); | ||
expect(cfg.separator).toBe('\t'); | ||
expect(cfg.inspectOptions).toEqual({ | ||
depth: Infinity, | ||
colors: false, | ||
maxArrayLength: 25 | ||
}); | ||
}); | ||
|
||
it('should skip the timestamp if includeTimestamp is false', () => { | ||
const msg = new LogMessage({ | ||
level: 'info', | ||
msg: 'info test', | ||
meta: {}, | ||
tags: [] | ||
}, defaultOpts); | ||
|
||
const formatter = fullFormatter({ | ||
includeTimestamp: false | ||
}); | ||
|
||
expect(formatter(msg, defaultOpts, stringify)).toMatch(/^INFO\tinfo test$/); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { FormatPlugin } from '../typings.js'; | ||
import { inspect, InspectOptions } from 'util'; | ||
|
||
type FullFormatterCfg = { | ||
includeTimestamp?: boolean; | ||
formatTimestamp?: (timestamp: Date) => string; | ||
includeTags?: boolean; | ||
includeMeta?: boolean; | ||
separator?: string; | ||
inspectOptions?: InspectOptions; | ||
}; | ||
|
||
/** | ||
* Full formatter for log messages. | ||
* @param {object} cfg Configuration object for the formatter. | ||
* @returns {FormatPlugin} The full formatter function. | ||
*/ | ||
export default function fullFormatter(cfg: FullFormatterCfg = {}): FormatPlugin { | ||
const fmCfg = { | ||
includeTimestamp: true, | ||
formatTimestamp: (timestamp: Date) => timestamp.toISOString(), | ||
includeTags: true, | ||
includeMeta: true, | ||
separator: '\t', | ||
...cfg | ||
}; | ||
|
||
fmCfg.inspectOptions = { | ||
depth: Infinity, | ||
colors: true, | ||
...(fmCfg.inspectOptions ?? {}) | ||
}; | ||
|
||
const fullFmt: FormatPlugin = (ctx): string => { | ||
const msg = []; | ||
if(fmCfg.includeTimestamp) { | ||
msg.push(fmCfg.formatTimestamp(new Date())); | ||
} | ||
|
||
msg.push(ctx.level.toUpperCase(), ctx.msg); | ||
|
||
const parts = [ | ||
msg.join(fmCfg.separator) | ||
]; | ||
|
||
if(fmCfg.includeTags && ctx.tags.length) { | ||
const tags = ctx.tags.map(tag => `${tag}`).join(', '); | ||
parts.push(`→ ${tags}`); | ||
} | ||
|
||
if(fmCfg.includeMeta && Object.keys(ctx.meta).length) { | ||
const meta = inspect(ctx.meta, fmCfg.inspectOptions!); | ||
parts.push(`→ ${meta.replace(/\n/g, '\n ')}`); | ||
} | ||
|
||
return parts.join('\n'); | ||
}; | ||
|
||
fullFmt._cfg = fmCfg; | ||
|
||
return fullFmt; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { default as json } from './json.js'; | ||
export { default as full } from './full.js'; | ||
export { default as minimal } from './minimal.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import 'expect-more-jest'; | ||
import stringify from 'fast-safe-stringify'; | ||
import LogMessage from '../LogMessage'; | ||
import jsonFormatter from './json'; | ||
|
||
const defaultOpts = { | ||
meta: {}, | ||
dynamicMeta: null, | ||
tags: [], | ||
levelKey: '_logLevel', | ||
messageKey: 'msg', | ||
tagsKey: '_tags', | ||
replacer: null | ||
}; | ||
|
||
const logObject = { | ||
level: 'info', | ||
msg: 'info test', | ||
meta: {}, | ||
tags: [] | ||
}; | ||
|
||
|
||
describe('formatters/json', () => { | ||
it('should export a closure function', () => { | ||
expect(typeof jsonFormatter).toBe('function'); | ||
}); | ||
|
||
it('should return a formatter function', () => { | ||
expect(typeof jsonFormatter()).toBe('function'); | ||
}); | ||
|
||
const replacerOpts = { | ||
...defaultOpts, | ||
meta: { ssn: '444-55-6666' }, | ||
replacer(key: string, value: unknown) { | ||
if(key === 'ssn') { | ||
return `${(value as string).substring(0, 3)}-**-****`; | ||
} | ||
|
||
return value; | ||
} | ||
}; | ||
|
||
const msg = new LogMessage({ ...logObject }, replacerOpts); | ||
|
||
const formatter = jsonFormatter(); | ||
const result = formatter(msg, replacerOpts, stringify); | ||
|
||
it('should return log in JSON format', () => { | ||
expect(result).toBeJsonString(); | ||
}); | ||
|
||
it('should run replacer function', () => { | ||
expect(JSON.parse(result).ssn).toBe('444-**-****'); | ||
}); | ||
|
||
it('should not run replacer function when not defined', () => { | ||
const msgNoReplacer = new LogMessage({ ...logObject }, { | ||
...defaultOpts, | ||
meta: { ssn: '444-55-6666' } | ||
}); | ||
|
||
const noReplacerResult = formatter(msgNoReplacer, defaultOpts, stringify); | ||
|
||
expect(JSON.parse(noReplacerResult).ssn).toBe('444-55-6666'); | ||
}); | ||
|
||
it('should pretty print JSON when dev is "true"', () => { | ||
const opts = { | ||
...defaultOpts, | ||
dev: true, | ||
meta: { ssn: '444-55-6666' } | ||
}; | ||
|
||
const msgDev = new LogMessage({ ...logObject }, opts); | ||
const prettyResult = formatter(msgDev, opts, stringify); | ||
|
||
expect(/\n/g.test(prettyResult)).toBe(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { FormatPlugin } from '../typings.js'; | ||
|
||
/** | ||
* JSON formatter for log messages. | ||
* @returns {FormatPlugin} The JSON formatter function. | ||
*/ | ||
export default function jsonFormatter(): FormatPlugin { | ||
const jsonFmt: FormatPlugin = (ctx, options, stringify): string => | ||
stringify(ctx.value, options.replacer ?? undefined, options.dev ? 2 : 0); | ||
|
||
return jsonFmt; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import 'expect-more-jest'; | ||
import stringify from 'fast-safe-stringify'; | ||
import LogMessage from '../LogMessage'; | ||
import minimalFormatter from './minimal'; | ||
|
||
const defaultOpts = { | ||
meta: {}, | ||
dynamicMeta: null, | ||
tags: [], | ||
levelKey: '_logLevel', | ||
messageKey: 'msg', | ||
tagsKey: '_tags', | ||
replacer: null | ||
}; | ||
|
||
describe('formatters/minmal', () => { | ||
it('should export a closure function', () => { | ||
expect(typeof minimalFormatter).toBe('function'); | ||
}); | ||
|
||
it('should return a formatter function', () => { | ||
expect(typeof minimalFormatter()).toBe('function'); | ||
}); | ||
|
||
it('should overrride configuration', () => { | ||
const formatter = minimalFormatter({ | ||
includeTimestamp: false, | ||
separator: '\t' | ||
}); | ||
|
||
const cfg = formatter._cfg!; | ||
|
||
expect(cfg.includeTimestamp).toBe(false); | ||
expect(cfg.separator).toBe('\t'); | ||
}); | ||
|
||
it('should format timestamp as ISO string by default', () => { | ||
const formatter = minimalFormatter(); | ||
|
||
// @ts-expect-error - we're testing the internals here | ||
expect((formatter._cfg!).formatTimestamp(new Date())).toMatch(/^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)$/); | ||
}); | ||
|
||
it('should include the timestamp if includeTimestamp is true', () => { | ||
const msg = new LogMessage({ | ||
level: 'info', | ||
msg: 'info test', | ||
meta: {}, | ||
tags: [] | ||
}, defaultOpts); | ||
|
||
const formatter = minimalFormatter({ | ||
includeTimestamp: true | ||
}); | ||
|
||
expect(formatter(msg, defaultOpts, stringify)).toMatch(/^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z) | INFO | info test$/); | ||
}); | ||
|
||
it('should skip the timestamp if includeTimestamp is false', () => { | ||
const msg = new LogMessage({ | ||
level: 'info', | ||
msg: 'info test', | ||
meta: {}, | ||
tags: [] | ||
}, defaultOpts); | ||
|
||
const formatter = minimalFormatter({ | ||
includeTimestamp: false | ||
}); | ||
|
||
expect(formatter(msg, defaultOpts, stringify)).toMatch(/^INFO | info test$/); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { FormatPlugin } from '../typings.js'; | ||
|
||
type MinimalFormatterCfg = { | ||
includeTimestamp?: boolean; | ||
formatTimestamp?: (timestamp: Date) => string; | ||
separator?: string; | ||
}; | ||
|
||
/** | ||
* Minimal formatter for log messages. | ||
* @param {object} cfg Configuration object for the formatter. | ||
* @returns {FormatPlugin} The minimal formatter function. | ||
*/ | ||
export default function minimalFormatter(cfg: MinimalFormatterCfg = {}): FormatPlugin { | ||
const fmCfg = { | ||
includeTimestamp: false, | ||
formatTimestamp: (timestamp: Date) => timestamp.toISOString(), | ||
separator: ' | ', | ||
...cfg | ||
}; | ||
|
||
const minimalFmt: FormatPlugin = (ctx): string => { | ||
const parts = []; | ||
if(fmCfg.includeTimestamp) { | ||
parts.push(fmCfg.formatTimestamp(new Date())); | ||
} | ||
|
||
parts.push(ctx.level.toUpperCase(), ctx.msg); | ||
|
||
return parts.join(fmCfg.separator); | ||
}; | ||
|
||
minimalFmt._cfg = fmCfg; | ||
|
||
return minimalFmt; | ||
} |