Skip to content

Pino #124

Open
Open
Pino#124
@mderriey

Description

@mderriey

From the back of #115 (comment), I took a look at pino and found we should be able to drop it in with a few tweaks.

Things I looked at

defaultMeta

We use this to enrich all log entries emitted by winston.

Pino has a similar concept, through the base option.

level

We tend to use verbose in localdev, which Pino doesn't support out of the box.

We could move to debug, and mention it in migration docs.
Or, use the customLevels notion to create verbose.

Error serialisation

Pino supports this out-of-the-box, with a caveat:

For it to be serialised, the Error instance needs to be either the full object we're passing (i.e. logger.error('error', new Error('boom')), or have a specific key in the object (i.e. logger.error('error', { err: new Error('boom') }).
The key is err by default and can be updated/augmented.

omitPaths

This is supported through the formatters.log option

const logger = pino({
  formatters: {
    log(object: Record<string, unknown>) {
      return omit(object, config.get('logging.omitPaths'))
    }
  }
})

How level is logged

By default, each level is associated an integer, which is used in log entries, so they look like:

{"level":30,[...]}

I personally don't love this, and there's a way to change this through the formatters.level option

const logger = pino({
  formatters: {
    level(label, number) {
      return { level: label }
    }
  }
})

OTel instrumentation

We "need" this when we run in AWS to get traces to logs correlation, as AWS expects log entries to have a property that has the value <trace-id>@<span-id>, see example in MakerGraph.

Luckily, such an instrumentation exists and has the same hook as the winston one.

Pretty printing

As mentioned in the link above, pino supports this out of the box.
Maybe I was holding it wrong, but it seemed some top-level config options needed to be set on the pino-pretty transport options to be applied, especially the ignore one.

export const localDevLogger = pino({
  ...commonOptions,
  transport: {
    target: 'pino-pretty',
    options: {
      colorize: true,
      translateTime: true,
      messageKey: commonOptions.messageKey,
      ignore: config.get('logging.omitPaths').join(','),
      minimumLevel: commonOptions.level as Level,
    } satisfies PrettyOptions,
  },
})

gave me this output on a client gig:

[09:42:51.105] DEBUG: Using StaticDatabaseConnectionDetailsProvider
    component: "Database resolution"
[09:42:52.287] INFO: Initialising express app
[09:42:52.289] INFO: Added GET /user
[09:42:52.289] INFO: Added GET /logout and /logged-out
[09:42:52.290] INFO: Added default GET / redirect to /graphql
[09:42:52.290] INFO: Auth reply handler added to route /auth
[09:42:52.291] INFO: Starting http server
[09:42:52.294] INFO: 🚀 Server ready at http://localhost:4000
[09:42:52.294] INFO: Initialising Apollo
[09:42:52.294] INFO: Starting apollo server

I haven't tried much more yet but will look into it.

Core logging API

By default, pino expects the meta/object/binding to be passed first, and the message second, i.e. pino().info({ some: 'information', count: 10 }, 'this is the message')

Luckily, they advertise a way around this in the official docs, by leveraging the hooks.logMethod option.

Keys

By default, messages have the msg key in log entries, and errors have err.
They're both configurable if we want to use whatever winston uses by default to not disrupt existing project.

See messageKey and errorKey.

If we want, we can also make it that the input object is serialised in its own key to avoid potential conflicts, see nestedKey.

silent

Some projects use the TransportOptions.silent option to shut the console up, like in test runs.

It seems we can achieve the same either with:

The difference is that level is updateable at runtime, where enabled is not, see https://getpino.io/#/docs/api?id=event-39level-change39.

Timestamp

By default, pino uses epoch-time seconds as the timestamp, but supports others through the timestamp option and provides built-in implementations with the stdTimeFunctions exported object.

Things missing

So far, the only missing thing I can think of is a built-in integration with the App Insights SDK, which only supports console, bunyan, and winston by default.

However, I'm sure we could come up with something of our own given the code is fairly simple.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions