Skip to content

Commit

Permalink
Enabled colorization (#42)
Browse files Browse the repository at this point in the history
* enabled custom colors per level

* added option to accept colors when setting up the transport

* fixed linting issues

* enabled colorization by default

* fixed linting

* fixed broken test from merge

* added TTY color availability

* updated pino-pretty dependency

* fixed tests for TTY without support for color

* added separate tests for both support and unsuported colors

* fixed linting

* change default handling

* removed duplicate test

* small test fix

* removed logs

* updated option and docs

* fixed linting issues

---------

Signed-off-by: Cristian Barlutiu <[email protected]>
  • Loading branch information
synapse authored Jun 12, 2024
1 parent ae73368 commit bf37842
Show file tree
Hide file tree
Showing 7 changed files with 314 additions and 138 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ const server = fastify({
});
```

## Colors

Colors are enabled by default when supported. To manually disable the colors you need to set the `transport.colorize` option to `false`. For more options check the `colorette` [docs](https://github.com/jorgebucaran/colorette?tab=readme-ov-file#environment).

```js
const server = fastify({
logger: {
transport: {
target: "@fastify/one-line-logger",
colorize: false,
},
},
});
```


## Custom levels

Custom levels could be used by passing it into logger opts
Expand Down
8 changes: 6 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ const pretty = require('pino-pretty')
const messageFormatFactory = require('./lib/messageFormatFactory')

const oneLineLogger = (opts = {}) => {
const { colorize, levels, colors, ...rest } = opts
const { levels, colors, ...rest } = opts

const messageFormat = messageFormatFactory(colorize, levels, colors)
const messageFormat = messageFormatFactory(
levels,
colors,
opts.colorize ?? pretty.isColorSupported
)

return pretty({
messageFormat,
Expand Down
25 changes: 15 additions & 10 deletions lib/messageFormatFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
const formatDate = require('./formatDate')
const colorizerFactory = require('pino-pretty').colorizerFactory

const messageFormatFactory = (colorize, levels, colors) => {
const customColors = colors != null
? Object.entries(colors).reduce((colors, [level, color]) => {
return [...colors, [level, color]]
}, [])
: undefined
const colorizer = colorizerFactory(colorize === true, customColors)
const messageFormatFactory = (levels, colors, useColors) => {
const customColors =
colors != null
? Object.entries(colors).reduce((colors, [level, color]) => {
return [...colors, [level, color]]
}, [])
: undefined
const colorizer = colorizerFactory(useColors, customColors)

const levelLookUp = {
60: colorizer('fatal').toLowerCase(),
Expand All @@ -24,18 +25,22 @@ const messageFormatFactory = (colorize, levels, colors) => {
Object.entries(levels).forEach(([name, level]) => {
const customLevels = { [level]: name }
const customLevelNames = { [name]: level }
levelLookUp[level] = colorizer(name, { customLevelNames, customLevels }).toLowerCase()
levelLookUp[level] = colorizer(name, {
customLevelNames,
customLevels
}).toLowerCase()
})
}

const colorizeMessage = colorizer.message

const messageFormat = (log, messageKey) => {
const time = formatDate(log.time)
const level = levelLookUp[log.level]

return log.req
? `${time} - ${level} - ${log.req.method} ${log.req.url} - ${colorizeMessage(log[messageKey])}`
? `${time} - ${level} - ${log.req.method} ${
log.req.url
} - ${colorizeMessage(log[messageKey])}`
: `${time} - ${level} - ${colorizeMessage(log[messageKey])}`
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"test": "test"
},
"dependencies": {
"pino-pretty": "^11.0.0"
"pino-pretty": "^11.1.0"
},
"devDependencies": {
"benchmark": "^2.1.4",
Expand Down
158 changes: 110 additions & 48 deletions test/fastify.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

const { serverFactory, TIME, unmockTime, mockTime } = require('./helpers')
const pretty = require('pino-pretty')
const tap = require('tap')

const { test } = tap
Expand All @@ -24,69 +25,130 @@ tap.beforeEach(() => {
})

test('should log server started messages', async (t) => {
await server.listen({ port: 63995 })
t.beforeEach(async () => {
await server.listen({ port: 63995 })
t.teardown(async () => await server.close())
})

t.test('colors supported in TTY', { skip: !pretty.isColorSupported }, (t) => {
const messagesExpected = [
`${TIME} - \x1B[32minfo\x1B[39m - \x1B[36mServer listening at http://127.0.0.1:63995\x1B[39m\n`,
`${TIME} - \x1B[32minfo\x1B[39m - \x1B[36mServer listening at http://[::1]:63995\x1B[39m\n`
]

const messagesExpected = [
`${TIME} - info - Server listening at http://127.0.0.1:63995\n`,
`${TIME} - info - Server listening at http://[::1]:63995\n`
]
// sort because the order of the messages is not guaranteed
t.same(messages.sort(), messagesExpected.sort())
t.end()
})

// sort because the order of the messages is not guaranteed
t.same(messages.sort(), messagesExpected.sort())
await server.close()
t.end()
t.test(
'colors not supported in TTY',
{ skip: pretty.isColorSupported },
(t) => {
const messagesExpected = [
`${TIME} - info - Server listening at http://127.0.0.1:63995\n`,
`${TIME} - info - Server listening at http://[::1]:63995\n`
]

// sort because the order of the messages is not guaranteed
t.same(messages.sort(), messagesExpected.sort())
t.end()
}
)
})

