DEV Community

Cover image for Observability Part 1-OpenTelemetry & Jaeger (Docker Tutorial)
AnkitDevCode
AnkitDevCode

Posted on • Edited on

Observability Part 1-OpenTelemetry & Jaeger (Docker Tutorial)

Introduction

In a microservices' architecture, a request may pass through 10+ services. Without distributed tracing, debugging latency issues is nearly impossible.
With telemetry data, you can correlate:

  • Traces → where the request spent time.
  • Metrics → how the system is performing overall.
  • Logs → detailed debugging info, tied to the same trace/span IDs.

This “three pillars of observability” approach lets teams quickly detect, triage, and resolve production issues.

What is OpenTelemetry (OTel)?

OpenTelemetry is a set of API, SDKs, libraries, and integrations aiming to standardize the generation, collection, and management of telemetry data(logs, metrics, and traces).

OpenTelemetry is a CNFC (Cloud Native Computing Foundation) project created after the merger of OpenCensus(from Google) and OpenTracing(From Uber). It is rapidly emerging as the industry standard for observability.

In short:

telemetry data = (logs, metrics, and traces)
OpenTelemetry = a set of API, SDKs, libraries, and integrations aiming to standardize the generation, collection, and management of telemetry data.

Before OpenTelemetry

Traditional APM Tools (2000s-2010s):

  • New Relic (2008) - One of the first SaaS APM platforms
  • AppDynamics (2008) - Enterprise APM with deep code-level visibility
  • Dynatrace (1998, evolved from Compuware) - AI-powered full-stack monitoring
  • Splunk (2003) - Log analysis and machine data platform
  • DataDog (2010) - Cloud-scale monitoring platform

Open Source Solutions:

  • Zipkin (2012) - Distributed tracing system by Twitter
  • Jaeger (2016) - Distributed tracing by Uber
  • Prometheus(2012)/Micrometer(2017) - Metrics monitoring
  • Grafana (2014) - Visualization and dashboards

Competing Standards:

OpenTracing and OpenCensus - Two separate projects that were created to solve the same problem: the lack of a standard for how to instrument code and send telemetry data.

Problems with the Pre-OpenTelemetry Landscape:

1. Vendor Lock-in

  • Each APM vendor had proprietary agents and APIs
  • Switching tools meant rewriting instrumentation code
  • Organizations became dependent on specific vendors

2. Fragmented Standards

  • OpenTracing focused on distributed tracing APIs
  • OpenCensus provided both tracing and metrics
  • No unified approach across the ecosystem

3. High Costs

  • Enterprise APM tools were extremely expensive
  • Per-host/per-transaction pricing models
  • Limited flexibility in data export

4. Limited Interoperability

  • Data couldn't be easily moved between tools
  • Each vendor used different data formats
  • Difficult to use best-of-breed solutions together

5. Complex Instrumentation

  • Manual instrumentation was vendor-specific
  • Inconsistent approaches across languages
  • High maintenance burden

Why OpenTelemetry Was Created

  • Standardization: OTLP provides a consistent way to export observability data, ensuring compatibility and reducing integration complexity. Unified model- Traces, metrics, and logs are captured using the same SDKs and standards, so you don’t need separate instrumentation for each vendor.
  • Flexibility: OTel doesn’t lock you into a single APM (like Datadog, New Relic, or Grafana). You can export to multiple backends simultaneously (Jaeger, Tempo, Zipkin, Prometheus, OTLP, etc.).
  • Future-Proofing: As the observability landscape evolves, OTLP enables your tracing data to remain accessible and usable across different systems.
  • Cost Efficiency Open source instrumentation and Flexibility in choosing storage/analysis tools. Also, Reduced vendor dependency

Different Ways to Integrate OpenTelemetry

Here are the various options to include OpenTelemetry in your applications:

1. Auto-Instrumentation (Agent-based)

  • Java Agent: Download and attach OTel Java agent JAR
  • No code changes required
  • Automatically instruments popular libraries
  • Run with: java -javaagent:opentelemetry-javaagent.jar -jar myapp.jar

2. SDK Integration (Manual)

  • Add OTel SDK dependencies to your project
  • Manual instrumentation in code
  • Full control over what gets traced
  • Requires code modifications

3. Spring Boot Starter

  • Spring-specific auto-configuration
  • Combines auto and manual approaches
  • Easy integration with Spring ecosystem
  • Dependency: opentelemetry-spring-boot-starter

4. Framework-Specific Integrations

  • Micrometer Bridge: Connect existing Micrometer metrics
  • Zipkin/Jaeger Bridge: Migrate from existing tracing
  • Actuator Integration: Spring Boot health/metrics endpoints

Tutorial: Tracing a Spring Boot App with OpenTelemetry Java Agent (No Code Changes Needed) & Jaeger (Dockerized)

In this example, we’ll take a look at how to enable OpenTelemetry in a Spring Boot application using the OTel Java Agent (No Code Changes Needed). With just a simple startup configuration, the agent can automatically instrument common libraries and frameworks without requiring any code changes. We’ll then configure the setup to forward traces and logs to a backend like Jaeger, so you can visualize request flows, latency, and errors in a distributed system.

