Logging

Logging

Enable logging

Logging is disabled by default, and you can enable it by passing { logger: true } or { logger: { level: 'info' } } when you create a Fastify instance. Note that if the logger is disabled, it is impossible to enable it at runtime. We use abstract-logging for this purpose.

As Fastify is focused on performance, it uses pino as its logger, with the default log level, when enabled, set to 'info'.

Enabling the production JSON logger:

  1. const fastify = require('fastify')({
  2. logger: true
  3. })

Enabling the logger with appropriate configuration for both local development and production environment requires bit more configuration:

  1. const fastify = require('fastify')({
  2. logger: {
  3. prettyPrint:
  4. environment === 'development'
  5. ? {
  6. translateTime: 'HH:MM:ss Z',
  7. ignore: 'pid,hostname'
  8. }
  9. : false
  10. }
  11. })

⚠️ pino-pretty needs to be installed as a dev dependency, it is not included by default for performance reasons.

Usage

You can use the logger like this in your route handlers:

  1. fastify.get('/', options, function (request, reply) {
  2. request.log.info('Some info about the current request')
  3. reply.send({ hello: 'world' })
  4. })

You can trigger new logs outside route handlers by using the Pino instance from the Fastify instance:

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

If you want to pass some options to the logger, just pass them to Fastify. You can find all available options in the Pino documentation. If you want to specify a file destination, use:

  1. const fastify = require('fastify')({
  2. logger: {
  3. level: 'info',
  4. file: '/path/to/file' // Will use pino.destination()
  5. }
  6. })
  7. fastify.get('/', options, function (request, reply) {
  8. request.log.info('Some info about the current request')
  9. reply.send({ hello: 'world' })
  10. })

If you want to pass a custom stream to the Pino instance, just add a stream field to the logger object.

  1. const split = require('split2')
  2. const stream = split(JSON.parse)
  3. const fastify = require('fastify')({
  4. logger: {
  5. level: 'info',
  6. stream: stream
  7. }
  8. })

By default, Fastify adds an ID to every request for easier tracking. If the “request-id” 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 is configured with a set of standard serializers that serialize objects with req, res, and err properties. The object received by req is the Fastify Request object, while the object received by res is the Fastify Reply object. This behaviour can be customized by specifying custom serializers.

  1. const fastify = require('fastify')({
  2. logger: {
  3. serializers: {
  4. req (request) {
  5. return { url: request.url }
  6. }
  7. }
  8. }
  9. })

For example, the response payload and headers could be logged using the approach below (even if it is not recommended):

  1. const fastify = require('fastify')({
  2. logger: {
  3. prettyPrint: true,
  4. serializers: {
  5. res (reply) {
  6. // The default
  7. return {
  8. statusCode: reply.statusCode
  9. }
  10. },
  11. req (request) {
  12. return {
  13. method: request.method,
  14. url: request.url,
  15. path: request.routerPath,
  16. parameters: request.params,
  17. // Including the headers in the log could be in violation
  18. // of privacy laws, e.g. GDPR. You should use the "redact" option to
  19. // remove sensitive fields. It could also leak authentication data in
  20. // the logs.
  21. headers: request.headers
  22. };
  23. }
  24. }
  25. }
  26. });

Note: The body cannot be serialized inside a req method because the request is serialized when we create the child logger. At that time, the body is not yet parsed.

See an approach to log req.body

  1. app.addHook('preHandler', function (req, reply, done) {
  2. if (req.body) {
  3. req.log.info({ body: req.body }, 'parsed body')
  4. }
  5. done()
  6. })

Any logger other than Pino will ignore this option.

You can also supply your own logger instance. Instead of passing configuration options, pass the instance. The logger you supply must conform to the Pino interface; that is, it must have the following methods: info, error, debug, fatal, warn, trace, child.

Example:

  1. const log = require('pino')({ level: 'info' })
  2. const fastify = require('fastify')({ logger: log })
  3. log.info('does not have request information')
  4. fastify.get('/', function (request, reply) {
  5. request.log.info('includes request information, but is the same logger instance as `log`')
  6. reply.send({ hello: 'world' })
  7. })

The logger instance for the current request is available in every part of the lifecycle.

Log Redaction

Pino supports low-overhead log redaction for obscuring values of specific properties in recorded logs. As an example, we might want to log all the HTTP headers minus the Authorization header for security concerns:

  1. const fastify = Fastify({
  2. logger: {
  3. stream: stream,
  4. redact: ['req.headers.authorization'],
  5. level: 'info',
  6. serializers: {
  7. req (request) {
  8. return {
  9. method: request.method,
  10. url: request.url,
  11. headers: request.headers,
  12. hostname: request.hostname,
  13. remoteAddress: request.ip,
  14. remotePort: request.socket.remotePort
  15. }
  16. }
  17. }
  18. }
  19. })

See https://getpino.io/#/docs/redaction for more details.