Skip to content

Commit 65136a1

Browse files
authored
Merge pull request #123 from AthennaIO/develop
feat(logger): add way to define default values to formatters
2 parents 35f2766 + b6ebe62 commit 65136a1

8 files changed

Lines changed: 133 additions & 10 deletions

File tree

.github/workflows/cd.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ on:
88
jobs:
99
build:
1010
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
id-token: write
1114
steps:
1215
- name: Checkout
1316
uses: actions/checkout@v2
1417
with:
1518
fetch-depth: 0
1619
- uses: actions/setup-node@v1
1720
with:
18-
node-version: '20.x'
21+
node-version: '21.x'
1922
registry-url: 'https://registry.npmjs.org'
2023

2124
- name: Install dependencies
@@ -32,8 +35,7 @@ jobs:
3235
id: release
3336

3437
- name: Publish to NPM Registry
35-
run: cd build && npm publish --access public
38+
run: cd build && npm publish --provenance --access public
3639
if: steps.release.outputs.released == 'true'
3740
env:
38-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
3941
name: Deploy

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@athenna/logger",
3-
"version": "5.13.0",
3+
"version": "5.14.0",
44
"description": "The Athenna logging solution. Log in stdout, files and buckets.",
55
"license": "MIT",
66
"author": "João Lenon <lenon@athenna.io>",

