- Lab
- Core Tech

Guided: Implement OpenTelemetry for JavaScript Observability
Gain deep insight into your application’s behavior with this hands-on guided Code Lab focused on implementing observability using OpenTelemetry in a JavaScript (Node.js) environment. You'll learn to set up tracing and metrics collection, use auto and manual instrumentation to monitor your code, and export data to powerful visualization tools like Jaeger and Grafana. Discover how to trace requests, diagnose performance bottlenecks, and collect custom metrics using Prometheus. This lab is perfect for developers aiming to build reliable, debuggable, and production-ready applications. This lab walks you through real-world tasks that strengthen your skills in system monitoring, performance optimization, and end-to-end observability.

Path Info
Table of Contents
-
Challenge
Introduction
Welcome to the Guided: Implement OpenTelemetry for JavaScript Observability lab. This hands-on Code Lab is ideal for JavaScript developers and backend engineers who want to bring robust observability into their Node.js applications using OpenTelemetry, the industry-standard telemetry framework. Through practical tasks, you’ll learn how to instrument your code for tracing and metrics, export telemetry data to tools like Jaeger and Prometheus, and visualize application behavior and performance in Grafana. ### Key Takeaways
- Set up and configure OpenTelemetry SDK for tracing and metrics in a Node.js application.
- Learn auto and manual instrumentation to capture spans, attributes, and events.
- Export traces to Jaeger and analyze performance bottlenecks using span timelines.
- Export metrics to Prometheus and visualize them using Grafana dashboards.
- Identify real issues in the code using observability insights and apply fixes. ### Prerequisites
JavaScript and Node.js Knowledge
- Learners should have a working knowledge of JavaScript and Node.js.
Observability Concepts (Optional)
- Learners would benefit from having familiarity with tracing, metrics, spans, and exporters. Although it is helpful, it's not required—they’ll be explained through guided practice in this lab.
Tools and Environment Familiarity
-
Learners should be comfortable using a terminal and running Node.js scripts.
-
Learners should be comfortable using an IDE, such as VS Code, WebStorm, or any other text editor.
-
Learners should have the ability to debug and test applications in a browser and read logs or outputs from a terminal. ### Observability
Observability is the ability to understand what's happening inside a system by examining its outputs—primarily traces, metrics, and logs. It enables teams to detect, diagnose, and resolve issues in complex, distributed systems.
OpenTelemetry is an open-source observability framework that provides standardized tools and APIs to collect, process, and export telemetry data from applications, regardless of the platform or vendor.
- Traces: Show the full journey of a request across services, helping pinpoint latency and failures.
- Metrics: Provide numeric insights into system performance and resource usage over time.
- Logs: Capture detailed, timestamped records of events for debugging and auditing.
In this lab, you will be focusing on traces and metrics. ### Jokes API App
A simple Node.js Express API is provided as the starting point for this lab. Throughout the lab, you’ll enhance it, step by step, with OpenTelemetry features such as tracing, spans, data export, and dashboard-based debugging.
__solution
Folder-
code
: This folder contains the final code for each step is stored in the__solution/code
folder. For instance, the final code for Step 2 is available in the__solution/code/Step02
directory. -
images
: This folder contains images depicting the lab's final state for each step. For example,__solution/images/Step02
contains images depicting the final state of Step 2.
-
Challenge
Add Tracing and Auto Instrumentation
In this step, you will set up OpenTelemetry in a Node.js app by enabling tracing and automatic instrumentation. Tracing allows you to monitor how requests flow through your app, while auto instrumentation automatically hooks into common Node.js libraries (like HTTP, Express) to generate spans without extra code. By the end of this step, you will have a basic OpenTelemetry SDK configuration in place that prints traces (spans) to the console.
Explanation NodeSDK
is the core class from@opentelemetry/sdk-node
sets up OpenTelemetry in a Node.js application. It manages all configuration for tracing, metrics, and exporters in one place, making setup easier and more consistent.ConsoleSpanExporter
is an exporter that prints span (trace) data directly to the console. This is helpful during development or debugging to see what traces are being captured without setting up an external backend.- The SDK is initialized by creating a new instance of
NodeSDK
and passing a configuration object. In this example, it includes only thetraceExporter
option withConsoleSpanExporter
, meaning all captured traces will be logged to the console. You can add more options like automatic instrumentations or different exporters as needed. sdk.start()
begins the tracing process. Once started, OpenTelemetry will begin recording and exporting traces according to the configuration.process.on('SIGTERM', ...)
ensures a graceful shutdown. When the app is about to exit, it callssdk.shutdown()
to flush any remaining traces and clean up resources properly.
Explanation
-
Instrumentation is the act of adding observability code to your application. This can be manual (writing code using OpenTelemetry APIs) or automatic (using pre-built instrumentations that hook into popular libraries).
-
The
@opentelemetry/auto-instrumentations-node
package provides a collection of automatic instrumentations for widely used Node.js libraries likehttp
,express
, andmysql
, so you don't have to instrument them manually.
npm run dev
The dev script above is configured to load the
instrumentation.js
file, enabling observability before the app starts. Navigate to your browser and open the https://{{hostname}}--4000.pluralsight.run/jokes?limit=2 URL.Reload the page a few times to trigger the endpoint and observe the Terminal output for printed trace spans as shown in the example below:
{ resource: { attributes: { ... 'telemetry.sdk.language': 'nodejs', 'telemetry.sdk.name': 'opentelemetry', 'telemetry.sdk.version': '2.0.1' } }, ... name: 'GET /jokes', id: '2fb7bb36d729ad55', ... attributes: { ... 'http.status_code': 200, 'http.status_text': 'OK', 'http.route': '/jokes' }, ... }
Each time the
/jokes
endpoint is accessed, OpenTelemetry automatically generates trace spans using the auto-instrumentation package. These spans are exported by theConsoleSpanExporter
, which prints their details to the console for easy debugging.You can view an example of the full span data for a request, for reference, by visiting the following link: https://{{hostname}}--4000.pluralsight.run/step2-sample-spans.txt. Congratulations! You’ve now configured tracing in your Node.js app using OpenTelemetry. You also enabled automatic instrumentation to collect spans from common libraries with zero manual effort. These spans are printed to your console using the
ConsoleSpanExporter
. -
Challenge
Add Metadata and Manual Instrumentation
In this step, you’ll enhance your tracing configuration by adding service metadata and implementing manual instrumentation. This helps you clearly identify services and gain deeper insight into specific parts of your application logic. By the end of this step, your application will have metadata, such as the service name and version attached to each span. You will also manually instrument a function to create a custom span named
getTheJokes
.Explanation The resource property adds contextual metadata to every span, making it easier to distinguish spans from different services when visualising traces.
Explanation
-
const { trace } = require('@opentelemetry/api')
imports thetrace
module from the@opentelemetry/api
package, which provides standard interfaces and helper functions for working with telemetry data such as traces, metrics, and logs in Node.js applications. -
const tracer = trace.getTracer('jokes-lib')
creates a named tracer instance. UsinggetTracer('jokes-lib')
helps identify the source of trace data, which is especially useful in distributed systems or microservices. -
This tracer instance will be used to manually create custom spans around specific parts of your application logic, allowing you to capture detailed trace information and gain deeper insight into runtime behavior. This will be implemented in the next step.
Explanation
- The code above introduces manual instrumentation using OpenTelemetry by adding an active span to the
getJokes
function. - A span represents a single unit of work or operation in your application (e.g., a function call or API request). It records timing, metadata, and context about that operation. Each span can include a name, start time, end time, attributes, events, and more.
startActiveSpan('getTheJokes', async (span) => {...})
starts a new span and sets it as the active context during the execution of the code block.span.end()
ends the span and records its duration. It should be called after all work is complete (just before returning the data) to ensure accurate trace recording.
Reload the page a few times to trigger the endpoint. You should see the following in your Terminal:
- A span named
getTheJokes
, which was created by the manual instrumentation as shown in the example span below - The spans should have
service.name
andservice.version
in their resource attribute as shown in the example span below
{ resource: { attributes: { ... 'service.name': 'jokes-server', 'service.version': '0.1.0' } }, ... name: 'getTheJokes', id: 'a29e75dfca36ae45', timestamp: 1749944859845000, ... }
You can view an example of the whole span data for reference by visiting the following link: https://{{hostname}}--4000.pluralsight.run/step3-sample-spans.txt. Congratulations! You’ve now enriched your tracing data with meaningful service metadata and added a custom span using manual instrumentation. These spans help isolate performance bottlenecks in specific logic, like the
getJokes
function. -
-
Challenge
Add Nested Span, Attribute, and Event
In this step, you will extend manual instrumentation by creating nested spans to represent sub-tasks within a parent operation, adding attributes to enrich spans with useful metadata, and including events to capture significant moments during the span's lifecycle.
Explanation This span is a child of the span created in the
getJokes
function. Such nesting helps visualize the hierarchy and timing of operations. When viewing traces, you'll seegetJokeRandomNumber:{i}
as a sub-span under thegetTheJokes
, giving you insight into how long each number generation takes.Explanation
-
Attributes are key-value pairs attached to spans that provide additional context or metadata about the operation being traced.
-
In this case, the attribute
randomNumber.generated
records the generated random number as a string. This makes spans more informative and easier to filter when viewed in observability tools such as Jaeger or Zipkin. -
Adding attributes like this enhances visibility into the function’s output, which is useful for debugging, performance monitoring, and understanding application behavior.
Explanation
-
The line
span.addEvent('More than Two jokes requested.')
adds an event to the active span when more than two jokes are requested, marking a noteworthy moment during the span's execution. -
Events in OpenTelemetry are time-stamped annotations used to provide additional context within a span, such as significant actions, state changes, or conditions met during execution.
-
This event improves trace visibility in observability tools (like Jaeger or Zipkin) by helping you understand not just what happened, but also why or under what condition—useful for debugging and analyzing application behavior.
Reload the page a few times to trigger the endpoint. You should see the following in your Terminal:
- Nested spans created for each random joke selection
- A span attribute attached to each child span, showing the generated random number as shown in the example span below
- An event in the parent span with the message
More than Two jokes requested.
as shown in the example span below
{ resource: { attributes: { ... 'service.name': 'jokes-server', 'service.version': '0.1.0' } }, instrumentationScope: { name: 'jokes-lib', version: undefined, schemaUrl: undefined }, ... name: 'getJokeRandomNumber:3', ... attributes: { 'randomNumber.generated': '24' }, ... } { resource: { attributes: { ... 'service.name': 'jokes-server', 'service.version': '0.1.0' } }, instrumentationScope: { name: 'jokes-lib', version: undefined, schemaUrl: undefined }, ... name: 'getTheJokes', ... events: [ { name: 'More than Two jokes requested.', attributes: {}, time: [ 1749945174, 623169118 ], droppedAttributesCount: 0 } ], }
You can view an example of the whole span data for reference by visiting the following link: https://{{hostname}}--4000.pluralsight.run/step4-sample-spans.txt. Congratulations! You’ve learned how to create nested spans to track smaller tasks inside your code, add attributes to store extra details like generated values, and add events to mark important actions. These techniques help you see exactly what your app is doing and make it easier to find and fix problems.
-
-
Challenge
Export Traces to Zipkin and Resolve a Bug
In this step, you will configure OpenTelemetry to export tracing data to Jaeger, a distributed tracing backend. Using span visualizations in the Jaeger UI, you’ll identify performance bottlenecks and fix a bug that causes delays when multiple jokes are requested.
By the end of this step, you’ll have successfully sent traces to Jaeger, analyzed span timings, and used tracing insights to resolve the delay issue.
Explanation -
The OpenTelemetry trace exporter for OTLP over HTTP sends tracing data to Jaeger using the OpenTelemetry Protocol (OTLP).
-
OTLP over HTTP is a modern, vendor-neutral protocol that is easy to configure, firewall-friendly, and well-suited for both development and production environments.
-
The exporter is pointed to
http://localhost:4318/v1/traces
, Jaeger’s default OTLP HTTP endpoint, allowing Jaeger to receive trace data natively. -
The OpenTelemetry SDK is configured to use this exporter, ensuring all trace data is sent in a standard format.
-
Exporting to Jaeger enables developers to visualize traces as spans across services, helping identify bottlenecks, analyze performance, and understand system behavior.
- Navigate to the https://{{hostname}}--16686.pluralsight.run/ URL to open the Jaeger dashboard in your browser.
- In the Jaeger UI, select jokes-server from the Service dropdown and click on the Find Traces button.
- Click on the most recent trace with the name
jokes-server: GET /jokes
in the main panel. - Review nested spans with indices greater than 2, such as
getJokeRandomNumber:3
orgetJokeRandomNumber:4
. Notice that these spans take longer than expected to execute—approximately 2 seconds. - The above observation indicates that there is a bug in the
getRandomNumber
function when indices are greater than 2. You will resolve this bug in the next task.
You can view an example of the Jaeger UI dashboard showing sample spans by visiting the following link: https://{{hostname}}--4000.pluralsight.run/step5-jaeger-ui.png.
Explanation Tracing in Jaeger UI, you identified a slowdown when more than 2 jokes were requested. The delay was caused by a two-second wait when
This task shows how powerful observability tools like Jaeger can help uncover hidden performance issues in complex systems.i > 2
. By removing that code, all random number generations now perform consistently. The bug is an intentional 2-second wait for this lab, but in a real-life scenario, it will be issues like long DB wait, API calls, etc.Congratulations! You’ve learned how to send traces to Jaeger, view how long each part of your app takes, and fix a delay using that information. This shows how tracing helps you understand and improve your app's performance.
-
-
Challenge
Export Metrics and View in Grafana
In this final step, you will enable metrics collection using OpenTelemetry and export them to a Prometheus server. These metrics will then be visualised in a Grafana dashboard. This gives you a complete observability setup: traces + metrics.
Explanation -
The
PrometheusExporter
is initialized with port9464
, which starts an HTTP server that exposes application metrics athttp://localhost:9464/metrics
. -
It is passed to the
metricReader
field in theNodeSDK
configuration, allowing OpenTelemetry to serve metrics that Prometheus can scrape at regular intervals.
Explanation
-
Metrics are numerical measurements that help monitor the health and performance of your application over time. In OpenTelemetry, they are a core part of observability—alongside traces and logs—and allow you to track things like request counts, response times, memory usage, and more.
-
A counter is a type of metric that only increases. It is used to count how many times a specific event occurs, such as the number of incoming requests or errors.
-
In this task, the
jokesRequestedCounter
is a counter metric created usingmeter.createCounter()
. It tracks how many times thegetJokes
function is called, providing insights into usage patterns. -
The line
jokesRequestedCounter.add(1);
increments the counter each time the function runs. This helps track the demand for jokes and can assist in performance tuning or identifying unusual traffic spikes.
View the Metrics in Grafana
- Open Grafana by navigating to https://{{hostname}}--3000.pluralsight.run/dashboards.
- Log in with the default credentials (Email or username:
admin
, Password:admin
) and skip the password-change prompt. - Find and open the Jokes Requested dashboard.
- In the Jokes requested panel header, click the kebab menu (⋮) and choose View to expand the graph.
- Observe the graph to see the total number of times jokes have been requested. You can view an example of the Grafana UI dashboard displaying the Jokes Requested graph by visiting the following link: https://{{hostname}}--4000.pluralsight.run/step6-grafana-ui.png. Congratulations! You’ve successfully completed the lab by implementing OpenTelemetry for observability in JavaScript. Prometheus now collects metrics on how often your app is used, and Grafana presents this data visually. With both traces and metrics in place, you now have full visibility into your application’s behavior and performance.
-
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.