Skip to content

Dağıtık İzleme (Jaeger, Zipkin)

Dağıtık izleme, mikroservis mimarisinde isteklerin servisler arasında nasıl ilerlediğini takip etmek için kullanılır.

Dağıtık İzleme Kavramları

Temel Kavramlar

Trace: Bir isteğin başından sonuna kadar olan tüm yaşam döngüsü Span: Trace içindeki bir operasyonun süresini ve detaylarını temsil eder Context Propagation: Trace bilgilerinin servisler arasında aktarımı

java
// Trace ve Span yapısı örneği
public class TraceExample {
    /*
    Trace: user-order-request-12345
    ├── Span: http-request (API Gateway) [100ms]
    │   ├── Span: user-authentication (Auth Service) [20ms]
    │   ├── Span: order-creation (Order Service) [60ms]
    │   │   ├── Span: inventory-check (Inventory Service) [15ms]
    │   │   ├── Span: payment-processing (Payment Service) [35ms]
    │   │   └── Span: database-insert (Order DB) [10ms]
    │   └── Span: notification-send (Notification Service) [20ms]
    */
}

Spring Boot Tracing Entegrasyonu

xml
<!-- pom.xml - Tracing bağımlılıkları -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-tracing-bridge-brave</artifactId>
    </dependency>
    <dependency>
        <groupId>io.zipkin.reporter2</groupId>
        <artifactId>zipkin-reporter-brave</artifactId>
    </dependency>
    <dependency>
        <groupId>io.zipkin.reporter2</groupId>
        <artifactId>zipkin-sender-okhttp3</artifactId>
    </dependency>
</dependencies>

Tracing Configuration

yaml
# application.yml - Tracing yapılandırması
management:
  tracing:
    sampling:
      probability: 1.0  # Production'da 0.1 (10%) kullanın
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans
      timeout: 10s
      read-timeout: 10s
      connect-timeout: 10s

spring:
  application:
    name: order-service
  sleuth:
    sampler:
      probability: 1.0
    zipkin:
      base-url: http://localhost:9411
    web:
      skip-pattern: /actuator.*

Span Management

Custom Span Creation

java
@Service
@Slf4j
public class OrderService {
    
    private final Tracer tracer;
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
    
    public OrderService(Tracer tracer, PaymentService paymentService, 
                       InventoryService inventoryService) {
        this.tracer = tracer;
        this.paymentService = paymentService;
        this.inventoryService = inventoryService;
    }
    
    public Order createOrder(CreateOrderRequest request) {
        // Ana operasyon için span oluştur
        Span orderSpan = tracer.nextSpan()
                .name("order.create")
                .tag("order.customer_id", request.getCustomerId())
                .tag("order.total_amount", String.valueOf(request.getTotalAmount()))
                .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(orderSpan)) {
            log.info("Starting order creation for customer: {}", request.getCustomerId());
            
            // Inventory kontrolü
            boolean inventoryAvailable = checkInventory(request);
            orderSpan.tag("inventory.available", String.valueOf(inventoryAvailable));
            
            if (!inventoryAvailable) {
                orderSpan.tag("order.status", "failed")
                        .tag("failure.reason", "insufficient_inventory");
                throw new InsufficientInventoryException("Insufficient inventory");
            }
            
            // Payment işlemi
            PaymentResult paymentResult = processPayment(request);
            orderSpan.tag("payment.status", paymentResult.getStatus());
            
            // Order oluşturma
            Order order = createOrderEntity(request, paymentResult);
            orderSpan.tag("order.id", order.getId())
                    .tag("order.status", "completed");
            
            log.info("Order created successfully: {}", order.getId());
            return order;
            
        } catch (Exception e) {
            orderSpan.tag("error", true)
                    .tag("error.message", e.getMessage())
                    .tag("order.status", "failed");
            log.error("Order creation failed", e);
            throw e;
        } finally {
            orderSpan.end();
        }
    }
    
    private boolean checkInventory(CreateOrderRequest request) {
        Span inventorySpan = tracer.nextSpan()
                .name("inventory.check")
                .tag("inventory.product_count", String.valueOf(request.getItems().size()))
                .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(inventorySpan)) {
            // Simulated inventory check
            Thread.sleep(50); // Simulate network call
            boolean available = inventoryService.checkAvailability(request.getItems());
            inventorySpan.tag("inventory.result", String.valueOf(available));
            return available;
        } catch (Exception e) {
            inventorySpan.tag("error", true).tag("error.message", e.getMessage());
            throw new RuntimeException("Inventory check failed", e);
        } finally {
            inventorySpan.end();
        }
    }
    
    private PaymentResult processPayment(CreateOrderRequest request) {
        Span paymentSpan = tracer.nextSpan()
                .name("payment.process")
                .tag("payment.amount", String.valueOf(request.getTotalAmount()))
                .tag("payment.method", request.getPaymentMethod())
                .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(paymentSpan)) {
            PaymentResult result = paymentService.processPayment(request);
            paymentSpan.tag("payment.transaction_id", result.getTransactionId())
                      .tag("payment.status", result.getStatus());
            return result;
        } catch (Exception e) {
            paymentSpan.tag("error", true).tag("error.message", e.getMessage());
            throw e;
        } finally {
            paymentSpan.end();
        }
    }
}

