OpenTelemetry in N|Solid
Introduction
N|Solid Runtime, the OSS runtime that powers N|Solid Pro, is an innovative, lightweight runtime for Node.js applications. It offers real-time insights into performance, memory usage, and CPU consumption, giving developers unparalleled visibility into their code without requiring any modifications. In today’s software landscape, understanding your application's production behavior is crucial. With cloud-native architectures, microservices, and distributed systems, pinpointing issues is challenging. This is where OpenTelemetry, a widely-adopted, open-source standard for collecting telemetry data, comes in. Leveraging OpenTelemetry with N|Solid, developers gain deep insights into application behavior, identify bottlenecks early, and optimize performance and reliability.
In this blog post, we will explore how to instrument your Node.js processes with OpenTelemetry within the N|Solid runtime. You'll learn how these powerful tools work together to provide comprehensive observability, ensuring your applications run smoothly and efficiently in production environments.
What is OpenTelemetry?
As defined in the official OpenTelemetry documentation
"OpenTelemetry is an Observability framework and toolkit designed to create and manage telemetry data such as traces, metrics, and logs. Crucially, OpenTelemetry is vendor- and tool-agnostic, meaning that it can be used with a broad variety of Observability backends, including open source tools like Jaeger and Prometheus, as well as commercial offerings. [...] … OpenTelemetry is focused on the generation, collection, management, and export of telemetry. A major goal of OpenTelemetry is that you can easily instrument your applications or systems, no matter their language, infrastructure, or runtime environment."
By providing a standardized, open-source approach to collecting telemetry data, OpenTelemetry helps bridge the gap between different monitoring and logging tools. This allows developers and operations teams to use their preferred tools and platforms without worrying about compatibility or integration issues. For example, with OpenTelemetry, you can collect metrics and logs from your application using one tool, such as Prometheus or the ELK Stack, while still using another tool for visualization, like Grafana or Kibana. This flexibility enables a more tailored approach to monitoring and logging, allowing teams to choose the best tools for their specific needs.
Moreover, OpenTelemetry's instrumentation allows you to collect telemetry data from multiple sources, including applications, services, and infrastructure components. This provides a comprehensive view of your entire architecture, enabling you to identify correlations between different components and better understand how they impact overall performance.
By standardizing the way telemetry data is collected and exported, OpenTelemetry facilitates integration with various monitoring and logging tools, making it easier to:
- Collect metrics from multiple sources
- Visualize complex system behavior
- Detect anomalies and alert on issues
- Retain and analyze historical data
This standardization ensures that regardless of the tools or platforms you use, OpenTelemetry can help you achieve a consistent and comprehensive observability strategy.
OpenTelemetry in N|Solid
To enhance observability through N|Solid, we've integrated robust OpenTelemetry support, offering several key features:
- Automatic Instrumentation: N|Solid automatically instruments specific Node.js core modules, capturing critical telemetry data for deeper insights into application behavior.
- OTLP Exporter: An OpenTelemetry Protocol (OTLP) exporter sends traces and metrics directly to tools like Prometheus or Jaeger.
- Seamless Ecosystem Integration: Leveraging the OpenTelemetry ecosystem, N|Solid taps into extensive libraries and tools, enhancing your application's observability with community-driven innovation.
In the following sections, we'll delve deeper into how you can configure N|Solid to utilize these OpenTelemetry features and start collecting valuable telemetry data about your application's behavior.
Automatic Instrumentation
N|Solid automatically collects process-wide and thread-specific metrics at intervals, adjustable via the NSOLID_INTERVAL
environment variable (default: 3 seconds). Tracing, which is off by default due to performance concerns, can be activated by setting the NSOLID_TRACING_ENABLED
environment variable. By default, N|Solid instruments node:http
and node:dns
modules, generating spans for every HTTP and DNS transaction. To exclude these modules, use the NSOLID_TRACING_MODULES_BLACKLIST
variable. This automatic instrumentation ensures accurate and detailed metrics and traces with minimal configuration.
Try N|Solid’s powerful observability features by signing up for our free SaaS tier. Run your application with the following command:
$ NSOLID_SAAS=your_nsolid_saas_token nsolid app.js
Log in to the N|Solid Console to view the collected metrics on the Application Dashboard, and experience enhanced observability and performance optimization for your Node.js applications.
Fig 1. N|Solid Application Dashboard
OTLP Exporter
When using N|Solid SaaS, the runtime exports collected telemetry data using the ZeroMQ protocol. For those who prefer sending the data to a different backend, N|Solid provides the option to export metrics and traces using OTLP over either HTTP or GRPC.
OTLP configuration in N|Solid is simple. We need to use the NSOLID_OTLP environment variable to enable the OTLP Exporter. Once it’s done, N|Solid will handle the OTEL_* environment variables defined by the OTLP Exporter specification.
Let’s showcase this with an example.
The following code implements the most basic Node.js HTTP server:
'use strict';
const port = process.env.PORT || 9999;
const http = require('node:http');
const server = http.createServer((req, res) => {
res.end('ok');
});
server.listen({port}, () => {
console.log('listening on port: ' + port);
});
We want to run this server with N|Solid so it collects and exports metrics and traces to a Prometheus and Jaeger server respectively using OTLP over HTTP. We can set up the 3 services (prometheus, jaeger and http_server) very easily with Docker Compose.
version: '3.7'
services:
prometheus:
image: prom/prometheus:latest
expose:
9090
ports:
9090:9090
volumes:
./prometheus.yml:/etc/prometheus/prometheus.yml
command:
"--config.file=/etc/prometheus/prometheus.yml"
"--storage.tsdb.path=/prometheus"
"--web.console.libraries=/usr/share/prometheus/console_libraries"
"--web.console.templates=/usr/share/prometheus/consoles"
"--enable-feature=otlp-write-receiver"
jaeger:
image: jaegertracing/all-in-one:latest
environment:
COLLECTOR_OTLP_ENABLED=true
JAEGER_DISABLED=true
expose:
4318
ports:
16686:16686
4318:4318
http_server:
image: "nodesource/nsolid:iron-latest"
user: "nsolid"
working_dir: /home/nsolid/app
environment:
NSOLID_APPNAME=http_server
NSOLID_TRACING_ENABLED=1
NSOLID_OTLP=otlp
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://prometheus:9090/api/v1/otlp/v1/metrics
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://jaeger:4318/v1/traces
ports:
9000:8080
volumes:
./api:/home/nsolid/app
command: "nsolid test.js"
Let’s take a brief look on how every service is configured:
- The prometheus service will listen on port 9090 with the feature otlp-write-receiver enabled, consuming metrics in OTLP at the following url: http://prometheus:9090/api/v1/otlp/v1/metrics.
- The jaeger service with COLLECTOR_OTLP_ENABLED will consume traces in OTLP at the following URL: http://jaeger:4318/v1/traces.
- The http_server service runs with N|Solid and has the OTLP Exporter enabled by setting NSOLID_OTLP to “otlp”. To configure the metrics and traces endpoints the OTEL_EXPORTER_OTLP_METRICS_ENDPOINT and OTEL_EXPORTER_OTLP_TRACES_ENDPOINT environment variables are set pointing to prometheus and jaeger urls. The rest of the config values for the OTLP Exporter are set to its default values as defined in https://opentelemetry.io/docs/specs/otel/protocol/exporter/, so for example, we don’t need to actually define the protocol to be used, as it defaults to OTLP over HTTP.
As a final step, we spin up the services by running:
$ docker-compose up
Next we can send an HTTP request to the server:
$ wget http://localhost:9000
We can check in the Prometheus and Jaeger UI’s that telemetry data is being consumed.
Fig 2. Process cpu and cpu_user time as shown in Prometheus
Fig 3. HTTP server transaction span as shown in Jaeger
OpenTelemetry ecosystem integration
Integrating N|Solid with the OpenTelemetry JavaScript ecosystem is straightforward, allowing us to take full advantage of the wide range of available modules.
We’re going to modify our existing http_server code to incorporate some @opentelemetry modules which will allow us to showcase some new functionality. The new code looks like this:
'use strict';
const port = process.env.PORT || 9999;
const http = require('node:http');
const nsolid = require('nsolid');
const api = require('@opentelemetry/api');
const { FsInstrumentation } = require('@opentelemetry/instrumentation-fs');
// Register Opentelemetry API in N|Solid.
if (!nsolid.otel.register(api)) {
throw new Error('Error registering api');
}
// Register Opentelemetry auto-instrumentation modules.
nsolid.otel.registerInstrumentations([
new FsInstrumentation({})
]);
// Need to require this after registering the instrumentations
const fs = require('node:fs');
const tracer = api.trace.getTracer('test');
const server = http.createServer((req, res) => {
const ctxt = api.context.active();
const span = tracer.startSpan('Fibonacci', { kind: api.SpanKind.INTERNAL }, ctxt);
fs.writeFileSync('./output_fib.txt', ](https://opentelemetry.io/docs/concepts/signals/metrics/);
span.end();
res.end();
})
function fib(n) {
if (n === 0 || n === 1) return n;
return fib(n - 1) + fib(n - 2);
}
server.listen(port, () => {
console.log('listening on port: ' + port);
});
Let’s describe the newly added functionality:
- We have added the @opentelemetry/api interface and registered in N|Solid using the
nsolid.otel.register()
API. By doing so, we will be able to perform all the operations defined in the Opentelemetry API specification that relate to tracing and they’re handled by the implementation embedded in N|Solid (metrics and logs integration aren’t implemented yet though it’s coming). - We have registered @opentelemetry/instrumentation-fs by using the
nsolid.otel.registerInstrumentations()
API which will automatically instrument the node:fs core modules. - We have added a custom Span using the
api.trace
API. Custom spans provide granular visibility into specific parts of your application. In this example, we create a custom span namedFibonacci
to monitor the Fibonacci calculation. This span started before the Fibonacci calculation and ended immediately after. By creating this custom span, we can precisely monitor the execution time and performance of the Fibonacci calculation, providing deeper insights into that part of the application.
Finally, we spin up the services again by running:
$ docker-compose up
Next we can send an HTTP request to the server:
$ wget http://localhost:9000
And now we can check in Jaeger how the trace generated by the HTTP differs from the original one.
Fig 4. Full trace with 3 spans as shown in Jaeger
Fig 5. Comparison between traces from Fig 2. and Fig 3.
Conclusion
Integrating OpenTelemetry with N|Solid enhances observability in your Node.js applications by providing comprehensive telemetry data collection and seamless integration with tools like Prometheus and Jaeger. This setup enables deeper insights into application performance, quick issue identification, and efficient optimization.
In this post, we demonstrated how to configure N|Solid for automatic instrumentation and OTLP export, showcasing practical examples. These features simplify the monitoring process, helping ensure your applications run smoothly and efficiently.
With N|Solid and OpenTelemetry, you have powerful tools to maintain high-performance and reliable applications. We encourage you to explore these features to optimize your observability strategy.
Thank you for reading!