Logging is disabled by default. Enable it by passing { logger: true }
or
{ logger: { level: 'info' } }
when creating a Fastify instance. Note that if
the logger is disabled, it cannot be enabled at runtime.
abstract-logging is used for
this purpose.
As Fastify is focused on performance, it uses
pino as its logger, with the default log
level set to 'info'
when enabled.
Enabling the production JSON logger:
const fastify = require('fastify')({
logger: true
})
Enabling the logger with appropriate configuration for local development, production, and test environments requires more configuration:
const envToLogger = {
development: {
transport: {
target: 'pino-pretty',
options: {
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
},
production: true,
test: false,
}
const fastify = require('fastify')({
logger: envToLogger[environment] ?? true // defaults to true if no entry matches in the map
})
pino-pretty
needs to be installed as a dev dependency. It is not included
by default for performance reasons.
The logger can be used in route handlers as follows:
fastify.get('/', options, function (request, reply) {
request.log.info('Some info about the current request')
reply.send({ hello: 'world' })
})
Trigger new logs outside route handlers using the Pino instance from the Fastify instance:
fastify.log.info('Something important happened!');
To pass options to the logger, provide them to Fastify. See the Pino documentation for available options. To specify a file destination, use:
const fastify = require('fastify')({
logger: {
level: 'info',
file: '/path/to/file' // Will use pino.destination()
}
})
fastify.get('/', options, function (request, reply) {
request.log.info('Some info about the current request')
reply.send({ hello: 'world' })
})
To pass a custom stream to the Pino instance, add a stream
field to the logger
object:
const split = require('split2')
const stream = split(JSON.parse)
const fastify = require('fastify')({
logger: {
level: 'info',
stream: stream
}
})
By default, Fastify adds an ID to every request for easier tracking. If the
requestIdHeader
option is set and the corresponding header is present, its
value is used; otherwise, a new incremental ID is generated. See Fastify Factory
requestIdHeader
and Fastify Factory
genReqId
for customization options.
The default logger uses standard serializers for objects with req
, res
, and
err
properties. The req
object is the Fastify Request
object, and the res
object is the Fastify Reply
object. This
behavior can be customized with custom serializers.
const fastify = require('fastify')({
logger: {
serializers: {
req (request) {
return { url: request.url }
}
}
}
})
For example, the response payload and headers could be logged using the approach below (not recommended):
const fastify = require('fastify')({
logger: {
transport: {
target: 'pino-pretty'
},
serializers: {
res (reply) {
// The default
return {
statusCode: reply.statusCode
}
},
req (request) {
return {
method: request.method,
url: request.url,
path: request.routeOptions.url,
parameters: request.params,
// Including headers in the log could violate privacy laws,
// e.g., GDPR. Use the "redact" option to remove sensitive
// fields. It could also leak authentication data in the logs.
headers: request.headers
};
}
}
}
});
🛈 Note: In some cases, the
Reply
object passed to theres
serializer cannot be fully constructed. When writing a customres
serializer, check for the existence of any properties onreply
aside fromstatusCode
, which is always present. For example, verify the existence ofgetHeaders
before calling it:
const fastify = require('fastify')({
logger: {
transport: {
target: 'pino-pretty'
},
serializers: {
res (reply) {
// The default
return {
statusCode: reply.statusCode,
headers: typeof reply.getHeaders === 'function'
? reply.getHeaders()
: {}
}
},
}
}
});
🛈 Note: The body cannot be serialized inside a
req
method because the request is serialized when the child logger is created. At that time, the body is not yet parsed.
See the following approach to log req.body
:
app.addHook('preHandler', function (req, reply, done) {
if (req.body) {
req.log.info({ body: req.body }, 'parsed body')
}
done()
})
🛈 Note: Ensure serializers never throw errors, as this can cause the Node process to exit. See the Pino documentation for more information.
Any logger other than Pino will ignore this option.
A custom logger instance can be supplied by passing it as loggerInstance
. The
logger must conform to the Pino interface, with methods: info
, error
,
debug
, fatal
, warn
, trace
, silent
, child
, and a string property
level
.
Example:
const log = require('pino')({ level: 'info' })
const fastify = require('fastify')({ loggerInstance: log })
log.info('does not have request information')
fastify.get('/', function (request, reply) {
request.log.info('includes request information, but is the same logger instance as `log`')
reply.send({ hello: 'world' })
})
The logger instance for the current request is available in every part of the lifecycle.
Pino supports low-overhead log redaction for obscuring
values of specific properties in recorded logs. For example, log all HTTP
headers except the Authorization
header for security:
const fastify = Fastify({
logger: {
stream: stream,
redact: ['req.headers.authorization'],
level: 'info',
serializers: {
req (request) {
return {
method: request.method,
url: request.url,
headers: request.headers,
host: request.host,
remoteAddress: request.ip,
remotePort: request.socket.remotePort
}
}
}
}
})
See https://getpino.io/#/docs/redaction for more details.