Tracing Aspect

java
@Aspect
@Component
@Slf4j
public class TracingAspect {
    
    private final Tracer tracer;
    
    public TracingAspect(Tracer tracer) {
        this.tracer = tracer;
    }
    
    @Around("@annotation(TraceMethod)")
    public Object traceMethod(ProceedingJoinPoint joinPoint, TraceMethod traceMethod) throws Throwable {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        String spanName = traceMethod.name().isEmpty() ? 
                className.toLowerCase() + "." + methodName : traceMethod.name();
        
        Span span = tracer.nextSpan()
                .name(spanName)
                .tag("class", className)
                .tag("method", methodName)
                .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
            // Method parametrelerini tag olarak ekle
            Object[] args = joinPoint.getArgs();
            if (args.length > 0 && traceMethod.includeArgs()) {
                for (int i = 0; i < args.length; i++) {
                    if (args[i] != null) {
                        span.tag("arg." + i, args[i].toString());
                    }
                }
            }
            
            Object result = joinPoint.proceed();
            span.tag("result.type", result != null ? result.getClass().getSimpleName() : "null");
            return result;
            
        } catch (Exception e) {
            span.tag("error", true)
                .tag("error.class", e.getClass().getSimpleName())
                .tag("error.message", e.getMessage());
            throw e;
        } finally {
            span.end();
        }
    }
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TraceMethod {
    String name() default "";
    boolean includeArgs() default false;
}

Jaeger Entegrasyonu

Jaeger Deployment

yaml
# docker-compose.yml - Jaeger all-in-one
version: '3.8'
services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    container_name: jaeger
    ports:
      - "16686:16686"  # Jaeger UI
      - "14250:14250"  # gRPC
      - "14268:14268"  # HTTP
      - "9411:9411"    # Zipkin compatible endpoint
    environment:
      - COLLECTOR_ZIPKIN_HOST_PORT=:9411
      - COLLECTOR_OTLP_ENABLED=true
    networks:
      - tracing

  # Production Jaeger deployment
  jaeger-collector:
    image: jaegertracing/jaeger-collector:latest
    container_name: jaeger-collector
    ports:
      - "14269:14269"  # Admin port
      - "14268:14268"  # HTTP
      - "14250:14250"  # gRPC
      - "9411:9411"    # Zipkin
    environment:
      - SPAN_STORAGE_TYPE=elasticsearch
      - ES_SERVER_URLS=http://elasticsearch:9200
      - ES_NUM_SHARDS=1
      - ES_NUM_REPLICAS=0
    depends_on:
      - elasticsearch
    networks:
      - tracing

  jaeger-query:
    image: jaegertracing/jaeger-query:latest
    container_name: jaeger-query
    ports:
      - "16686:16686"
    environment:
      - SPAN_STORAGE_TYPE=elasticsearch
      - ES_SERVER_URLS=http://elasticsearch:9200
    depends_on:
      - elasticsearch
    networks:
      - tracing

