90-Second Hack To Install A Node.JS Agent With No Code Changes
Installing Rookout on your Node.JS application is usually a breeze. Run `npm install`, add a line of code, and you are done. Yet, in some rare cases, we encounter more frustrating use cases. For example:
- You have tons of different repositories, and you want to add Rookout to all of them.
- You have a very large or complex repository that you are not overly familiar with and want to add Rookout to it.
- In some environments, you prefer to only add the Rookout package to your deployment (or container image).
- You have a repository where it’s easier to edit the runtime configuration than the source code, and you want to add Rookout to it.
When those tough situations arise, our customers often refer to this blog post we wrote years ago about deploying a Java Agent. So, if you are looking for an easy and portable way to deploy Node agents without changing your code, you’ll want to read this article.
Let’s Dive In
Our story begins with a little-known Node.JS CLI flag ‘–require’. As the name suggests, this is a way to require a module directly from the command line. More specifically, the module is preloaded before the main script is executed. One minor caveat is that require is limited to CommonJS modules, so await in global scope is not supported, and we can’t synchronously finish initialization (`–import` doesn’t have this limitation, but is only available from Node 19).
To accommodate the `–require` flag, we added a start script that can easily be used with the require flag:
node --require rookout/start
Not So Fast
While modifying the command line doesn’t require any code changes, it’s not always a trivial task. Taking the container use-case, for example, the final command line may be defined in any of the below:
- The container image using the Dockefile CMD instruction (or an equivalent).
- The container orchestration configuration, such as the Kubernetes YAML file, Helm Chart, or Amazon ECS CloudFormation.
- The package.json file as a start or other script.
- In some bash file that is used to spin up the Node instance.
This very challenge is how the original Java hack blog post came into existence, and ironically enough, the exact same solution works here as well. The NODE_OPTIONS environment variable allows you to easily append any command line options to your Node applications.
The benefit of that is that we can add any of the above configuration elements (Contaienr image, container orchestration, package.json, etc…) without worrying about where the command line is defined. For example, for a container this would look like:
ENV NODE_OPTIONS="--require rookout/start"
One Final Challenge
Our approach does still have one last caveat – we do rely on the Rookout package being available in the node_modules directory. This can easily be achieved by changing the `package.json` or Dockerfile, but not so easy to do without changing the build artifacts, which is not always desirable.
To bypass this limitation, we can use the NODE_PATH environment variable, which allows us to load modules from any directory. We can deploy the Node package through an initContainer or mounted volume, and then point to the relevant directory through the NODE_PATH environment variable, and we are done. That’s it.
If you always wondered if it’s possible to add a Node.JS agent such as Rookout to your application without editing your source code, now you know how. Whether you have one large repository or many small ones, you can easily get an agent up and running by following these short series of steps.
And yet, in this case, the obvious approach is definitely the easiest one. Setting up Rookout as an NPM package takes no more than 90 seconds. Don’t take our word for it 😉