Dockerfile

# -------- Stage 1: Build with Maven-------- # Use Eclipse Temurin JDK 17 with Alpine Linux FROM eclipse-temurin:17-jdk-alpine AS builder # Set working directory WORKDIR /app # Copy pom.xml and maven wrapper download dependencies COPY ./pom.xml ./pom.xml COPY ./mvnw ./mvnw COPY ./.mvn ./.mvn # Make Maven wrapper executable and download dependencies RUN chmod +x ./mvnw && ./mvnw dependency:go-offline # Copy source files and build COPY src ./src/ # Build the application RUN ./mvnw clean package -DskipTests && mv target/docker-demo-0.0.1.jar docker-demo.jar && rm -rf target # Download agent with verification RUN wget -q https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar \ -O opentelemetry-javaagent.jar # -------- Stage 2: Runtime -------- FROM eclipse-temurin:17-jre-alpine AS runtime # Set the working directory and make it writable by the non-root user WORKDIR /app # Define build arguments for user and group ARG USER_ID=1001 ARG GROUP_ID=1001 ARG USERNAME=springuser ARG GROUPNAME=springuser # Create group and user using ARGs RUN addgroup -g ${GROUP_ID} ${GROUPNAME} \ && adduser -u ${USER_ID} -G ${GROUPNAME} -s /bin/sh -D ${USERNAME} # Copy built JAR from builder stage COPY --from=builder --chown=springuser:springgroup /app/docker-demo.jar docker-demo.jar COPY --from=builder --chown=springuser:springgroup /app/opentelemetry-javaagent.jar opentelemetry-javaagent.jar # Switch to non-root user USER ${USERNAME} # Expose application port EXPOSE 8080 # Alternative using wget (no additional package needed) HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1 ENTRYPOINT ["java","-javaagent:/app/opentelemetry-javaagent.jar","-jar", "/app/docker-demo.jar"] 
Enter fullscreen mode Exit fullscreen mode

Build docker image with below command:

 docker build -t docker-demo . 
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml

version: '3.8' services: # Jaeger - Tracing Backend jaeger: image: jaegertracing/all-in-one:1.51 container_name: jaeger ports: - "16686:16686" # Jaeger UI - "14250:14250" # Jaeger gRPC - "4318:4318" # OTLP HTTP environment: - COLLECTOR_OTLP_ENABLED=true networks: - app-network # Your Spring Boot Application docker-demo: image: docker-demo:latest container_name: docker-demo ports: - "8080:8080" environment: # OpenTelemetry Configuration - OTEL_SERVICE_NAME=docker-demo - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318 - OTEL_TRACES_EXPORTER=otlp - OTEL_METRICS_EXPORTER=none - OTEL_LOGS_EXPORTER=none - OTEL_TRACES_SAMPLER=always_on # Sample ALL traces - OTEL_INSTRUMENTATION_COMMON_DEFAULT_ENABLED=true - OTEL_INSTRUMENTATION_HTTP_ENABLED=true - OTEL_INSTRUMENTATION_SPRING_WEB_ENABLED=true - OTEL_LOG_LEVEL=DEBUG # Application Configuration - JAVA_OPTS=-javaagent:/app/opentelemetry-javaagent.jar depends_on: - jaeger networks: - app-network networks: app-network: driver: bridge 
Enter fullscreen mode Exit fullscreen mode

Build and start your containers (Spring Boot app + Jaeger) with:

 docker-compose up --build 
Enter fullscreen mode Exit fullscreen mode

You should see logs from both services. The app logs will include lines like:

Which confirms that OpenTelemetry is sending traces to Jaeger.

Open the Jaeger UI

Once everything is up, head to the Jaeger UI in your browser:

http://localhost:16686/search

This UI is where you can search and explore your traces. Initially, the service list may be empty — because traces are only sent once you actually call your Spring Boot app.

Call your API Now hit your Spring Boot endpoint:

http://localhost:8080/api/customers

This request will:

  • Be intercepted by the OpenTelemetry Java Agent.
  • Generate a trace for the request.
  • Export the trace via OTLP to the Jaeger collector.

View your traces in Jaeger

Go back to the Jaeger UI http://localhost:16686/search

In the Service dropdown, you should now see your application (e.g., docker-demo).

Select it and click Find Traces.

You’ll see traces corresponding to the requests you made to /api/customers.

Click on a trace to expand and see spans (individual operations), timings, and dependencies.

Summary

At this point:

  • Your Spring Boot app is running with OpenTelemetry Java Agent attached.
  • Traces are exported via OTLP to Jaeger.
  • You can interact with your app and instantly see distributed traces in Jaeger UI.

Next Steps:

  • Add more endpoints or services to see how traces propagate.
  • Connect additional backends (Prometheus, Tempo, Zipkin, etc.) by just changing environment variables — thanks to OpenTelemetry’s vendor-agnostic design.

References & Credits

AI tools were used to assist in research and writing but final content was reviewed and verified by the author.
opentelemetry

Top comments (0)