javascriptnode.jsstrictdynamic-scope

How to we do dynamic-scoping to implement transaction tracing in NodeJs?


I am building a client and a server side framework (NodeJs) in which I want to trace transactions. I have the ability to pass headers (transaction_id) between client and server, however, I want to be able to set the transaction_id automatically. That means that if a header is defined, a middleware should be able to read and set the transaction id in the context so that down-stream calls can read it.

Building wrappers is outside the scope of the question. What I am struggling with is being able to create a scope dynamically and storing values there.

NOTE – I am using 'strict' mode which disallows dynamic scoping in node. So need another way. NOTE – I am using Promises to make client-server calls.


Solution

  • Here is how I solved it finally –

    I used CLS which allows us to keep track of dynamic scope.

    Reading through all the text around CLS took a while, so here is a summary of what I did (layman terms)

    NOTE - I am using NodeJs with 'strict' mode. This means I can't use dynamic scoping. Given its a production system, i'd want to keep strict mode. Hence an alternate way to achieve dynamic scoping.

    1) CLS creates a dynamic context/scope. That allows us to then set / get key-value pairs that are visible only till we are within the created scope.

    2) Since I am using Bluebird's Promises, CLS required me to use the patch to keep the context/scope available from within Promises. https://www.npmjs.com/package/cls-bluebird

    3) Using CLS with Promises took time to figure out. Here is a great discussion on how different libraries have used CLS to produce different outcomes. https://github.com/TimBeyer/cls-bluebird/issues/6

    4) This is how I used CLS (paraphrased and simplified) –

    var cls = require('continuation-local-storage');
    var clsbluebird = require('cls-bluebird');
    var namespace = cls.createNamespace('ns');
    clsbluebird( namespace );
    
    var result;
    namespace.run(function() {
      namespace.set('key', 'value');
      result = abc(); // returns 'value'
    });
    
    // versus doing –
    result = abc(); // returns undefined
    
    function abc() {
      return namespace.get('key');
    }
    

    5) Usecase – This way, I implemented basic tracing of transactions. Eg. NewRelic, Trace, etc.