pino
pino copied to clipboard
Provide solution for creating custom loggers easily
Judging by https://github.com/fastify/fastify/blob/main/test/types/logger.test-d.ts, the following used to be OK in pino 6:
class CustomLoggerImpl implements CustomLogger {
customMethod (msg: string, ...args: unknown[]) { console.log(msg, args) }
// Implementation signature must be compatible with all overloads of FastifyLogFn
info (arg1: unknown, arg2?: unknown, ...args: unknown[]): void {
console.log(arg1, arg2, ...args)
}
warn (...args: unknown[]) { console.log(args) }
error (...args: unknown[]) { console.log(args) }
fatal (...args: unknown[]) { console.log(args) }
trace (...args: unknown[]) { console.log(args) }
debug (...args: unknown[]) { console.log(args) }
child () { return new CustomLoggerImpl() }
}
const customLogger = new CustomLoggerImpl()
However, our Logger interface relies on having such things as
level: LevelWithSilent | string;
/**
* Returns the integer value for the logger instance's logging level.
*/
levelVal: number;
which rely on internal getters. We probably don't want our users to implement all that from a scratch, so I wonder if we should expose some kind of a class they could extend for their custom loggers, or at least a prototype they could inherit from.
Alternatively we can officially embrace minimal subset of logger fields for custom loggers, as originally expected by fastify and split within Pino in https://github.com/pinojs/pino/pull/1099
pino relies on prototypical inheritance for implementing the child loggers, so we can't provide a prototype to extend.
Maybe an interface?
Interface we already have, but it cannot provide any logic by itself, only types. And what I'm trying to say is that only having interface forces users to implement way too much internal stuff on their own. If BaseLogger as defined in #1099 is ok, though, this might be a non-issue, as that provides a more sensible surface for users to cover.
Maybe add a test for this use case?
Wrapping a Pino instance is really not that difficult. There shouldn't be any need to mutate the prototype.
I ditched prettyprint and wrote this minimal transformer to output colorised terminal output, with a short timestamp. It could be extended easiy with the options object.
logger.js
import pino from 'pino'
const log = pino({
transport: {
pipeline: [
{
// logger output for terminal, colorised by level for easy reading
// https://gist.github.com/replete/3220dd280c60279cee5396c6f65b02dd
target: './logger.colorisedTerminalTransform.mjs',
options: {},
},
],
},
})
export default log
logger.colorisedTerminalTransform.mjs (source in this gist)
import { Writable } from 'stream'
export default (options) => {
const levels = {
// https://github.com/pinojs/pino/blob/master/docs/api.md#logger-level
10: `\x1b[2m[>]\x1b[0m`, //trace
20: `\x1b[35m[DEBUG]\x1b[0m`, //debug
30: `\x1b[36m`, //info
40: `\x1b[33m[?]`, //warn
50: `\x1b[31m[!]`, //error
60: `\x1b[41m[!!!]`, //fatal
Infinity: ``,
}
const colorizedLoggerTransportStream = new Writable({
write(chunk, enc, cb) {
const logEvents = chunk.toString().match(/[^\r\n]+/g) // split into lines
logEvents.forEach((logEvent) => {
const { level, time, msg } = JSON.parse(logEvent)
const timestamp = new Date(time).toISOString().slice(11).substring(8, 0)
console.log(`\x1b[2m${timestamp}\x1b[0m ${levels[level] || ''} ${msg}\x1b[0m`)
})
cb()
},
})
return colorizedLoggerTransportStream
}
Don't use console.log, use pino.destination() instead (otherwise you loose all the benefit of worker_threads).
Don't use console.log, use pino.destination() instead (otherwise you loose all the benefit of worker_threads).
The documentation gave an example using console.log
The 'transport' implementation I wrote above sometimes misses errors, which has been pretty frustrating given I only wrote this transformer because I got a 'pino-pretty is deprecated' type warnings.
What I want to do seems like a typical use-case to me, could you please recommend how I approach this?
This is what I want to do:
- In 'development environment:
- Write transformed (human-readable, colors, short timestamp, json expansion etc) output to stdout
- In 'production' environment:
- write raw json to stdout
- write raw json to a logfile
- maybe pipe raw json to some third-party logging service
- In 'test' environment:
- Write transformed (human-readable, colors, short timestamp, json expansion etc) output to stdout
- write raw json to a logfile
- Anywhere:
- write logfile contents to stdout with the same human-readable transformer used in 'development'
pino-pretty is not deprecated, only the prettyPrint option.
The following would do what you want:
const pino = require('pino')
const transport = pino.transport({
target: 'pino-pretty',
options: { destination: 1 } // use 2 for stderr
})
pino(transport)
Don't use console.log, use pino.destination() instead (otherwise you loose all the benefit of worker_threads).
But how I could use pino.destination() to show the log in the stdout of the application?
I don't understand the question, could you clarify?
Please excuse my ignorance @mcollina
I was trying to create a custom transporter which would format the log similarly to pino-pretty. Then this formatted log would be transport to the stdout of the application and then to a Telegram ChatId.
The problem I was facing was: How to send the log to stdout but without using the console.log()?
It wasn't so clear to me that to send the log to stdout or stderr I should pass 1 or 2 as destination to SonicBoom destination. I thought SonicBoom should only be used to transport logs to files. But now I got it. 😅
fd: 1 is standard output. fd: 2 is standard error.
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
The documentation gave