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 to provide end-to-end visibility.

Key Features

  • Automatic Tracing: Zero-code instrumentation for AWS services
  • Distributed Tracing: End-to-end request tracking across services
  • Performance Monitoring: Latency, memory usage, and cold start tracking
  • Debugging Tools: Interactive trace exploration and error analysis
  • Security Insights: Vulnerability detection and compliance monitoring

Implementation Guide

Dependencies

Add to your pom.xml:

<properties>
<aws.lambda.java.version>1.2.3</aws.lambda.java.version>
<lumigo.version>1.0.13</lumigo.version>
<awssdk.version>2.20.56</awssdk.version>
</properties>
<dependencies>
<!-- AWS Lambda 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>${awssdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>${awssdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sns</artifactId>
<version>${awssdk.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sqs</artifactId>
<version>${awssdk.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>

Basic Lambda Function 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.Lumigo;
import io.lumigo.models.Span;
import java.util.Map;
import java.util.HashMap;
public class BasicLumigoFunction implements RequestHandler<Map<String, Object>, Map<String, Object>> {
static {
// Initialize Lumigo with your token
Lumigo.init("your-lumigo-token-here");
}
@Override
public Map<String, Object> handleRequest(Map<String, Object> input, Context context) {
LambdaLogger logger = context.getLogger();
// Start Lumigo span for this invocation
Span span = Lumigo.startSpan("BasicLumigoFunction");
try {
logger.log("Processing request: " + input);
// Add custom attributes to the span
span.addAttribute("functionName", context.getFunctionName());
span.addAttribute("awsRequestId", context.getAwsRequestId());
span.addAttribute("inputSize", input.size());
// Business logic
Map<String, Object> result = processBusinessLogic(input, context);
// Add response attributes
span.addAttribute("processingStatus", "success");
span.addAttribute("resultSize", result.size());
return result;
} catch (Exception e) {
// Record exception in Lumigo
span.recordException(e);
span.addAttribute("processingStatus", "failed");
logger.log("Error processing request: " + e.getMessage());
throw e;
} finally {
// End the span
Lumigo.endSpan(span);
}
}
private Map<String, Object> processBusinessLogic(Map<String, Object> input, Context context) {
Map<String, Object> result = new HashMap<>();
result.put("status", "processed");
result.put("message", "Hello from Lumigo-instrumented Lambda!");
result.put("timestamp", System.currentTimeMillis());
result.put("inputKeys", input.keySet());
// Simulate some work
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return result;
}
}

Advanced Lumigo Configuration

package com.example.lumigo;
import io.lumigo.Lumigo;
import io.lumigo.LumigoConfiguration;
import io.lumigo.models.Span;
public class LumigoConfigurator {
public static void initializeLumigo() {
LumigoConfiguration config = LumigoConfiguration.builder()
.token(System.getenv("LUMIGO_TRACER_TOKEN"))
.debugEnabled(Boolean.parseBoolean(System.getenv("LUMIGO_DEBUG")))
.edgeHost(System.getenv().getOrDefault("LUMIGO_EDGE", "default-edge.lumigo.io"))
.timeout(Integer.parseInt(System.getenv().getOrDefault("LUMIGO_TIMEOUT", "10000")))
.maxSizeForRequest(Integer.parseInt(System.getenv().getOrDefault("LUMIGO_MAX_REQUEST_SIZE", "2048")))
.reportTimeout(Integer.parseInt(System.getenv().getOrDefault("LUMIGO_REPORT_TIMEOUT", "60000")))
.build();
Lumigo.init(config);
}
public static LumigoConfiguration getDefaultConfig() {
return LumigoConfiguration.builder()
.token("your-lumigo-token")
.debugEnabled(false)
.timeout(10000)
.maxSizeForRequest(2048)
.reportTimeout(60000)
.edgeHost("default-edge.lumigo.io")
.build();
}
}

Serverless Application with Multiple AWS Services

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.Lumigo;
import io.lumigo.models.Span;
import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
import software.amazon.awssdk.core.SdkBytes;
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.PutObjectRequest;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.PublishRequest;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.SendMessageRequest;
import java.util.Map;
import java.util.HashMap;
import java.util.UUID;
public class OrderProcessingFunction implements RequestHandler<Map<String, Object>, Map<String, Object>> {
private final DynamoDbClient dynamoDb;
private final S3Client s3Client;
private final SnsClient snsClient;
private final SqsClient sqsClient;
private static final String ORDERS_TABLE = System.getenv("ORDERS_TABLE");
private static final String PROCESSING_BUCKET = System.getenv("PROCESSING_BUCKET");
private static final String NOTIFICATION_TOPIC = System.getenv("NOTIFICATION_TOPIC");
private static final String DEAD_LETTER_QUEUE = System.getenv("DEAD_LETTER_QUEUE");
static {
LumigoConfigurator.initializeLumigo();
}
public OrderProcessingFunction() {
this.dynamoDb = 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.snsClient = SnsClient.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();
}
@Override
public Map<String, Object> handleRequest(Map<String, Object> input, Context context) {
LambdaLogger logger = context.getLogger();
Span rootSpan = Lumigo.startSpan("OrderProcessingFunction");
try {
logger.log("Starting order processing: " + input);
// Validate input
if (!isValidOrder(input)) {
throw new IllegalArgumentException("Invalid order data");
}
String orderId = generateOrderId();
rootSpan.addAttribute("orderId", orderId);
rootSpan.addAttribute("customerId", input.get("customerId").toString());
// Step 1: Store order in DynamoDB
storeOrderInDynamoDB(input, orderId, rootSpan);
// Step 2: Save order details to S3
saveOrderToS3(input, orderId, rootSpan);
// Step 3: Process payment
boolean paymentSuccess = processPayment(input, orderId, rootSpan);
if (paymentSuccess) {
// Step 4: Send success notification
sendSuccessNotification(input, orderId, rootSpan);
Map<String, Object> result = new HashMap<>();
result.put("status", "SUCCESS");
result.put("orderId", orderId);
result.put("message", "Order processed successfully");
rootSpan.addAttribute("finalStatus", "success");
return result;
} else {
// Step 5: Send to dead letter queue for failed payments
sendToDeadLetterQueue(input, orderId, rootSpan);
Map<String, Object> result = new HashMap<>();
result.put("status", "PAYMENT_FAILED");
result.put("orderId", orderId);
result.put("message", "Payment processing failed");
rootSpan.addAttribute("finalStatus", "payment_failed");
return result;
}
} catch (Exception e) {
rootSpan.recordException(e);
rootSpan.addAttribute("finalStatus", "error");
logger.log("Error in order processing: " + e.getMessage());
throw e;
} finally {
Lumigo.endSpan(rootSpan);
}
}
private boolean isValidOrder(Map<String, Object> order) {
return order.containsKey("customerId") && 
order.containsKey("items") &&
order.containsKey("totalAmount");
}
private String generateOrderId() {
return "ORD-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
}
private void storeOrderInDynamoDB(Map<String, Object> order, String orderId, Span parentSpan) {
Span span = Lumigo.startSpan("DynamoDB-StoreOrder", parentSpan);
try {
Map<String, AttributeValue> item = new HashMap<>();
item.put("orderId", AttributeValue.builder().s(orderId).build());
item.put("customerId", AttributeValue.builder().s(order.get("customerId").toString()).build());
item.put("totalAmount", AttributeValue.builder().n(order.get("totalAmount").toString()).build());
item.put("createdAt", AttributeValue.builder().n(String.valueOf(System.currentTimeMillis())).build());
item.put("status", AttributeValue.builder().s("PROCESSING").build());
if (order.containsKey("items")) {
item.put("items", AttributeValue.builder().s(order.get("items").toString()).build());
}
PutItemRequest request = PutItemRequest.builder()
.tableName(ORDERS_TABLE)
.item(item)
.build();
dynamoDb.putItem(request);
span.addAttribute("dynamodb.table", ORDERS_TABLE);
span.addAttribute("dynamodb.operation", "PutItem");
span.addAttribute("orderId", orderId);
} catch (Exception e) {
span.recordException(e);
throw e;
} finally {
Lumigo.endSpan(span);
}
}
private void saveOrderToS3(Map<String, Object> order, String orderId, Span parentSpan) {
Span span = Lumigo.startSpan("S3-SaveOrder", parentSpan);
try {
String key = "orders/" + orderId + ".json";
String orderJson = convertToJson(order);
PutObjectRequest request = PutObjectRequest.builder()
.bucket(PROCESSING_BUCKET)
.key(key)
.contentType("application/json")
.build();
s3Client.putObject(request, 
software.amazon.awssdk.core.sync.RequestBody.fromBytes(orderJson.getBytes()));
span.addAttribute("s3.bucket", PROCESSING_BUCKET);
span.addAttribute("s3.key", key);
span.addAttribute("s3.operation", "PutObject");
} catch (Exception e) {
span.recordException(e);
throw e;
} finally {
Lumigo.endSpan(span);
}
}
private boolean processPayment(Map<String, Object> order, String orderId, Span parentSpan) {
Span span = Lumigo.startSpan("ProcessPayment", parentSpan);
try {
// Simulate payment processing
double amount = Double.parseDouble(order.get("totalAmount").toString());
span.addAttribute("payment.amount", amount);
span.addAttribute("payment.currency", "USD");
// Simulate random failures for demo (10% failure rate)
if (Math.random() < 0.1) {
span.addAttribute("payment.status", "failed");
span.addAttribute("payment.failureReason", "Insufficient funds");
return false;
}
// Simulate processing time
Thread.sleep(200 + (long)(Math.random() * 300));
span.addAttribute("payment.status", "success");
span.addAttribute("payment.gateway", "mock-gateway");
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
span.recordException(e);
span.addAttribute("payment.status", "interrupted");
return false;
} catch (Exception e) {
span.recordException(e);
span.addAttribute("payment.status", "error");
return false;
} finally {
Lumigo.endSpan(span);
}
}
private void sendSuccessNotification(Map<String, Object> order, String orderId, Span parentSpan) {
Span span = Lumigo.startSpan("SNS-SendNotification", parentSpan);
try {
String message = String.format(
"Order %s processed successfully for customer %s. Amount: %s",
orderId, order.get("customerId"), order.get("totalAmount")
);
PublishRequest request = PublishRequest.builder()
.topicArn(NOTIFICATION_TOPIC)
.message(message)
.subject("Order Processing Complete")
.build();
snsClient.publish(request);
span.addAttribute("sns.topic", NOTIFICATION_TOPIC);
span.addAttribute("sns.operation", "Publish");
span.addAttribute("notification.type", "success");
} catch (Exception e) {
span.recordException(e);
// Don't throw - notification failure shouldn't fail the whole process
} finally {
Lumigo.endSpan(span);
}
}
private void sendToDeadLetterQueue(Map<String, Object> order, String orderId, Span parentSpan) {
Span span = Lumigo.startSpan("SQS-SendToDLQ", parentSpan);
try {
Map<String, Object> dlqMessage = new HashMap<>();
dlqMessage.put("orderId", orderId);
dlqMessage.put("originalOrder", order);
dlqMessage.put("failureReason", "payment_failed");
dlqMessage.put("timestamp", System.currentTimeMillis());
SendMessageRequest request = SendMessageRequest.builder()
.queueUrl(DEAD_LETTER_QUEUE)
.messageBody(convertToJson(dlqMessage))
.build();
sqsClient.sendMessage(request);
span.addAttribute("sqs.queue", DEAD_LETTER_QUEUE);
span.addAttribute("sqs.operation", "SendMessage");
span.addAttribute("dlq.reason", "payment_failed");
} catch (Exception e) {
span.recordException(e);
// Log but don't throw - DLQ failure is serious but shouldn't break main flow
} finally {
Lumigo.endSpan(span);
}
}
private String convertToJson(Map<String, Object> data) {
try {
// Simple JSON conversion - use Jackson in production
StringBuilder json = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, Object> entry : data.entrySet()) {
if (!first) json.append(",");
json.append("\"").append(entry.getKey()).append("\":");
if (entry.getValue() instanceof String) {
json.append("\"").append(entry.getValue()).append("\"");
} else {
json.append(entry.getValue());
}
first = false;
}
json.append("}");
return json.toString();
} catch (Exception e) {
return "{\"error\": \"JSON conversion failed\"}";
}
}
}

Custom Span Creation for Business Logic

package com.example.lumigo;
import io.lumigo.Lumigo;
import io.lumigo.models.Span;
import java.util.concurrent.TimeUnit;
public class BusinessSpanManager {
public static <T> T executeWithSpan(String operationName, SpanableOperation<T> operation) {
return executeWithSpan(operationName, null, operation);
}
public static <T> T executeWithSpan(String operationName, Span parentSpan, SpanableOperation<T> operation) {
Span span = Lumigo.startSpan(operationName, parentSpan);
try {
T result = operation.execute();
span.addAttribute("operation.status", "success");
return result;
} catch (Exception e) {
span.recordException(e);
span.addAttribute("operation.status", "failed");
throw e;
} finally {
Lumigo.endSpan(span);
}
}
public static Span startBusinessSpan(String domain, String operation) {
Span span = Lumigo.startSpan(domain + "." + operation);
span.addAttribute("business.domain", domain);
span.addAttribute("business.operation", operation);
span.addAttribute("business.timestamp", System.currentTimeMillis());
return span;
}
public static void addBusinessContext(Span span, String key, Object value) {
if (span != null && value != null) {
span.addAttribute("business.context." + key, value.toString());
}
}
public static void recordBusinessMetric(Span span, String metricName, double value) {
if (span != null) {
span.addAttribute("business.metric." + metricName, value);
}
}
@FunctionalInterface
public interface SpanableOperation<T> {
T execute();
}
}
// Usage example
class OrderService {
public Order processOrder(Order order) {
return BusinessSpanManager.executeWithSpan("OrderService.processOrder", () -> {
BusinessSpanManager.addBusinessContext(
Lumigo.getCurrentSpan(), "orderId", order.getId()
);
// Business logic here
validateOrder(order);
calculateTax(order);
applyDiscounts(order);
BusinessSpanManager.recordBusinessMetric(
Lumigo.getCurrentSpan(), "orderAmount", order.getTotalAmount()
);
return order;
});
}
private void validateOrder(Order order) {
BusinessSpanManager.executeWithSpan("OrderService.validateOrder", () -> {
// Validation logic
return null;
});
}
private void calculateTax(Order order) {
// Tax calculation with span
}
private void applyDiscounts(Order order) {
// Discount application with span
}
}

Async Lambda Function 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 com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import io.lumigo.Lumigo;
import io.lumigo.models.Span;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.Map;
import java.util.HashMap;
public class AsyncLumigoFunction implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
private final ExecutorService executorService;
static {
LumigoConfigurator.initializeLumigo();
}
public AsyncLumigoFunction() {
this.executorService = Executors.newFixedThreadPool(10);
}
@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) {
LambdaLogger logger = context.getLogger();
Span rootSpan = Lumigo.startSpan("AsyncLumigoFunction");
try {
logger.log("Processing async request: " + input.getBody());
// Extract trace context for async operations
Map<String, String> traceContext = extractTraceContext(input);
rootSpan.addAttribute("http.method", input.getHttpMethod());
rootSpan.addAttribute("http.path", input.getPath());
// Process asynchronously
CompletableFuture<String> asyncResult = processAsync(input.getBody(), traceContext);
// Wait for completion (with timeout)
String result = asyncResult.get(30, java.util.concurrent.TimeUnit.SECONDS);
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
response.setStatusCode(200);
response.setBody(result);
rootSpan.addAttribute("async.completed", true);
return response;
} catch (Exception e) {
rootSpan.recordException(e);
rootSpan.addAttribute("async.completed", false);
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
response.setStatusCode(500);
response.setBody("{\"error\": \"" + e.getMessage() + "\"}");
return response;
} finally {
Lumigo.endSpan(rootSpan);
}
}
private CompletableFuture<String> processAsync(String input, Map<String, String> traceContext) {
return CompletableFuture.supplyAsync(() -> {
// Create a new span for the async operation
Span asyncSpan = Lumigo.startSpan("AsyncProcessing");
try {
// Add trace context to maintain distributed tracing
if (traceContext != null) {
traceContext.forEach(asyncSpan::addAttribute);
}
// Simulate async processing
Thread.sleep(1000);
// Business logic
String result = "Processed: " + input.toUpperCase();
asyncSpan.addAttribute("processing.result", "success");
asyncSpan.addAttribute("processing.inputLength", input.length());
return result;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
asyncSpan.recordException(e);
asyncSpan.addAttribute("processing.result", "interrupted");
throw new RuntimeException("Processing interrupted", e);
} catch (Exception e) {
asyncSpan.recordException(e);
asyncSpan.addAttribute("processing.result", "failed");
throw e;
} finally {
Lumigo.endSpan(asyncSpan);
}
}, executorService);
}
private Map<String, String> extractTraceContext(APIGatewayProxyRequestEvent input) {
Map<String, String> traceContext = new HashMap<>();
// Extract headers that might contain trace information
if (input.getHeaders() != null) {
String traceId = input.getHeaders().get("X-Amzn-Trace-Id");
if (traceId != null) {
traceContext.put("xray.traceId", traceId);
}
// Extract other relevant headers
input.getHeaders().forEach((key, value) -> {
if (key.toLowerCase().startsWith("x-")) {
traceContext.put("header." + key, value);
}
});
}
return traceContext;
}
@Override
protected void finalize() throws Throwable {
executorService.shutdown();
super.finalize();
}
}

Error Handling and Custom Exceptions

package com.example.lumigo;
import io.lumigo.Lumigo;
import io.lumigo.models.Span;
public class LumigoErrorHandler {
public static void recordBusinessException(String errorCode, String message, Map<String, Object> context) {
Span currentSpan = Lumigo.getCurrentSpan();
if (currentSpan != null) {
currentSpan.addAttribute("error.code", errorCode);
currentSpan.addAttribute("error.message", message);
currentSpan.addAttribute("error.type", "business");
if (context != null) {
context.forEach((key, value) -> 
currentSpan.addAttribute("error.context." + key, value.toString())
);
}
}
}
public static void recordTechnicalException(Exception e, String component, Map<String, Object> technicalContext) {
Span currentSpan = Lumigo.getCurrentSpan();
if (currentSpan != null) {
currentSpan.recordException(e);
currentSpan.addAttribute("error.component", component);
currentSpan.addAttribute("error.type", "technical");
if (technicalContext != null) {
technicalContext.forEach((key, value) -> 
currentSpan.addAttribute("technical.context." + key, 
value != null ? value.toString() : "null")
);
}
}
}
public static class BusinessException extends RuntimeException {
private final String errorCode;
private final Map<String, Object> context;
public BusinessException(String errorCode, String message) {
this(errorCode, message, null);
}
public BusinessException(String errorCode, String message, Map<String, Object> context) {
super(message);
this.errorCode = errorCode;
this.context = context;
// Automatically record in Lumigo
recordBusinessException(errorCode, message, context);
}
public String getErrorCode() { return errorCode; }
public Map<String, Object> getContext() { return context; }
}
}
// Usage example
class PaymentService {
public void processPayment(PaymentRequest request) {
try {
if (request.getAmount() <= 0) {
throw new LumigoErrorHandler.BusinessException(
"INVALID_AMOUNT", 
"Payment amount must be positive",
Map.of("amount", request.getAmount(), "currency", request.getCurrency())
);
}
// Payment processing logic
if (!isPaymentMethodValid(request.getPaymentMethod())) {
throw new LumigoErrorHandler.BusinessException(
"INVALID_PAYMENT_METHOD",
"Unsupported payment method",
Map.of("paymentMethod", request.getPaymentMethod())
);
}
} catch (TechnicalException e) {
LumigoErrorHandler.recordTechnicalException(e, "PaymentGateway", 
Map.of("gateway", "stripe", "requestId", request.getId()));
throw e;
}
}
private boolean isPaymentMethodValid(String paymentMethod) {
// Validation logic
return true;
}
}

SAM Template for Deployment

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: "false"
Resources:
BasicLumigoFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: target/lumigo-demo-1.0.0.jar
Handler: com.example.lumigo.BasicLumigoFunction::handleRequest
Policies:
- AWSLambdaBasicExecutionRole
Tracing: Active
OrderProcessingFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: target/lumigo-demo-1.0.0.jar
Handler: com.example.lumigo.OrderProcessingFunction::handleRequest
Environment:
Variables:
ORDERS_TABLE: !Ref OrdersTable
PROCESSING_BUCKET: !Ref ProcessingBucket
NOTIFICATION_TOPIC: !Ref NotificationTopic
DEAD_LETTER_QUEUE: !Ref DeadLetterQueue
Policies:
- AWSLambdaBasicExecutionRole
- DynamoDBCrudPolicy:
TableName: !Ref OrdersTable
- S3CrudPolicy:
BucketName: !Ref ProcessingBucket
- SNSPublishMessagePolicy:
TopicName: !Ref NotificationTopic
- SQSSendMessagePolicy:
QueueName: !Ref DeadLetterQueue
Tracing: Active
Events:
ApiEvent:
Type: Api
Properties:
Path: /orders
Method: post
OrdersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: orders
AttributeDefinitions:
- AttributeName: orderId
AttributeType: S
KeySchema:
- AttributeName: orderId
KeyType: HASH
BillingMode: PAY_PER_REQUEST
ProcessingBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub "${AWS::StackName}-processing-${AWS::AccountId}"
NotificationTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub "${AWS::StackName}-notifications"
DeadLetterQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: !Sub "${AWS::StackName}-dlq"
Outputs:
BasicLumigoFunctionArn:
Description: "Basic Lumigo Function ARN"
Value: !GetAtt BasicLumigoFunction.Arn
OrderProcessingFunctionArn:
Description: "Order Processing Function ARN"
Value: !GetAtt OrderProcessingFunction.Arn
ApiEndpoint:
Description: "API Gateway Endpoint"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/orders/"

Build Configuration (Maven)

<!-- pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<mainClass>com.example.lumigo.TestRunner</mainClass>
</configuration>
</plugin>
</plugins>
</build>

Testing with Lumigo

package com.example.lumigo;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import io.lumigo.Lumigo;
import io.lumigo.models.Span;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
class OrderProcessingFunctionTest {
@Mock
private Context mockContext;
@Mock
private LambdaLogger mockLogger;
private OrderProcessingFunction function;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
when(mockContext.getLogger()).thenReturn(mockLogger);
when(mockContext.getFunctionName()).thenReturn("TestFunction");
when(mockContext.getAwsRequestId()).thenReturn("test-request-id");
// Initialize Lumigo with test token
Lumigo.init("test-token");
function = new OrderProcessingFunction();
}
@Test
void testValidOrderProcessing() {
// Given
Map<String, Object> input = new HashMap<>();
input.put("customerId", "cust-123");
input.put("items", "[{\"id\": \"item1\", \"quantity\": 2}]");
input.put("totalAmount", "100.50");
// When
Map<String, Object> result = function.handleRequest(input, mockContext);
// Then
assertNotNull(result);
assertEquals("SUCCESS", result.get("status"));
assertNotNull(result.get("orderId"));
verify(mockLogger, atLeastOnce()).log(anyString());
}
@Test
void testInvalidOrder() {
// Given
Map<String, Object> input = new HashMap<>();
input.put("customerId", "cust-123");
// Missing required fields
// When & Then
assertThrows(IllegalArgumentException.class, () -> {
function.handleRequest(input, mockContext);
});
}
@Test
void testBusinessSpanManager() {
// Given
String expectedResult = "test-result";
// When
String result = BusinessSpanManager.executeWithSpan("TestOperation", () -> {
Span currentSpan = Lumigo.getCurrentSpan();
assertNotNull(currentSpan);
currentSpan.addAttribute("test.attribute", "value");
return expectedResult;
});
// Then
assertEquals(expectedResult, result);
}
}
// Local testing runner
class TestRunner {
public static void main(String[] args) {
// Initialize Lumigo for local testing
Lumigo.init("local-test-token");
// Create test context
Context context = new Context() {
public String getAwsRequestId() { return "local-test-id"; }
public String getLogGroupName() { return "/aws/lambda/local-test"; }
public String getLogStreamName() { return "2023/01/01/[$LATEST]test"; }
public String getFunctionName() { return "LocalTestFunction"; }
public String getFunctionVersion() { return "$LATEST"; }
public String getInvokedFunctionArn() { return "arn:aws:lambda:us-east-1:123456789012:function:LocalTestFunction"; }
public com.amazonaws.services.lambda.runtime.CognitoIdentity getIdentity() { return null; }
public com.amazonaws.services.lambda.runtime.ClientContext getClientContext() { return null; }
public int getRemainingTimeInMillis() { return 30000; }
public int getMemoryLimitInMB() { return 512; }
public LambdaLogger getLogger() { return System.out::println; }
};
// Test the function
OrderProcessingFunction function = new OrderProcessingFunction();
Map<String, Object> input = Map.of(
"customerId", "test-customer",
"items", "[{\"id\": \"test-item\", \"quantity\": 1}]",
"totalAmount", "50.00"
);
try {
Map<String, Object> result = function.handleRequest(input, context);
System.out.println("Test completed successfully: " + result);
} catch (Exception e) {
System.err.println("Test failed: " + e.getMessage());
e.printStackTrace();
}
}
}

