AWS X-Ray in Java: Complete Distributed Tracing Guide

AWS X-Ray helps developers analyze and debug distributed applications by providing end-to-end tracing capabilities. This guide demonstrates comprehensive X-Ray integration for Java applications running on AWS.

Why Use AWS X-Ray?

  • Distributed Tracing: Track requests across microservices
  • Performance Analysis: Identify bottlenecks and latency issues
  • Error Detection: Pinpoint failures in complex architectures
  • Service Map Visualization: Automatic dependency mapping
  • AWS Integration: Seamless integration with AWS services

Prerequisites

  • AWS Account with appropriate permissions
  • Java 8+ application
  • AWS SDK dependencies
  • X-Ray Daemon or AWS X-Ray API access

Step 1: Project Dependencies

Maven (pom.xml):

<dependencies> <!-- AWS X-Ray SDK --> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-core</artifactId> <version>2.14.0</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-aws-sdk</artifactId> <version>2.14.0</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-aws-sdk-v2</artifactId> <version>2.14.0</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-spring</artifactId> <version>2.14.0</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-apache-http</artifactId> <version>2.14.0</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-sql</artifactId> <version>2.14.0</version> </dependency> <!-- AWS SDK v2 --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>aws-sdk-java</artifactId> <version>2.20.0</version> </dependency> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <version>2.7.0</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.14</version> </dependency> <!-- Database --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.7.0</version> </dependency> </dependencies>

Step 2: AWS X-Ray Configuration

application.yml:

# AWS X-Ray Configuration aws: xray: enabled: true daemon: address: 127.0.0.1:2000 sampling: rules: classpath:xray-sampling-rules.json logging: level: INFO plugins: ec2: true ecs: true elastic-beanstalk: true context-missing: LOG_ERROR streaming: enabled: true batch-size: 100 batch-timeout: 1s # Spring Configuration spring: application: name: order-service profiles: active: ${ENVIRONMENT:local} # Logging for X-Ray logging: level: com.amazonaws.xray: DEBUG software.amazon.awssdk: INFO

xray-sampling-rules.json:

{ "version": 2, "rules": [ { "description": "Default rule for all requests", "host": "*", "http_method": "*", "url_path": "*", "fixed_target": 1, "rate": 0.05 }, { "description": "High priority for critical endpoints", "host": "*", "http_method": "*", "url_path": "/api/orders/*", "fixed_target": 10, "rate": 0.5 }, { "description": "Health check - no sampling", "host": "*", "http_method": "GET", "url_path": "/health", "fixed_target": 0, "rate": 0 } ], "default": { "fixed_target": 1, "rate": 0.1 } }

Step 3: X-Ray Configuration Classes

