Tracing requests in Node.js?

henryjw

Henry Williams

Posted on January 21, 2019

Tracing requests in Node.js?

TLDR;

  • How can I maintain request context without having to pass the logger (or requestId) around and without using cls-hooked?
  • If there's no better way than passing the logger, how do I the carry the type info of the logger(ideally without JSDocs)?

Any one using a different pattern that's cleaner than passing the logger around? I've tried using express-http-context (and other cls-hooked based libraries), but it isn't very reliable because the context gets lost in some cases.
The only slightly better approach I've come up with is to make all modules into classes so that only the constructor needs the logger, but then the logger still has to be passed down from the caller.

Another problem with passing the logger around is that the typing information is lost, so I have to remember the structure of the object. It's not a big deal for me, but it leads to subtle bugs, especially for developer that aren't as familiar with the codebase as I am.

Current solution

// routes.js

app.get(
    '/api/v1/customer/:id',
    async (req, res, next) => {
        const id = req.params.id
        // The logger contains multiple fields request-specific fields like
        // request ID and authorization level
        const logger = req.logger
        const customer = await customerService.get(id, logger)

        /// ... process and return response
    }
)
Enter fullscreen mode Exit fullscreen mode
// customer-service.js

module.exports.get = async (id, logger) {
    /// ...
}

module.exports.create = async (customer, logger) {
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Improved solution

// routes.js

app.get(
    '/api/v1/customer/:id',
    async (req, res, next) => {
        const id = req.params.id
        // The logger contains multiple fields request-specific fields like
        // request ID and authorization level
        const logger = req.logger
        const customerService = new CustomerService(logger)
        const customer = await customerService.get(id)

        /// ... process and return response
    }
)

app.post(
    '/api/v1/customer',
    async (req, res, next) => {
        const customer = req.body
        const logger = req.logger
        // Downside: still have to pass logger to constructors
        const customerService = new CustomerService(logger)
        const customer = await customerService.create(customer)

        // ... process and return response
    }
)
Enter fullscreen mode Exit fullscreen mode
// customer-service.js

class CustomerService {
    constructor(logger) {
        // logger is only passed into the constructor
        this.logger = logger
    }

    module.exports.get = async (id) {
        // ...
        this.logger.info(/*data to log*/)
        // ...
    }

    module.exports.create = async (customer, logger) {
        // ...
        this.logger.info(/*data to log*/)
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

Any solutions or tips are greatly appreciated :)

💖 💪 🙅 🚩
henryjw
Henry Williams

Posted on January 21, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Tracing requests in Node.js?
api Tracing requests in Node.js?

January 21, 2019