Skip to content

Latest commit

 

History

History
260 lines (222 loc) · 6.87 KB

Logging.md

File metadata and controls

260 lines (222 loc) · 6.87 KB

Fastify

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

Enabling the production JSON logger:

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

Environment-Specific Configuration

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.

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

Trigger new logs outside route handlers using the Pino instance from 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 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
  }
})

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

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 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 the res serializer cannot be fully constructed. When writing a custom res serializer, check for the existence of any properties on reply aside from statusCode, which is always present. 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 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.

Using Custom Loggers

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.

Log Redaction

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.