The OpenTelemetry Collector is a vendor-agnostic service that receives, processes, and exports telemetry data such as logs, metrics, and traces. It acts as a middle layer between instrumented applications and telemetry backends, providing a flexible and scalable way to manage observability data.
By separating telemetry collection from your application code, the Collector helps you keep your telemetry workflows centralized and organized. It supports capabilities like batching, filtering, and resource transformation. You can even export your telemetry data to multiple observability platforms.
One of the most popular approaches to running the Collector is spinning it up as a Docker container. There are several advantages to this approach, including:
- Isolates telemetry concerns from your app container: Your application container remains focused on business logic
- Easier to update or replace: You can roll out changes to the Collector independently
- Can serve multiple services on the same host or network: One Collector container can collect from many apps
- Ideal for local dev, CI pipelines, or lightweight deployments: It’s quick to set up and easy to tear down
Introduction to the demo application
The example demo project you'll use is a product inventory API built with Node.js and Express. The application uses PostgreSQL for data storage and Redis for caching. The entire application stack—including the API server, PostgreSQL database, and Redis cache—is containerized using Docker and orchestrated with Docker Compose.
The code for this demo project can be found at this GitHub repository.
In this post, you'll learn how to deploy the OpenTelemetry Collector within your “dockerized” application. You will:
- Add the OpenTelemetry collector to our Docker Compose setup
- Configure the collector to receive telemetry data via OpenTelemetry Protocol (OTLP) and export it to SolarWinds® Observability SaaS
- Instrument your Node.js application using the OpenTelemetry JavaScript SDK to capture distributed traces, structured logs, and metrics
- Show how to view telemetry data in SolarWinds Observability SaaS
Step 1: Add the collector to your Docker Compose
To begin, add a new service to your docker-compose.yml
. This service will use the official OpenTelemetry Collector Docker image and mount the collector configuration file you will create in Step 3.
For our example project, we modified docker-compose.yml
so the main application would know where to send its telemetry data (to the endpoint specified by <span style="background-color:#fff0cf;">OTEL_EXPORTER_OTLP_ENDPOINT</span>
). Then, we added the otel-collector
service, passing along an <span style="background-color:#fff0cf;">API_TOKEN</span>
for SolarWinds Observability SaaS data ingestion, which we'll select on Docker startup.
<span style="font-weight:400;">services:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> app:</span>
<span style="font-weight:400;"> …</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> environment:</span>
<span style="font-weight:400;"> …</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> -</span> <span style="font-weight:400;">OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> depends_on:</span>
<span style="font-weight:400;"> …</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> -</span> <span style="font-weight:400;">otel-collector</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> postgres:</span>
<span style="font-weight:400;"> …</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> redis:</span>
<span style="font-weight:400;"> …</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> otel-collector:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> image:</span> <span style="font-weight:400;">otel/opentelemetry-collector-contrib:latest</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> command:</span> <span style="font-weight:400;">["--config=/etc/otel-collector-config.yaml"]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> environment:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> API_TOKEN:</span> <span style="font-weight:400;">${API_TOKEN}</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> volumes:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> - type:</span> <span style="font-weight:400;">bind</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> source:</span> <span style="font-weight:400;">./otel-collector-config.yaml</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> target:</span> <span style="font-weight:400;">/etc/otel-collector-config.yaml</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> read_only:</span> <span style="font-weight:400;">true</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> ports:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> -</span> <span style="font-weight:400;">"14317:4317"</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> -</span> <span style="font-weight:400;">"14318:4318"</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> -</span> <span style="font-weight:400;">"13133:13133"</span>
Step 2: Obtain your SolarWinds Observability SaaS endpoint and API token
To send telemetry data to SolarWinds Observability SaaS, you will need to find your OpenTelemetry endpoint and create an API token for data ingestion. Follow these steps.
Log in to SolarWinds Observability SaaS. The URL, after you log in, may look similar to this:
<span style="font-weight:400;"><a id="" href="https://my.na-01.cloud.solarwinds.com/">https://my.na-01.cloud.solarwinds.com/</a></span>
The <span style="background-color:#fff0cf;font-weight:400;">xx-yy</span>
part of the URL (<span style="background-color:#fff0cf;font-weight:400;">na-01</span>
in this example) will depend on the data center used for your SolarWinds Observability SaaS account and organization. Take note of this.
Navigate to Settings > API Tokens.

On the API Tokens page, click Create API Token.

Specify a name for your new API token. Select Ingestion as the token type. You can also add tags if you wish. Click Create API Token.

Copy the resulting token value.

