Introducing Our Node.js Exporter for OpenTelemetry

November 9, 2021

Introducing Our Node.js Exporter for OpenTelemetry

Written with Willian Carvalho

Since Instana Node.js collector version 1.134.0 in September, the Node.js tracer includes the Instana OpenTelemetry exporter for Node.js. You can find a guide for using the Instana Node.js exporter at the end of this blog post.

What is OpenTelemetry?

OpenTelemetry is an open source-project hosted by the CNCF that provides APIs and SDKs for a variety of programming languages to instrument and collect observability data from applications.

The telemetry part is a bit misleading here, since observability tools are really consuming “signals.” Those signals, in terms of OpenTelemetry, are span data (“tracing”), metrics and logs.

OpenTelemetry offers a vendor-neutral data format which can be integrated with any data processing backend. This is possible thanks to a concept called “exporters.” An exporter is basically a way to hand the data over for further use. For example, a span exporter can send all of the span data that is created to a third party, such as Instana, and convert it to a vendor-specific format. This separation of concerns allows vendors to keep their proprietary data format and consume it in the best possible way.

How is it applied?

To collect signals, the OpenTelemetry API must be required into your application.

However, while Instana tracers require a single source to be imported into the application, responsible for auto-instrumenting several APIs, OpenTelemetry works differently. The strategy with OpenTelemetry instrumentation libraries is that you would include each of the library-specific instrumentations one by one and then configure it.

For instance, if your application receives HTTP requests and either calls a Postgres database or sends an SQS message as follow up action, you have to import the corresponding instrumentation for each of these libraries into the project.

OpenTelemetry and Node.js

Image: See how the Instana Node.js tracer works.Among the supported programming languages, OpenTelemetry supports Node.js.

When instrumenting a Node.js application with OpenTelemetry, you will create a tracing module where all OpenTelemetry core functionalities, exporters and library instrumentations are put together for the tracing to take effect.

Then the tracing module must be loaded into the entry point of the application before anything else, so all further required libraries can be traced.

This is similar to how the Instana Node.js tracer works, with the exception that Instana customers only need to import the collector at the very top of the entry point of the application, without implementing any extra code.

When writing the tracing module, two essential pieces of code must be added: the instrumentation API of libraries to be traced and an exporter, which we will cover next.

The Span Exporter

Once the OpenTelemetry tracer is initialized, it will trace all provided instrumentation libraries, generating spans for every time that an instrumented library is called.

But you have to tell the tracer what to do with these spans. That’s where the exporter comes in.

The exporter understands the vendor-neutral span format, converts it to a specific format, and sends this data to a backend to be processed and displayed later.

In the OpenTelemetry Node.js tool set, there is a console.log exporter for “playground” purposes that you can use to see how the spans look.

Practical example

Please keep in mind that this example works with the version 0.25.0 (and 0.24.0 for the ConsoleSpanExporter). Readers are encouraged to check the OpenTelemetry repository for changes in new versions.

Our example will consist of three essential parts:

  1. A simple application
  2. The tracing module
  3. Tying the application and the tracing together

Let’s start with a basic Express web server that receives GET requests to /cities.

[index.js]

Code: a basic Express web server that receives GET requests to /cities

You can start the application and test it just to be sure it works by calling http://localhost:9090/cities from a browser or cUrl:

$ curl http://localhost:9090/cities
["Berlin","New York","Rio de Janeiro"]

Now, let’s create the tracing module:

[tracing.js]

Code: the tracing module

Apart from the boilerplate code, some important bits deserve attention:

const opentelemetry = require(“@opentelemetry/sdk-node”)

This is the actual OpenTelemetry SDK for Node.js, and it provides all the core functionality to instrument Node.js applications.

const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node");

In normal circumstances, we would have to add specific instrumentation for each library that we wish to trace.

For instance, if you want to trace Express, you have to install and import https://www.npmjs.com/package/@opentelemetry/instrumentation-express to your project, or https://www.npmjs.com/package/@opentelemetry/instrumentation-pg for tracing Postrgres, and so on.

The @opentelemetry/auto-instrumentations-node package bundles a set of features, including Node.js built-in functions and commonly used libraries such as Express, MySQL and Redis, among others. For the full coverage list, visit https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node.

