Logger
The core of the framework ships with an inbuilt logger built on top of Pino (one of the fastest logging libraries for Node.js). You can import and use the Logger as follows:
import Logger from '@ioc:Adonis/Core/Logger'
Logger.info('A info message')
Logger.warn('A warning')
During an HTTP request, you must use the ctx.logger
object. It is an isolated child instance of the logger that adds the unique request-id to all the log messages.
Make sure to enable request id generation by setting generateRequestId = true
inside config/app.ts
file.
Route.get('/', async ({ logger }) => {
logger.info('An info message')
return 'handled'
})
Config
The configuration for the logger is stored inside the config/app.ts
file under the logger
export. The options are the same as documented by Pino
.
Following the bare minimum options required to configure the logger:
{
name: Env.get('APP_NAME'),
enabled: true,
level: Env.get('LOG_LEVEL', 'info'),
redact: {
paths: ['password', '*.password'],
},
prettyPrint: Env.get('NODE_ENV') === 'development',
}
name
The name of the logger. The APP_NAME
environment variable uses the name
property inside the package.json file.
enabled
Toggle switch to enable/disable the logger
level
The current logging level. It is derived from the LOG_LEVEL
environment variable.
redact
Remove/redact sensitive paths from the logging output. Read the Redact section .
prettyPrint
Whether or not to pretty-print the logs. We recommend turning off pretty printing in production, as it has some performance overhead.
How does AdonisJS Logger work?
Since Node.js is a single-threaded event-loop, it is very important to keep the main thread free from any extra work required to process or reformat logs.
For this very reason, we opted for Pino , which does not perform any in-process log formatting and instead encourages you to use a separate process for that. In a nutshell, this is how logging works.
- You can log at different levels using the Logger API, for example:
Logger.info('some message')
. - The logs are always sent out to
stdout
. - You can redirect the
stdout
stream to a file or use a separate process to read and format them.
Logging in development
Since logs are always written to stdout
, there is nothing special required in the development environment. Also, AdonisJS will automatically pretty print
the logs when NODE_ENV=development
.
Logging in production
In production, you would want to stream your logs to an external service like Datadog or Papertrail. Following are some of the ways to send logs to an external service.
There is an additional operational overhead of piping the stdout stream to a service. But, the trade-off is worth the performance boost you receive. Make sure to check Pino benchmarks as well.
Using Pino transports
The simplest way to process the stdout
stream is to use Pino transports
. All you need to do is pipe the output to the transport of your choice.
For demonstration, let's install the `pino-datadog package to send logs to Datadog.
npm i pino-datadog
Next, start the production server and pipe the stdout
output to pino-datadog
.
node build/server.js | ./node_modules/.bin/pino-datadog --key DD_API_KEY
Streaming to a file
Another approach is to forward the output of stdout
to a physical file on the disk and then configure your logging service to read and rotate the log files.
node build/server.js >> app.log
Now, configure your logging service to read logs from the app.log
file.
Redact values
You can redact/remove sensitive values from the logging output by defining a path to the keys to remove. For example: Removing user password from the logging output.
{
redact: {
paths: ['password'],
}
}
The above config will remove the password from the merging object.
Logger.info({ username: 'virk', password: 'secret' }, 'user signup')
// output: {"username":"virk","password":"[Redacted]","msg":"user signup"}
You can define a custom placeholder for the redacted values or remove them altogether from the output.
{
redact: {
paths: ['password'],
censor: '[PRIVATE]'
}
}
// or remove the property
{
redact: {
paths: ['password'],
remove: true
}
}
Check out the fast-redact package to view the expressions available for the paths array.
Logger API
Following is the list of available methods/properties on the Logger module. All of the logging methods accept the following arguments.
- The first argument can be a string message or an object of properties to merge with the final log message.
- If the first argument was a merging object, then the second argument is the string message.
- Rest of the parameters are the interpolation values for the message placeholders.
import Logger from '@ioc:Adonis/Core/Logger'
Logger.info('hello %s', 'world')
// output: {"msg": "hello world"}
Logger.info('user details: %o', { username: 'virk' })
// output: {"msg":"user details: {\"username\":\"virk\"}"
Define a merging object as follows:
import Logger from '@ioc:Adonis/Core/Logger'
Logger.info({ username: 'virk' }, 'user signup')
// output: {"username":"virk","msg":"user signup"}
You can pass error objects under the err
key.
import Logger from '@ioc:Adonis/Core/Logger'
Logger.error({ err: new Error('signup failed') }, 'user signup')
// output: {"err":{"type":"Error","message":"foo","stack":"..."},"msg":"user signup"}
Following is the list of logging methods.
Logger.trace
Logger.debug
Logger.info
Logger.warn
Logger.error
Logger.fatal
isLevelEnabled
Find if a given logging level is enabled inside the config file.
Logger.isLevelEnabled('info')
Logger.isLevelEnabled('trace')
bindings
Returns an object containing all the current bindings, cloned from the ones passed in via Logger.child()
.
Logger.bindings()
child
Create a child logger instance. You can create the child logger with a different logging level as well.
const childLogger = Logger.child({ level: 'trace' })
childLogger.info('an info message')
You can also define custom bindings for a child logger. The bindings are added to the logging output.
const childLogger = Logger.child({ userId: user.id })
childLogger.info('an info message')
level
The current logging level value, as a string.
console.log(Logger.level)
// info
levelNumber
The current logging level value, as a number.
console.log(Logger.levelNumber)
// 30
levels
An object of logging labels
and values
.
console.log(Logger.levels)
/**
{
labels: {
'10': 'trace',
'20': 'debug',
'30': 'info',
'40': 'warn',
'50': 'error',
'60': 'fatal'
},
values: {
trace: 10,
debug: 20,
info: 30,
warn: 40,
error: 50,
fatal: 60
}
}
*/
pinoVersion
The version of Pino.
console.log(Logger.pinoVersion)
// '6.11.2'