Back to Blog
OpenTelemetry Demo: When Logs, Traces, and Metrics Finally Click
8 min readJul 10, 20260 views

OpenTelemetry Demo: When Logs, Traces, and Metrics Finally Click

We've all heard the buzzwords: logs, traces, metrics. OpenTelemetry. But how do they actually come together to tell a coherent story about your application's behavior? The OpenTelemetry Demo is a fantastic, living example of a microservice architecture instrumented end-to-end, showing you exactly ho

ObservabilityDistributed SystemsBackendToolingFull-Stack
Share

by Sunil Band

Why Observability Matters (Beyond Buzzwords)

We build complex systems. Microservices, serverless functions, message queues – the days of a single, monolithic application neatly packaged on one server are long gone for most of us. This complexity brings immense power and scalability, but it also introduces a huge problem: when something goes wrong, how do you find it?

"Just check the logs" doesn't cut it anymore. Which logs? From which service? How do you connect a slow database query in service A to a failed API call in service B, which was triggered by a user action in service C? This is where observability steps in. It’s not just about collecting data; it’s about having enough insight into your system to understand its internal state from external outputs. Logs, metrics, and traces are the three pillars of achieving that insight.

I've seen too many teams struggle with production incidents because they lacked proper observability. They'd spend hours sifting through disconnected log files, guessing at the root cause, and deploying fixes that didn't actually solve the problem. It's a frustrating, expensive cycle. That's why understanding and implementing observability isn't a nice-to-have; it's a fundamental requirement for operating modern distributed systems.

The OpenTelemetry Astronomy Shop: A Full-Stack Example

OpenTelemetry (OTel) is quickly becoming the de facto standard for instrumenting applications. It provides a vendor-agnostic set of APIs, SDKs, and tools to generate, collect, and export telemetry data. But if you're new to OTel, or even just trying to get your head around how all three pillars (logs, metrics, traces) fit together in a real system, the documentation can feel a bit abstract.

Enter the OpenTelemetry Demo. This isn't just a toy example; it's a fully-fledged, microservice-based e-commerce application – the "Astronomy Shop." It's designed specifically to showcase how to implement OpenTelemetry across different languages (Python, Java, Node.js, Go, .NET, PHP, Ruby, Erlang) and technologies, all feeding into a unified observability backend.

This demo is a godsend because it provides a concrete, runnable example of an entire distributed system instrumented with OTel. You can deploy it, break it, and then use tools like Jaeger (for traces), Prometheus/Grafana (for metrics), and Loki (for logs) to see exactly how OTel helps you diagnose issues. It’s the best way I've found to bridge the gap between theoretical knowledge and practical application.

Diving into the Architecture

The Astronomy Shop demo is built from a collection of services, each with a specific role, mimicking a real e-commerce platform. You have a frontend, an ad service, a checkout service, a product catalog, a recommendation engine, and more. Each of these services is instrumented with OpenTelemetry SDKs appropriate for its language.

When you run the demo, all this telemetry data flows through OpenTelemetry Collectors. These collectors are crucial. They're like traffic cops for your telemetry, receiving data from your services, processing it (e.g., enriching, filtering, sampling), and then exporting it to various backends. This decoupling of instrumentation from the backend is one of OTel's biggest strengths – you can swap out your observability backend without changing a single line of application code.

For instance, here's a simplified look at how a service might initialize its OpenTelemetry setup. This example uses Node.js, but the concepts are portable:

typescript
// src/instrumentation.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';

// Configure the trace exporter to send data to the OpenTelemetry Collector
const traceExporter = new OTLPTraceExporter({
  url: 'http://localhost:4318/v1/traces', // Default OTel Collector gRPC endpoint for traces
});

// Configure the metric exporter
const metricExporter = new OTLPMetricExporter({
  url: 'http://localhost:4318/v1/metrics', // Default OTel Collector gRPC endpoint for metrics
});

const sdk = new NodeSDK({
  traceExporter: traceExporter,
  metricReader: new PeriodicExportingMetricReader({
    exporter: metricExporter,
    interval: 5000, // Export metrics every 5 seconds
  }),
  instrumentations: [getNodeAutoInstrumentations()], // Auto-instrument common libraries like Express, http, fs
});

sdk.start();
console.log('OpenTelemetry SDK initialized.');

// Ensure SDK is shutdown gracefully on process exit
process.on('SIGTERM', () => {
  sdk.shutdown()
    .then(() => console.log('OpenTelemetry SDK shut down successfully'))
    .catch((error) => console.error('Error shutting down OpenTelemetry SDK', error))
    .finally(() => process.exit(0));
});

This snippet demonstrates a few key things: we're using auto-instrumentation to capture telemetry from common Node.js libraries without writing boilerplate. We're configuring exporters to send traces and metrics to a local OpenTelemetry Collector (running on port 4318 by default). This is the standard pattern you'll see across different languages in the demo.

