google-site-verification: google61fe8ba583a51912.html
Lumigo for Serverless in Java: Complete Distributed Tracing Guide

Introduction to Lumigo

Lumigo is a serverless monitoring and debugging platform that provides distributed tracing, performance monitoring, and troubleshooting capabilities for serverless applications. It automatically instruments AWS Lambda functions and provides end-to-end visibility across your serverless architecture.

Key Features

  • Automatic Distributed Tracing: Track requests across Lambda functions and AWS services
  • Performance Monitoring: Identify bottlenecks and cold starts
  • Debugging Tools: Real-time debugging and error analysis
  • AWS Service Integration: Automatic instrumentation for DynamoDB, S3, SQS, SNS, etc.
  • Security Insights: Detect misconfigurations and security issues

Implementation Guide

Dependencies

Add to your pom.xml:

<properties> <aws.lambda.java.version>1.2.3</aws.lambda.java.version> <lumigo.version>1.0.15</lumigo.version> <aws.java.sdk.version>2.20.56</aws.java.sdk.version> </properties> <dependencies> <!-- AWS Lambda Java Core --> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> <version>${aws.lambda.java.version}</version> </dependency> <!-- Lumigo Tracer --> <dependency> <groupId>io.lumigo</groupId> <artifactId>lumigo-java-tracer</artifactId> <version>${lumigo.version}</version> </dependency> <!-- AWS SDK v2 --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>dynamodb</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>s3</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>sqs</artifactId> <version>${aws.java.sdk.version}</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> </dependencies>

Basic Lumigo Setup

Environment Configuration