const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD']
methods.forEach((method) => {
test('should log request and response messages for %p', async (t) => {
const serverMethod = method === 'HEAD' ? 'GET' : method
server[serverMethod.toLowerCase()]('/path', (_, req) => {
req.send()
})

await server.inject({
method,
url: '/path'
t.beforeEach(async () => {
const serverMethod = method === 'HEAD' ? 'GET' : method
server[serverMethod.toLowerCase()]('/path', (_, req) => {
req.send()
})

await server.inject({
method,
url: '/path'
})
})

const messagesExpected = [
`${TIME} - info - ${method} /path - incoming request\n`,
`${TIME} - info - request completed\n`
]

t.same(messages, messagesExpected)
t.end()
t.test(
'colors supported in TTY',
{ skip: !pretty.isColorSupported },
(t) => {
const messagesExpected = [
`${TIME} - \x1B[32minfo\x1B[39m - ${method} /path - \x1B[36mincoming request\x1B[39m\n`,
`${TIME} - \x1B[32minfo\x1B[39m - \x1B[36mrequest completed\x1B[39m\n`
]
t.same(messages, messagesExpected)
t.end()
}
)

t.test(
'colors not supported in TTY',
{ skip: pretty.isColorSupported },
(t) => {
const messagesExpected = [
`${TIME} - info - ${method} /path - incoming request\n`,
`${TIME} - info - request completed\n`
]
t.same(messages, messagesExpected)
t.end()
}
)
})
})

test('should handle user defined log', async (t) => {
server = serverFactory(messages, { minimumLevel: 'trace' })
t.beforeEach(async () => {
server = serverFactory(messages, { minimumLevel: 'trace' })

server.get('/a-path-with-user-defined-log', (res, req) => {
res.log.fatal('a user defined fatal log')
res.log.error('a user defined error log')
res.log.warn('a user defined warn log')
res.log.info('a user defined info log')
res.log.debug('a user defined debug log')
res.log.trace('a user defined trace log')

server.get('/a-path-with-user-defined-log', (res, req) => {
res.log.fatal('a user defined fatal log')
res.log.error('a user defined error log')
res.log.warn('a user defined warn log')
res.log.info('a user defined info log')
res.log.debug('a user defined debug log')
res.log.trace('a user defined trace log')
req.send()
})

req.send()
await server.inject('/a-path-with-user-defined-log')
})

t.test('colors supported in TTY', { skip: !pretty.isColorSupported }, (t) => {
const messagesExpected = [
`${TIME} - \x1B[32minfo\x1B[39m - GET /a-path-with-user-defined-log - \x1B[36mincoming request\x1B[39m\n`,
`${TIME} - \x1B[41mfatal\x1B[49m - \x1B[36ma user defined fatal log\x1B[39m\n`,
`${TIME} - \x1B[31merror\x1B[39m - \x1B[36ma user defined error log\x1B[39m\n`,
`${TIME} - \x1B[33mwarn\x1B[39m - \x1B[36ma user defined warn log\x1B[39m\n`,
`${TIME} - \x1B[32minfo\x1B[39m - \x1B[36ma user defined info log\x1B[39m\n`,
`${TIME} - \x1B[34mdebug\x1B[39m - \x1B[36ma user defined debug log\x1B[39m\n`,
`${TIME} - \x1B[90mtrace\x1B[39m - \x1B[36ma user defined trace log\x1B[39m\n`,
`${TIME} - \x1B[32minfo\x1B[39m - \x1B[36mrequest completed\x1B[39m\n`
]
t.same(messages, messagesExpected)
t.end()
})

await server.inject('/a-path-with-user-defined-log')

const messagesExpected = [
`${TIME} - info - GET /a-path-with-user-defined-log - incoming request\n`,
`${TIME} - fatal - a user defined fatal log\n`,
`${TIME} - error - a user defined error log\n`,
`${TIME} - warn - a user defined warn log\n`,
`${TIME} - info - a user defined info log\n`,
`${TIME} - debug - a user defined debug log\n`,
`${TIME} - trace - a user defined trace log\n`,
`${TIME} - info - request completed\n`
]

t.same(messages, messagesExpected)
t.end()
t.test(
'colors not supported in TTY',
{ skip: pretty.isColorSupported },
(t) => {
const messagesExpected = [
`${TIME} - info - GET /a-path-with-user-defined-log - incoming request\n`,
`${TIME} - fatal - a user defined fatal log\n`,
`${TIME} - error - a user defined error log\n`,
`${TIME} - warn - a user defined warn log\n`,
`${TIME} - info - a user defined info log\n`,
`${TIME} - debug - a user defined debug log\n`,
`${TIME} - trace - a user defined trace log\n`,
`${TIME} - info - request completed\n`
]
t.same(messages, messagesExpected)
t.end()
}
)
})
5 changes: 2 additions & 3 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const pinoFactory = (opts) => {
return pino(
{ level },
target({
colorize: false,
...opts
})
)
Expand Down Expand Up @@ -57,9 +56,9 @@ const mockTime = () => {
const unmockTime = () => {
Date.now = dateOriginalNow
// eslint-disable-next-line
Date.prototype.getTimezoneOffset = dateGetTimezoneOffset
Date.prototype.getTimezoneOffset = dateGetTimezoneOffset;
// eslint-disable-next-line
Date.prototype.getHours = dateOriginalGetHours
Date.prototype.getHours = dateOriginalGetHours;
}

module.exports = {
Expand Down
Loading

0 comments on commit bf37842

Please sign in to comment.