Using OpenTelemetry as a node module instead of common js

To start of, using OpenTelemetry in Node.js modules are planned but not officially supported yet. This article is to give you a workaround to make OpenTelemetry mostly work.


I recently wanted to implement OpenTelemetry (or Otel) into an existing project which is set up to use import instead of require() for our packages. I did a bit of a naive approach and decided to set it up like I do with all other packages, import them and initialize OpenTelemetry at the start. After running the project for a few days and adding more telemetry, I started noticing errors such as:

@opentelemetry/instrumentation-fastify Module fastify has been loaded before @opentelemetry/instrumentation-fastify so it might not work, please initialize it before requiring fastify

@opentelemetry/instrumentation-pg Module pg has been loaded before @opentelemetry/instrumentation-pg so it might not work, please initialize it before requiring pg

Telemetry information still seemed to still be sent but why did this error occur and whatever I seemed to try to do in code didn't help. So I decided to do some digging.

The issue

OpenTelemetry supports what they call auto instrumentation. One of the recommended ways of setting up OpenTelemetry is through doing i.e node --require ./tracing.js src/server.js the reason behind this is because it does something called "require-in-the-middle" that loads and runs the OpenTelemetry script before requiring anything else. This is because OpenTelemetry nowadays patches libraries before they get loaded, it's a bit of a dirty way to do monkey patching. This usually works but the issue is that node.js modules are made to prevent this behaviour for security reasons and you can't import packages in runtime in the same way as you do with common js.

What I did previously was to import the OpenTelemetry package and then initialize it in the same "runtime" script as where my server and package imports was defined. This throws the pesky errors as you saw above, but it did work with most libraries I used, even if it sometimes missed some tracing when the server was starting up.

The solution "import-in-the-middle"

The import-in-the-middle is a bit similar to require-in-the-middle, it uses an experimental feature in node.js that's called ecma modules (not to be confused with Node.js modules) which was a proposed feature in 2017. Ecma modules is a feature made to support Ecmascript in Node.js in the same way it is implemented in the browser. The feature is deemed "unstable - 1" so it's not recommended for production environments. However we're just using a small part of the implementation called "loaders". To keep it frank, instead of running require with a tracing script. We can use import when starting our server in the following fashion:

node --loader ./tracing.js src/server.js

Using the loader feature will load the tracing library first (where you got your OpenTelemetry auto-instrumentation) and then your server script will be ran.

The OpenTelemetry team is currently trying to implement a better way to do auto-telemetry and add native support for import statements. But in the meantime, you can use this workaround to develop tracing libraries and test on existing projects.

Not everything will work this way

I've tested a few libraries and it seems like most of the libraries that OpenTelemetry has developed (the ones prefixed with @OpenTelemetry) work properly. I've tested instrumentation-http and instrumentation-pg but thirtparty libaries such as opentelemetry-instrumentation-kafkajs does not. This is probably because of the efforts that the OpenTelemetry team has already done to support importing with modules.

Hope that helps!