Observability in serverless is challenging. Lambda functions execute in ephemeral environments, making traditional APM tools ineffective. AWS Lambda Powertools for .NET provides structured logging, distributed tracing, and custom metrics out of the box—implementing AWS Well-Architected best practices without boilerplate. This guide covers installation, configuration, and production patterns for each Powertools utility.
The Observability Challenge in Serverless
Traditional observability relies on agents running alongside your application. In Lambda, you cannot install agents. The execution environment is recycled. Logs must be structured for CloudWatch Logs Insights queries. Traces must integrate with AWS X-Ray. Metrics must flow to CloudWatch Metrics.
Lambda Powertools solves this with three core utilities:
flowchart TB
subgraph Powertools ["Lambda Powertools"]
Logger["Logger (Structured Logging)"]
Tracer["Tracer (X-Ray Integration)"]
Metrics["Metrics (CloudWatch EMF)"]
end
Logger --> CWLogs["CloudWatch Logs"]
Tracer --> XRay["AWS X-Ray"]
Metrics --> CWMetrics["CloudWatch Metrics"]
style Logger fill:#E1F5FE,stroke:#0277BD
style Tracer fill:#C8E6C9,stroke:#2E7D32
style Metrics fill:#FFF3E0,stroke:#E65100
Installation
dotnet add package AWS.Lambda.Powertools.Logging
dotnet add package AWS.Lambda.Powertools.Tracing
dotnet add package AWS.Lambda.Powertools.Metrics
Structured Logging
The Logging utility outputs JSON-structured logs that CloudWatch Logs Insights can query efficiently.
using AWS.Lambda.Powertools.Logging;
public class OrderFunction
{
[Logging(LogEvent = true, Service = "OrderService")]
public async Task<APIGatewayProxyResponse> Handler(
APIGatewayProxyRequest request,
ILambdaContext context)
{
// Add custom keys to all subsequent logs
Logger.AppendKey("orderId", "12345");
Logger.AppendKey("customerId", "C-789");
Logger.LogInformation("Processing order");
try
{
var result = await ProcessOrderAsync();
Logger.LogInformation("Order processed successfully", new {
Total = result.Total,
ItemCount = result.Items.Count
});
return new APIGatewayProxyResponse { StatusCode = 200 };
}
catch (Exception ex)
{
Logger.LogError(ex, "Order processing failed");
throw;
}
}
}
Log Output
{
"level": "Information",
"message": "Processing order",
"timestamp": "2022-06-02T10:30:00.000Z",
"service": "OrderService",
"cold_start": true,
"function_name": "OrderFunction",
"function_memory_size": 1024,
"function_arn": "arn:aws:lambda:...",
"function_request_id": "abc-123",
"orderId": "12345",
"customerId": "C-789"
}
Querying in CloudWatch Logs Insights
fields @timestamp, message, orderId, customerId
| filter service = "OrderService" and level = "Error"
| sort @timestamp desc
| limit 100
Distributed Tracing
The Tracing utility integrates with AWS X-Ray, capturing subsegments for method calls, HTTP requests, and AWS SDK operations.
using AWS.Lambda.Powertools.Tracing;
public class OrderFunction
{
private readonly IOrderRepository _repository;
private readonly IPaymentService _paymentService;
[Tracing(CaptureMode = TracingCaptureMode.ResponseAndError)]
public async Task<APIGatewayProxyResponse> Handler(
APIGatewayProxyRequest request,
ILambdaContext context)
{
var order = await ValidateOrderAsync(request);
var payment = await ProcessPaymentAsync(order);
await SaveOrderAsync(order);
return new APIGatewayProxyResponse { StatusCode = 200 };
}
[Tracing(SegmentName = "ValidateOrder")]
private async Task<Order> ValidateOrderAsync(APIGatewayProxyRequest request)
{
// Custom subsegment created automatically
Tracing.AddAnnotation("OrderType", "Standard");
Tracing.AddMetadata("RawPayload", request.Body);
return JsonSerializer.Deserialize<Order>(request.Body);
}
[Tracing(SegmentName = "ProcessPayment")]
private async Task<PaymentResult> ProcessPaymentAsync(Order order)
{
// HTTP calls to external services are automatically traced
return await _paymentService.ChargeAsync(order.Total);
}
}
Custom Metrics
The Metrics utility uses CloudWatch Embedded Metric Format (EMF) to publish high-cardinality metrics without API calls.
using AWS.Lambda.Powertools.Metrics;
public class OrderFunction
{
[Metrics(Namespace = "Ecommerce", Service = "OrderService")]
public async Task<APIGatewayProxyResponse> Handler(
APIGatewayProxyRequest request,
ILambdaContext context)
{
var stopwatch = Stopwatch.StartNew();
try
{
var order = await ProcessOrderAsync();
// Record custom metrics
Metrics.AddMetric("OrdersProcessed", 1, MetricUnit.Count);
Metrics.AddMetric("OrderTotal", order.Total, MetricUnit.None);
Metrics.AddDimension("Region", order.ShippingRegion);
Metrics.AddDimension("CustomerTier", order.CustomerTier);
return new APIGatewayProxyResponse { StatusCode = 200 };
}
catch (Exception ex)
{
Metrics.AddMetric("OrdersFailed", 1, MetricUnit.Count);
throw;
}
finally
{
Metrics.AddMetric("ProcessingTime", stopwatch.ElapsedMilliseconds, MetricUnit.Milliseconds);
}
}
}
Environment Configuration
# SAM template
Globals:
Function:
Environment:
Variables:
POWERTOOLS_SERVICE_NAME: OrderService
POWERTOOLS_METRICS_NAMESPACE: Ecommerce
POWERTOOLS_LOG_LEVEL: Information
POWERTOOLS_TRACER_CAPTURE_RESPONSE: true
POWERTOOLS_TRACER_CAPTURE_ERROR: true
Key Takeaways
- Install all three Powertools packages for comprehensive observability
- Logger outputs structured JSON for CloudWatch Logs Insights
- Tracer integrates with X-Ray automatically
- Metrics uses EMF for zero-latency metric publishing
- Configure via environment variables for consistency
- Use decorators/attributes for clean, declarative instrumentation
References
Discover more from C4: Container, Code, Cloud & Context
Subscribe to get the latest posts sent to your email.