networks:
  tracing:
    driver: bridge

Jaeger Client Configuration

java
@Configuration
@ConditionalOnProperty(name = "management.tracing.enabled", havingValue = "true", matchIfMissing = true)
public class JaegerConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public io.opentracing.Tracer jaegerTracer(@Value("${spring.application.name}") String serviceName) {
        return io.jaegertracing.Configuration.fromEnv(serviceName)
                .withSampler(io.jaegertracing.Configuration.SamplerConfiguration.fromEnv()
                        .withType(ConstSampler.TYPE)
                        .withParam(1))
                .withReporter(io.jaegertracing.Configuration.ReporterConfiguration.fromEnv()
                        .withLogSpans(true)
                        .withFlushInterval(1000)
                        .withMaxQueueSize(10000))
                .getTracer();
    }
    
    @Bean
    public TracingConfiguration tracingConfiguration() {
        return TracingConfiguration.builder()
                .sampler(Sampler.create(1.0f))  // 100% sampling for development
                .spanProcessor(BatchSpanProcessor.builder(
                        JaegerGrpcSpanExporter.builder()
                                .setEndpoint("http://localhost:14250")
                                .build())
                        .setMaxExportBatchSize(512)
                        .setExportTimeout(Duration.ofSeconds(2))
                        .setScheduleDelay(Duration.ofSeconds(5))
                        .build())
                .build();
    }
}

Zipkin Entegrasyonu

Zipkin Deployment

yaml
# docker-compose.yml - Zipkin
version: '3.8'
services:
  zipkin:
    image: openzipkin/zipkin:latest
    container_name: zipkin
    ports:
      - "9411:9411"
    environment:
      - STORAGE_TYPE=elasticsearch
      - ES_HOSTS=http://elasticsearch:9200
      - ES_INDEX=zipkin
      - ES_DATE_SEPARATOR=-
      - ES_INDEX_SHARDS=1
      - ES_INDEX_REPLICAS=0
    depends_on:
      - elasticsearch
    networks:
      - tracing

  zipkin-dependencies:
    image: openzipkin/zipkin-dependencies:latest
    container_name: zipkin-dependencies
    environment:
      - STORAGE_TYPE=elasticsearch
      - ES_HOSTS=elasticsearch:9200
      - ES_INDEX=zipkin
    depends_on:
      - elasticsearch
    networks:
      - tracing

Zipkin Configuration

java
@Configuration
public class ZipkinConfiguration {
    
    @Bean
    public Sender sender() {
        return OkHttpSender.create("http://localhost:9411/api/v2/spans");
    }
    
    @Bean
    public AsyncReporter<Span> spanReporter() {
        return AsyncReporter.create(sender());
    }
    
    @Bean
    public Tracing tracing(@Value("${spring.application.name}") String serviceName) {
        return Tracing.newBuilder()
                .localServiceName(serviceName)
                .spanReporter(spanReporter())
                .sampler(Sampler.create(1.0f))
                .build();
    }
    
    @Bean
    public brave.Tracer braveTracer(Tracing tracing) {
        return tracing.tracer();
    }
}

Advanced Tracing Patterns

Database Tracing

java
@Component
public class TracedDataSource {
    
    @Bean
    @Primary
    public DataSource dataSource(@Autowired DataSource actualDataSource, 
                                 @Autowired Tracer tracer) {
        return new TracingDataSourceWrapper(actualDataSource, tracer);
    }
}

public class TracingDataSourceWrapper implements DataSource {
    
    private final DataSource delegate;
    private final Tracer tracer;
    