ConsoleSpanExporter

As mentioned previously, the OpenTelemetry SDK offers a console.log-like exporter for debugging purposes, called ConsoleSpanExporter.

It needs to be instantiated and provided during the tracing initialization in the traceExporter option.

Instrumentations option

The instrumentations option expects an array of instrumentation instances. In our example, as already mentioned, we provide a list of commonly used Node.js instrumentation libraries provided by the getNodeAutoInstrumentations() function.

You are free to add other instrumentations in the array to be traced.

Stitching it all together

Now we need to add the tracing module (tracing.js) to the very top of the application entry point (index.js).

We can achieve this by requiring tracing.js in the beginning of index.js or by running the application with the option –require (or -r for short):

$ node --require ./tracing.js index.js

After the application starts, the output “Tracing initialized” can be seen in the terminal. This means that the tracing module was successfully imported and initialized within the application.

Now, let’s call the application again with CUrl or via a browser. After a few seconds

Then, go back to terminal A (where the application is running) and after a few seconds, you should see an output similar to this:

{
traceId: '589a1a499950a2d129a3ffd0d0a1bac0',
parentId: 'd5784117a9217b2b',
name: 'middleware - query',
id: '3320bf9485687ccd',
kind: 0,
timestamp: 1632488539501017,
duration: 431,
attributes: {
'http.route': '/',
'express.name': 'query',
'express.type': 'middleware'
},
status: { code: 0 },
events: []
}
{
traceId: '589a1a499950a2d129a3ffd0d0a1bac0',
parentId: 'd5784117a9217b2b',
name: 'middleware - expressInit',
id: 'f6d3ce819b2ac7d1',
kind: 0,
timestamp: 1632488539502070,
duration: 228,
attributes: {
'http.route': '/',
'express.name': 'expressInit',
'express.type': 'middleware'
},
status: { code: 0 },
events: []
}
{
traceId: '589a1a499950a2d129a3ffd0d0a1bac0',
parentId: 'd5784117a9217b2b',
name: 'request handler - /cities',
id: 'cde3df40c4991524',
kind: 0,
timestamp: 1632488539502589,
duration: 37,
attributes: {
'http.route': '/cities',
'express.name': '/cities',
'express.type': 'request_handler'
},
status: { code: 0 },
events: []
}
{
traceId: '589a1a499950a2d129a3ffd0d0a1bac0',
parentId: undefined,
name: 'GET /cities',
id: 'd5784117a9217b2b',
kind: 1,
timestamp: 1632488539498121,
duration: 7964,
attributes: {
'http.url': 'http://localhost:9090/cities',
'http.host': 'localhost:9090',
'net.host.name': 'localhost',
'http.method': 'GET',
'http.route': '/cities',
'http.target': '/cities',
'http.user_agent': 'curl/7.64.1',
'http.flavor': '1.1',
'net.transport': 'ip_tcp',
'net.host.ip': '::1',
'net.host.port': 9090,
'net.peer.ip': '::1',
'net.peer.port': 52126,
'http.status_code': 200,
'http.status_text': 'OK'
},
status: { code: 1 },
events: []
}

You can see four spans: the entry span, named “GET /cities,” followed by three internal Express spans:

  • request handler – /cities
  • middleware – expressInit
  • middleware – query

That’s it! The OpenTelemetry SDK is now monitoring the application, and the span data is collected and displayed in the terminal through the ConsoleSpanExporter.

The Instana Node.js OpenTelemetry Exporter

Instana customers or entities testing the Instana platform via trial programs that have Node.js applications can now benefit from OpenTelemetry instrumentation.

OpenTelemetry is already supported at Instana for customers who use the Instana Agent running on their host machines, SaaS or On Premise, as the OpenTelemetry spans are pre-processed by the Agent and later on, converted to Instana spans in the backend.

However, for customers running applications in a serverless environment, like AWS Lambda or AWS Fargate, the Instana Agent works slightly differently, and spans are currently not pre-processed by the serverless Agent.

With the Instana Node.js OpenTelemetry exporter, the pre-processing happens at the application level and it is delivered to the backend further on.

