Skip to main content
Version: v5.9.x

Logging

Logging

Enable Logging

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.

Basic Logging Setup

The following enables the production JSON logger:

const fastify = require('fastify')({
logger: true
})

Environment-Specific Configuration

Enabling the logger for local development, production, and test environments requires additional 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 matching environment is found
})

⚠ Warning: pino-pretty needs to be installed as a dev dependency. It is not included by default for performance reasons.

Usage

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' })
})

To log outside route handlers, use the logger available on the Fastify instance:

fastify.log.info('Something important happened!')

Passing Logger Options

To pass options to the logger, provide them to Fastify. See the Pino documentation for the full list of available options. To specify a file destination, use:

const fastify = require('fastify')({
logger: {
level: 'info',
file: '/path/to/file' // Uses 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
}
})

Advanced Logger Configuration

Request ID Tracking

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 the Fastify factory options requestIdHeader and genReqId for customization options.

⚠ Warning: Enabling requestIdHeader allows callers to set reqId to an arbitrary value. No validation is performed on the header value.

Serializers

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 overridden with custom serializers.

const fastify = require('fastify')({
logger: {
serializers: {
req (request) {
return { url: request.url }
}
}
}
})

⚠ Warning: Logging response headers may expose sensitive data, including authentication data, and may violate privacy regulations. Use log redaction to remove sensitive information. The following example is for demonstration purposes only:

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,
headers: request.headers
}
}
}
}
})

ℹ️ Note: In some cases, the Reply object passed to the res serializer cannot be fully constructed. When writing a custom res serializer, verify that any properties other than statusCode exist on reply before accessing them. For example, verify the existence of getHeaders 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 the req serializer because the request is serialized when the child logger is created. At that time, the body is not yet parsed.

To log req.body, use the preHandler hook:

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.js process to exit. See the Pino documentation for more information.

Any logger other than Pino will ignore the serializers option.

Using Custom Loggers

A custom logger instance can be supplied by passing it as loggerInstance. The logger must conform to the Pino interface with the following:

  • Methods: info, error, debug, fatal, warn, trace, silent, child
  • Properties: level (string)

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.

Log Redaction

Pino supports low-overhead log redaction for masking 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 the Pino redaction documentation for more details.