Here’s what a basic <span style="background-color:#fff0cf;">otel-collector-config.yaml</span>
should include:
- OTLP receiver: Accepting telemetry data, typically over ports 4318 for HTTP and 4317 for gRPC.
- Processors for batching, resource attribution, and memory limits: Groups and enriches data, attaches service metadata (like
<span style="background-color:#fff0cf;">service.name</span>
), and ensures the Collector stays within memory constraints. - Exporters to forward telemetry to external backends: These exporters allow you to send telemetry to the observability platform of your choice. In our example, we’ll forward data to SolarWinds Observability SaaS.
The <span style="background-color:#fff0cf;">otel-collector-config.yaml</span>
file for your project looks like this:
<span style="font-weight:400;">receivers:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> otlp:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> protocols:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> http:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> endpoint:</span> <span style="font-weight:400;">0.0.0.0:4318</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> grpc:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> endpoint:</span> <span style="font-weight:400;">0.0.0.0:4317</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">processors:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> batch:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> timeout:</span> <span style="font-weight:400;">1s</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> send_batch_size:</span> <span style="font-weight:400;">1024</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> resource:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> attributes:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> - key:</span> <span style="font-weight:400;">service.name</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> value:</span> <span style="font-weight:400;">"product-inventory-demo"</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> action:</span> <span style="font-weight:400;">upsert</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> memory_limiter:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> check_interval:</span> <span style="font-weight:400;">1s</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> limit_mib:</span> <span style="font-weight:400;">1500</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> spike_limit_mib:</span> <span style="font-weight:400;">512</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">exporters:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">otlp/swo:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> endpoint:</span> <span style="font-weight:400;">"otel.collector.na-01.cloud.solarwinds.com:443"</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> tls:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> insecure:</span> <span style="font-weight:400;">false</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> headers:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> "authorization":</span> <span style="font-weight:400;">"Bearer ${API_TOKEN}"</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> debug:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> verbosity:</span> <span style="font-weight:400;">detailed</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">extensions:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> pprof:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> endpoint:</span> <span style="font-weight:400;">0.0.0.0:1777</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> health_check:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> endpoint:</span> <span style="font-weight:400;">0.0.0.0:13133</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> zpages:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> endpoint:</span> <span style="font-weight:400;">0.0.0.0:55679</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">service:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> extensions:</span> <span style="font-weight:400;">[health_check,</span> <span style="font-weight:400;">pprof,</span> <span style="font-weight:400;">zpages]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> telemetry:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> logs:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> level:</span> <span style="font-weight:400;">debug</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> pipelines:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> traces:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> receivers:</span> <span style="font-weight:400;">[otlp]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> processors:</span> <span style="font-weight:400;">[memory_limiter,</span> <span style="font-weight:400;">batch,</span> <span style="font-weight:400;">resource]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> exporters:</span> <span style="font-weight:400;">[otlp/swo,</span> <span style="font-weight:400;">debug]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> logs:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> receivers:</span> <span style="font-weight:400;">[otlp]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> processors:</span> <span style="font-weight:400;">[memory_limiter,</span> <span style="font-weight:400;">batch,</span> <span style="font-weight:400;">resource]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> exporters:</span> <span style="font-weight:400;">[otlp/swo,</span> <span style="font-weight:400;">debug]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> metrics:</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> receivers:</span> <span style="font-weight:400;">[otlp]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> processors:</span> <span style="font-weight:400;">[memory_limiter,</span> <span style="font-weight:400;">batch,</span> <span style="font-weight:400;">resource]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> exporters:</span> <span style="font-weight:400;">[otlp/swo,</span> <span style="font-weight:400;">debug]</span>
Note that this is where you specify the OpenTelemetry data ingestion endpoint for SolarWinds Observability SaaS. Make sure to replace <span style="background-color:#fff0cf;">na-01</span>
with your organization's xx-yy data center code.
Step 4: Instrument your application
Next, you'll need to ensure your application is instrumented to emit telemetry via OTLP.
Add project dependencies
For the Node.js app used in this walkthrough, we started by installing the OpenTelemetry SDK for Node.js. We updated the package.json
file to include the following dependencies:
<span style="background-color:#fff0cf;"><span style="font-weight:400;">"@opentelemetry/api"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"1.7.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/auto-instrumentations-node"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"0.41.1"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/exporter-trace-otlp-http"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"0.48.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/exporter-logs-otlp-http"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"0.48.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/exporter-metrics-otlp-http"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"0.48.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/instrumentation-express"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"0.35.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/instrumentation-http"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"0.48.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/instrumentation-pg"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"0.35.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/instrumentation-redis"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"0.35.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/resources"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"1.21.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/sdk-logs"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"0.48.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/sdk-metrics"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"1.21.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/sdk-node"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"0.48.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/sdk-trace-base"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"1.21.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/sdk-trace-node"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"1.21.0"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">"@opentelemetry/semantic-conventions"</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"1.21.0"</span><span style="font-weight:400;">,</span></span>
Initialize the OpenTelemetry SDK
Then, we created a helper file (<span style="background-color:#fff0cf;">src/telemetry.js</span>
) to initialize the SDK and set up our instrumentation. This also sets up basic tracing.
<span style="background-color:#fff0cf;"><span style="font-weight:400;">const</span><span style="font-weight:400;"> opentelemetry = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/sdk-node'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { getNodeAutoInstrumentations } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/auto-instrumentations-node'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { OTLPTraceExporter } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/exporter-trace-otlp-http'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { Resource } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/resources'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { SemanticResourceAttributes } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/semantic-conventions'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { NodeSDK } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/sdk-node'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { PgInstrumentation } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/instrumentation-pg'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { RedisInstrumentation } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/instrumentation-redis'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { loggerProvider } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'./logger'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">async</span> <span style="font-weight:400;">function</span> <span style="font-weight:400;">setupTelemetry</span><span style="font-weight:400;">() {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> resource = </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> Resource({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> [SemanticResourceAttributes.SERVICE_NAME]: </span><span style="font-weight:400;">'product-inventory-api'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> [SemanticResourceAttributes.SERVICE_VERSION]: </span><span style="font-weight:400;">'1.0.0'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> traceExporter = </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> OTLPTraceExporter({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">url</span><span style="font-weight:400;">: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + </span><span style="font-weight:400;">'/v1/traces'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> sdk = </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> NodeSDK({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> resource,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> traceExporter,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">instrumentations</span><span style="font-weight:400;">: [</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> getNodeAutoInstrumentations(),</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> PgInstrumentation(),</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> RedisInstrumentation()</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> ],</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">try</span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Start the SDK</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">await</span><span style="font-weight:400;"> sdk.start();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">console</span><span style="font-weight:400;">.log(</span><span style="font-weight:400;">'Tracing initialized'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Gracefully shut down the SDK on process exit</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> process.on(</span><span style="font-weight:400;">'SIGTERM'</span><span style="font-weight:400;">, () => {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> sdk.shutdown()</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> .then(() => </span><span style="font-weight:400;">console</span><span style="font-weight:400;">.log(</span><span style="font-weight:400;">'Tracing terminated'</span><span style="font-weight:400;">))</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> .catch((error) => </span><span style="font-weight:400;">console</span><span style="font-weight:400;">.log(</span><span style="font-weight:400;">'Error terminating tracing'</span><span style="font-weight:400;">, error))</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> .finally(() => process.exit(</span><span style="font-weight:400;">0</span><span style="font-weight:400;">));</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">return</span><span style="font-weight:400;"> { sdk };</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> } </span><span style="font-weight:400;">catch</span><span style="font-weight:400;"> (error) {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">console</span><span style="font-weight:400;">.error(</span><span style="font-weight:400;">'Error initializing tracing:'</span><span style="font-weight:400;">, error);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">throw</span><span style="font-weight:400;"> error;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">}</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">module</span><span style="font-weight:400;">.exports = { setupTelemetry };</span></span>
Use the <span style="background-color:#fff0cf;">OTLPLogExporter</span>
We also modified our Logger (<span style="background-color:#fff0cf;">src/logger.js</span>
), which was originally just a simple winston logging setup, to use the <span style="background-color:#fff0cf;">OTLPLogExporter</span>
.
<span style="font-weight:400;">const</span><span style="font-weight:400;"> winston = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'winston'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { LoggerProvider, SimpleLogRecordProcessor } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/sdk-logs'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { OTLPLogExporter } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/exporter-logs-otlp-http'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { Resource } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/resources'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { SemanticResourceAttributes } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/semantic-conventions'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Create OpenTelemetry LoggerProvider</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> loggerProvider = </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> LoggerProvider({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">resource</span><span style="font-weight:400;">: </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> Resource({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> [SemanticResourceAttributes.SERVICE_NAME]: </span><span style="font-weight:400;">'product-inventory-api'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> [SemanticResourceAttributes.SERVICE_VERSION]: </span><span style="font-weight:400;">'1.0.0'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV || </span><span style="font-weight:400;">'development'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }),</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Create OTLP Log Exporter</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> logExporter = </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> OTLPLogExporter({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">url</span><span style="font-weight:400;">: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + </span><span style="font-weight:400;">'/v1/logs'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">headers</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">'Authorization'</span><span style="font-weight:400;">: </span><span style="font-weight:400;">`Bearer </span><span style="font-weight:400;">${process.env.API_TOKEN}</span><span style="font-weight:400;">`</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Add the log processor to the logger provider</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">loggerProvider.addLogRecordProcessor(</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> SimpleLogRecordProcessor(logExporter)</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Get the OpenTelemetry logger</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> otelLogger = loggerProvider.getLogger(</span><span style="font-weight:400;">'product-inventory-api'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Create Winston logger for console output</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> winstonLogger = winston.createLogger({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">level</span><span style="font-weight:400;">: process.env.LOG_LEVEL || </span><span style="font-weight:400;">'info'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">format</span><span style="font-weight:400;">: winston.format.combine(</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> winston.format.timestamp(),</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> winston.format.printf(({ timestamp, level, ...meta }) => {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">return</span> <span style="font-weight:400;">JSON</span><span style="font-weight:400;">.stringify({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> timestamp,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> level,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> ...meta</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }, </span><span style="font-weight:400;">null</span><span style="font-weight:400;">, </span><span style="font-weight:400;">2</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> })</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> ),</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">defaultMeta</span><span style="font-weight:400;">: { </span><span style="font-weight:400;">service</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'product-inventory-api'</span><span style="font-weight:400;"> },</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">transports</span><span style="font-weight:400;">: [</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> winston.transports.Console()</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> ]</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Create a combined logger that sends to both Winston and OpenTelemetry</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> logger = {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">info</span><span style="font-weight:400;">: (logData) => {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> logObject = </span><span style="font-weight:400;">typeof</span><span style="font-weight:400;"> logData === </span><span style="font-weight:400;">'string'</span><span style="font-weight:400;"> ? { </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: logData } : logData;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> winstonLogger.info(logObject);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> otelLogger.emit({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">severityText</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'INFO'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">body</span><span style="font-weight:400;">: </span><span style="font-weight:400;">JSON</span><span style="font-weight:400;">.stringify(logObject),</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">attributes</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> ...logObject,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">logLevel</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'info'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">environment</span><span style="font-weight:400;">: process.env.NODE_ENV || </span><span style="font-weight:400;">'development'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> },</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">error</span><span style="font-weight:400;">: (logData) => {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> logObject = </span><span style="font-weight:400;">typeof</span><span style="font-weight:400;"> logData === </span><span style="font-weight:400;">'string'</span><span style="font-weight:400;"> ? { </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: logData } : logData;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> winstonLogger.error(logObject);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> otelLogger.emit({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">severityText</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'ERROR'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">body</span><span style="font-weight:400;">: </span><span style="font-weight:400;">JSON</span><span style="font-weight:400;">.stringify(logObject),</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">attributes</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> ...logObject,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">logLevel</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'error'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">environment</span><span style="font-weight:400;">: process.env.NODE_ENV || </span><span style="font-weight:400;">'development'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> },</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">warn</span><span style="font-weight:400;">: (logData) => {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> logObject = </span><span style="font-weight:400;">typeof</span><span style="font-weight:400;"> logData === </span><span style="font-weight:400;">'string'</span><span style="font-weight:400;"> ? { </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: logData } : logData;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> winstonLogger.warn(logObject);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> otelLogger.emit({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">severityText</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'WARN'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">body</span><span style="font-weight:400;">: </span><span style="font-weight:400;">JSON</span><span style="font-weight:400;">.stringify(logObject),</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">attributes</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> ...logObject,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">logLevel</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'warn'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">environment</span><span style="font-weight:400;">: process.env.NODE_ENV || </span><span style="font-weight:400;">'development'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> },</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">debug</span><span style="font-weight:400;">: (logData) => {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> logObject = </span><span style="font-weight:400;">typeof</span><span style="font-weight:400;"> logData === </span><span style="font-weight:400;">'string'</span><span style="font-weight:400;"> ? { </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: logData } : logData;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> winstonLogger.debug(logObject);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> otelLogger.emit({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">severityText</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'DEBUG'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">body</span><span style="font-weight:400;">: </span><span style="font-weight:400;">JSON</span><span style="font-weight:400;">.stringify(logObject),</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">attributes</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> ...logObject,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">logLevel</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'debug'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">environment</span><span style="font-weight:400;">: process.env.NODE_ENV || </span><span style="font-weight:400;">'development'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">};</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">module</span><span style="font-weight:400;">.exports = { logger, loggerProvider };</span>
<span style="font-weight:400;"></span>
Use the <span style="background-color:#fff0cf;">OTLPMetricExporter</span>
We also created a metrics helper (<span style="background-color:#fff0cf;">src/<a style="background-color:#fff0cf;" href="http://metrics.js/">metrics.js</a></span>
), using <span style="background-color:#fff0cf;">OTLPMetricExporter</span>
to capture some key metrics for our Node.js application.
<span style="background-color:#fff0cf;"><span style="font-weight:400;">const</span><span style="font-weight:400;"> { MeterProvider, PeriodicExportingMetricReader } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/sdk-metrics'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { OTLPMetricExporter } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/exporter-metrics-otlp-http'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { Resource } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/resources'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { SemanticResourceAttributes } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/semantic-conventions'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { metrics } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'@opentelemetry/api'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> os = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'os'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Create OTLP Metric Exporter</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> metricExporter = </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> OTLPMetricExporter({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">url</span><span style="font-weight:400;">: process.env.OTEL_EXPORTER_OTLP_ENDPOINT + </span><span style="font-weight:400;">'/v1/metrics'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">headers</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">'Authorization'</span><span style="font-weight:400;">: </span><span style="font-weight:400;">`Bearer </span><span style="font-weight:400;">${process.env.API_TOKEN}</span><span style="font-weight:400;">`</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Create a meter provider with the exporter</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> meterProvider = </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> MeterProvider({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">resource</span><span style="font-weight:400;">: </span><span style="font-weight:400;">new</span><span style="font-weight:400;"> Resource({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> [SemanticResourceAttributes.SERVICE_NAME]: </span><span style="font-weight:400;">'product-inventory-api'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> [SemanticResourceAttributes.SERVICE_VERSION]: </span><span style="font-weight:400;">'1.0.0'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV || </span><span style="font-weight:400;">'development'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> })</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Register the metric exporter with a periodic reader</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">meterProvider.addMetricReader(</span><span style="font-weight:400;">new</span><span style="font-weight:400;"> PeriodicExportingMetricReader({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">exporter</span><span style="font-weight:400;">: metricExporter,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">exportIntervalMillis</span><span style="font-weight:400;">: </span><span style="font-weight:400;">1000</span> <span style="font-weight:400;">// Export metrics every second</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">}));</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Set the global meter provider</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">metrics.setGlobalMeterProvider(meterProvider);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Get a meter instance</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> meter = metrics.getMeter(</span><span style="font-weight:400;">'product-inventory-api'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Create metrics</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> requestCounter = meter.createCounter(</span><span style="font-weight:400;">'http.requests.total'</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">description</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'Total number of HTTP requests'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">unit</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'1'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> errorCounter = meter.createCounter(</span><span style="font-weight:400;">'http.errors.total'</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">description</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'Total number of HTTP errors'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">unit</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'1'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> requestDuration = meter.createHistogram(</span><span style="font-weight:400;">'http.request.duration'</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">description</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'HTTP request duration in milliseconds'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">unit</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'ms'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> dbQueryDuration = meter.createHistogram(</span><span style="font-weight:400;">'db.query.duration'</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">description</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'Database query duration in milliseconds'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">unit</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'ms'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> cacheOperationDuration = meter.createHistogram(</span><span style="font-weight:400;">'cache.operation.duration'</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">description</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'Cache operation duration in milliseconds'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">unit</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'ms'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> cacheHitCounter = meter.createCounter(</span><span style="font-weight:400;">'cache.hits.total'</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">description</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'Total number of cache hits'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">unit</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'1'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> cacheMissCounter = meter.createCounter(</span><span style="font-weight:400;">'cache.misses.total'</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">description</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'Total number of cache misses'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">unit</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'1'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// System metrics</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> cpuUsage = meter.createObservableGauge(</span><span style="font-weight:400;">'system.cpu.usage'</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">description</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'CPU usage percentage'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">unit</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'%'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> memoryUsage = meter.createObservableGauge(</span><span style="font-weight:400;">'system.memory.usage'</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">description</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'Memory usage in bytes'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">unit</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'bytes'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Register system metrics using individual callbacks</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">cpuUsage.addCallback((result) => {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> cpuUsagePercent = os.loadavg()[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">] * </span><span style="font-weight:400;">100</span><span style="font-weight:400;"> / os.cpus().length;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> result.observe(cpuUsagePercent, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">type</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'process'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">memoryUsage.addCallback((result) => {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> memUsage = process.memoryUsage();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> result.observe(memUsage.heapUsed, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">type</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'heap'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> result.observe(memUsage.rss, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">type</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'rss'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">});</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">// Create a middleware to track HTTP metrics</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> metricsMiddleware = (req, res, next) => {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> startTime = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> path = req.route?.path || req.path;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> method = req.method;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Increment request counter</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestCounter.add(</span><span style="font-weight:400;">1</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> method,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> path,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">status</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'pending'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Track response</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> res.on(</span><span style="font-weight:400;">'finish'</span><span style="font-weight:400;">, () => {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> duration = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now() - startTime;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> status = res.statusCode;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Record request duration</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestDuration.record(duration, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> method,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> path,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">status</span><span style="font-weight:400;">: status.toString()</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Update request counter with final status</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestCounter.add(</span><span style="font-weight:400;">1</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> method,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> path,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">status</span><span style="font-weight:400;">: status.toString()</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Increment error counter for 4xx and 5xx status codes</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">if</span><span style="font-weight:400;"> (status >= </span><span style="font-weight:400;">400</span><span style="font-weight:400;">) {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> errorCounter.add(</span><span style="font-weight:400;">1</span><span style="font-weight:400;">, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> method,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> path,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">status</span><span style="font-weight:400;">: status.toString()</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> next();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">};</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">module</span><span style="font-weight:400;">.exports = {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> meter,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestCounter,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> errorCounter,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestDuration,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> dbQueryDuration,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> cacheOperationDuration,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> cacheHitCounter,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> cacheMissCounter,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> metricsMiddleware</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">};</span></span>
<span style="background-color:#fff0cf;"><span style="font-weight:400;"></span></span>
<span style="background-color:#fff0cf;"><span style="font-weight:400;"></span></span>
Finally, we sprinkled the actual logging and metric instrumentation calls throughout our main application code (<span style="background-color:#fff0cf;">src/index.js</span>
). For example:
<span style="font-weight:400;">…</span>
<span style="font-weight:400;">const</span><span style="font-weight:400;"> { setupTelemetry } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'./telemetry'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { logger } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'./logger'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { metricsMiddleware, dbQueryDuration, cacheOperationDuration, cacheHitCounter, cacheMissCounter } = </span><span style="font-weight:400;">require</span><span style="font-weight:400;">(</span><span style="font-weight:400;">'./metrics'</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /><br /></span>
<span style="font-weight:400;">…</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;">async</span> <span style="font-weight:400;">function</span> <span style="font-weight:400;">startServer</span><span style="font-weight:400;">() {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">try</span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">await</span><span style="font-weight:400;"> setupTelemetry();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> …</span>
<span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Wrapper functions for Redis metrics</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">async</span> <span style="font-weight:400;">function</span> <span style="font-weight:400;">getCachedDataWithMetrics</span><span style="font-weight:400;">(key) {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> startTime = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">try</span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> result = </span><span style="font-weight:400;">await</span><span style="font-weight:400;"> redisClient.get(key);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> duration = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now() - startTime;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> cacheOperationDuration.record(duration, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">operation</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'get'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> key</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">if</span><span style="font-weight:400;"> (result) {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> cacheHitCounter.add(</span><span style="font-weight:400;">1</span><span style="font-weight:400;">, { key });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">return</span> <span style="font-weight:400;">JSON</span><span style="font-weight:400;">.parse(result);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> } </span><span style="font-weight:400;">else</span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> cacheMissCounter.add(</span><span style="font-weight:400;">1</span><span style="font-weight:400;">, { key });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">return</span> <span style="font-weight:400;">null</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> } </span><span style="font-weight:400;">catch</span><span style="font-weight:400;"> (error) {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> duration = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now() - startTime;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> cacheOperationDuration.record(duration, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">operation</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'get'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> key,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">error</span><span style="font-weight:400;">: error.message</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">throw</span><span style="font-weight:400;"> error;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /><br /></span>
<span style="font-weight:400;"> …</span><span style="font-weight:400;"><br /><br /></span>
<span style="font-weight:400;"> </span><span style="font-weight:400;">// DB queries with metrics capture added</span>
<span style="font-weight:400;"> </span><span style="font-weight:400;">async</span> <span style="font-weight:400;">function</span> <span style="font-weight:400;">executeQuery</span><span style="font-weight:400;">(query, params) {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> startTime = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">try</span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> result = </span><span style="font-weight:400;">await</span><span style="font-weight:400;"> pool.query(query, params);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> duration = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now() - startTime;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> dbQueryDuration.record(duration, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">query</span><span style="font-weight:400;">: query.split(</span><span style="font-weight:400;">' '</span><span style="font-weight:400;">)[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">].toLowerCase(), </span><span style="font-weight:400;">// GET, INSERT, UPDATE, DELETE</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> table: query.includes(</span><span style="font-weight:400;">'FROM'</span><span style="font-weight:400;">) ? query.split(</span><span style="font-weight:400;">'FROM'</span><span style="font-weight:400;">)[</span><span style="font-weight:400;">1</span><span style="font-weight:400;">].split(</span><span style="font-weight:400;">' '</span><span style="font-weight:400;">)[</span><span style="font-weight:400;">1</span><span style="font-weight:400;">] : </span><span style="font-weight:400;">'unknown'</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">return</span><span style="font-weight:400;"> result;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> } </span><span style="font-weight:400;">catch</span><span style="font-weight:400;"> (error) {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> duration = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now() - startTime;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> dbQueryDuration.record(duration, {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">query</span><span style="font-weight:400;">: query.split(</span><span style="font-weight:400;">' '</span><span style="font-weight:400;">)[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">].toLowerCase(),</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">table</span><span style="font-weight:400;">: query.includes(</span><span style="font-weight:400;">'FROM'</span><span style="font-weight:400;">) ? query.split(</span><span style="font-weight:400;">'FROM'</span><span style="font-weight:400;">)[</span><span style="font-weight:400;">1</span><span style="font-weight:400;">].split(</span><span style="font-weight:400;">' '</span><span style="font-weight:400;">)[</span><span style="font-weight:400;">1</span><span style="font-weight:400;">] : </span><span style="font-weight:400;">'unknown'</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">error</span><span style="font-weight:400;">: error.message</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">throw</span><span style="font-weight:400;"> error;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Get a single product by ID</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> app.get(</span><span style="font-weight:400;">'/api/products/:id'</span><span style="font-weight:400;">, </span><span style="font-weight:400;">async</span><span style="font-weight:400;"> (req, res) => {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> requestId = </span><span style="font-weight:400;">Math</span><span style="font-weight:400;">.random().toString(</span><span style="font-weight:400;">36</span><span style="font-weight:400;">).substring(</span><span style="font-weight:400;">7</span><span style="font-weight:400;">);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">try</span><span style="font-weight:400;"> {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> { id } = req.params;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> cacheKey = </span><span style="font-weight:400;">`product:</span><span style="font-weight:400;">${id}</span><span style="font-weight:400;">`</span><span style="font-weight:400;">;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> startTime = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Try to get from cache first</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> cacheStart = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> logger.debug({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"Cache lookup"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestId,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> cacheKey,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">cacheType</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"single-product"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">productId</span><span style="font-weight:400;">: id</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> cachedData = </span><span style="font-weight:400;">await</span><span style="font-weight:400;"> getCachedDataWithMetrics(cacheKey);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> cacheDuration = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now() - cacheStart;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">if</span><span style="font-weight:400;"> (cachedData) {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> totalDuration = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now() - startTime;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> logger.info({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"Cache hit"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestId,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> cacheKey,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">cacheType</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"single-product"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">productId</span><span style="font-weight:400;">: id,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">performance</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">cacheDuration</span><span style="font-weight:400;">: </span><span style="font-weight:400;">`</span><span style="font-weight:400;">${cacheDuration}</span><span style="font-weight:400;">ms`</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">totalDuration</span><span style="font-weight:400;">: </span><span style="font-weight:400;">`</span><span style="font-weight:400;">${totalDuration}</span><span style="font-weight:400;">ms`</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> },</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">data</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">product</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">id</span><span style="font-weight:400;">: cachedData.id,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">name</span><span style="font-weight:400;">: cachedData.name,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">category</span><span style="font-weight:400;">: cachedData.category,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">price</span><span style="font-weight:400;">: cachedData.price,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">quantity</span><span style="font-weight:400;">: cachedData.quantity</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">return</span><span style="font-weight:400;"> res.json(cachedData);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> logger.info({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"Cache miss"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestId,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> cacheKey,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">cacheType</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"single-product"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">productId</span><span style="font-weight:400;">: id,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">performance</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">cacheDuration</span><span style="font-weight:400;">: </span><span style="font-weight:400;">`</span><span style="font-weight:400;">${cacheDuration}</span><span style="font-weight:400;">ms`</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> dbStart = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> result = </span><span style="font-weight:400;">await</span><span style="font-weight:400;"> pool.query(</span><span style="font-weight:400;">'SELECT * FROM products WHERE id = $1'</span><span style="font-weight:400;">, [id]);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> dbDuration = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now() - dbStart;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">if</span><span style="font-weight:400;"> (result.rows.length === </span><span style="font-weight:400;">0</span><span style="font-weight:400;">) {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> totalDuration = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now() - startTime;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> logger.warn({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"Product not found"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestId,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">productId</span><span style="font-weight:400;">: id,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">performance</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">totalDuration</span><span style="font-weight:400;">: </span><span style="font-weight:400;">`</span><span style="font-weight:400;">${totalDuration}</span><span style="font-weight:400;">ms`</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">return</span><span style="font-weight:400;"> res.status(</span><span style="font-weight:400;">404</span><span style="font-weight:400;">).json({ </span><span style="font-weight:400;">error</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'Product not found'</span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">// Cache the result</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> cacheSetStart = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now();</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> logger.debug({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"Caching result"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestId,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> cacheKey,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">cacheType</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"single-product"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">productId</span><span style="font-weight:400;">: id,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">data</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">product</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">name</span><span style="font-weight:400;">: result.rows[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">].name,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">category</span><span style="font-weight:400;">: result.rows[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">].category,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">price</span><span style="font-weight:400;">: result.rows[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">].price,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">quantity</span><span style="font-weight:400;">: result.rows[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">].quantity</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">await</span><span style="font-weight:400;"> setCachedDataWithMetrics(cacheKey, result.rows[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">]);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> cacheSetDuration = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now() - cacheSetStart;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">const</span><span style="font-weight:400;"> totalDuration = </span><span style="font-weight:400;">Date</span><span style="font-weight:400;">.now() - startTime;</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> logger.info({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"Database fetch"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestId,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">cacheType</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"single-product"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">productId</span><span style="font-weight:400;">: id,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">performance</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">dbDuration</span><span style="font-weight:400;">: </span><span style="font-weight:400;">`</span><span style="font-weight:400;">${dbDuration}</span><span style="font-weight:400;">ms`</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">cacheSetDuration</span><span style="font-weight:400;">: </span><span style="font-weight:400;">`</span><span style="font-weight:400;">${cacheSetDuration}</span><span style="font-weight:400;">ms`</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">totalDuration</span><span style="font-weight:400;">: </span><span style="font-weight:400;">`</span><span style="font-weight:400;">${totalDuration}</span><span style="font-weight:400;">ms`</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> },</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">data</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">product</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">id</span><span style="font-weight:400;">: result.rows[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">].id,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">name</span><span style="font-weight:400;">: result.rows[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">].name,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">category</span><span style="font-weight:400;">: result.rows[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">].category,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">price</span><span style="font-weight:400;">: result.rows[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">].price,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">quantity</span><span style="font-weight:400;">: result.rows[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">].quantity</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> res.json(result.rows[</span><span style="font-weight:400;">0</span><span style="font-weight:400;">]);</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> } </span><span style="font-weight:400;">catch</span><span style="font-weight:400;"> (error) {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> logger.error({</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: </span><span style="font-weight:400;">"Error fetching product:"</span><span style="font-weight:400;">,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> requestId,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">error</span><span style="font-weight:400;">: {</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">message</span><span style="font-weight:400;">: error.message,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">stack</span><span style="font-weight:400;">: error.stack,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">id</span><span style="font-weight:400;">: req.params.id,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">timestamp</span><span style="font-weight:400;">: </span><span style="font-weight:400;">new</span> <span style="font-weight:400;">Date</span><span style="font-weight:400;">().toISOString(),</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">errorType</span><span style="font-weight:400;">: error.name,</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> </span><span style="font-weight:400;">errorCode</span><span style="font-weight:400;">: error.code</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> res.status(</span><span style="font-weight:400;">500</span><span style="font-weight:400;">).json({ </span><span style="font-weight:400;">error</span><span style="font-weight:400;">: </span><span style="font-weight:400;">'Internal server error'</span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> }</span><span style="font-weight:400;"><br /></span><span style="font-weight:400;"> });</span><span style="font-weight:400;"><br /><br /></span>
<span style="font-weight:400;"> …</span>
Step 5: Use Docker Compose to bring everything up
With the application instrumented with OpenTelemetry to emit logs, traces, and metrics, it’s time to orchestrate the full setup with Docker Compose. This simplifies the process of spinning up the collector, the app, and supporting services.
To spin up your full application, you need to make sure to pass your SolarWinds Observability SaaS data ingestion API token through the environment variable. First, run this command:
<span style="background-color:#fff0cf;"><span style="font-weight:400;">$ export API_TOKEN=</span><b>replace-this-with-your-API-token</b></span>
Then, run the following command:
<span style="background-color:#fff0cf;font-weight:400;">$ docker-compose up --build -d</span><span style="background-color:#fff0cf;"><b></b></span>
With the application up and running, you can use <span style="background-color:#fff0cf;">curl</span>
to send requests to your API, currently running at <span style="background-color:#fff0cf;">localhost:3000</span>
. For this walkthrough’s convenience, we built a few scripts (<span style="background-color:#fff0cf;">demo.sh</span>
and <span style="background-color:#fff0cf;">demo_continuous.sh</span>
) to automate a series of API calls.
Step 6: Verify that telemetry is Flowing
Your dockerized application is up and running, and the OpenTelemetry Collector (running in its own Docker container) is collecting data and exporting it to SolarWinds Observability SaaS. To verify this, navigate to the APM page.

There, you’ll see your application listed.

When you click on the application, you can see from the tab bar that there are associated traces, metrics, and logs.

View trace information
Clicking on the Traces tab shows you traces that were captured by your instrumentation.

You can click on the details for any trace to see more information.

View log information
Returning to the application overview, you can navigate to the Logs tag. This will show you the log messages captured by the OpenTelemetry Collector which were subsequently exported to SolarWinds Observability SaaS. You can also click on Logs in the main left sidebar navigation.

View metric information
You can view captured metrics either by clicking the Metrics tab in the application overview or through Analyze > Metrics in the main sidebar navigation.
This will show your custom metrics, any metrics from trace auto instrumentation, and SolarWinds Observability SaaS platform metrics.

Clicking on an individual metric will show a chart of that metric over time.

Create custom dashboards
You can create custom dashboards to visualize multiple metrics for correlation and better understanding of your telemetry data. On the Dashboards page, click Create Dashboard. Select the Standard dashboard type and click Next.

An empty dashboard will be created. Click Save. Specify a dashboard name, and then click Save again.

Returning the list of metrics for your application, you can choose any metric of interest, click on its action menu, and select Send to Dashboard.

Multiple metric series can be overlaid upon one another within a single chart.

Wrap-up: Move from local setup to production observability
In this walkthrough, we deployed the OpenTelemetry Collector in a Docker container, configured it to receive telemetry from a containerized app, and forwarded that data to SolarWinds Observability SaaS.
This setup mirrors what you might use in production—centralized, decoupled, and flexible. When you’re ready to level up your observability pipeline, try out SolarWinds Observability SaaS to see the complete picture of your system in real time.
<span style="background-color:#fff0cf;font-weight:400;"></span>
<span style="font-weight:400;"></span>
<b></b>
<span style="font-weight:400;"></span>
<b></b>
<span style="font-weight:400;"> </span>
</code></span><span style="background-color:#fff0cf;"><code><span style="font-weight:400;"></span>
<span style="font-weight:400;"> </span>
<span style="background-color:#fff0cf;"></span>
<span style="font-weight:400;"></span>
<span style="font-weight:400;"></span>
<span style="font-weight:400;"></span><span style="font-weight:400;"></span>