Lambda Loadout
AWS Lambda Powertools for Ruby — A developer toolkit to implement serverless best practices and increase developer velocity.
Lambda Loadout provides structured logging, CloudWatch metrics via Embedded Metric Format (EMF), error handling, error notifications via SNS, and alerting for AWS Lambda functions. Inspired by AWS Lambda Powertools for Python.
Features
- CloudWatch Metrics (EMF) — Custom metrics via CloudWatch Embedded Metric Format (zero API calls, zero latency overhead)
- Structured Logging — JSON structured logging with automatic Lambda context enrichment
- Error Notifications — Detailed error alerts via SNS with CloudWatch deep links and event source detection
- Error Handling & Alerting — Automatic error capture with CloudWatch alarm configuration helpers (CloudFormation & Terraform)
- Cold Start Tracking — Built-in cold start metric capture
- Middleware Pattern — Clean
with_observabilitywrapper andHandlerDSL for Ruby Lambda handlers - Global Configuration — Module-level API for shared logger/metrics instances
- Lambda Layer Support — Build and publish as a Lambda layer via Rake tasks
Installation
Add to your Lambda function's Gemfile:
gem 'lambda_loadout'
Or install directly:
gem install lambda_loadout
Dependencies
json(~> 2.0) — JSON serialization (Ruby stdlib)aws-sdk-sns(~> 1.0) — SNS error notifications
Quick Start
Basic Usage
require 'lambda_loadout'
LOGGER = LambdaLoadout::Logger.new(service: "payment")
METRICS = LambdaLoadout::Metrics.new(namespace: "MyApp", service: "payment")
def lambda_handler(event:, context:)
LambdaLoadout.with_logging_and_metrics(LOGGER, METRICS, context) do
LOGGER.info("Processing payment", order_id: event['orderId'])
METRICS.add_metric(name: "PaymentProcessed", unit: "Count", value: 1)
METRICS.add_dimension(name: "payment_type", value: event['type'])
{ statusCode: 200, body: "Payment processed" }
end
end
With Error Notifications
def lambda_handler(event:, context:)
LambdaLoadout.with_logging_and_metrics(
LOGGER, METRICS, context,
event: event,
error_notification_config: { sns_topic_arn: ENV['ERROR_NOTIFICATION_TOPIC_ARN'] }
) do
process_payment(event)
{ statusCode: 200, body: "Success" }
end
end
When an error occurs, the ErrorNotifier sends a detailed SNS message including:
- Error class, message, and stack trace (first 15 lines)
- Lambda request context (function name, request ID, memory, remaining time)
- Event source detection (API Gateway, SQS, SNS, DynamoDB Streams, EventBridge)
- Deep links to CloudWatch Logs Insights (pre-filtered query) and log streams
- JSON-formatted error data for programmatic consumption
Notification failures are caught and logged — they won't break your Lambda execution.
Using Middleware Pattern
require 'lambda_loadout'
class PaymentHandler
include LambdaLoadout::Middleware
def initialize
@logger = LambdaLoadout::Logger.new(service: "payment")
@metrics = LambdaLoadout::Metrics.new(namespace: "MyApp", service: "payment")
end
def call(event:, context:)
with_observability(context) do
@logger.info("Processing payment", event: event)
result = process_payment(event)
@metrics.add_metric(name: "PaymentProcessed", unit: "Count", value: 1)
{ statusCode: 200, body: result.to_json }
end
end
end
HANDLER = PaymentHandler.new
def lambda_handler(event:, context:)
HANDLER.call(event: event, context: context)
end
Using Handler DSL
require 'lambda_loadout'
LambdaLoadout::Handler.configure do |config|
config.service = "payment"
config.namespace = "MyApp"
config.log_level = :info
config.capture_cold_start = true
end
def lambda_handler(event:, context:)
LambdaLoadout::Handler.call(event, context) do |logger, metrics|
logger.info("Processing event", event_type: event['type'])
metrics.add_metric(name: "EventProcessed", unit: "Count", value: 1)
{ statusCode: 200, body: "OK" }
end
end
Global Configuration
require 'lambda_loadout'
LambdaLoadout.configure do |config|
config.service = "payment-api"
config.namespace = "MyApp"
config.log_level = :debug
end
# Access global instances anywhere
LambdaLoadout.logger.info("Starting up")
LambdaLoadout.metrics.add_metric(name: "Boot", unit: "Count", value: 1)
CloudWatch Metrics (EMF)
Metrics are published via CloudWatch Embedded Metric Format — structured JSON written to stdout that CloudWatch automatically extracts. No PutMetricData API calls, no additional latency.
Creating Metrics
metrics = LambdaLoadout::Metrics.new(namespace: "MyApp", service: "payment")
metrics.add_metric(name: "BookingConfirmation", unit: "Count", value: 1)
metrics.add_metric(name: "ResponseTime", unit: "Milliseconds", value: 145.5)
metrics.add_dimension(name: "environment", value: "production")
metrics.(key: "booking_id", value: "abc-123")
metrics.flush
High-Resolution Metrics
metrics.add_metric(name: "HighPrecisionMetric", unit: "Count", value: 1, resolution: 1)
Default Dimensions
metrics.set_default_dimensions(environment: "production", region: "us-east-1")
# These persist across flushes
Custom Timestamps
metrics.(Time.now) # Time object
metrics.(1699876543000) # Epoch milliseconds
# Validates within CloudWatch limits (14 days past, 2 hours future)
Auto-Flush
Metrics automatically flush when the 100-metric limit is reached. You can also call metrics.flush manually or rely on the with_logging_and_metrics / with_observability wrappers to flush in their ensure block.
Supported Units
Count, Seconds, Milliseconds, Microseconds, Bytes, Kilobytes, Megabytes, Gigabytes, Terabytes, Bits, Kilobits, Megabits, Gigabits, Terabits, Percent, Count/Second, Bytes/Second, Kilobytes/Second, Megabytes/Second, Gigabytes/Second, Terabytes/Second, Bits/Second, Kilobits/Second, Megabits/Second, Gigabits/Second, Terabits/Second, None
EMF Output Example
{
"_aws": {
"Timestamp": 1699876543000,
"CloudWatchMetrics": [{
"Namespace": "MyApp",
"Dimensions": [["service", "environment"]],
"Metrics": [
{"Name": "PaymentProcessed", "Unit": "Count"},
{"Name": "ResponseTime", "Unit": "Milliseconds"}
]
}]
},
"service": "payment",
"environment": "production",
"PaymentProcessed": 1.0,
"ResponseTime": 145.5,
"booking_id": "abc-123"
}
Structured Logging
Basic Logging
logger = LambdaLoadout::Logger.new(service: "payment", level: :info)
logger.info("Payment processed", order_id: "12345", amount: 99.99)
# => {"level":"INFO","timestamp":"2025-11-13T12:00:00.000Z","message":"Payment processed","service":"payment","order_id":"12345","amount":99.99}
Lambda Context Injection
def lambda_handler(event:, context:)
logger.inject_lambda_context(context)
logger.info("Processing request")
# Output includes: function_name, function_version, function_request_id, function_memory_size, cold_start
end
Exception Logging
begin
risky_operation()
rescue StandardError => e
logger.error("Operation failed", e, order_id: order_id)
# Automatically includes error message, error_class, and backtrace (first 10 lines)
# Or use the dedicated exception method
logger.exception(e, message: "Operation failed", order_id: order_id)
end
Persistent Fields
logger.append_keys(correlation_id: "abc-123", tenant_id: "tenant-1")
logger.info("Event 1") # Includes correlation_id and tenant_id
logger.remove_keys(:tenant_id)
logger.info("Event 2") # Only includes correlation_id
Debug Log Sampling
logger = LambdaLoadout::Logger.new(service: "payment", level: :debug, sampling_rate: 0.1)
# Only 10% of debug logs will be emitted
Log Levels
debug, info, warn, error, fatal
Error Notifications via SNS
Sends rich error alerts when Lambda functions fail.
Standalone Usage
notifier = LambdaLoadout::ErrorNotifier.new(
sns_topic_arn: ENV['ERROR_NOTIFICATION_TOPIC_ARN'],
logger: logger,
region: 'us-east-1' # optional, defaults to AWS_REGION env var
)
notifier.notify(error: e, context: context, event: event)
Integrated Usage
Pass error_notification_config to with_logging_and_metrics (see Quick Start above). Notifications are sent automatically on unhandled exceptions.
What's Included in Each Notification
- Error class, message, and stack trace (first 15 lines)
- Lambda request context (function name, request ID, memory, remaining time)
- Event source detection (API Gateway, SQS, SNS, DynamoDB Streams, EventBridge)
- Deep links to CloudWatch Logs Insights (pre-filtered query) and log streams
- JSON-formatted error data for programmatic consumption
Error Handling & Alarms
Automatic Error Capture
handler = LambdaLoadout::ErrorHandler.new(
logger: logger,
metrics: metrics,
capture_stack_trace: true,
error_metric_name: "LambdaError" # default
)
def lambda_handler(event:, context:)
handler.handle(context) do
process_event(event)
end
end
CloudWatch Alarm Configuration
Generate CloudFormation or Terraform for alarms:
alarm = LambdaLoadout::AlarmConfig.new(
metric_name: "LambdaError",
namespace: "MyApp",
threshold: 1,
evaluation_periods: 1,
period: 60,
statistic: "Sum",
comparison_operator: "GreaterThanOrEqualToThreshold"
)
puts alarm.to_cloudformation(
alarm_name: "PaymentErrorAlarm",
sns_topic_arn: "arn:aws:sns:us-east-1:123456789012:alerts",
dimensions: { service: "payment" }
)
puts alarm.to_terraform(
resource_name: "payment_error_alarm",
sns_topic_arn: "arn:aws:sns:us-east-1:123456789012:alerts",
dimensions: { service: "payment" }
)
Lambda Layer
Lambda Loadout can be published as a Lambda layer so your functions don't need to bundle it in their deployment packages. This is the recommended approach for teams sharing the gem across multiple Lambda functions.
Rake Tasks
# Build the layer zip
bundle exec rake layer:build
# Build and publish to AWS (requires configured AWS CLI)
bundle exec rake layer:publish
# Clean build artifacts
bundle exec rake layer:clean
The layer targets the ruby3.4 runtime. After publishing, add the returned LayerVersionArn to your Lambda function configuration.
Shell Scripts (alternative)
./scripts/build_layer.sh # Build only
./scripts/publish_layer.sh # Publish (requires prior build)
./scripts/release.sh # Build + publish interactively
Using the Layer in Your Lambda
Terraform:
resource "aws_lambda_function" "my_function" {
# ...
layers = ["arn:aws:lambda:us-east-1:ACCOUNT:layer:lambda-loadout:VERSION"]
}
CloudFormation / SAM:
MyFunction:
Type: AWS::Lambda::Function
Properties:
Layers:
- arn:aws:lambda:us-east-1:ACCOUNT:layer:lambda-loadout:VERSION
Environment Variables
| Variable | Description |
|---|---|
POWERTOOLS_SERVICE_NAME |
Default service name for Logger and Metrics |
POWERTOOLS_METRICS_NAMESPACE |
Default metrics namespace |
ERROR_NOTIFICATION_TOPIC_ARN |
SNS topic ARN for error notifications |
ENVIRONMENT / STAGE |
Environment name (included in error notifications) |
AWS_REGION |
AWS region (used by ErrorNotifier, defaults to us-east-1) |
Testing
Testing with Metrics
output = StringIO.new
metrics = LambdaLoadout::Metrics.new(namespace: "MyApp", output: output)
metrics.add_metric(name: "TestMetric", unit: "Count", value: 1)
metrics.flush
emf_output = JSON.parse(output.string)
expect(emf_output['TestMetric']).to eq(1)
Testing with Logger
output = StringIO.new
logger = LambdaLoadout::Logger.new(service: "test", output: output)
logger.info("Test message", data: "value")
log_entry = JSON.parse(output.string)
expect(log_entry['message']).to eq("Test message")
Both Logger and Metrics accept an output: parameter, making it easy to capture and assert on output in tests without mocking stdout.
Development
# Install dependencies
bundle install
# Run tests
bundle exec rake spec
# Run linter
bundle exec rake rubocop
# Run both (default task)
bundle exec rake
# List all available tasks
bundle exec rake -T
Project Structure
lambda-loadout/
├── lib/lambda_loadout/
│ ├── version.rb # Gem version
│ ├── logger.rb # Structured JSON logging
│ ├── metrics.rb # CloudWatch EMF metrics
│ ├── errors.rb # ErrorHandler, AlarmConfig, custom error classes
│ ├── error_notifier.rb # SNS error notifications with CloudWatch deep links
│ ├── middleware.rb # Middleware module + Handler DSL
│ └── global.rb # Module-level API & global config
├── examples/ # Working Lambda handler examples
├── spec/ # RSpec test suite
├── scripts/ # Layer build & publish scripts
└── docs/ # Integration guide, alarm templates
Roadmap
- [x] CloudWatch Metrics (EMF)
- [x] Structured Logging
- [x] Error Handling & Alarm Config (CloudFormation + Terraform)
- [x] Cold Start Tracking
- [x] Error Notifications via SNS
- [x] Middleware Pattern & Handler DSL
- [x] Global Configuration API
- [ ] X-Ray Tracing Integration
- [ ] Parameter Store / Secrets Manager Integration
- [ ] Idempotency Support
- [ ] Batch Processing Utilities
- [ ] Event Source Data Classes
Related Projects
License
MIT — see LICENSE.txt for details.