aigoncharov/cls-proxify

What would be the recommended way to use with Hapi and specifically hapi-pino

josh803316 opened this issue · 1 comments

I read this article about adding tracing to logs and it inspired me to want to enable this inside my hapi/hapi-pino project.

I registered hapi-pino as a hapi plugin just like the docs recommend (below). I'm wondering if it would make more sense to register a separate cls-pino-logger plugin or if it would be recommended to do it somehow inside of hapi-pino? Thanks in advance for any guidance.

await server.register({
    plugin: require('hapi-pino'),
    options: {
      prettyPrint: process.env.NODE_ENV !== 'production',
      // Redact Authorization headers, see https://getpino.io/#/docs/redaction
      redact: ['req.headers.authorization']
    }
  })

I created a plugin per the hapi plugin docs:

import { clsProxify, clsProxifyNamespace, setClsProxyValue } from 'cls-proxify'
import * as Pino from "pino";

const logger = Pino();
const loggerCls = clsProxify('clsKeyLogger', logger)

const handler = function (request, h) {
    clsProxifyNamespace.bindEmitter(request);
    clsProxifyNamespace.bindEmitter(request.response);

    clsProxifyNamespace.run(() => {
        const headerRequestID = request.headers.Traceparent
        // this value will be accesible in CLS by key 'clsKeyLogger'
        // it will be used as a proxy for `loggerCls`
        const loggerProxy = {
            info: (msg: string) => `${headerRequestID}: ${msg}`,
        }
        setClsProxyValue('clsKeyLogger', loggerProxy)
    })
};

exports.plugin = {
    name: 'cls-trace-logger',
    register: function (server, options) {
        server.route({ method: 'GET', path: '/test/cls', handler });

        loggerCls.info('My message!');
    }
};

But I get this error:

1597365833550] ERROR (55306 on ip-192-168-2-50.ec2.internal): request error
    err: {
      "type": "AssertionError",
      "message": "can only bind real EEs",
      "stack":
          AssertionError [ERR_ASSERTION]: can only bind real EEs

@josh803316 AFAICS, hapi-pino has an "instance" option field. I would suggest to use it.

I have never used hapi myself, but roughly it could look like this:

import { clsProxify, clsProxifyNamespace, setClsProxyValue } from 'cls-proxify'

const logger = pino()
const loggerCls = clsProxify('clsKeyLogger', logger)

const server = Hapi.server({
  port: 3000,
  host: 'localhost'
})

await server.register({
  name: 'cls-trace-logger',
  plugin: async (server, options) => {
    server.ext('onRequest', (request, h) => {
      clsProxifyNamespace.bindEmitter(request.raw.req)
      clsProxifyNamespace.bindEmitter(request.raw.res)

      const clsCtx = clsProxifyNamespace.createContext()
      clsProxifyNamespace.enter(clsCtx)

      request.plugins['cls-trace-logger'] = {
        context: clsCtx
      }

      const requestId = req.headers.Traceparent || uuidv1()
      const childLoggerWithRequestID =  logger.child({ requestId })

      setClsProxyValue('clsKeyLogger', childLoggerWithRequestID)

      return h.continue
    })

    server.events.on('response', () => {
      const clsCtx = request.plugins['cls-trace-logger'].context
      clsProxifyNamespace.exit(clsCtx)
    })
  }
})

await server.register({
    plugin: require('hapi-pino'),
    options: {
      prettyPrint: process.env.NODE_ENV !== 'production',
      // Redact Authorization headers, see https://getpino.io/#/docs/redaction
      redact: ['req.headers.authorization'],
      instance: loggerCls
    }
  })

So the idea is to create a child pino logger with an embedded request ID for each request and put it into our CLS context. clsProxify wraps the original pino logger with a Proxy. Later on, we we use our loggerCls, it looks up that child pino logger with the embedded request ID in the CLS context and uses it instead. At last, we feed our wrapped logger instance to hapi-pino.

Let me know if it helps.