From eb3dd2d11ea3623791371cbdd5d1cc57869175ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Wed, 29 Jul 2026 07:22:03 +0200 Subject: [PATCH 1/3] Add AWS documentation for Swift Lambda runtime Cross-checked against source code in Plugins/, Sources/AWSLambdaRuntime/, and Examples/. Fixed inaccuracies found during review: - swift-context.md: deadline type corrected to LambdaClock.Instant (was DispatchWallTime), added missing tenantID/logGroupName/logStreamName - swift-handler.md: removed nonexistent LambdaInitializationContext, fixed protocol-based handler examples to use correct LambdaRuntime wiring - swift-http-events.md: same protocol-based handler fix --- aws-doc/README | 1 + aws-doc/lambda-swift.md | 28 +++ aws-doc/swift-context.md | 71 +++++++ aws-doc/swift-handler.md | 368 +++++++++++++++++++++++++++++++++++ aws-doc/swift-http-events.md | 129 ++++++++++++ aws-doc/swift-layers.md | 24 +++ aws-doc/swift-logging.md | 147 ++++++++++++++ aws-doc/swift-package.md | 203 +++++++++++++++++++ 8 files changed, 971 insertions(+) create mode 100644 aws-doc/README create mode 100644 aws-doc/lambda-swift.md create mode 100644 aws-doc/swift-context.md create mode 100644 aws-doc/swift-handler.md create mode 100644 aws-doc/swift-http-events.md create mode 100644 aws-doc/swift-layers.md create mode 100644 aws-doc/swift-logging.md create mode 100644 aws-doc/swift-package.md diff --git a/aws-doc/README b/aws-doc/README new file mode 100644 index 00000000..a33cc7cc --- /dev/null +++ b/aws-doc/README @@ -0,0 +1 @@ +This is the source for the AWS documentation pages at https://docs.aws.amazon.com/lambda/latest/dg/lambda-programming-languages.html \ No newline at end of file diff --git a/aws-doc/lambda-swift.md b/aws-doc/lambda-swift.md new file mode 100644 index 00000000..739df252 --- /dev/null +++ b/aws-doc/lambda-swift.md @@ -0,0 +1,28 @@ +# Building Lambda functions with Swift + +Because Swift compiles to native code, you don't need a dedicated runtime to run Swift code on Lambda. Instead, use the Swift runtime client to build your project locally, and then deploy it to Lambda using an OS-only runtime. When you use an OS-only runtime, Lambda automatically keeps the operating system up to date with the latest patches. + +## Tools and libraries for Swift + +- **AWS SDK for Swift**: The [AWS SDK for Swift](https://github.com/awslabs/aws-sdk-swift) provides Swift APIs to interact with Amazon Web Services infrastructure services. +- **Swift runtime client for Lambda**: The [Swift runtime client](https://github.com/awslabs/swift-aws-lambda-runtime) makes it easy to run Lambda functions written in Swift. +- **Swift AWS Lambda Events**: This [library](https://github.com/awslabs/swift-aws-lambda-events) provides type definitions for common event source integrations. +- **Swift OpenAPI Lambda**: This [library](https://github.com/awslabs/swift-openapi-lambda) provides an AWS Lambda transport for Swift OpenAPI, allowing you to expose OpenAPI-based services as Lambda functions. + +## Sample Lambda applications for Swift + +- [Simple Lambda function](https://github.com/awslabs/swift-aws-lambda-runtime/blob/main/Examples/HelloJSON): A Swift function that shows how to process basic JSON events. +- [Lambda function with background tasks](https://github.com/awslabs/swift-aws-lambda-runtime/tree/main/Examples/BackgroundTasks): A Swift function that shows how to perform background processing after sending a response. +- [Lambda function with streaming responses](https://github.com/awslabs/swift-aws-lambda-runtime/tree/main/Examples/Streaming%2BAPIGateway): A Swift function that streams responses back to the client. +- [Lambda HTTP events](https://github.com/awslabs/swift-aws-lambda-runtime/tree/main/Examples/APIGatewayV2): A Swift function that handles API Gateway HTTP events. +- [Lambda function with Service Lifecycle](https://github.com/awslabs/swift-aws-lambda-runtime/tree/main/Examples/ServiceLifecycle%2BPostgres): A Swift project that initializes shared resources using Swift Service Lifecycle before creating the Lambda function. + +## Topics + +- [Define Lambda function handlers in Swift](swift-handler.md) +- [Using the Lambda context object to retrieve Swift function information](swift-context.md) +- [Processing HTTP events with Swift](swift-http-events.md) +- [Deploy Swift Lambda functions with .zip file archives](swift-package.md) +- [Working with layers for Swift Lambda functions](swift-layers.md) +- [Log and monitor Swift Lambda functions](swift-logging.md) + diff --git a/aws-doc/swift-context.md b/aws-doc/swift-context.md new file mode 100644 index 00000000..6ea30bc8 --- /dev/null +++ b/aws-doc/swift-context.md @@ -0,0 +1,71 @@ +# Using the Lambda context object to retrieve Swift function information + +When Lambda runs your function, it passes a context object to the handler. This object provides properties with information about the invocation, function, and execution environment. + +## Context properties + +- **requestID**: The AWS request ID generated by the Lambda service. +- **traceID**: The AWS X-Ray tracing header. +- **tenantID**: The tenant ID, if present. This field is `nil` unless a tenant ID is provided by the Lambda service. +- **invokedFunctionARN**: The Amazon Resource Name (ARN) of the Lambda function, version, or alias that's specified in the invocation. +- **deadline**: The timestamp that the function times out, as a `LambdaClock.Instant`. +- **cognitoIdentity**: For invocations from the AWS Mobile SDK, data about the Amazon Cognito identity provider. This field is `nil` unless the invocation request to the Lambda APIs was made using AWS credentials issued by Amazon Cognito identity pools. +- **clientContext**: For invocations from the AWS Mobile SDK, data about the client application and device. This field is `nil` unless the function is invoked using an AWS Mobile SDK. +- **logger**: A `Logger` instance to produce log output. The `LogLevel` can be configured using the `LOG_LEVEL` environment variable. +- **logGroupName**: The name of the Amazon CloudWatch Logs group for the function. +- **logStreamName**: The name of the Amazon CloudWatch Logs stream for the current invocation of the function. + +## Accessing invoke context information + +Lambda functions have access to metadata about their environment and the invocation request. The `LambdaContext` object is passed directly to your handler as a parameter: + +```swift +import AWSLambdaRuntime +import Foundation + +let runtime = LambdaRuntime { + (event: Request, context: LambdaContext) in + + let invokedFunctionARN = context.invokedFunctionARN + return Response(message: "Hello, this is function \(invokedFunctionARN)!") +} + +try await runtime.run() +``` + +## Getting the remaining time + +The context object provides a method to retrieve how much time remains before the function times out. You can use this to ensure your function completes critical operations before the timeout: + +```swift +let runtime = LambdaRuntime { + (event: Request, context: LambdaContext) in + + let remainingTime = context.getRemainingTime() + context.logger.info("Time remaining: \(remainingTime)") + + // Perform time-sensitive operations + return Response(message: "Completed") +} + +try await runtime.run() +``` + +## Using the context logger + +The `LambdaContext` includes a pre-configured `Logger` instance that automatically includes the AWS request ID and trace ID in every log message. Use this logger instead of `print()` statements: + +```swift +let runtime = LambdaRuntime { + (event: Request, context: LambdaContext) in + + context.logger.info("Processing request \(context.requestID)") + context.logger.debug("Event received: \(event)") + + return Response(message: "Done") +} + +try await runtime.run() +``` + +For more information about logging, see [Log and monitor Swift Lambda functions](swift-logging.md). diff --git a/aws-doc/swift-handler.md b/aws-doc/swift-handler.md new file mode 100644 index 00000000..8b0fa011 --- /dev/null +++ b/aws-doc/swift-handler.md @@ -0,0 +1,368 @@ +# Define Lambda function handlers in Swift + +The Lambda function handler is the method in your function code that processes events. When your function is invoked, Lambda runs the handler method. Your function runs until the handler returns a response, exits, or times out. + +This page describes how to work with Lambda function handlers in Swift, including project initialization, naming conventions, and best practices. This page also includes an example of a Swift Lambda function that takes in information about an order, produces a text file receipt, and puts this file in an Amazon Simple Storage Service (S3) bucket. For more information about how to deploy your function after writing it, see [Deploy Swift Lambda functions with .zip file archives](swift-package.md). + +## Setting up your Swift handler project + +When working with Lambda functions in Swift, the process involves writing your code, compiling it, and deploying the compiled artifacts to Lambda. The simplest way to set up a Lambda handler project in Swift is to use the [Swift AWS Lambda Runtime](https://github.com/awslabs/swift-aws-lambda-runtime). Despite its name, the Swift AWS Lambda Runtime is not a managed runtime in the same sense as it is in Lambda for Python, Java, or Node.js. Instead, the Swift AWS Lambda Runtime is a Swift package (`AWSLambdaRuntime`) that supports writing Lambda functions in Swift and interfacing with AWS Lambda's execution environment. + +Use the following commands to create a new Swift Lambda function handler project: + +```bash +mkdir MyLambda && cd MyLambda +swift package init --type executable --name MyLambda +swift package add-dependency https://github.com/awslabs/swift-aws-lambda-runtime.git --from 3.0.0 +swift package add-target-dependency AWSLambdaRuntime MyLambda --package swift-aws-lambda-runtime + +``` + +After the commands run successfully, use the built-in plugin to scaffold a starting point: + +```bash +swift package lambda-init --allow-writing-to-package-directory + +``` + +This command generates a `MyLambda.swift` file in the `Sources/MyLambda/` directory. The `Package.swift` file contains metadata about your package and lists its external dependencies. + +## Example Swift Lambda function code + +The following example Swift Lambda function code takes in information about an order, produces a text file receipt, and puts this file in an Amazon S3 bucket. + +**Example — MyLambda.swift Lambda function** + +```swift +import AWSLambdaRuntime +import AWSS3 +import Foundation + +struct Order: Decodable { + let orderID: String + let amount: Double + let item: String +} + +struct OrderResponse: Encodable { + let message: String +} + +let runtime = LambdaRuntime { + (event: Order, context: LambdaContext) in + + let bucketName = Lambda.env("RECEIPT_BUCKET") ?? "" + + let receiptContent = """ + OrderID: \(event.orderID) + Amount: $\(String(format: "%.2f", event.amount)) + Item: \(event.item) + """ + let key = "receipts/\(event.orderID).txt" + + let client = try await S3Client() + + let input = PutObjectInput( + body: .from(data: receiptContent.data(using: .utf8)!), + bucket: bucketName, + contentType: "text/plain", + key: key + ) + _ = try await client.putObject(input: input) + + return OrderResponse(message: "Success") +} + +try await runtime.run() + +``` + +This file contains the following sections of code: + +- **import statements**: Use these to import Swift packages and modules that your Lambda function requires. +- **struct Order: Decodable**: Define the shape of the expected input event in this Swift struct. The struct conforms to `Decodable` so the runtime can automatically deserialize the incoming JSON. +- **struct OrderResponse: Encodable**: Define the shape of the response. The struct conforms to `Encodable` so the runtime can automatically serialize the return value to JSON. +- **let runtime = LambdaRuntime { ... }**: This is the main handler closure, which contains your main application logic. The runtime passes an event of the specified input type and a `LambdaContext` object. +- **try await runtime.run()**: This starts the Lambda runtime loop. It receives events from the Lambda service, invokes your handler, and sends back responses. + +The following `Package.swift` file accompanies this function: + +```swift +// swift-tools-version: 6.3 +import PackageDescription + +let package = Package( + name: "MyLambda", + platforms: [.macOS(.v15)], + dependencies: [ + .package(url: "https://github.com/awslabs/swift-aws-lambda-runtime.git", from: "3.0.0"), + .package(url: "https://github.com/awslabs/aws-sdk-swift.git", from: "1.0.0"), + ], + targets: [ + .executableTarget( + name: "MyLambda", + dependencies: [ + .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), + .product(name: "AWSS3", package: "aws-sdk-swift"), + ] + ) + ] +) + +``` + +For this function to work properly, its execution role must allow the `s3:PutObject` action. Also, ensure that you define the `RECEIPT_BUCKET` environment variable. After a successful invocation, the Amazon S3 bucket should contain a receipt file. + +## Valid handler definitions for Swift + +Lambda handlers in Swift can be defined using either a closure-based approach or a protocol-based approach. + +### Closure-based handler + +The most concise way to define a handler uses the `LambdaRuntime` initializer with a closure: + +```swift +let runtime = LambdaRuntime { + (event: Input, context: LambdaContext) in + // your logic here + return output +} + +try await runtime.run() + +``` + +For this handler: + +- `Input` is the deserialized event type. It must conform to `Decodable` so the runtime can convert the incoming JSON to your struct. For example, `Input` can be a custom struct like `Order`, or a predefined event type like `APIGatewayV2Request` from the `AWSLambdaEvents` library. +- `Output` is the serialized return type. It must conform to `Encodable` so the runtime can convert the return value to JSON. For example, `Output` can be a simple type like `String`, or a custom struct as long as it conforms to `Encodable`. +- `context` is of type `LambdaContext`, which provides Lambda-specific metadata such as the request ID of the invocation. +- When your handler throws an error, your function logs the error in Amazon CloudWatch and returns an error response. + +### Protocol-based handler + +For larger applications, you can define a struct that conforms to the `LambdaHandler` protocol: + +```swift +import AWSLambdaRuntime + +struct MyHandler: LambdaHandler { + func handle( + _ event: Order, + context: LambdaContext + ) async throws -> OrderResponse { + // your logic here + return OrderResponse(message: "Success") + } +} + +let handler = MyHandler() +let runtime = LambdaRuntime(handler: LambdaCodableAdapter(handler: LambdaHandlerAdapter(handler: handler))) +try await runtime.run() + +``` + +Other valid handler patterns include: + +- **Omitting the return type** — If your function doesn't need to return a value (for example, processing SQS messages), you can omit the return statement:```swift let runtime = LambdaRuntime { (event: SQSEvent, context: LambdaContext) in // process event, no return needed } + +``` + +## Handler naming conventions + +Lambda handlers in Swift don't have strict naming restrictions. For the closure-based approach, the handler is anonymous. For the protocol-based approach, the `handle` method name is required by the protocol. + +For smaller applications, you can use a single `main.swift` file (or a file named after your target) to contain all of your code. For larger projects, you should separate your code into logical modules. For example, you might have the following file structure: + +``` + +/MyLambda ├── Sources/ + │ └── MyLambda/ + │ ├─── MyLambda.swift # Entry point with handler + │ ├─── Services.swift # [Optional] Back-end service calls + │ ├─── Models.swift # [Optional] Data models + ├── Package.swift + +``` + +## Defining and accessing the input event object + +JSON is the most common and standard input format for Lambda functions. In this example, the function expects an input similar to the following: + +```json +{ + "orderID": "12345", + "amount": 199.99, + "item": "Wireless Headphones" +} + +``` + +In Swift, you define the shape of the expected input event in a struct that conforms to `Decodable`. In this example, we define the following struct to represent an `Order`: + +```swift +struct Order: Decodable { + let orderID: String + let amount: Double + let item: String +} + +``` + +This struct matches the expected input shape. The `Decodable` conformance allows the Swift AWS Lambda Runtime to automatically deserialize the incoming JSON into your struct. Within your handler, you can directly access the fields of the event object. For example, `event.orderID` retrieves the value of `orderID` from the original input. + +## Pre-defined input event types + +There are many pre-defined input event types available in the [Swift AWS Lambda Events](https://github.com/awslabs/swift-aws-lambda-events) package. For example, if you intend to invoke your function with API Gateway, add the following dependency: + +```swift +.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events") + +``` + +Then, use the pre-defined type in your handler: + +```swift +import AWSLambdaEvents +import AWSLambdaRuntime + +let runtime = LambdaRuntime { + (event: APIGatewayV2Request, context: LambdaContext) -> APIGatewayV2Response in + let body = event.body ?? "" + return APIGatewayV2Response(statusCode: .ok, body: body) +} + +try await runtime.run() + +``` + +Refer to the [Swift AWS Lambda Events repository](https://github.com/awslabs/swift-aws-lambda-events) for more information about other pre-defined input event types, including S3, SQS, SNS, CloudWatch, and Cognito events. + +## Accessing and using the Lambda context object + +The Lambda context object contains information about the invocation, function, and execution environment. The `LambdaContext` is passed directly to your handler. For example, you can use the context object to retrieve the request ID of the current invocation with the following code: + +```swift +let runtime = LambdaRuntime { + (event: Order, context: LambdaContext) in + let requestID = context.requestID + // ... +} + +``` + +For more information about the context object, see [Using the Lambda context object to retrieve Swift function information](swift-context.md). + +## Using the AWS SDK for Swift in your handler + +Often, you'll use Lambda functions to interact with or make updates to other AWS resources. The simplest way to interface with these resources is to use the [AWS SDK for Swift](https://github.com/awslabs/aws-sdk-swift). + +To add SDK dependencies to your function, add them in your `Package.swift` file. We recommend only adding the libraries that you need for your function. In the example code earlier, we used the `AWSS3` module. In the `Package.swift` file, you can add this dependency: + +```swift +.package(url: "https://github.com/awslabs/aws-sdk-swift.git", from: "1.0.0"), + +``` + +Then add the product to your target: + +```swift +.product(name: "AWSS3", package: "aws-sdk-swift"), + +``` + +Import the dependencies directly in your code: + +```swift +import AWSS3 + +``` + +The example code then initializes an Amazon S3 client as follows: + +```swift +let client = try await S3Client() + +``` + +After you initialize your SDK client, you can use it to interact with other AWS services. The example code calls the Amazon S3 `PutObject` API in the handler. + +## Accessing environment variables + +In your handler code, you can reference any environment variables by using the `Lambda.env()` function or `ProcessInfo`. In this example, we reference the defined `RECEIPT_BUCKET` environment variable using the following line of code: + +```swift +let bucketName = Lambda.env("RECEIPT_BUCKET") ?? "" + +``` + +Alternatively, you can use Foundation: + +```swift +let bucketName = ProcessInfo.processInfo.environment["RECEIPT_BUCKET"] ?? "" + +``` + +## Using shared state + +You can initialize shared resources before creating the `LambdaRuntime`. These resources persist across invocations within the same execution environment. For example, you can initialize an Amazon S3 client outside the handler: + +```swift +import AWSLambdaRuntime +import AWSS3 + +let client = try await S3Client() + +let runtime = LambdaRuntime { + (event: Order, context: LambdaContext) in + // Use the shared client + let input = PutObjectInput( + body: .from(data: "receipt".data(using: .utf8)!), + bucket: "my-bucket", + key: "receipts/\(event.orderID).txt" + ) + _ = try await client.putObject(input: input) + return OrderResponse(message: "Success") +} + +try await runtime.run() + +``` + +For more complex initialization scenarios, use the protocol-based approach with an `init` method: + +```swift +struct MyHandler: LambdaHandler { + let s3Client: S3Client + + init() async throws { + self.s3Client = try await S3Client() + } + + func handle(_ event: Order, context: LambdaContext) async throws -> OrderResponse { + // use self.s3Client + return OrderResponse(message: "Success") + } +} + +let handler = try await MyHandler() +let runtime = LambdaRuntime(handler: LambdaCodableAdapter(handler: LambdaHandlerAdapter(handler: handler))) +try await runtime.run() + +``` + +## Code best practices for Swift Lambda functions + +Adhere to the guidelines in the following list to use best coding practices when building your Lambda functions: + +- **Separate the Lambda handler from your core logic.** This allows you to make a more unit-testable function. +- **Minimize the complexity of your dependencies.** Prefer simpler frameworks that load quickly on execution environment startup. +- **Minimize your deployment package size to its runtime necessities.** This will reduce the amount of time that it takes for your deployment package to be downloaded and unpacked ahead of invocation. +- **Take advantage of execution environment reuse to improve the performance of your function.** Initialize SDK clients and database connections outside of the function handler, and cache static assets locally in the `/tmp` directory. Subsequent invocations processed by the same instance of your function can reuse these resources. This saves cost by reducing function run time. +- **To avoid potential data leaks across invocations, don't use the execution environment to store user data, events, or other information with security implications.** If your function relies on a mutable state that can't be stored in memory within the handler, consider creating a separate function or separate versions of a function for each user. +- **Use a keep-alive directive to maintain persistent connections.** Lambda purges idle connections over time. Attempting to reuse an idle connection when invoking a function will result in a connection error. To maintain your persistent connection, use the keep-alive directive associated with your runtime. +- **Use environment variables to pass operational parameters to your function.** For example, if you are writing to an Amazon S3 bucket, instead of hard-coding the bucket name you are writing to, configure the bucket name as an environment variable. +- **Avoid using recursive invocations in your Lambda function**, where the function invokes itself or initiates a process that may invoke the function again. This could lead to unintended volume of function invocations and escalated costs. If you see an unintended volume of invocations, set the function reserved concurrency to `0` immediately to throttle all invocations to the function, while you update the code. +- **Do not use non-documented, non-public APIs in your Lambda function code.** For AWS Lambda managed runtimes, Lambda periodically applies security and functional updates to Lambda's internal APIs. These internal API updates may be backwards-incompatible, leading to unintended consequences such as invocation failures if your function has a dependency on these non-public APIs. See the [API reference](https://docs.aws.amazon.com/lambda/latest/api/welcome.html) for a list of publicly available APIs. +- **Write idempotent code.** Writing idempotent code for your functions ensures that duplicate events are handled the same way. Your code should properly validate events and gracefully handle duplicate events. For more information, see [How do I make my Lambda function idempotent?](https://repost.aws/knowledge-center/lambda-function-idempotent). + diff --git a/aws-doc/swift-http-events.md b/aws-doc/swift-http-events.md new file mode 100644 index 00000000..e4977d0a --- /dev/null +++ b/aws-doc/swift-http-events.md @@ -0,0 +1,129 @@ +# Processing HTTP events with Swift + +Amazon API Gateway APIs, Application Load Balancers, and Lambda function URLs can send HTTP events to Lambda. You can use the [Swift AWS Lambda Events](https://github.com/awslabs/swift-aws-lambda-events) package to process events from these sources. + +## Example — Handle API Gateway V2 proxy request + +Note the following: + +- `import AWSLambdaEvents`: The `AWSLambdaEvents` package includes many Lambda event types. Add it to your `Package.swift` as a dependency of your target. +- `APIGatewayV2Request` and `APIGatewayV2Response`: These are the pre-defined request and response types for API Gateway HTTP API (V2) events. + +```swift +import AWSLambdaRuntime +import AWSLambdaEvents + +let runtime = LambdaRuntime { + (event: APIGatewayV2Request, context: LambdaContext) -> APIGatewayV2Response in + + return APIGatewayV2Response( + statusCode: .ok, + body: "Hello AWS Lambda HTTP request" + ) +} + +try await runtime.run() +``` + +## Example — Handle API Gateway V1 (REST API) proxy request + +If you use API Gateway REST API (V1), use the `APIGatewayRequest` and `APIGatewayResponse` types instead: + +```swift +import AWSLambdaRuntime +import AWSLambdaEvents + +let runtime = LambdaRuntime { + (event: APIGatewayRequest, context: LambdaContext) -> APIGatewayResponse in + + return APIGatewayResponse( + statusCode: .ok, + headers: ["content-type": "text/html"], + body: "Hello AWS Lambda HTTP request" + ) +} + +try await runtime.run() +``` + +## Example — Handle Lambda Function URL request + +Lambda function URLs use the same event format as API Gateway HTTP API (V2). You can use `FunctionURLRequest` and `FunctionURLResponse` types, which are equivalent to `APIGatewayV2Request` and `APIGatewayV2Response`: + +```swift +import AWSLambdaRuntime +import AWSLambdaEvents + +let runtime = LambdaRuntime { + (event: FunctionURLRequest, context: LambdaContext) -> FunctionURLResponse in + + return FunctionURLResponse( + statusCode: .ok, + headers: ["content-type": "application/json"], + body: #"{"message": "Hello from Lambda Function URL"}"# + ) +} + +try await runtime.run() +``` + +## Example — Using the protocol-based approach + +For larger applications, you can use the `LambdaHandler` protocol: + +```swift +import AWSLambdaRuntime +import AWSLambdaEvents + +struct APIGatewayLambda: LambdaHandler { + func handle( + _ request: APIGatewayV2Request, + context: LambdaContext + ) async throws -> APIGatewayV2Response { + context.logger.debug("HTTP Method: \(request.context.http.method.rawValue)") + context.logger.debug("Path: \(request.rawPath)") + + return APIGatewayV2Response( + statusCode: .ok, + body: #"{"message": "Hello, World!"}"# + ) + } +} + +let handler = APIGatewayLambda() +let runtime = LambdaRuntime(handler: LambdaCodableAdapter(handler: LambdaHandlerAdapter(handler: handler))) +try await runtime.run() +``` + +## Adding the AWSLambdaEvents dependency + +To use the pre-defined event types, add the `swift-aws-lambda-events` package to your `Package.swift`: + +```swift +// swift-tools-version: 6.3 +import PackageDescription + +let package = Package( + name: "MyHTTPFunction", + platforms: [.macOS(.v15)], + dependencies: [ + .package(url: "https://github.com/awslabs/swift-aws-lambda-runtime.git", from: "3.0.0"), + .package(url: "https://github.com/awslabs/swift-aws-lambda-events.git", from: "1.0.0"), + ], + targets: [ + .executableTarget( + name: "MyHTTPFunction", + dependencies: [ + .product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), + .product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"), + ] + ) + ] +) +``` + +## Sample HTTP Lambda events for Swift + +- [API Gateway example](https://github.com/awslabs/swift-aws-lambda-runtime/tree/main/Examples/APIGatewayV2): A Swift function that handles API Gateway HTTP events. +- [Lambda function URL example](https://github.com/awslabs/swift-aws-lambda-runtime/tree/main/Examples/HelloJSON): A Swift function that handles Lambda function URL events. +- [Streaming response example](https://github.com/awslabs/swift-aws-lambda-runtime/tree/main/Examples/Streaming%2BAPIGateway): A Swift function that streams HTTP responses back to the client. diff --git a/aws-doc/swift-layers.md b/aws-doc/swift-layers.md new file mode 100644 index 00000000..abab1b1c --- /dev/null +++ b/aws-doc/swift-layers.md @@ -0,0 +1,24 @@ +# Working with layers for Swift Lambda functions + +We don't recommend using layers to manage dependencies for Lambda functions written in Swift. This is because Lambda functions in Swift compile into a single executable, which you provide to Lambda when you deploy your function. This executable contains your compiled function code, along with all of its dependencies. Using layers not only complicates this process, but also leads to increased cold start times because your functions need to manually load extra assemblies into memory during the init phase. + +To use external dependencies with your Swift handlers, include them directly in your deployment package. By doing so, you simplify the deployment process and also take advantage of built-in Swift compiler optimizations such as dead code elimination and whole-module optimization. For an example of how to import and use a dependency like the AWS SDK for Swift in your function, see [Define Lambda function handlers in Swift](swift-handler.md). + +## When layers might still be useful + +Although layers are not recommended for dependency management in Swift, there are limited scenarios where layers can still be useful: + +- **Shared configuration files**: If multiple functions need access to the same configuration files (such as `.json` or `.yaml` files), you can package them in a layer. +- **Machine learning models**: If your function loads large model files at runtime, packaging them in a layer can simplify updates to the model without redeploying the function code. +- **Shared native libraries**: If your function depends on native C libraries that aren't included in the Amazon Linux base image and are difficult to statically link, you can package them in a layer. + +In these cases, the layer content is extracted to `/opt` in the function execution environment. You can access these files from your Swift code using standard file I/O: + +```swift +import Foundation + +let configPath = "/opt/config/settings.json" +let data = try Data(contentsOf: URL(fileURLWithPath: configPath)) +``` + +For more general information about layers, see [Managing Lambda dependencies with layers](https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html). diff --git a/aws-doc/swift-logging.md b/aws-doc/swift-logging.md new file mode 100644 index 00000000..c4edcfad --- /dev/null +++ b/aws-doc/swift-logging.md @@ -0,0 +1,147 @@ +# Log and monitor Swift Lambda functions + +AWS Lambda automatically monitors Lambda functions on your behalf and sends logs to Amazon CloudWatch. Your Lambda function comes with a CloudWatch Logs log group and a log stream for each instance of your function. The Lambda runtime environment sends details about each invocation to the log stream, and relays logs and other output from your function's code. For more information, see [Sending Lambda function logs to CloudWatch Logs](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html). This page describes how to produce log output from your Lambda function's code. + +## Creating a function that writes logs + +To output logs from your function code, use the `Logger` instance provided by the `LambdaContext`. The Swift AWS Lambda Runtime uses [swift-log](https://github.com/apple/swift-log), the logging API developed by Apple and the Swift Server Workgroup. + +For every invocation, the runtime creates a request-scoped `Logger` carrying the invocation's `requestID` and `traceID` as metadata, and passes it to your handler as `context.logger`: + +```swift +import AWSLambdaRuntime + +struct Request: Decodable { + let firstName: String +} + +struct Response: Encodable { + let message: String +} + +let runtime = LambdaRuntime { + (event: Request, context: LambdaContext) in + + context.logger.info("Swift function invoked") + context.logger.info("Swift function responds to \(event.firstName)") + + return Response(message: "Hello, \(event.firstName)!") +} + +try await runtime.run() +``` + +> **Note**: Avoid using `print()` for logging in Lambda functions. By default, `print()` output is buffered and may not be immediately sent to CloudWatch. The `context.logger` writes directly to `stderr`, ensuring logs are flushed immediately. + +## Logging without passing the logger around + +Threading `context.logger` through every function your handler calls is tedious. Instead, the runtime binds the request logger as the task-local `Logger.current` for the duration of the handler call. Code anywhere in the handler's call tree can read `Logger.current` and inherit the invocation's metadata, without a `LambdaContext` or `Logger` parameter: + +```swift +import Logging + +func validate(_ event: Request) { + // No logger parameter — reads the task-local logger bound by the runtime. + Logger.current.debug("Validating request") +} + +let runtime = LambdaRuntime { + (event: Request, context: LambdaContext) in + + validate(event) // its log lines still carry this invocation's requestID / traceID + return Response(message: "Hello, \(event.firstName)!") +} + +try await runtime.run() +``` + +Inside the handler itself, `context.logger` and `Logger.current` are equivalent. Use whichever reads better; `context.logger` is more explicit at the call site. + +> **Note**: Task-local values propagate through structured concurrency (`async let`, `withTaskGroup`, child `Task {}`) but are **not** inherited by `Task.detached`. You must capture the logger explicitly across a detached boundary. + +## Log levels + +The Swift `Logger` supports the following log levels, ordered from least severe to most severe: + +- `trace` +- `debug` +- `info` +- `notice` +- `warning` +- `error` +- `critical` + +By default, the log level is set to `info`, meaning that `trace` and `debug` logs are ignored. + +## Configuring the log format and level + +The log format and level are controlled by environment variables set on your Lambda function: + +- **`AWS_LAMBDA_LOG_FORMAT`**: Set to `Text` (default) or `JSON` to control the output format. +- **`AWS_LAMBDA_LOG_LEVEL`** or **`LOG_LEVEL`**: Set to one of the log levels listed above (e.g. `debug`, `info`, `warning`). + +To change the log level, set the `LOG_LEVEL` environment variable on your Lambda function in the AWS Console or in your deployment template: + +```yaml +Resources: + MyFunction: + Type: AWS::Serverless::Function + Properties: + # ... + Environment: + Variables: + LOG_LEVEL: debug + AWS_LAMBDA_LOG_FORMAT: JSON +``` + +## Implementing structured logging + +When `AWS_LAMBDA_LOG_FORMAT` is set to `JSON`, each log line is emitted as a valid JSON object. The runtime automatically includes the `requestID` and `traceID` metadata fields. You can add additional metadata to your log messages: + +```swift +let runtime = LambdaRuntime { + (event: Request, context: LambdaContext) in + + // Log with additional metadata + context.logger.info( + "Processing request", + metadata: ["firstName": "\(event.firstName)"] + ) + + // Different log levels + context.logger.debug("Debug information for development") + context.logger.warning("Something might be wrong") + context.logger.error("An error occurred") + + return Response(message: "Hello, \(event.firstName)!") +} + +try await runtime.run() +``` + +When this Swift function is invoked with `AWS_LAMBDA_LOG_FORMAT=JSON`, it produces log lines similar to the following in CloudWatch: + +```json +{"level":"info","message":"Processing request","metadata":{"firstName":"David","requestID":"a1234-5678-90ab","traceID":"Root=1-abc-def"}} +{"level":"debug","message":"Debug information for development","metadata":{"requestID":"a1234-5678-90ab","traceID":"Root=1-abc-def"}} +``` + +## Binding a logger at application startup + +You can bind a logger before and around `runtime.run()`. This is useful when combining Lambda with other services, such as [Swift Service Lifecycle](https://github.com/swift-server/swift-service-lifecycle): + +```swift +import Logging +import AWSLambdaRuntime + +let logger = Logger(label: "my-function") +try await withLogger(logger) { _ in + let runtime = LambdaRuntime { (event: Request, context: LambdaContext) in + context.logger.info("Processing request") + return Response(message: "Hello!") + } + try await runtime.run() +} +``` + +For information about configuring log formats in Lambda, see [Configuring JSON and plain text log formats](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs-advanced.html). diff --git a/aws-doc/swift-package.md b/aws-doc/swift-package.md new file mode 100644 index 00000000..203c5346 --- /dev/null +++ b/aws-doc/swift-package.md @@ -0,0 +1,203 @@ +# Deploy Swift Lambda functions with .zip file archives or OCI container images + +This page describes how to compile your Swift function, and then deploy the compiled binary to AWS Lambda. It shows how to deploy using the Swift AWS Lambda Runtime plugin, the AWS Command Line Interface, and the AWS Serverless Application Model CLI. + +## Building Swift functions on macOS, Windows, or Linux + +The following steps demonstrate how to create the project for your first Lambda function with Swift and compile it using the Swift AWS Lambda Runtime's built-in SwiftPM plugin, which simplifies building and deploying Swift Lambda functions. + +### Prerequisites + +- Swift 6.3 toolchain or later (macOS 15 Sequoia or later on macOS) +- Docker, Apple container, or the Swift Static Linux SDK installed — to cross-compile for Amazon Linux +- AWS CLI configured with `aws configure` + +### Steps + +1. **Create a new Swift Lambda function project:** + + ```bash + mkdir MyFunction && cd MyFunction + swift package init --type executable + swift package add-dependency https://github.com/awslabs/swift-aws-lambda-runtime.git --from 3.0.0 + swift package add-target-dependency AWSLambdaRuntime MyFunction --package swift-aws-lambda-runtime + ``` + +2. **Scaffold a minimal function using the built-in plugin:** + + ```bash + swift package lambda-init --allow-writing-to-package-directory + ``` + +3. **Test your function locally.** `swift run` starts a local server on port 7000: + + ```bash + swift run & + curl --header "Content-Type: application/json" \ + --data '{"name":"World","age":30}' \ + http://127.0.0.1:7000/invoke + ``` + +4. **Build for Amazon Linux using the plugin:** + + AWS Lambda runs on Amazon Linux. You must cross-compile your code for that platform. The `lambda-build` plugin handles this automatically using one of three cross-compilation methods: + + - **Docker** (default) — uses a Docker container with the Swift toolchain for Amazon Linux 2023. + - **Apple container** — uses Apple's lightweight container runtime (available on macOS 15+) instead of Docker. + - **Swift Static Linux SDK** — uses a pre-installed Static Linux SDK (musl-based). This method needs no Docker or container runtime. Install it with `swift sdk install `. + + Select the method with the `--cross-compile` flag (`docker`, `container`, or `swift-static-sdk`). The default is `docker`. + + ```bash + swift package --allow-network-connections docker lambda-build + ``` + + By default, the plugin compiles for the host machine's architecture (arm64 on Apple Silicon Macs, x86_64 on Intel Macs). To target a different architecture, use the `--architecture` flag: + + ```bash + # Cross-compile for x86_64 from an Apple Silicon Mac + swift package --allow-network-connections docker lambda-build --architecture x64 + ``` + + The architecture is recorded in the build manifest. When you deploy with `lambda-deploy`, the function is automatically configured for the correct architecture. + +### Building as an OCI container image + +Instead of a `.zip` archive, the plugin can produce an OCI container image ready to be pushed to Amazon Elastic Container Registry (ECR). This is useful for large deployment packages or when your function requires additional system libraries. Use the `--archive-format oci` flag: + +```bash +swift package --allow-network-connections docker lambda-build --archive-format oci +``` + +You can customize the base image with `--base-oci-image` (default: `public.ecr.aws/amazonlinux/amazonlinux:2023-minimal`). The base image must be glibc-compatible Amazon Linux 2023. + +When you deploy with `lambda-deploy`, the plugin detects the OCI format and handles pushing the image to ECR and configuring the Lambda function to use it. + +## Deploying the Swift function binary with the runtime plugin + +Use the deploy command to deploy the compiled binary to Lambda. This command creates an execution role and then creates the Lambda function: + +```bash +swift package --allow-network-connections all:443 lambda-deploy +``` + +The plugin creates the IAM role, uploads the code, and creates the Lambda function automatically. When the deployment succeeds, it reports the function ARN and a ready-to-use `aws lambda invoke` command. To specify an existing execution role, use the `--role` flag. + +## Deploying your Swift function binary with the AWS CLI + +You can also deploy your binary with the AWS CLI. + +1. **Build the .zip deployment package.** After building with the `lambda-build` command, the plugin outputs a `.zip` archive ready for deployment. + + ```bash + swift package --allow-network-connections docker lambda-build + ``` + + The resulting archive is located at `.build/plugins/AWSLambdaBuilder/outputs/AWSLambdaBuilder/MyFunction/MyFunction.zip`. + +2. **Deploy the .zip package to Lambda** by running the `create-function` command. + + - For `--runtime`, specify `provided.al2023`. This is an OS-only runtime. OS-only runtimes are used to deploy compiled binaries and custom runtimes to Lambda. + - For `--role`, specify the ARN of the execution role. + + ```bash + aws lambda create-function \ + --function-name my-function \ + --runtime provided.al2023 \ + --role arn:aws:iam::111122223333:role/lambda-role \ + --handler swift.bootstrap \ + --zip-file fileb://.build/plugins/AWSLambdaBuilder/outputs/AWSLambdaBuilder/MyFunction/MyFunction.zip + ``` + + If you built for a specific architecture (e.g. arm64 from an Intel Mac, or x64 from an Apple Silicon Mac), add the matching `--architectures` flag: + + ```bash + aws lambda create-function \ + --function-name my-function \ + --runtime provided.al2023 \ + --role arn:aws:iam::111122223333:role/lambda-role \ + --handler swift.bootstrap \ + --architectures arm64 \ + --zip-file fileb://.build/plugins/AWSLambdaBuilder/outputs/AWSLambdaBuilder/MyFunction/MyFunction.zip + ``` + +## Deploying your Swift function binary with the AWS SAM CLI + +You can also deploy your binary with the AWS SAM CLI. + +1. **Create an AWS SAM template** with the resource and property definition. For `Runtime`, specify `provided.al2023`. This is an OS-only runtime. OS-only runtimes are used to deploy compiled binaries and custom runtimes to Lambda. + + For more information about deploying Lambda functions using AWS SAM, see [AWS::Serverless::Function](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html) in the *AWS Serverless Application Model Developer Guide*. + + **Example — SAM resource and property definition for a Swift binary** + + ```yaml + AWSTemplateFormatVersion: '2010-09-09' + Transform: AWS::Serverless-2016-10-31 + Description: SAM template for Swift Lambda functions + Resources: + SwiftFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: .build/plugins/AWSLambdaBuilder/outputs/AWSLambdaBuilder/MyFunction/MyFunction.zip + Handler: swift.bootstrap + Runtime: provided.al2023 + MemorySize: 128 + Architectures: + - arm64 + Outputs: + SwiftFunction: + Description: "Lambda Function ARN" + Value: !GetAtt SwiftFunction.Arn + ``` + +2. **Build the function:** + + ```bash + swift package --allow-network-connections docker lambda-build + ``` + +3. **Deploy the function:** + + ```bash + sam deploy --guided + ``` + +## Invoking your Swift function locally + +You can test your function locally without deploying. The Swift AWS Lambda Runtime starts a local HTTP server on port 7000 when you run it outside the Lambda execution environment: + +```bash +swift run & +curl --header "Content-Type: application/json" \ + --data '{"name":"World","age":30}' \ + http://127.0.0.1:7000/invoke +``` + +You can also configure the local server address with environment variables: + +- `LOCAL_LAMBDA_HOST` — bind a different TCP address +- `LOCAL_LAMBDA_PORT` — bind a different TCP port +- `LOCAL_LAMBDA_INVOCATION_ENDPOINT` — use a different endpoint path + +## Deleting your Swift function + +When you're done, clean up the function and its IAM role: + +```bash +swift package --allow-network-connections all:443 lambda-deploy --delete +``` + +## Invoking your Swift function with the AWS CLI + +You can use the AWS CLI to invoke the deployed function: + +```bash +aws lambda invoke \ + --function-name my-function \ + --cli-binary-format raw-in-base64-out \ + --payload '{"name":"World","age":30}' \ + /dev/stdout +``` + +The `cli-binary-format` option is required if you're using AWS CLI version 2. To make this the default setting, run `aws configure set cli-binary-format raw-in-base64-out`. For more information, see [AWS CLI supported global command line options](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-options.html) in the *AWS Command Line Interface User Guide for Version 2*. From c221655932f80d21e938a0c53bdf1bf713f03cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Wed, 29 Jul 2026 07:25:54 +0200 Subject: [PATCH 2/3] Exclude aws-doc/ from Swift Format and License check --- .licenseignore | 1 + .swiftformatignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.licenseignore b/.licenseignore index cc4418d3..42de3f9f 100644 --- a/.licenseignore +++ b/.licenseignore @@ -39,3 +39,4 @@ Sources/AWSLambdaPluginHelper/GeneratedClients/** **/*.txt *.toml .kiro/* +aws-doc/* diff --git a/.swiftformatignore b/.swiftformatignore index b708db42..a7f2ab62 100644 --- a/.swiftformatignore +++ b/.swiftformatignore @@ -1,2 +1,3 @@ Sources/AWSLambdaPluginHelper/lambda-init/Template.swift Sources/AWSLambdaPluginHelper/GeneratedClients +aws-doc/ \ No newline at end of file From b28ba044cb4cca7107dde350d54a9ffb59d16cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= Date: Wed, 29 Jul 2026 07:29:47 +0200 Subject: [PATCH 3/3] fix swift format --- .../Sources/LambdaStreaming+Codable.swift | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Examples/Streaming+Codable/Sources/LambdaStreaming+Codable.swift b/Examples/Streaming+Codable/Sources/LambdaStreaming+Codable.swift index 5325ffd3..23a90fcb 100644 --- a/Examples/Streaming+Codable/Sources/LambdaStreaming+Codable.swift +++ b/Examples/Streaming+Codable/Sources/LambdaStreaming+Codable.swift @@ -48,11 +48,11 @@ public protocol StreamingLambdaHandlerWithEvent: _Lambda_SendableMetatype { /// - If ``LambdaResponseStreamWriter/finish()`` has already been called before the error is thrown, the /// error will be logged. nonisolated(nonsending) - mutating func handle( - _ event: Event, - responseWriter: some LambdaResponseStreamWriter, - context: LambdaContext - ) async throws + mutating func handle( + _ event: Event, + responseWriter: some LambdaResponseStreamWriter, + context: LambdaContext + ) async throws } /// Adapts a ``StreamingLambdaHandlerWithEvent`` to work as a ``StreamingLambdaHandler`` @@ -131,11 +131,12 @@ public struct StreamingFromEventClosureHandler: StreamingLambd /// - responseWriter: The response writer for streaming output. /// - context: The Lambda context. nonisolated(nonsending) - public func handle( - _ event: Event, - responseWriter: some LambdaResponseStreamWriter, - context: LambdaContext - ) async throws { + public func handle( + _ event: Event, + responseWriter: some LambdaResponseStreamWriter, + context: LambdaContext + ) async throws + { try await self.body(event, responseWriter, context) } } @@ -162,7 +163,9 @@ extension LambdaRuntime { public convenience init( decoder: JSONDecoder = JSONDecoder(), logger: Logger = Logger.current, - streamingBody: nonisolated(nonsending) @Sendable @escaping (Event, LambdaResponseStreamWriter, LambdaContext) async throws -> Void + streamingBody: + nonisolated(nonsending) @Sendable @escaping (Event, LambdaResponseStreamWriter, LambdaContext) async throws + -> Void ) where Handler == StreamingLambdaCodableAdapter<