Using the Instana Node.js Exporter

Using the Instana Node.js exporter consists of simply requiring the @instana/opentelemetry-exporter package and passing an instance of InstanaExporter as a value to the tracerExporter option.

To illustrate that, we can modify a few lines of our sample code above by replacing the ConsoleSpanExporter with the InstanaExporter, as seen below.

First and foremost, install the @instana/opentelemetry-exporter package:

$ npm i --save @instana/opentelemetry-exporter

Then, let’s replace the ConsoleSpanExporter with the InstanaExporer. The complete code should look like this:

[index.js with InstanaExporter]

Code screenshot: replace the ConsoleSpanExporter with the InstanaExporer

The InstanaExporter expects two important values: The agent key and the endpoint URL. These arguments can be provided in the constructor, but this alternative must be avoided, since this is sensitive data that customers want to avoid to leak. So we will use the already know Instana environment variables INSTANA_AGENT_KEY and INSTANA_ENDPOINT_URL.

Now the command to start the application should look like this:

$ node INSTANA_AGENT_KEY=the_agent_key INSTANA_ENDPOINT_URL=endpoint_url --require ./tracing.js index.js

If any of the environment variables values are incorrect, a warning message will be displayed in the terminal. Otherwise, the message “Tracing initialized” will be displayed.

Now let’s call our test application once more, but this time nothing will be displayed in the terminal, as we replaced the console exporter with the Instana exporter, which sends data to Stan.

Now you can navigate through Unbounded Analytics and search the endpoint’s name by “GET /cities”. The trace is now visible in the dashboard:

Screenshot: Navigate through Unbounded Analytics and search the endpoint's name by “GET /cities”. The trace is now visible in the dashboard

By clicking in the call, the trace details are displayed:

Screenshot: The trace details are displayed

Screenshot: The converted and processed span data from OpenTelemetry in the Stan dashboard

We can see the converted and processed span data from OpenTelemetry in the Stan dashboard.

In conclusion, OpenTelemetry is rapidly gaining popularity in the observability world, especially with the flexibility to be exported, consumed and processed by a vendor specific backend.

Instana offers a convenient way to convert OpenTelemetry traces to the Instana platform for customers hosting the Instana Agent. But also for Node.js applications running in a serverless environment through the InstanaExporter.

Instana is wherever our customers are. By introducing an OpenTelemetry exporter for span data, we continue supporting use cases of our customers and continue with our integration of OpenTelemetry as a first class citizen into our observability platform.

Visit our repository on Github

Let us know what you think! Head over to our repository on Github and leave us some feedback there or with [email protected].

Come back to see how we work with and support the OpenTelemetry project. For more on Instana’s support of OpenTelemetry, here’s some recreational reading material:

Play with Instana’s APM Observability Sandbox

Developer, Product, Thought Leadership
In my last blog post, I talked about the history and rapid adoption of the OpenTelemetry project. Today we discuss the progress made in terms of end-to-end compatibility between distributed tracing with...
|
Developer, Thought Leadership
In the past few years, distributed tracing has emerged in the global DevOps consciousness as an indispensable tool in the microservices arsenal. In April 2019, the open source observability community rose to...
|
Announcement, Conceptual, Developer, Engineering, Product
According to AWS: “[Graviton2 is] custom built by Amazon Web Services using 64-bit Arm Neoverse cores to deliver the best price performance for your cloud workloads running in Amazon EC2” At Instana...
|

Start your FREE TRIAL today!

Instana, an IBM company, provides an Enterprise Observability Platform with automated application monitoring capabilities to businesses operating complex, modern, cloud-native applications no matter where they reside – on-premises or in public and private clouds, including mobile devices or IBM Z.

Control hybrid modern applications with Instana’s AI-powered discovery of deep contextual dependencies inside hybrid applications. Instana also gives visibility into development pipelines to help enable closed-loop DevOps automation.

This provides actionable feedback needed for clients as they to optimize application performance, enable innovation and mitigate risk, helping Dev+Ops add value and efficiency to software delivery pipelines while meeting their service and business level objectives.

For further information, please visit instana.com.