The beauty of this approach is that your application code doesn't need to know if it's sending data to Jaeger, Zipkin, or a proprietary vendor solution. It just sends it to the OTel Collector, and the collector handles the routing and translation.

Traces: Following the Request's Journey

Traces are arguably the most powerful aspect of OpenTelemetry for distributed systems. A trace represents the end-to-end journey of a single request or transaction through your system. It's composed of spans, where each span represents a discrete operation, like an HTTP request, a database query, or a function call within a service.

The demo makes this incredibly clear. When you navigate the Astronomy Shop, add items to a cart, and check out, you're initiating a complex series of inter-service calls. In Jaeger (which is part of the demo's default observability stack), you can select a trace and see a visual waterfall diagram of every service involved, the order of operations, and the time spent in each. This instantly highlights bottlenecks and failures.

Imagine a user complains about a slow checkout. Without traces, you might be guessing if the frontend is slow, the API is slow, or the database is slow. With traces, you can pinpoint the exact span that's taking too long, whether it's an external API call from the checkoutservice or a complex query in the paymentservice.

Metrics: Quantifying System Health

While traces give you the detail of a single request, metrics give you an aggregated, statistical view of your system's health over time. Think request rates, error rates, CPU usage, memory consumption, latency percentiles, and so on. These are crucial for dashboards, alerts, and spotting trends.

In the Astronomy Shop demo, each service emits various metrics. The frontend might track page load times, the product catalog might track database query durations, and the ad service might track ad impressions. These metrics are collected by the OpenTelemetry Collector and then exported to Prometheus, which is a popular time-series database.

Once in Prometheus, you can then visualize these metrics using Grafana. The demo includes pre-built Grafana dashboards that let you see the health of individual services, overall system performance, and quickly identify if a particular service is experiencing a surge in errors or increased latency. This is your early warning system, telling you something is wrong even before you dive into specific traces.

Logs: The Granular Details

Logs are the oldest form of telemetry, and they're still essential for specific debugging. While traces tell you what happened in terms of flow, and metrics tell you how often or how much, logs provide the detailed context within a single service.

In the OTel Demo, services generate traditional log messages. The crucial part is how they're integrated with traces and metrics. Each log line should ideally include trace IDs and span IDs. This allows you to jump from a problematic span in Jaeger directly to the relevant log messages in Loki (the log aggregation system used in the demo) for that specific request.

This correlation is game-changing. Instead of searching a massive, undifferentiated log stream, you're immediately looking at the logs for the exact execution path that failed. This significantly reduces debugging time and cognitive load.

Trade-offs and Gotchas

While the OpenTelemetry Demo is fantastic for learning, remember that deploying and managing OpenTelemetry in a real production environment comes with its own set of challenges:

  1. Overhead: Instrumentation, especially full auto-instrumentation, can introduce some performance overhead. It's usually negligible, but it's something to monitor. Manually instrumenting critical paths can offer more control.
  2. Cardinality: Be mindful of the number of unique values for attributes you attach to your spans and metrics. High-cardinality attributes (like a user ID on every span) can explode storage costs and slow down query performance in your observability backend.
  3. Collector Management: The OpenTelemetry Collector is powerful, but it needs to be managed, scaled, and secured. This adds another component to your infrastructure, which requires operational expertise.
  4. Backend Choice: OTel is vendor-agnostic, but you still need to choose and configure a backend for traces (Jaeger, Tempo, DataDog), metrics (Prometheus, Grafana Cloud), and logs (Loki, ElasticSearch). Each has its own operational considerations.

The demo handles most of these complexities for you, which is great for learning, but it abstracts away some of the operational burden you'd face in a real setup. Don't let that deter you; it's a necessary complexity for robust systems.

Wrapping up

The OpenTelemetry Demo is more than just a code repository; it's an interactive learning environment for modern distributed systems observability. If you're looking to truly understand how logs, metrics, and traces fit together, and how OpenTelemetry can be the glue, this is the place to start.

My concrete next step for you: clone the opentelemetry-demo repository, run it with Docker Compose, and then spend an hour exploring the Astronomy Shop's functionality and the various dashboards in Jaeger and Grafana. Try to deliberately break a service (e.g., kill a database) and see how the telemetry changes. It'll give you a practical understanding that no amount of reading documentation can match.

bash
git clone https://github.com/open-telemetry/opentelemetry-demo.git
cd opentelemetry-demo
docker compose up -d

Then navigate to http://localhost:8080/ for the shop, http://localhost:16686/ for Jaeger, and http://localhost:3000/ for Grafana (login with admin/admin if prompted). Experiment, explore, and learn how to build truly observable systems.

More from the blog
Available for projectsReady to make something fun 🎈

Ready to build the next system?Wanna build something awesome together?

Currently accepting high-impact opportunities in frontend engineering and scalable web applications.Got a cool idea rattling around? Let's grab a virtual coffee and turn it into something people love. ☕