Skip to content
Back to skills

Opentelemetry

ASecurity

Adding traces, metrics, or log correlation to a Java / Spring Boot service

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 1, 2026
ai-agentsgojavabashspringdockerapidatabasebackendsecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 1, 2026

npx -y skills add sharmapuneet1510/awesome-prompts --skill opentelemetry --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Opentelemetry?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Opentelemetry
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sharmapuneet1510-opentelemetry/badge)](https://www.skillsdirectory.com/skills/sharmapuneet1510-opentelemetry)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: opentelemetry
description: Adding traces, metrics, or log correlation to a Java / Spring Boot service
---

# OpenTelemetry Observability & Distributed Tracing — v1.1

## Quick Card

> Read this card first. Load a section below only when the task needs it.

| | |
|---|---|
| **Use when** | Adding traces, metrics, or log correlation to a Java / Spring Boot service |
| **Skip when** | Log formatting alone — `logger_skill` |
| **Inputs** | Service name, backend (Jaeger, Tempo, vendor), sampling needs |
| **Produces** | Dependencies via the instrumentation BOM, starter config, custom spans and metrics |
| **Steps** | 1. Import the instrumentation BOM → 2. Add the Spring Boot starter → 3. Export OTLP → 4. Add spans where auto-instrumentation stops → 5. Metrics for business events → 6. Correlate logs |
| **Done when** | A request shows one trace across services; §10 checklist passes |
| **Senior defaults** | OTLP everywhere — Jaeger, Tempo, and vendors ingest it; the Jaeger exporters are gone · one BOM for all OTel versions · `parentbased_*` samplers so a trace is never half-sampled · set `service.name` and `deployment.environment` resources · low-cardinality metric attributes — never user or order IDs |
| **Load on demand** | §2 dependencies · §3 spans · §4 propagation · §5 metrics · §6 Spring Boot · §7 DB · §9 production patterns |
| **Run report** | `html_report_skill` — adds: Spans and metrics added |
| **Pairs with** | `logger_skill`, `spring_advanced_skill` |

---

## 1. OpenTelemetry Overview

### 1.1 Three Pillars of Observability

```text
┌─────────────────────────────────────────────────────────┐
│  OBSERVABILITY = Traces + Metrics + Logs                │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  TRACES: Request flow through distributed system       │
│  ├─ Span: Single operation (HTTP request, DB query)    │
│  ├─ Trace ID: Correlate spans across services          │
│  └─ Baggage: Context passed between services           │
│                                                          │
│  METRICS: Numerical measurements (counters, gauges)    │
│  ├─ Request count, latency, error rate                │
│  ├─ JVM memory, CPU, thread count                      │
│  └─ Business metrics (orders/sec, revenue)             │
│                                                          │
│  LOGS: Structured text events                          │
│  ├─ Application logs                                   │
│  ├─ Correlation with traces via trace IDs              │
│  └─ Machine-readable JSON format                       │
│                                                          │
└─────────────────────────────────────────────────────────┘
```

### 1.2 OpenTelemetry Architecture

```text
Application Code
        ↓
   OpenTelemetry API (Facade)
        ↓
   OpenTelemetry SDK (Implementation)
        ↓
   Exporters (Jaeger, Prometheus, OTLP)
        ↓
   Backend (Jaeger, Prometheus, ELK, DataDog, etc.)
```

---

## 2. Maven Setup & Dependencies

### 2.1 OpenTelemetry Dependencies

```xml
<!-- Instrumentation BOM: also imports the core opentelemetry-bom, so SDK,
     exporters, and the Spring Boot starter stay on matching versions.
     2.31.1 was current on 2026-09-28 — check Maven Central before pinning. -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.opentelemetry.instrumentation</groupId>
            <artifactId>opentelemetry-instrumentation-bom</artifactId>
            <version>2.31.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<!-- Core OpenTelemetry API -->
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-api</artifactId>
</dependency>

<!-- OpenTelemetry SDK -->
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk</artifactId>
</dependency>

<!-- Auto-instrumentation (Spring Boot starter) -->
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>

<!-- No Jaeger exporter: the opentelemetry-exporter-jaeger-* artifacts ended at
     1.34.1. Jaeger ingests OTLP natively on 4317 (gRPC) / 4318 (HTTP), so the
     OTLP exporter below serves Jaeger too. -->

<!-- Prometheus exporter for metrics -->
<dependency>
    <groupId>io.opentelemetry.exporter</groupId>
    <artifactId>opentelemetry-exporter-prometheus</artifactId>
</dependency>

<!-- OTLP exporter (gRPC protocol) -->
<dependency>
    <groupId>io.opentelemetry.exporter</groupId>
    <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
```

### 2.2 Spring Boot Auto-Configuration

```yaml
# application.yml
otel:
  # Service identification
  service:
    name: order-service
    version: 1.0.0

  # Tracing configuration
  exporter:
    otlp:
      endpoint: http://localhost:4317  # Jaeger/OTLP collector

  # Sampling: What percentage of traces to collect
  traces:
    sampler: always_on  # or: always_off, parentbased_always_on, parentbased_always_off

  # Metrics configuration
  metrics:
    export:
      interval: 60000  # Export metrics every 60 seconds

logging:
  pattern:
    level: "%5p"
```

---

## 3. Distributed Tracing with Spans

### 3.1 Basic Span Creation

```java
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.Span;

@Service
public class OrderService {

    private static final Tracer tracer = GlobalOpenTelemetry.getTracer(
            "com.example.orders",
            "1.0.0"
    );

    /**
     * Manual span creation.
     */
    public Order createOrder(CreateOrderRequest request) {
        Span span = tracer.spanBuilder("createOrder")
                .setAttribute("customerId", request.getCustomerId())
                .setAttribute("amount", request.getAmount().doubleValue())
                .startSpan();

        try (Scope scope = span.makeCurrent()) {
            logger.info("Creating order");

            // Validate
            var order = new Order(request);

            // Add event to span
            span.addEvent("order_validated");

            // Save
            orderRepository.save(order);
            span.addEvent("order_saved", Attributes.of(
                    AttributeKey.stringKey("orderId"), order.getId().toString()
            ));

            return order;

        } catch (Exception e) {
            span.recordException(e);
            span.setStatus(StatusCode.ERROR, e.getMessage());
            throw e;

        } finally {
            span.end();
        }
    }
}
```

### 3.2 Automatic HTTP Instrumentation

```java
/**
 * Spring Boot with OpenTelemetry starter: Automatic instrumentation.
 * No manual span creation needed for HTTP requests.
 */
@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

/**
 * HTTP requests automatically instrumented:
 * - Incoming HTTP requests create spans
 * - Outgoing HTTP calls add child spans
 * - Database queries create spans
 */
@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @PostMapping
    public ResponseEntity<Order> createOrder(
            @RequestBody CreateOrderRequest request) {
        // Automatically traced
        var order = orderService.createOrder(request);
        return ResponseEntity.ok(order);
    }
}
```

### 3.3 Span Attributes & Events

```java
/**
 * Add rich context to spans.
 */
public void processOrderWithDetails(Order order) {
    Span span = tracer.spanBuilder("processOrder")
            .setAttribute("orderId", order.getId())
            .setAttribute("customerId", order.getCustomerId())
            .setAttribute("amount", order.getAmount().doubleValue())
            .setAttribute("status", order.getStatus())
            .startSpan();

    try (Scope scope = span.makeCurrent()) {
        // Add events as operations complete
        span.addEvent("validation_started");
        validateOrder(order);
        span.addEvent("validation_completed");

        span.addEvent("payment_processing_started");
        processPayment(order);
        span.addEvent("payment_completed", Attributes.of(
                AttributeKey.stringKey("paymentId"), "PAY-123",
                AttributeKey.stringKey("method"), "CREDIT_CARD"
        ));

        span.addEvent("order_confirmed");

    } finally {
        span.end();
    }
}
```

---

## 4. Context Propagation in Distributed Systems

### 4.1 Trace ID & Baggage

```java
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.baggage.Baggage;

/**
 * Context flows automatically across:
 * - HTTP headers (traceparent, tracestate)
 * - Message queues (Kafka, RabbitMQ)
 * - Async calls
 */
@Service
public class OrderService {

    /**
     * Access current span and trace ID.
     */
    public void logTraceContext() {
        Span span = Span.current();
        var spanContext = span.getSpanContext();

        logger.info("Trace ID: {}", spanContext.getTraceId());
        logger.info("Span ID: {}", spanContext.getSpanId());
    }

    /**
     * Add baggage: Context passed to downstream services.
     */
    public void setUserContext(String userId) {
        // Baggage is automatically propagated
        Baggage baggage = Baggage.builder()
                .put("userId", userId)
                .put("tenantId", "acme-corp")
                .build();

        try (Scope scope = baggage.makeCurrent()) {
            // userId and tenantId available in downstream services
            orderService.createOrder(request);
        }
    }

    /**
     * Read baggage in downstream service.
     */
    public void processWithBaggage() {
        var userId = Baggage.current().getEntryValue("userId");
        var tenantId = Baggage.current().getEntryValue("tenantId");

        logger.info("Processing for user {} in tenant {}", userId, tenantId);
    }
}
```

### 4.2 Kafka Message Instrumentation

```java
/**
 * OpenTelemetry automatically propagates context through Kafka messages.
 */
@Component
public class OrderEventProducer {

    private final KafkaTemplate<String, OrderEvent> kafkaTemplate;

    public void publishOrderEvent(Order order) {
        var event = new OrderEvent(order.getId(), order.getCustomerId());

        // Trace context automatically added to message headers
        kafkaTemplate.send("orders-topic", event);
        // Downstream consumer will receive the same trace ID
    }
}

@Component
public class OrderEventConsumer {

    @KafkaListener(topics = "orders-topic")
    public void handleOrderEvent(OrderEvent event) {
        // Automatic span created for this message
        // Trace ID from producer available here
        logger.info("Order event received: {}", event.getOrderId());

        Span span = Span.current();
        logger.info("Parent span: {}", span.getSpanContext().getTraceId());
    }
}
```

---

## 5. Metrics with OpenTelemetry

### 5.1 Meter Provider & Instruments

```java
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.MeterProvider;

@Service
public class OrderMetrics {

    private final Meter meter;
    private final LongCounter orderCounter;
    private final LongHistogram orderAmountHistogram;
    private final AtomicReference<Integer> activeOrders;

    public OrderMetrics(MeterProvider meterProvider) {
        this.meter = meterProvider.get("com.example.orders");

        // Counter: Increments (never decreases)
        this.orderCounter = meter.counterBuilder("orders.created")
                .setDescription("Number of orders created")
                .setUnit("1")
                .build();

        // Histogram: Distribution of values
        this.orderAmountHistogram = meter.histogramBuilder("orders.amount")
                .setDescription("Order amount in USD")
                .setUnit("USD")
                .ofLongs()
                .build();

        // Gauge: Current value (e.g., active connections)
        this.activeOrders = new AtomicReference<>(0);
        meter.gaugeBuilder("orders.active")
                .setDescription("Number of active orders")
                .buildWithCallback(result -> result.observe(activeOrders.get()));
    }

    public void createOrder(Order order) {
        // Increment counter
        orderCounter.add(1, Attributes.of(
                AttributeKey.stringKey("status"), "new"
        ));

        // Record histogram
        orderAmountHistogram.record(order.getAmount().longValue());

        // Update gauge
        activeOrders.set(activeOrders.get() + 1);
        // ... process order ...
        activeOrders.set(activeOrders.get() - 1);
    }
}
```

### 5.2 Prometheus Metrics Export

```java
/**
 * Spring Boot configuration for Prometheus metrics.
 */
@Configuration
public class PrometheusConfig {

    @Bean
    public MeterRegistry meterRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }
}

// application.yml
management:
  endpoints:
    web:
      exposure:
        include: prometheus,health,metrics
  endpoint:
    metrics:
      enabled: true
    prometheus:
      enabled: true

// Access metrics at:
// http://localhost:8080/actuator/prometheus
```

### 5.3 Prometheus Query Examples

```prometheus
# Counter: Total orders created
rate(orders_created_total[5m])

# Histogram: P95 latency
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

# Gauge: Active orders
orders_active

# Error rate
rate(http_requests_total{status=~"5.."}[5m])
```

---

## 6. Spring Boot OpenTelemetry Integration

### 6.1 Spring Boot Starter Configuration

```yaml
# application.yml
spring:
  application:
    name: order-service

otel:
  # Service metadata
  resource:
    attributes:
      service.name: order-service
      service.version: 1.0.0
      deployment.environment: production

  # Tracing
  traces:
    exporter: otlp
    sampler: parentbased_always_on

  exporter:
    otlp:
      endpoint: http://localhost:4317  # Jaeger's native OTLP gRPC receiver

  # Metrics
  metrics:
    exporter: prometheus
    interval: 60000

  # Logs correlation with traces
  logs:
    exporter: otlp
    context:
      api:
        enabled: true
```

### 6.2 Auto-Instrumented HTTP Requests

```java
/**
 * No code changes needed: HTTP requests automatically traced.
 */
@RestController
public class OrderController {

    private static final Logger logger = LoggerFactory.getLogger(OrderController.class);

    @PostMapping("/api/orders")
    public ResponseEntity<Order> createOrder(
            @RequestBody CreateOrderRequest request,
            HttpServletRequest httpRequest) {

        // Trace ID automatically injected
        var traceId = httpRequest.getHeader("X-Trace-Id");
        logger.info("Creating order with trace ID: {}", traceId);

        var order = orderService.createOrder(request);
        return ResponseEntity.ok(order);
    }
}
```

### 6.3 Manual Instrumentation When Needed

```java
/**
 * For custom logic not auto-instrumented.
 */
@Service
public class ComplexOrderService {

    private final Tracer tracer;

    public ComplexOrderService(Tracer tracer) {
        this.tracer = tracer;
    }

    public Order processComplexOrder(Order order) {
        Span span = tracer.spanBuilder("processComplexOrder")
                .setAttribute("orderId", order.getId())
                .startSpan();

        try (Scope scope = span.makeCurrent()) {
            // Business logic with automatic child spans
            validateOrder(order);           // Auto-instrumented by HTTP client
            processPayment(order);          // Auto-instrumented by HTTP client
            updateInventory(order);         // Auto-instrumented by HTTP client

            span.setStatus(StatusCode.OK);
            return order;

        } catch (Exception e) {
            span.recordException(e);
            span.setStatus(StatusCode.ERROR);
            throw e;

        } finally {
            span.end();
        }
    }
}
```

---

## 7. Database Query Instrumentation

### 7.1 JDBC Instrumentation

```java
/**
 * OpenTelemetry auto-instrumentation covers JDBC queries.
 * No manual span creation for database access.
 */
@Repository
public class OrderRepository {

    private final JdbcTemplate jdbcTemplate;

    public Optional<Order> findById(Long id) {
        // Automatically traced as child span
        var order = jdbcTemplate.queryForObject(
                "SELECT * FROM orders WHERE id = ?",
                new OrderRowMapper(),
                id
        );
        // Span includes: query text, duration, result count
        return Optional.ofNullable(order);
    }
}
```

### 7.2 JPA/Hibernate Instrumentation

```java
/**
 * JPA queries automatically instrumented via Hibernate bytecode enhancement.
 */
@Service
public class OrderService {

    private final OrderRepository orderRepository;

    public List<Order> getRecentOrders() {
        // Automatically traced with span name:
        // "SELECT Order FROM Order WHERE createdAt > ?"
        return orderRepository.findRecentOrders();
    }

    public void saveOrder(Order order) {
        // Automatically traced:
        // "INSERT INTO orders ..."
        // "UPDATE orders ..."
        orderRepository.save(order);
    }
}
```

---

## 8. Jaeger Backend Setup

### 8.1 Docker Compose: Jaeger Stack

```yaml
# docker-compose.yml
version: '3.8'

services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      # OTLP gRPC receiver
      - "4317:4317"
      # OTLP HTTP receiver
      - "4318:4318"
      # Jaeger agent (legacy)
      - "6831:6831/udp"
      # Jaeger compact protocol
      - "6832:6832/udp"
      # Jaeger binary protocol
      - "5775:5775/udp"
      # UI
      - "16686:16686"
    environment:
      COLLECTOR_OTLP_ENABLED: "true"

  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
```

### 8.2 Jaeger Query & Analysis

```bash
# Access Jaeger UI
open http://localhost:16686

# Search traces by service name
- Select "order-service" from dropdown
- View all traces for that service

# Filter by tag
- Select service, add filters:
  - http.status_code = 500
  - error = true
  - customerId = 123

# View trace details
- Click trace to see span waterfall
- Inspect attributes, events, exceptions
- Correlate with other services' spans
```

---

## 9. Production Observability Patterns

### 9.1 Sampling Strategies

```java
/**
 * Production requires sampling to reduce overhead.
 */
@Configuration
public class TracingSamplerConfig {

    @Bean
    public Sampler sampler() {
        // Sample 10% of traces
        return Sampler.traceIdRatioBased(0.1);

        // OR: Always sample traces with errors
        // return new ErrorAwareSampler();

        // OR: Sample based on trace ID
        // return Sampler.parentBased(Sampler.traceIdRatioBased(0.1));
    }
}

/**
 * Custom sampler: Always sample error responses.
 */
public class ErrorAwareSampler implements Sampler {

    @Override
    public SamplingResult shouldSample(Context parentContext, String traceId,
                                      String name, SpanKind spanKind,
                                      Attributes attributes, List<LinkData> parentLinks) {

        // Check if response has error attribute
        if (attributes.get(AttributeKey.stringKey("http.status_code"))
                .equals("500")) {
            return SamplingResult.recordAndSample();  // Always sample errors
        }

        // Otherwise sample 1% of requests
        return Sampler.traceIdRatioBased(0.01).shouldSample(
                parentContext, traceId, name, spanKind, attributes, parentLinks
        );
    }
}
```

### 9.2 Correlation ID Flow

```java
/**
 * Correlate logs, metrics, and traces with correlation ID.
 */
@Component
public class CorrelationIdFilter extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger(CorrelationIdFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                   HttpServletResponse response,
                                   FilterChain filterChain) throws ServletException, IOException {

        // Get or create correlation ID
        String correlationId = request.getHeader("X-Correlation-Id");
        if (correlationId == null) {
            correlationId = UUID.randomUUID().toString();
        }

        // Add to MDC for logging
        MDC.put("correlationId", correlationId);

        // Add to baggage for tracing
        Baggage baggage = Baggage.builder()
                .put("correlationId", correlationId)
                .build();

        try (Scope scope = baggage.makeCurrent()) {
            response.addHeader("X-Correlation-Id", correlationId);
            filterChain.doFilter(request, response);

        } finally {
            MDC.remove("correlationId");
        }
    }
}
```

---

## 10. OpenTelemetry Checklist

✅ Add OpenTelemetry BOM and dependencies
✅ Configure Spring Boot starter auto-instrumentation
✅ Set service name and version in configuration
✅ Configure the OTLP trace exporter (Jaeger, Tempo, and most vendors accept OTLP)
✅ Configure metrics exporter (Prometheus)
✅ Set sampling strategy appropriate for production
✅ Implement correlation ID flow
✅ Use baggage for context propagation
✅ Instrument message queue producers/consumers
✅ Add custom spans for business logic
✅ Set up Jaeger backend with Docker
✅ Configure Prometheus scrape targets
✅ Create Grafana dashboards
✅ Monitor error rates and latencies
✅ Document tracing strategy for team
✅ Train team on reading trace waterfall
✅ Set alerts on SLO violations
✅ Regularly review and optimize sampling
✅ Test context propagation across services
✅ Validate trace quality in staging before production

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…