@Configuration @EnableAwsXRay @Slf4j public class XRayConfiguration { @Value("${spring.application.name:unknown}") private String applicationName; @Value("${aws.xray.daemon.address:127.0.0.1:2000}") private String daemonAddress; @Bean @Primary public AWSXRayRecorder xRayRecorder() { try { AWSXRayRecorderBuilder builder = AWSXRayRecorderBuilder.standard() .withSamplingStrategy(new LocalizedSamplingStrategy()) .withDaemonAddress(daemonAddress) .withSegmentListener(new DefaultSegmentListener()) .withTraceIdGenerator(new SecureTraceIdGenerator()); // Configure plugins builder.withPlugin(new EC2Plugin()); builder.withPlugin(new ECSPlugin()); AWSXRayRecorder recorder = builder.build(); // Set global recorder AWSXRay.setGlobalRecorder(recorder); log.info("AWS X-Ray Recorder initialized for application: {}", applicationName); return recorder; } catch (Exception e) { log.error("Failed to initialize AWS X-Ray Recorder", e); throw new RuntimeException("X-Ray initialization failed", e); } } @Bean public ServletFilter xRayFilter() { return new AWSXRayServletFilter(applicationName); } @Bean public XRaySqlDataSourceDecorator xRaySqlDataSourceDecorator() { return new XRaySqlDataSourceDecorator(); } @Bean public ApacheHttpClient xRayHttpClient() { return ApacheHttpClient.builder() .build(); } } @Configuration @Slf4j public class AwsSdkXRayConfiguration { @Bean public AwsXrayInstrumentor awsXrayInstrumentor() { return new AwsXrayInstrumentor(); } @Bean @Primary public S3Client xRayS3Client(S3Client s3Client) { return S3ClientXrayInstrumentor.instrument(s3Client); } @Bean @Primary public DynamoDbClient xRayDynamoDbClient(DynamoDbClient dynamoDbClient) { return DynamoDbClientXrayInstrumentor.instrument(dynamoDbClient); } @Bean @Primary public SqsClient xRaySqsClient(SqsClient sqsClient) { return SqsClientXrayInstrumentor.instrument(sqsClient); } }

Step 4: Core X-Ray Service Classes

@Service @Slf4j public class XRayTracingService { private final AWSXRayRecorder xRayRecorder; public XRayTracingService(AWSXRayRecorder xRayRecorder) { this.xRayRecorder = xRayRecorder; } // Create custom subsegment public <T> T traceMethod(String name, String namespace, Supplier<T> operation) { Subsegment subsegment = xRayRecorder.beginSubsegment(name); try { subsegment.putNamespace(namespace); T result = operation.get(); subsegment.setError(false); return result; } catch (Exception e) { subsegment.addException(e); subsegment.setError(true); throw e; } finally { xRayRecorder.endSubsegment(); } } public void traceMethod(String name, String namespace, Runnable operation) { Subsegment subsegment = xRayRecorder.beginSubsegment(name); try { subsegment.putNamespace(namespace); operation.run(); subsegment.setError(false); } catch (Exception e) { subsegment.addException(e); subsegment.setError(true); throw e; } finally { xRayRecorder.endSubsegment(); } } // Add metadata to current segment public void addMetadata(String key, Object value) { try { Segment segment = AWSXRay.getCurrentSegment(); if (segment != null) { segment.putMetadata(key, value); } } catch (Exception e) { log.warn("Failed to add metadata to X-Ray segment", e); } } public void addMetadata(String namespace, String key, Object value) { try { Segment segment = AWSXRay.getCurrentSegment(); if (segment != null) { segment.putMetadata(namespace, key, value); } } catch (Exception e) { log.warn("Failed to add namespaced metadata to X-Ray segment", e); } } // Add annotation to current segment public void addAnnotation(String key, Object value) { try { Segment segment = AWSXRay.getCurrentSegment(); if (segment != null) { segment.putAnnotation(key, value); } } catch (Exception e) { log.warn("Failed to add annotation to X-Ray segment", e); } } // Get current trace ID public String getTraceId() { try { Segment segment = AWSXRay.getCurrentSegment(); return segment != null ? segment.getTraceId().toString() : "unknown"; } catch (Exception e) { log.warn("Failed to get trace ID from X-Ray", e); return "unknown"; } } // Check if sampling decision is made public boolean isSampled() { try { Segment segment = AWSXRay.getCurrentSegment(); return segment != null && segment.isSampled(); } catch (Exception e) { return false; } } // Create custom segment for background jobs public <T> T traceBackgroundJob(String jobName, Supplier<T> job) { try { Segment segment = xRayRecorder.beginSegment(jobName); segment.setService("background-job"); segment.putAnnotation("job_type", "background"); try { T result = job.get(); segment.setError(false); return result; } catch (Exception e) { segment.addException(e); segment.setError(true); throw e; } finally { xRayRecorder.endSegment(); } } catch (Exception e) { log.warn("Failed to trace background job: {}", jobName, e); return job.get(); // Fallback to execution without tracing } } // HTTP client tracing public HttpGet createTracedHttpGet(String url) { HttpGet httpGet = new HttpGet(url); try { // Add trace headers for downstream services Segment segment = AWSXRay.getCurrentSegment(); if (segment != null) { TracingHeader tracingHeader = segment.getTraceHeader(); httpGet.setHeader(TracingHeader.HEADER_KEY, tracingHeader.toString()); } } catch (Exception e) { log.warn("Failed to add tracing headers to HTTP request", e); } return httpGet; } } @Component @Slf4j public class XRayHttpClientInterceptor { private final ApacheHttpClient xrayHttpClient; public XRayHttpClientInterceptor() { this.xrayHttpClient = ApacheHttpClient.builder().build(); } public <T> T executeTracedRequest(HttpUriRequest request, ResponseHandler<T> responseHandler) throws IOException { return xrayHttpClient.execute(request, responseHandler); } public HttpResponse executeTracedRequest(HttpUriRequest request) throws IOException { return xrayHttpClient.execute(request); } // For WebClient in reactive applications public ExchangeFilterFunction xRayTracingFilter() { return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { // Add X-Ray tracing headers ClientRequest tracedRequest = ClientRequest.from(clientRequest) .headers(headers -> addTracingHeaders(headers)) .build(); return Mono.just(tracedRequest); }); } private void addTracingHeaders(HttpHeaders headers) { try { Segment segment = AWSXRay.getCurrentSegment(); if (segment != null) { TracingHeader tracingHeader = segment.getTraceHeader(); headers.add(TracingHeader.HEADER_KEY, tracingHeader.toString()); } } catch (Exception e) { log.warn("Failed to add X-Ray tracing headers", e); } } }

Step 5: Database Tracing with X-Ray

@Configuration @Slf4j public class XRayDataSourceConfiguration { @Bean @Primary public DataSource xRayDataSource(DataSource dataSource) { log.info("Wrapping DataSource with X-Ray tracing"); return new XRayDataSource(dataSource); } } @Service @Slf4j public class DatabaseTracingService { private final XRayTracingService tracingService; public DatabaseTracingService(XRayTracingService tracingService) { this.tracingService = tracingService; } public <T> T traceQuery(String queryName, String sql, Supplier<T> query) { return tracingService.traceMethod(queryName, "remote", () -> { // Add SQL query as metadata (obfuscated for security) tracingService.addMetadata("sql", obfuscateSql(sql)); tracingService.addAnnotation("query_type", getQueryType(sql)); long startTime = System.currentTimeMillis(); try { T result = query.get(); long duration = System.currentTimeMillis() - startTime; // Add performance metrics tracingService.addAnnotation("query_duration_ms", duration); tracingService.addAnnotation("query_success", true); return result; } catch (Exception e) { long duration = System.currentTimeMillis() - startTime; tracingService.addAnnotation("query_duration_ms", duration); tracingService.addAnnotation("query_success", false); throw e; } }); } public void traceUpdate(String operationName, String sql, Runnable update) { tracingService.traceMethod(operationName, "remote", () -> { tracingService.addMetadata("sql", obfuscateSql(sql)); tracingService.addAnnotation("operation_type", "update"); long startTime = System.currentTimeMillis(); try { update.run(); long duration = System.currentTimeMillis() - startTime; tracingService.addAnnotation("operation_duration_ms", duration); tracingService.addAnnotation("operation_success", true); } catch (Exception e) { long duration = System.currentTimeMillis() - startTime; tracingService.addAnnotation("operation_duration_ms", duration); tracingService.addAnnotation("operation_success", false); throw e; } }); } private String obfuscateSql(String sql) { // Basic SQL obfuscation for security if (sql == null) return null; return sql.replaceAll("\\b\\d+\\b", "?") .replaceAll("'.*?'", "?") .replaceAll("\\b(?:password|pwd|secret|token|key)\\s*=\\s*'.*?'", "?"); } private String getQueryType(String sql) { if (sql == null) return "unknown"; String lowerSql = sql.trim().toLowerCase(); if (lowerSql.startsWith("select")) return "select"; if (lowerSql.startsWith("insert")) return "insert"; if (lowerSql.startsWith("update")) return "update"; if (lowerSql.startsWith("delete")) return "delete"; if (lowerSql.startsWith("create")) return "create"; if (lowerSql.startsWith("drop")) return "drop"; return "other"; } }

Step 6: Spring Boot Integration

@Aspect @Component @Slf4j public class XRayTracingAspect { private final XRayTracingService tracingService; public XRayTracingAspect(XRayTracingService tracingService) { this.tracingService = tracingService; } @Around("@annotation(traceable)") public Object traceMethod(ProceedingJoinPoint joinPoint, Traceable traceable) throws Throwable { String methodName = getMethodName(joinPoint); String namespace = traceable.namespace(); return tracingService.traceMethod(methodName, namespace, () -> { try { // Add method parameters as metadata addMethodParametersToXRay(joinPoint); return joinPoint.proceed(); } catch (Throwable throwable) { if (throwable instanceof RuntimeException) { throw (RuntimeException) throwable; } else { throw new RuntimeException(throwable); } } }); } @Around("execution(* com.yourcompany..*Service.*(..))") public Object traceServiceMethods(ProceedingJoinPoint joinPoint) throws Throwable { String className = joinPoint.getTarget().getClass().getSimpleName(); String methodName = joinPoint.getSignature().getName(); String segmentName = className + "." + methodName; return tracingService.traceMethod(segmentName, "local", () -> { try { addMethodParametersToXRay(joinPoint); return joinPoint.proceed(); } catch (Throwable throwable) { if (throwable instanceof RuntimeException) { throw (RuntimeException) throwable; } else { throw new RuntimeException(throwable); } } }); } private String getMethodName(ProceedingJoinPoint joinPoint) { String className = joinPoint.getTarget().getClass().getSimpleName(); String methodName = joinPoint.getSignature().getName(); return className + "." + methodName; } private void addMethodParametersToXRay(ProceedingJoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); String[] parameterNames = getParameterNames(joinPoint); for (int i = 0; i < args.length; i++) { String paramName = parameterNames != null && i < parameterNames.length ? parameterNames[i] : "param" + i; Object paramValue = args[i]; if (paramValue != null && !isSensitiveParameter(paramName)) { tracingService.addMetadata("method_parameters", paramName, paramValue.toString()); } } } private String[] getParameterNames(ProceedingJoinPoint joinPoint) { // Implementation to get parameter names MethodSignature signature = (MethodSignature) joinPoint.getSignature(); return signature.getParameterNames(); } private boolean isSensitiveParameter(String paramName) { String lowerParamName = paramName.toLowerCase(); return lowerParamName.contains("password") || lowerParamName.contains("secret") || lowerParamName.contains("token") || lowerParamName.contains("key"); } } // Custom annotation for tracing @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Traceable { String namespace() default "local"; boolean captureArgs() default true; } @ControllerAdvice @Slf4j public class XRayExceptionHandler { private final XRayTracingService tracingService; public XRayExceptionHandler(XRayTracingService tracingService) { this.tracingService = tracingService; } @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleException(Exception e, HttpServletRequest request) { // Record exception in X-Ray tracingService.addAnnotation("error_type", e.getClass().getSimpleName()); tracingService.addAnnotation("error_message", e.getMessage()); tracingService.addMetadata("exception", "stack_trace", getStackTrace(e)); log.error("Request failed with exception", e); ErrorResponse errorResponse = new ErrorResponse( "INTERNAL_ERROR", "An unexpected error occurred", tracingService.getTraceId() ); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse); } private String getStackTrace(Exception e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); return sw.toString(); } } @Data @AllArgsConstructor @NoArgsConstructor class ErrorResponse { private String errorCode; private String message; private String traceId; private Instant timestamp = Instant.now(); }

Step 7: Service Implementation with X-Ray

@Service @Slf4j public class OrderService { private final OrderRepository orderRepository; private final PaymentService paymentService; private final InventoryService inventoryService; private final XRayTracingService tracingService; private final DatabaseTracingService databaseTracingService; public OrderService(OrderRepository orderRepository, PaymentService paymentService, InventoryService inventoryService, XRayTracingService tracingService, DatabaseTracingService databaseTracingService) { this.orderRepository = orderRepository; this.paymentService = paymentService; this.inventoryService = inventoryService; this.tracingService = tracingService; this.databaseTracingService = databaseTracingService; } @Traceable(namespace = "business") public Order processOrder(Order order) { // Add business context to X-Ray tracingService.addAnnotation("order_id", order.getId()); tracingService.addAnnotation("customer_id", order.getCustomerId()); tracingService.addAnnotation("order_amount", order.getAmount()); tracingService.addMetadata("order", "currency", order.getCurrency()); try { // Validate order validateOrder(order); // Process payment with tracing PaymentResult paymentResult = tracingService.traceMethod( "processPayment", "remote", () -> paymentService.processPayment(order) ); // Update inventory with tracing tracingService.traceMethod("updateInventory", "remote", () -> inventoryService.updateInventory(order) ); // Save order to database with tracing Order savedOrder = databaseTracingService.traceQuery( "saveOrder", "INSERT INTO orders ...", () -> orderRepository.save(order) ); // Record business metrics tracingService.addAnnotation("order_status", "completed"); tracingService.addMetadata("business", "revenue", order.getAmount()); log.info("Order processed successfully: {}", order.getId()); return savedOrder; } catch (Exception e) { tracingService.addAnnotation("order_status", "failed"); tracingService.addMetadata("error", "order_processing_error", e.getMessage()); throw e; } } @Traceable(namespace = "database") public List<Order> getOrdersByCustomer(String customerId, Pageable pageable) { tracingService.addAnnotation("customer_id", customerId); tracingService.addAnnotation("page_size", pageable.getPageSize()); String sql = "SELECT * FROM orders WHERE customer_id = ? LIMIT ? OFFSET ?"; return databaseTracingService.traceQuery( "getOrdersByCustomer", sql, () -> orderRepository.findByCustomerId(customerId, pageable) ); } @Traceable(namespace = "background") public void processBatchOrders(List<Order> orders) { tracingService.addAnnotation("batch_size", orders.size()); int successCount = 0; int errorCount = 0; for (Order order : orders) { try { processOrder(order); successCount++; } catch (Exception e) { errorCount++; log.error("Failed to process order: {}", order.getId(), e); } } // Add batch processing results to X-Ray tracingService.addAnnotation("batch_success_count", successCount); tracingService.addAnnotation("batch_error_count", errorCount); tracingService.addAnnotation("batch_success_rate", orders.size() > 0 ? (double) successCount / orders.size() : 0); } private void validateOrder(Order order) { tracingService.traceMethod("validateOrder", "local", () -> { if (order.getAmount() <= 0) { throw new IllegalArgumentException("Invalid order amount"); } if (order.getCustomerId() == null || order.getCustomerId().trim().isEmpty()) { throw new IllegalArgumentException("Customer ID is required"); } }); } } @RestController @Slf4j public class OrderController { private final OrderService orderService; private final XRayTracingService tracingService; public OrderController(OrderService orderService, XRayTracingService tracingService) { this.orderService = orderService; this.tracingService = tracingService; } @PostMapping("/orders") public ResponseEntity<Order> createOrder(@RequestBody Order order) { log.info("Creating order for customer: {}", order.getCustomerId()); // Add HTTP request context to X-Ray tracingService.addAnnotation("http_method", "POST"); tracingService.addAnnotation("http_path", "/orders"); tracingService.addMetadata("request", "content_type", "application/json"); try { Order processedOrder = orderService.processOrder(order); // Add response context tracingService.addAnnotation("http_status", 200); tracingService.addMetadata("response", "order_id", processedOrder.getId()); return ResponseEntity.ok(processedOrder); } catch (Exception e) { tracingService.addAnnotation("http_status", 500); throw e; } } @GetMapping("/orders/customer/{customerId}") public ResponseEntity<List<Order>> getCustomerOrders( @PathVariable String customerId, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { tracingService.addAnnotation("http_method", "GET"); tracingService.addAnnotation("http_path", "/orders/customer/{customerId}"); tracingService.addAnnotation("customer_id", customerId); try { Pageable pageable = PageRequest.of(page, size); List<Order> orders = orderService.getOrdersByCustomer(customerId, pageable); tracingService.addAnnotation("http_status", 200); tracingService.addAnnotation("result_count", orders.size()); return ResponseEntity.ok(orders); } catch (Exception e) { tracingService.addAnnotation("http_status", 500); throw e; } } @GetMapping("/health") public ResponseEntity<HealthResponse> healthCheck() { // Health check endpoint - minimal tracing HealthResponse health = new HealthResponse("healthy", tracingService.getTraceId()); return ResponseEntity.ok(health); } } @Data @AllArgsConstructor class HealthResponse { private String status; private String traceId; private Instant timestamp = Instant.now(); }

Step 8: AWS Service Integration

@Service @Slf4j public class AwsServiceIntegration { private final S3Client s3Client; private final DynamoDbClient dynamoDbClient; private final SqsClient sqsClient; private final XRayTracingService tracingService; public AwsServiceIntegration(S3Client s3Client, DynamoDbClient dynamoDbClient, SqsClient sqsClient, XRayTracingService tracingService) { this.s3Client = s3Client; this.dynamoDbClient = dynamoDbClient; this.sqsClient = sqsClient; this.tracingService = tracingService; } @Traceable(namespace = "aws") public String uploadToS3(String bucketName, String key, byte[] data) { return tracingService.traceMethod("s3Upload", "aws", () -> { try { PutObjectRequest request = PutObjectRequest.builder() .bucket(bucketName) .key(key) .contentLength((long) data.length) .build(); s3Client.putObject(request, RequestBody.fromBytes(data)); tracingService.addAnnotation("s3_bucket", bucketName); tracingService.addAnnotation("s3_key", key); tracingService.addAnnotation("s3_operation", "putObject"); log.info("Successfully uploaded file to S3: {}/{}", bucketName, key); return String.format("s3://%s/%s", bucketName, key); } catch (Exception e) { tracingService.addAnnotation("s3_operation_failed", true); throw new RuntimeException("S3 upload failed", e); } }); } @Traceable(namespace = "aws") public void saveToDynamoDB(String tableName, Map<String, AttributeValue> item) { tracingService.traceMethod("dynamoDbPut", "aws", () -> { try { PutItemRequest request = PutItemRequest.builder() .tableName(tableName) .item(item) .build(); dynamoDbClient.putItem(request); tracingService.addAnnotation("dynamodb_table", tableName); tracingService.addAnnotation("dynamodb_operation", "putItem"); log.info("Successfully saved item to DynamoDB table: {}", tableName); } catch (Exception e) { tracingService.addAnnotation("dynamodb_operation_failed", true); throw new RuntimeException("DynamoDB put failed", e); } }); } @Traceable(namespace = "aws") public String sendSqsMessage(String queueUrl, String messageBody) { return tracingService.traceMethod("sqsSend", "aws", () -> { try { SendMessageRequest request = SendMessageRequest.builder() .queueUrl(queueUrl) .messageBody(messageBody) .build(); SendMessageResponse response = sqsClient.sendMessage(request); tracingService.addAnnotation("sqs_queue_url", queueUrl); tracingService.addAnnotation("sqs_operation", "sendMessage"); tracingService.addMetadata("sqs", "message_id", response.messageId()); log.info("Successfully sent message to SQS: {}", response.messageId()); return response.messageId(); } catch (Exception e) { tracingService.addAnnotation("sqs_operation_failed", true); throw new RuntimeException("SQS send failed", e); } }); } }

Step 9: Testing and Verification

@SpringBootTest @Slf4j class XRayIntegrationTest { @Autowired private XRayTracingService tracingService; @Autowired private OrderService orderService; @MockBean private OrderRepository orderRepository; @Test void testTracingService() { String result = tracingService.traceMethod("testOperation", "test", () -> { tracingService.addAnnotation("test_key", "test_value"); return "success"; }); assertEquals("success", result); } @Test void testMethodTracingWithException() { assertThrows(RuntimeException.class, () -> { tracingService.traceMethod("failingOperation", "test", () -> { throw new RuntimeException("Test exception"); }); }); } @Test void testOrderProcessingTracing() { Order testOrder = new Order("test-order", "test-customer", 100.0); when(orderRepository.save(any(Order.class))).thenReturn(testOrder); Order result = orderService.processOrder(testOrder); assertNotNull(result); assertEquals("test-order", result.getId()); // Verify tracing was applied verify(orderRepository, times(1)).save(any(Order.class)); } @Test void testBackgroundJobTracing() { String result = tracingService.traceBackgroundJob("testJob", () -> { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return "job-completed"; }); assertEquals("job-completed", result); } } // Test configuration @TestConfiguration class XRayTestConfig { @Bean @Primary public AWSXRayRecorder testXRayRecorder() { return AWSXRayRecorderBuilder.standard() .withSamplingStrategy(new NoSamplingStrategy()) .withSegmentListener(new NoOpSegmentListener()) .build(); } }

Step 10: Deployment Configuration

Dockerfile:

FROM openjdk:11-jre-slim # Install X-Ray daemon RUN apt-get update && apt-get install -y curl RUN curl -o /tmp/xray.deb https://s3.dualstack.us-east-2.amazonaws.com/aws-xray-assets.us-east-2/xray-daemon/aws-xray-daemon-3.x.deb RUN dpkg -i /tmp/xray.deb # Copy application COPY target/application.jar /app/application.jar # X-Ray daemon configuration ENV AWS_REGION=us-east-1 ENV AWS_XRAY_DAEMON_ADDRESS=0.0.0.0:2000 # Start X-Ray daemon and application CMD xray -b 0.0.0.0:2000 & java -jar /app/application.jar

Kubernetes Deployment:

apiVersion: apps/v1 kind: Deployment metadata: name: order-service labels: app: order-service spec: replicas: 3 selector: matchLabels: app: order-service template: metadata: labels: app: order-service annotations: # X-Ray sidecar configuration proxy.istio.io/config: | tracing: zipkin: address: xray-daemon:2000 spec: containers: - name: order-service image: order-service:latest env: - name: AWS_XRAY_DAEMON_ADDRESS value: "xray-daemon:2000" - name: AWS_REGION value: "us-east-1" - name: SPRING_PROFILES_ACTIVE value: "production" ports: - containerPort: 8080 resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1000m" # X-Ray daemon sidecar - name: xray-daemon image: amazon/aws-xray-daemon:latest env: - name: AWS_REGION value: "us-east-1" ports: - containerPort: 2000 protocol: UDP resources: requests: memory: "64Mi" cpu: "100m" limits: memory: "128Mi" cpu: "200m"

Key Features Implemented

  1. Distributed Tracing: End-to-end request tracing across services
  2. AWS Service Integration: Automatic tracing for S3, DynamoDB, SQS
  3. Database Tracing: SQL query performance monitoring
  4. HTTP Client Tracing: Outbound HTTP request tracing
  5. Custom Annotations: Business context and metadata
  6. Performance Analysis: Latency and bottleneck identification
  7. Error Tracking: Comprehensive error and exception tracking
  8. Spring Boot Integration: Seamless Spring application integration

Best Practices

  1. Meaningful Segment Names: Use descriptive names for segments and subsegments
  2. Namespace Organization: Use namespaces to categorize tracing data
  3. Security: Obfuscate sensitive data in traces and metadata
  4. Sampling Strategy: Configure appropriate sampling for your workload
  5. Error Handling: Properly capture and annotate exceptions
  6. Performance: Use async operations where appropriate
  7. Monitoring: Set up alerts based on trace data and performance metrics

This comprehensive AWS X-Ray implementation provides robust distributed tracing for Java applications, enabling deep insights into application performance, error detection, and dependency analysis in microservices architectures.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper