Is Your Application a Black Box? Time to Fix That
\n\nHave you ever caught yourself thinking, "Why is my Node.js service slow, even though the code is perfect?" Or received a complaint from a user but couldn't figure out what went wrong? Sound familiar? Welcome to the world where without observability in JavaScript, you're working blind.
\n\nObservability is not just a buzzword. It's an approach that transforms your code from a black box into a transparent system. We'll learn to ask it questions: "Who is to blame?" (tracing), "What is happening right now?" (metrics), and "What happened yesterday at 3:15 AM?" (logs).
\n\nIn this article, we'll break down the three pillars of observability in Node.js: tracing, metrics, and logs. You'll get ready-to-use code examples and understand how to implement these practices today. Let's go!
\n\nTracing in JavaScript: The Journey of a Request
\n\nImagine a user clicks the "Place Order" button. This request passes through the API Gateway, authentication service, cart, payment gateway, and database. If something breaks — where do you look? Tracing (or distributed tracing) gives you a map of this journey.
\n\nWhat is Tracing in Simple Terms
\n\nTracing is a way to track the path of a specific request through all microservices and functions. Each step is a span, and the entire chain is a trace. You can see how long each stage took and where the error occurred.
\n\nOpenTelemetry: The Standard for Observability in JavaScript
\n\nOpenTelemetry (OTel) is the industry standard for collecting traces, metrics, and logs. It is supported by all major clouds and tools (Jaeger, Zipkin, Grafana).
\n\nInstallation:
\nnpm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node\n\nBasic tracing example for an Express application:
\nconst { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');\nconst { SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base');\nconst { ConsoleSpanExporter } = require('@opentelemetry/exporter-console');\nconst { ExpressInstrumentation } = require('@opentelemetry/instrumentation-express');\nconst { HttpInstrumentation } = require('@opentelemetry/instrumentation-http');\n\nconst provider = new NodeTracerProvider();\nprovider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));\nprovider.register();\n\nconst { getTracer } = require('@opentelemetry/api');\nconst tracer = getTracer('my-app');\n\n// Automatic tracing for HTTP and Express\nconst { registerInstrumentations } = require('@opentelemetry/instrumentation');\nregisterInstrumentations({\n tracerProvider: provider,\n instrumentations: [\n new ExpressInstrumentation(),\n new HttpInstrumentation()\n ]\n});\n\n// Now every request will create a trace with spans\nconst express = require('express');\nconst app = express();\n\napp.get('/api/order', async (req, res) => {\n // Manual span for business logic\n const span = tracer.startSpan('processPayment');\n try {\n // ... your logic\n await new Promise(r => setTimeout(r, 100)); // simulation\n span.setStatus({ code: 1 }); // OK\n } catch (err) {\n span.setStatus({ code: 2, message: err.message }); // ERROR\n throw err;\n } finally {\n span.end();\n }\n res.send('Order created');\n});\n\napp.listen(3000);\n\nNow in the console you'll see structured data: which request, how long each middleware took, where the error was. This is observability javascript in action.
\n\nMetrics: The Pulse of Your Application
\n\nTracing is great for individual requests, but what about the big picture? How many requests per second? What is the latency? How much memory is your Node.js consuming? Metrics handle this.
\n\nPrometheus and the Node.js Client
\n\nPrometheus is the de facto standard for collecting metrics. It "pulls" data from your application over HTTP.