Best Practices

1. Environment Configuration

public class LumigoEnvironment {
public static final String LUMIGO_TOKEN = System.getenv("LUMIGO_TRACER_TOKEN");
public static final boolean LUMIGO_DEBUG = Boolean.parseBoolean(System.getenv("LUMIGO_DEBUG"));
public static final String LUMIGO_EDGE = System.getenv().getOrDefault("LUMIGO_EDGE", "default-edge.lumigo.io");
public static void validateConfiguration() {
if (LUMIGO_TOKEN == null || LUMIGO_TOKEN.trim().isEmpty()) {
throw new IllegalStateException("LUMIGO_TRACER_TOKEN environment variable is required");
}
}
}

2. Span Naming Conventions

public class SpanNamingConventions {
// Format: Domain.Operation
public static final String ORDER_PROCESSING = "Order.Process";
public static final String PAYMENT_PROCESSING = "Payment.Process";
public static final String NOTIFICATION_SENDING = "Notification.Send";
// AWS Service operations
public static String dynamoDbOperation(String operation, String table) {
return "DynamoDB." + operation + "." + table;
}
public static String s3Operation(String operation, String bucket) {
return "S3." + operation + "." + bucket;
}
}

3. Cold Start Optimization

public class ColdStartOptimizer {
private static boolean initialized = false;
static {
initializeLumigo();
warmUpDependencies();
}
private static void initializeLumigo() {
if (!initialized) {
LumigoConfigurator.initializeLumigo();
initialized = true;
}
}
private static void warmUpDependencies() {
// Pre-initialize AWS clients and other dependencies
try {
Class.forName("software.amazon.awssdk.services.dynamodb.DynamoDbClient");
Class.forName("software.amazon.awssdk.services.s3.S3Client");
} catch (ClassNotFoundException e) {
// Ignore in production
}
}
}

Conclusion

This comprehensive Lumigo implementation for Java serverless applications provides:

  • Automatic instrumentation for AWS Lambda functions
  • Distributed tracing across multiple AWS services
  • Custom span creation for business logic monitoring
  • Error handling with detailed context
  • Async operation support with proper context propagation
  • Testing utilities for local development
  • Best practices for production deployment

Key benefits include reduced debugging time, performance optimization insights, and comprehensive observability across your serverless architecture. The implementation follows AWS and Lumigo best practices while providing flexibility for custom instrumentation needs.

Remember to:

  • Keep your Lumigo token secure using environment variables
  • Follow span naming conventions for consistency
  • Instrument both synchronous and asynchronous operations
  • Add meaningful context to spans for better debugging
  • Test locally before deploying to production

Leave a Reply

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


Macro Nepal Helper