src/formatters/JsonFormatter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Formatter } from '#src/formatters/Formatter'
1313
export class JsonFormatter extends Formatter {
1414
public format(message: any): string {
1515
const base: any = {
16+
...(this.configs.defaults || {}),
1617
level: this.level(),
1718
time: Date.now(),
1819
pid: this.pid(),

src/logger/Logger.ts

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import { Config } from '@athenna/config'
1111
import { Driver } from '#src/drivers/Driver'
12-
import { Color, Macroable } from '@athenna/common'
12+
import { Color, Json, Macroable } from '@athenna/common'
1313
import { DriverFactory } from '#src/factories/DriverFactory'
1414
import { VANILLA_CHANNELS } from '#src/constants/VanillaChannels'
1515

@@ -24,6 +24,21 @@ export class Logger extends Macroable {
2424
*/
2525
private runtimeConfigs = {}
2626

27+
/**
28+
* Default message content that should be included
29+
* in every structured log emitted by this instance.
30+
*/
31+
private messageDefaults = {}
32+
33+
/**
34+
* Store the current logger strategy so create()
35+
* can clone the logger preserving its behavior.
36+
*/
37+
private selection: { type: 'channel' | 'vanilla'; values: any[] } = {
38+
type: 'vanilla',
39+
values: []
40+
}
41+
2742
public constructor() {
2843
super()
2944
this.channelOrVanilla(Config.get('logging.default'))
@@ -52,14 +67,36 @@ export class Logger extends Macroable {
5267
return this
5368
}
5469

70+
/**
71+
* Create a new logger instance inheriting the current
72+
* configuration and adding default message content.
73+
*/
74+
public create(defaults: any = {}): Logger {
75+
const logger = new Logger()
76+
77+
logger.runtimeConfigs = Json.copy(this.runtimeConfigs)
78+
logger.messageDefaults = {
79+
...Json.copy(this.messageDefaults),
80+
...Json.copy(defaults || {})
81+
}
82+
83+
return logger[this.selection.type](...Json.copy(this.selection.values))
84+
}
85+
5586
/**
5687
* Change the log channel.
5788
*/
5889
public channel(...channels: string[]): Logger {
5990
this.drivers = []
91+
this.selection = {
92+
type: 'channel',
93+
values: [...channels]
94+
}
95+
96+
const runtimeConfigs = this.withDefaultFormatterConfig(this.runtimeConfigs)
6097

6198
channels.forEach(channel => {
62-
this.drivers.push(DriverFactory.fabricate(channel, this.runtimeConfigs))
99+
this.drivers.push(DriverFactory.fabricate(channel, runtimeConfigs))
63100
})
64101

65102
return this
@@ -72,15 +109,23 @@ export class Logger extends Macroable {
72109
*/
73110
public vanilla(...configs: any[]): Logger {
74111
this.drivers = []
112+
this.selection = {
113+
type: 'vanilla',
114+
values: Json.copy(configs)
115+
}
75116

76117
if (!configs.length) {
77-
this.drivers.push(DriverFactory.fabricateVanilla())
118+
this.drivers.push(
119+
DriverFactory.fabricateVanilla(this.withDefaultFormatterConfig())
120+
)
78121

79122
return this
80123
}
81124

82125
configs.forEach(config => {
83-
this.drivers.push(DriverFactory.fabricateVanilla(config))
126+
this.drivers.push(
127+
DriverFactory.fabricateVanilla(this.withDefaultFormatterConfig(config))
128+
)
84129
})
85130

86131
return this
@@ -171,4 +216,25 @@ export class Logger extends Macroable {
171216

172217
return Promise.all(promises)
173218
}
219+
220+
/**
221+
* Attach message defaults to formatter configs so
222+
* structured formatters can enrich the output.
223+
*/
224+
private withDefaultFormatterConfig(configs: any = {}): any {
225+
if (!Object.keys(this.messageDefaults).length) {
226+
return configs
227+
}
228+
229+
return {
230+
...configs,
231+
formatterConfig: {
232+
...(configs.formatterConfig || {}),
233+
defaults: {
234+
...((configs.formatterConfig || {}).defaults || {}),
235+
...this.messageDefaults
236+
}
237+
}
238+
}
239+
}
174240
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @athenna/logger
3+
*
4+
* (c) João Lenon <lenon@athenna.io>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { Config } from '@athenna/config'
11+
import { Log, LoggerProvider } from '#src'
12+
import { Folder, Path } from '@athenna/common'
13+
14+
await new Folder(Path.fixtures('config')).copy(Path.config())
15+
await Config.safeLoad(Path.config('logging.ts'))
16+
17+
new LoggerProvider().register()
18+
19+
const logger = Log.create({ namespace: 'UserService' }).config({ formatter: 'json' }).channel('application')
20+
21+
logger.error('failed to retrieve user')
22+
23+
await Folder.safeRemove(Path.config())
24+
await Folder.safeRemove(Path.storage())

tests/unit/formatters/JsonFormatterTest.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,20 @@ export default class JsonFormatterTest {
3838
assert.isTrue(Is.Uuid(message.traceId))
3939
})
4040
}
41+
42+
@Test()
43+
public async shouldBeAbleToFormatLogsToJsonFormatWithDefaults({ assert }: Context) {
44+
const formatter = new JsonFormatter().config({
45+
level: 'info',
46+
defaults: {
47+
namespace: 'UserService'
48+
}
49+
})
50+
51+
const message = JSON.parse(formatter.format('hello'))
52+
53+
assert.equal(message.msg, 'hello')
54+
assert.equal(message.namespace, 'UserService')
55+
assert.equal(message.level, 'info')
56+
}
4157
}

tests/unit/logger/LoggerTest.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,18 @@ export default class LoggerTest {
2424
assert.isTrue(log.includes('hello'))
2525
})
2626
}
27+
28+
@Test()
29+
public async shouldBeAbleToCreateLoggerWithDefaultMessageContent({ assert }: Context) {
30+
const { stdout, stderr } = await Exec.node(Path.fixtures('transporters/loggerCreate.ts'))
31+
32+
const logs = [...stdout.split('\n').filter(l => l !== ''), ...stderr.split('\n').filter(l => l !== '')].filter(
33+
v => !v.startsWith('(')
34+
)
35+
const message = JSON.parse(logs[0])
36+
37+
assert.equal(message.msg, 'failed to retrieve user')
38+
assert.equal(message.level, 'error')
39+
assert.equal(message.namespace, 'UserService')
40+
}
2741
}

0 commit comments

Comments
 (0)