package com.example.lumigo; import java.util.HashMap; import java.util.Map; public class LumigoConfig { // Lumigo environment variables public static final String LUMIGO_TOKEN = "LUMIGO_TRACER_TOKEN"; public static final String LUMIGO_DEBUG = "LUMIGO_DEBUG"; public static final String LUMIGO_TAGS = "LUMIGO_TAGS"; public static final String AWS_REGION = "AWS_REGION"; public static Map<String, String> getLumigoConfig() { Map<String, String> config = new HashMap<>(); // Required: Lumigo tracer token config.put(LUMIGO_TOKEN, System.getenv(LUMIGO_TOKEN)); // Optional: Enable debug mode config.put(LUMIGO_DEBUG, "true"); // Optional: Add custom tags config.put(LUMIGO_TAGS, "environment=production,team=backend"); return config; } public static void validateConfig() { String token = System.getenv(LUMIGO_TOKEN); if (token == null || token.trim().isEmpty()) { throw new IllegalStateException("LUMIGO_TRACER_TOKEN environment variable is required"); } } public static String getAwsRegion() { return System.getenv(AWS_REGION) != null ? System.getenv(AWS_REGION) : "us-east-1"; } }

Basic Lambda Handler with Lumigo

package com.example.lumigo; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.LambdaLogger; import io.lumigo.core.Lumigo; import io.lumigo.core.utils.LumigoLoggingUtils; import java.util.Map; public class BasicLumigoHandler implements RequestHandler<Map<String, Object>, Map<String, Object>> { static { // Initialize Lumigo tracer Lumigo.getInstance().init(); } @Override public Map<String, Object> handleRequest(Map<String, Object> input, Context context) { LambdaLogger logger = context.getLogger(); try { // Log with Lumigo context LumigoLoggingUtils.info("Starting request processing", Map.of("requestId", context.getAwsRequestId(), "functionName", context.getFunctionName())); // Add custom business data to trace Lumigo.getInstance().addExecutionTag("customerId", input.getOrDefault("customerId", "unknown").toString()); Lumigo.getInstance().addExecutionTag("operation", "processOrder"); // Business logic Map<String, Object> result = processBusinessLogic(input, context); LumigoLoggingUtils.info("Request completed successfully"); return result; } catch (Exception e) { // Errors are automatically captured by Lumigo LumigoLoggingUtils.error("Error processing request", e); throw e; } } private Map<String, Object> processBusinessLogic(Map<String, Object> input, Context context) { // Simulate business logic Map<String, Object> result = new java.util.HashMap<>(); result.put("status", "success"); result.put("processedAt", java.time.Instant.now().toString()); result.put("requestId", context.getAwsRequestId()); // Add custom span for business logic Lumigo.getInstance().startSpan("businessLogic"); try { // Simulate work Thread.sleep(100); // Add custom metrics Lumigo.getInstance().addExecutionTag("processingTime", "100ms"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Processing interrupted", e); } finally { Lumigo.getInstance().stopSpan(); } return result; } }

Advanced Lumigo Implementation

Custom Lumigo Tracer Wrapper

package com.example.lumigo; import io.lumigo.core.Lumigo; import io.lumigo.core.utils.LumigoLoggingUtils; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SdkResponse; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; public class LumigoTracer { private static final Lumigo lumigo = Lumigo.getInstance(); public static void init() { LumigoConfig.validateConfig(); lumigo.init(); } public static <T> T traceOperation(String operationName, Supplier<T> operation) { return traceOperation(operationName, Map.of(), operation); } public static <T> T traceOperation(String operationName, Map<String, Object> tags, Supplier<T> operation) { lumigo.startSpan(operationName); try { // Add custom tags to the span tags.forEach((key, value) -> lumigo.addSpanTag(key, value.toString())); T result = operation.get(); lumigo.addSpanTag("status", "success"); return result; } catch (Exception e) { lumigo.addSpanTag("status", "error"); lumigo.addSpanTag("error", e.getMessage()); throw e; } finally { lumigo.stopSpan(); } } public static void traceAsyncOperation(String operationName, Runnable operation) { traceAsyncOperation(operationName, Map.of(), operation); } public static void traceAsyncOperation(String operationName, Map<String, Object> tags, Runnable operation) { lumigo.startSpan(operationName); try { tags.forEach((key, value) -> lumigo.addSpanTag(key, value.toString())); operation.run(); lumigo.addSpanTag("status", "success"); } catch (Exception e) { lumigo.addSpanTag("status", "error"); lumigo.addSpanTag("error", e.getMessage()); throw e; } finally { lumigo.stopSpan(); } } public static <T extends SdkRequest> T wrapRequest(T request, String serviceName) { // Add tracing headers to AWS SDK requests return lumigo.injectSpanToAwsSdkRequest(request, serviceName); } public static <T extends SdkResponse> T wrapResponse(T response, String operation) { // Capture response metadata if (response != null) { lumigo.addSpanTag("aws.requestId", response.responseMetadata().requestId()); lumigo.addSpanTag("aws.operation", operation); } return response; } public static void addCustomMetric(String name, Number value) { lumigo.addExecutionTag("metric." + name, value.toString()); } public static void addBusinessContext(String key, Object value) { lumigo.addExecutionTag("business." + key, value.toString()); } public static void recordColdStart() { lumigo.addExecutionTag("coldStart", "true"); } public static void recordWarmStart() { lumigo.addExecutionTag("coldStart", "false"); } }

AWS Service Integration

package com.example.lumigo.aws; import com.example.lumigo.LumigoTracer; import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.core.SdkRequest; import software.amazon.awssdk.core.SdkResponse; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.*; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.*; import software.amazon.awssdk.services.sqs.SqsClient; import software.amazon.awssdk.services.sqs.model.*; import java.util.Map; import java.util.concurrent.CompletableFuture; public class AWSServiceWrapper { private final DynamoDbClient dynamoDbClient; private final S3Client s3Client; private final SqsClient sqsClient; public AWSServiceWrapper() { this.dynamoDbClient = DynamoDbClient.builder() .region(Region.of(System.getenv("AWS_REGION"))) .credentialsProvider(EnvironmentVariableCredentialsProvider.create()) .build(); this.s3Client = S3Client.builder() .region(Region.of(System.getenv("AWS_REGION"))) .credentialsProvider(EnvironmentVariableCredentialsProvider.create()) .build(); this.sqsClient = SqsClient.builder() .region(Region.of(System.getenv("AWS_REGION"))) .credentialsProvider(EnvironmentVariableCredentialsProvider.create()) .build(); } // DynamoDB Operations public CompletableFuture<GetItemResponse> getItemAsync(String tableName, Map<String, AttributeValue> key) { return LumigoTracer.traceOperation("DynamoDB.GetItem", Map.of("tableName", tableName, "key", key.toString()), () -> { GetItemRequest request = GetItemRequest.builder() .tableName(tableName) .key(key) .build(); // Wrap request for tracing GetItemRequest tracedRequest = LumigoTracer.wrapRequest(request, "dynamodb"); return dynamoDbClient.getItem(tracedRequest); }); } public PutItemResponse putItem(String tableName, Map<String, AttributeValue> item) { return LumigoTracer.traceOperation("DynamoDB.PutItem", Map.of("tableName", tableName, "itemSize", item.size()), () -> { PutItemRequest request = PutItemRequest.builder() .tableName(tableName) .item(item) .build(); PutItemRequest tracedRequest = LumigoTracer.wrapRequest(request, "dynamodb"); PutItemResponse response = dynamoDbClient.putItem(tracedRequest); return LumigoTracer.wrapResponse(response, "PutItem"); }); } // S3 Operations public GetObjectResponse getObject(String bucketName, String key) { return LumigoTracer.traceOperation("S3.GetObject", Map.of("bucketName", bucketName, "key", key), () -> { GetObjectRequest request = GetObjectRequest.builder() .bucket(bucketName) .key(key) .build(); GetObjectRequest tracedRequest = LumigoTracer.wrapRequest(request, "s3"); GetObjectResponse response = s3Client.getObject(tracedRequest); return LumigoTracer.wrapResponse(response, "GetObject"); }); } public PutObjectResponse putObject(String bucketName, String key, String content) { return LumigoTracer.traceOperation("S3.PutObject", Map.of("bucketName", bucketName, "key", key, "contentLength", content.length()), () -> { PutObjectRequest request = PutObjectRequest.builder() .bucket(bucketName) .key(key) .build(); PutObjectRequest tracedRequest = LumigoTracer.wrapRequest(request, "s3"); // Note: You'd need to handle the actual content streaming PutObjectResponse response = s3Client.putObject(tracedRequest, software.amazon.awssdk.core.sync.RequestBody.fromString(content)); return LumigoTracer.wrapResponse(response, "PutObject"); }); } // SQS Operations public SendMessageResponse sendMessage(String queueUrl, String messageBody) { return LumigoTracer.traceOperation("SQS.SendMessage", Map.of("queueUrl", queueUrl, "messageSize", messageBody.length()), () -> { SendMessageRequest request = SendMessageRequest.builder() .queueUrl(queueUrl) .messageBody(messageBody) .build(); SendMessageRequest tracedRequest = LumigoTracer.wrapRequest(request, "sqs"); SendMessageResponse response = sqsClient.sendMessage(tracedRequest); LumigoTracer.addCustomMetric("sqs.messageId", response.messageId()); return LumigoTracer.wrapResponse(response, "SendMessage"); }); } public ReceiveMessageResponse receiveMessages(String queueUrl) { return LumigoTracer.traceOperation("SQS.ReceiveMessage", Map.of("queueUrl", queueUrl), () -> { ReceiveMessageRequest request = ReceiveMessageRequest.builder() .queueUrl(queueUrl) .maxNumberOfMessages(10) .build(); ReceiveMessageRequest tracedRequest = LumigoTracer.wrapRequest(request, "sqs"); ReceiveMessageResponse response = sqsClient.receiveMessage(tracedRequest); LumigoTracer.addCustomMetric("sqs.messagesReceived", response.messages().size()); return LumigoTracer.wrapResponse(response, "ReceiveMessage"); }); } public void close() { dynamoDbClient.close(); s3Client.close(); sqsClient.close(); } }

Complete Serverless Application

Order Processing Lambda

package com.example.lumigo.handlers; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import com.example.lumigo.LumigoTracer; import com.example.lumigo.aws.AWSServiceWrapper; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; public class OrderProcessingHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { private final AWSServiceWrapper awsServices; private final ObjectMapper objectMapper; private static boolean isColdStart = true; public OrderProcessingHandler() { // Initialize Lumigo LumigoTracer.init(); this.awsServices = new AWSServiceWrapper(); this.objectMapper = new ObjectMapper(); // Record cold start if (isColdStart) { LumigoTracer.recordColdStart(); isColdStart = false; } else { LumigoTracer.recordWarmStart(); } } @Override public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { return LumigoTracer.traceOperation("OrderProcessing", Map.of("httpMethod", input.getHttpMethod(), "path", input.getPath(), "requestId", context.getAwsRequestId()), () -> { try { // Parse and validate input OrderRequest orderRequest = parseOrderRequest(input); // Add business context LumigoTracer.addBusinessContext("orderId", orderRequest.getOrderId()); LumigoTracer.addBusinessContext("customerId", orderRequest.getCustomerId()); LumigoTracer.addBusinessContext("totalAmount", orderRequest.getTotalAmount()); // Process the order OrderResponse orderResponse = processOrder(orderRequest, context); // Return successful response return createSuccessResponse(orderResponse); } catch (ValidationException e) { LumigoTracer.addCustomMetric("validationErrors", 1); return createErrorResponse(400, e.getMessage()); } catch (PaymentException e) { LumigoTracer.addCustomMetric("paymentErrors", 1); return createErrorResponse(402, e.getMessage()); } catch (Exception e) { LumigoTracer.addCustomMetric("unhandledErrors", 1); return createErrorResponse(500, "Internal server error"); } }); } private OrderRequest parseOrderRequest(APIGatewayProxyRequestEvent input) throws ValidationException { try { if (input.getBody() == null) { throw new ValidationException("Request body is required"); } OrderRequest request = objectMapper.readValue(input.getBody(), OrderRequest.class); // Validate required fields if (request.getCustomerId() == null || request.getCustomerId().trim().isEmpty()) { throw new ValidationException("customerId is required"); } if (request.getItems() == null || request.getItems().isEmpty()) { throw new ValidationException("At least one item is required"); } return request; } catch (Exception e) { throw new ValidationException("Invalid request format: " + e.getMessage()); } } private OrderResponse processOrder(OrderRequest orderRequest, Context context) { return LumigoTracer.traceOperation("ProcessOrderBusinessLogic", Map.of("orderId", orderRequest.getOrderId(), "itemCount", orderRequest.getItems().size()), () -> { // Step 1: Validate inventory validateInventory(orderRequest); // Step 2: Process payment processPayment(orderRequest); // Step 3: Create order record createOrderRecord(orderRequest); // Step 4: Update inventory updateInventory(orderRequest); // Step 5: Send confirmation sendConfirmation(orderRequest); return new OrderResponse( orderRequest.getOrderId(), "COMPLETED", "Order processed successfully", context.getAwsRequestId() ); }); } private void validateInventory(OrderRequest orderRequest) { LumigoTracer.traceOperation("ValidateInventory", () -> { // Simulate inventory validation for (OrderItem item : orderRequest.getItems()) { // Check if item is available boolean available = checkItemAvailability(item.getProductId(), item.getQuantity()); if (!available) { throw new ValidationException("Product " + item.getProductId() + " is out of stock"); } } LumigoTracer.addCustomMetric("inventoryChecks", orderRequest.getItems().size()); }); } private void processPayment(OrderRequest orderRequest) { LumigoTracer.traceOperation("ProcessPayment", Map.of("amount", orderRequest.getTotalAmount(), "paymentMethod", orderRequest.getPaymentMethod()), () -> { // Simulate payment processing boolean paymentSuccess = simulatePaymentProcessing(orderRequest); if (!paymentSuccess) { throw new PaymentException("Payment processing failed"); } LumigoTracer.addCustomMetric("paymentAmount", orderRequest.getTotalAmount()); }); } private void createOrderRecord(OrderRequest orderRequest) { LumigoTracer.traceOperation("CreateOrderRecord", () -> { try { // Save order to DynamoDB Map<String, software.amazon.awssdk.services.dynamodb.model.AttributeValue> item = createDynamoDBItem(orderRequest); awsServices.putItem("orders-table", item); LumigoTracer.addCustomMetric("orderCreated", 1); } catch (Exception e) { throw new RuntimeException("Failed to create order record", e); } }); } private void updateInventory(OrderRequest orderRequest) { LumigoTracer.traceOperation("UpdateInventory", () -> { // Simulate inventory update for (OrderItem item : orderRequest.getItems()) { updateItemInventory(item.getProductId(), item.getQuantity()); } // Send SQS message for inventory updates String message = createInventoryUpdateMessage(orderRequest); awsServices.sendMessage("inventory-updates-queue", message); }); } private void sendConfirmation(OrderRequest orderRequest) { LumigoTracer.traceOperation("SendConfirmation", () -> { // Send SNS notification or email String notification = createConfirmationMessage(orderRequest); // Implementation would use SNS or SES }); } // Helper methods private boolean checkItemAvailability(String productId, int quantity) { // Simulate inventory check return true; } private boolean simulatePaymentProcessing(OrderRequest orderRequest) { // Simulate payment processing return orderRequest.getTotalAmount() < 1000; // Reject large amounts for demo } private void updateItemInventory(String productId, int quantity) { // Simulate inventory update } private Map<String, software.amazon.awssdk.services.dynamodb.model.AttributeValue> createDynamoDBItem(OrderRequest orderRequest) { Map<String, software.amazon.awssdk.services.dynamodb.model.AttributeValue> item = new HashMap<>(); item.put("orderId", software.amazon.awssdk.services.dynamodb.model.AttributeValue.builder() .s(orderRequest.getOrderId()).build()); item.put("customerId", software.amazon.awssdk.services.dynamodb.model.AttributeValue.builder() .s(orderRequest.getCustomerId()).build()); item.put("totalAmount", software.amazon.awssdk.services.dynamodb.model.AttributeValue.builder() .n(String.valueOf(orderRequest.getTotalAmount())).build()); return item; } private String createInventoryUpdateMessage(OrderRequest orderRequest) { try { Map<String, Object> message = new HashMap<>(); message.put("orderId", orderRequest.getOrderId()); message.put("items", orderRequest.getItems()); message.put("timestamp", System.currentTimeMillis()); return objectMapper.writeValueAsString(message); } catch (Exception e) { throw new RuntimeException("Failed to create inventory update message", e); } } private String createConfirmationMessage(OrderRequest orderRequest) { return String.format("Order %s confirmed for customer %s", orderRequest.getOrderId(), orderRequest.getCustomerId()); } private APIGatewayProxyResponseEvent createSuccessResponse(OrderResponse orderResponse) { try { String body = objectMapper.writeValueAsString(orderResponse); return new APIGatewayProxyResponseEvent() .withStatusCode(200) .withBody(body) .withHeaders(Map.of("Content-Type", "application/json")); } catch (Exception e) { throw new RuntimeException("Failed to create success response", e); } } private APIGatewayProxyResponseEvent createErrorResponse(int statusCode, String message) { try { Map<String, String> errorResponse = Map.of( "error", message, "timestamp", java.time.Instant.now().toString() ); String body = objectMapper.writeValueAsString(errorResponse); return new APIGatewayProxyResponseEvent() .withStatusCode(statusCode) .withBody(body) .withHeaders(Map.of("Content-Type", "application/json")); } catch (Exception e) { throw new RuntimeException("Failed to create error response", e); } } // Exception classes public static class ValidationException extends RuntimeException { public ValidationException(String message) { super(message); } } public static class PaymentException extends RuntimeException { public PaymentException(String message) { super(message); } } } // Data classes class OrderRequest { private String orderId; private String customerId; private double totalAmount; private String paymentMethod; private java.util.List<OrderItem> items; // Constructors, getters, and setters public OrderRequest() {} public String getOrderId() { return orderId; } public void setOrderId(String orderId) { this.orderId = orderId; } public String getCustomerId() { return customerId; } public void setCustomerId(String customerId) { this.customerId = customerId; } public double getTotalAmount() { return totalAmount; } public void setTotalAmount(double totalAmount) { this.totalAmount = totalAmount; } public String getPaymentMethod() { return paymentMethod; } public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; } public java.util.List<OrderItem> getItems() { return items; } public void setItems(java.util.List<OrderItem> items) { this.items = items; } } class OrderItem { private String productId; private String productName; private int quantity; private double price; // Constructors, getters, and setters public OrderItem() {} public String getProductId() { return productId; } public void setProductId(String productId) { this.productId = productId; } public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity = quantity; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } } class OrderResponse { private String orderId; private String status; private String message; private String requestId; public OrderResponse(String orderId, String status, String message, String requestId) { this.orderId = orderId; this.status = status; this.message = message; this.requestId = requestId; } // Getters and setters public String getOrderId() { return orderId; } public void setOrderId(String orderId) { this.orderId = orderId; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getRequestId() { return requestId; } public void setRequestId(String requestId) { this.requestId = requestId; } }

SAM Template Configuration

template.yaml

AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Parameters: LumigoTracerToken: Type: String Description: Lumigo tracer token NoEcho: true Globals: Function: Timeout: 30 MemorySize: 512 Runtime: java11 Environment: Variables: LUMIGO_TRACER_TOKEN: !Ref LumigoTracerToken LUMIGO_DEBUG: "true" POWERTOOLS_SERVICE_NAME: order-service Resources: OrderProcessingFunction: Type: AWS::Serverless::Function Properties: FunctionName: order-processing CodeUri: target/order-service.jar Handler: com.example.lumigo.handlers.OrderProcessingHandler::handleRequest Policies: - DynamoDBCrudPolicy: TableName: !Ref OrdersTable - SQSSendMessagePolicy: QueueName: !GetRef InventoryUpdatesQueue - S3ReadPolicy: BucketName: !Ref ConfigBucket Events: ApiEvent: Type: Api Properties: Path: /orders Method: post Layers: - !Sub "arn:aws:lambda:${AWS::Region}:114300393969:layer:lumigo-java-tracer:32" OrdersTable: Type: AWS::DynamoDB::Table Properties: TableName: orders-table AttributeDefinitions: - AttributeName: orderId AttributeType: S KeySchema: - AttributeName: orderId KeyType: HASH BillingMode: PAY_PER_REQUEST InventoryUpdatesQueue: Type: AWS::SQS::Queue Properties: QueueName: inventory-updates-queue ConfigBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub "order-service-config-${AWS::AccountId}" Outputs: OrderProcessingApi: Description: "API Gateway endpoint URL for Order Processing function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/orders" OrderProcessingFunction: Description: "Order Processing Lambda Function ARN" Value: !GetAtt OrderProcessingFunction.Arn

Performance Monitoring

Custom Metrics and Monitoring

package com.example.lumigo.monitoring; import com.example.lumigo.LumigoTracer; import java.util.concurrent.atomic.AtomicLong; public class PerformanceMonitor { private static final AtomicLong totalRequests = new AtomicLong(0); private static final AtomicLong successfulRequests = new AtomicLong(0); private static final AtomicLong failedRequests = new AtomicLong(0); private static final AtomicLong totalProcessingTime = new AtomicLong(0); public static void recordRequestStart() { totalRequests.incrementAndGet(); } public static void recordRequestSuccess(long processingTime) { successfulRequests.incrementAndGet(); totalProcessingTime.addAndGet(processingTime); // Send custom metrics to Lumigo LumigoTracer.addCustomMetric("requests.successful", 1); LumigoTracer.addCustomMetric("processing.time", processingTime); } public static void recordRequestFailure(String errorType) { failedRequests.incrementAndGet(); LumigoTracer.addCustomMetric("requests.failed", 1); LumigoTracer.addCustomMetric("error." + errorType, 1); } public static void recordColdStart() { LumigoTracer.addCustomMetric("coldStarts", 1); } public static void recordMemoryUsage() { Runtime runtime = Runtime.getRuntime(); long usedMemory = runtime.totalMemory() - runtime.freeMemory(); long maxMemory = runtime.maxMemory(); LumigoTracer.addCustomMetric("memory.used", usedMemory); LumigoTracer.addCustomMetric("memory.max", maxMemory); LumigoTracer.addCustomMetric("memory.percent", (usedMemory * 100) / maxMemory); } public static void calculateAndEmitMetrics() { long total = totalRequests.get(); long successful = successfulRequests.get(); long failed = failedRequests.get(); if (total > 0) { double successRate = (successful * 100.0) / total; double averageProcessingTime = totalProcessingTime.get() / successful; LumigoTracer.addCustomMetric("success.rate", successRate); LumigoTracer.addCustomMetric("avg.processing.time", averageProcessingTime); } } }

Testing with Lumigo

Unit Tests

package com.example.lumigo; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class LumigoTracerTest { @Mock private io.lumigo.core.Lumigo lumigo; @Test void testTraceOperationSuccess() { AtomicBoolean operationExecuted = new AtomicBoolean(false); String result = LumigoTracer.traceOperation("testOperation", Map.of("testKey", "testValue"), () -> { operationExecuted.set(true); return "success"; }); assertTrue(operationExecuted.get()); assertEquals("success", result); } @Test void testTraceOperationException() { assertThrows(RuntimeException.class, () -> { LumigoTracer.traceOperation("testOperation", () -> { throw new RuntimeException("Test exception"); }); }); } @Test void testAddCustomMetric() { // This would test metric addition // Implementation depends on actual Lumigo API } } class OrderProcessingHandlerTest { @Test void testOrderProcessingSuccess() { // Test successful order processing // Mock AWS services and verify Lumigo tracing calls } @Test void testOrderProcessingValidationError() { // Test validation errors // Verify appropriate Lumigo tags are set } @Test void testColdStartTracking() { // Test cold start detection and tracking } }

Best Practices

1. Environment-specific Configuration

package com.example.lumigo.config; import java.util.Map; public class EnvironmentConfig { public enum Environment { DEV, STAGING, PRODUCTION } public static Environment getCurrentEnvironment() { String env = System.getenv("ENVIRONMENT"); if ("production".equalsIgnoreCase(env)) { return Environment.PRODUCTION; } else if ("staging".equalsIgnoreCase(env)) { return Environment.STAGING; } else { return Environment.DEV; } } public static Map<String, String> getLumigoConfig() { Environment env = getCurrentEnvironment(); Map<String, String> config = Map.of( "LUMIGO_TRACER_TOKEN", System.getenv("LUMIGO_TRACER_TOKEN"), "LUMIGO_DEBUG", env == Environment.PRODUCTION ? "false" : "true", "LUMIGO_TAGS", getEnvironmentTags(env) ); return config; } private static String getEnvironmentTags(Environment env) { return String.format("environment=%s,version=%s", env.name().toLowerCase(), System.getenv("APP_VERSION") != null ? System.getenv("APP_VERSION") : "unknown"); } }

2. Error Handling and Retry Logic

package com.example.lumigo.resilience; import com.example.lumigo.LumigoTracer; import java.util.function.Supplier; public class RetryHandler { public static <T> T withRetry(Supplier<T> operation, String operationName, int maxRetries) { int attempt = 0; Exception lastException = null; while (attempt <= maxRetries) { try { return LumigoTracer.traceOperation( operationName + (attempt > 0 ? "-retry-" + attempt : ""), Map.of("attempt", attempt, "maxRetries", maxRetries), operation); } catch (Exception e) { lastException = e; attempt++; if (attempt <= maxRetries) { LumigoTracer.addCustomMetric("retries." + operationName, 1); try { Thread.sleep(calculateBackoff(attempt)); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException("Retry interrupted", ie); } } } } throw new RuntimeException("Operation failed after " + maxRetries + " retries", lastException); } private static long calculateBackoff(int attempt) { return Math.min(1000 * (long) Math.pow(2, attempt), 30000); // Exponential backoff, max 30s } }

Conclusion

This comprehensive Lumigo implementation for Java serverless applications provides:

  • Automatic distributed tracing across Lambda functions and AWS services
  • Custom business context for better debugging and monitoring
  • Performance monitoring with custom metrics
  • Error tracking and resilience patterns
  • Environment-specific configurations for different deployment stages

Key benefits include:

  1. End-to-end visibility across your serverless architecture
  2. Reduced debugging time with detailed traces and context
  3. Performance optimization insights
  4. Production-ready error handling and monitoring
  5. AWS service integration with automatic instrumentation

The implementation follows serverless best practices and provides a solid foundation for building observable, maintainable serverless applications in Java.

Leave a Reply

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


Macro Nepal Helper