    public TracingDataSourceWrapper(DataSource delegate, Tracer tracer) {
        this.delegate = delegate;
        this.tracer = tracer;
    }
    
    @Override
    public Connection getConnection() throws SQLException {
        Span span = tracer.nextSpan()
                .name("db.connection.get")
                .tag("db.type", "postgresql")
                .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
            Connection connection = delegate.getConnection();
            return new TracingConnectionWrapper(connection, tracer);
        } catch (SQLException e) {
            span.tag("error", true).tag("error.message", e.getMessage());
            throw e;
        } finally {
            span.end();
        }
    }
}

public class TracingConnectionWrapper implements Connection {
    
    private final Connection delegate;
    private final Tracer tracer;
    
    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        Span span = tracer.nextSpan()
                .name("db.query.prepare")
                .tag("db.statement", sql)
                .tag("db.type", "postgresql")
                .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
            PreparedStatement statement = delegate.prepareStatement(sql);
            return new TracingPreparedStatementWrapper(statement, tracer, sql);
        } catch (SQLException e) {
            span.tag("error", true).tag("error.message", e.getMessage());
            throw e;
        } finally {
            span.end();
        }
    }
}

HTTP Client Tracing

java
@Configuration
public class TracingRestTemplateConfiguration {
    
    @Bean
    public RestTemplate restTemplate(Tracer tracer) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(List.of(new TracingClientHttpRequestInterceptor(tracer)));
        return restTemplate;
    }
}

@Component
public class TracingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
    
    private final Tracer tracer;
    
    public TracingClientHttpRequestInterceptor(Tracer tracer) {
        this.tracer = tracer;
    }
    
    @Override
    public ClientHttpResponse intercept(
            HttpRequest request, 
            byte[] body,
            ClientHttpRequestExecution execution) throws IOException {
        
        Span span = tracer.nextSpan()
                .name("http.client.request")
                .tag("http.method", request.getMethod().name())
                .tag("http.url", request.getURI().toString())
                .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
            // Trace context'i header'lara ekle
            TraceContext.Injector<HttpHeaders> injector = 
                    tracing.propagation().injector(HttpHeaders::set);
            injector.inject(span.context(), request.getHeaders());
            
            ClientHttpResponse response = execution.execute(request, body);
            span.tag("http.status_code", String.valueOf(response.getStatusCode().value()));
            
            if (response.getStatusCode().is4xxClientError() || 
                response.getStatusCode().is5xxServerError()) {
                span.tag("error", true);
            }
            
            return response;
            
        } catch (IOException e) {
            span.tag("error", true).tag("error.message", e.getMessage());
            throw e;
        } finally {
            span.end();
        }
    }
}

Kafka Tracing

java
@Configuration
public class TracingKafkaConfiguration {
    
    @Bean
    public ProducerFactory<String, Object> producerFactory(Tracer tracer) {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
        
        DefaultKafkaProducerFactory<String, Object> factory = 
                new DefaultKafkaProducerFactory<>(configProps);
        
        // Tracing interceptor ekle
        factory.addPostProcessor(producer -> new TracingProducer<>(producer, tracer));
        return factory;
    }
    
    @Bean
    public ConsumerFactory<String, Object> consumerFactory(Tracer tracer) {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        configProps.put(ConsumerConfig.GROUP_ID_CONFIG, "order-service");
        configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
        
        DefaultKafkaConsumerFactory<String, Object> factory = 
                new DefaultKafkaConsumerFactory<>(configProps);
        
        // Tracing interceptor ekle
        factory.addPostProcessor(consumer -> new TracingConsumer<>(consumer, tracer));
        return factory;
    }
}

Bu Türkçe tracing dokümantasyonu, Jaeger ve Zipkin entegrasyonu, custom span yönetimi ve advanced tracing patterns'ini kapsamlı bir şekilde ele alır.

Eren Demir tarafından oluşturulmuştur.