From 65a56cd73f4f8b6777fc46bd2426b6c280b81c65 Mon Sep 17 00:00:00 2001 From: thisismana Date: Fri, 5 Sep 2025 10:43:21 +0200 Subject: [PATCH 1/7] feat: added lambda logging config Allow configuring aws lambda logging Sample: ``` logging_config = { log_format = "JSON" application_log_level = "INFO" system_log_level = "WARN" } ``` --- README.md | 1 + examples/complete/main.tf | 6 ++++++ examples/fixtures/context/index.js | 10 +++++++--- main.tf | 21 +++++++++++++++++++++ variables.tf | 15 +++++++++++++++ 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index def6c61..7bbe42a 100644 --- a/README.md +++ b/README.md @@ -415,6 +415,7 @@ No modules. | [kms\_key\_arn](#input\_kms\_key\_arn) | Amazon Resource Name (ARN) of the AWS Key Management Service (KMS) key that is used to encrypt environment variables. If this configuration is not provided when environment variables are in use, AWS Lambda uses a default service key. If this configuration is provided when environment variables are not in use, the AWS Lambda API does not save this configuration and Terraform will show a perpetual difference of adding the key. To fix the perpetual difference, remove this configuration. | `string` | `""` | no | | [lambda\_at\_edge](#input\_lambda\_at\_edge) | Enable Lambda@Edge for your Node.js or Python functions. Required trust relationship and publishing of function versions will be configured. | `bool` | `false` | no | | [layers](#input\_layers) | List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function. | `list(string)` | `[]` | no | +| [logging\_config](#input\_logging\_config) | The function's Amazon CloudWatch Logs configuration settings. |
object({
log_format = string
application_log_level = optional(string, null)
log_group = optional(string, null)
system_log_level = optional(string, null)
})
| `null` | no | | [memory\_size](#input\_memory\_size) | Amount of memory in MB your Lambda Function can use at runtime. | `number` | `128` | no | | [package\_type](#input\_package\_type) | The Lambda deployment package type. Valid values are Zip and Image. | `string` | `"Zip"` | no | | [publish](#input\_publish) | Whether to publish creation/change as new Lambda Function Version. | `bool` | `false` | no | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 1c95ff9..184de52 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -37,6 +37,12 @@ module "lambda" { } } + logging_config = { + log_format = "JSON" + application_log_level = "INFO" + system_log_level = "WARN" + } + // AWS Systems Manager (SSM) Parameter Store ssm = { parameter_names = ["/internal/params", "/external/params"] diff --git a/examples/fixtures/context/index.js b/examples/fixtures/context/index.js index d7836d5..61216ca 100644 --- a/examples/fixtures/context/index.js +++ b/examples/fixtures/context/index.js @@ -1,4 +1,8 @@ -exports.handler = async function(event, context) { - console.log("EVENT: \n" + JSON.stringify(event, null, 2)) - return context.logStreamName +exports.handler = async function (event, context) { + + console.debug({ event, context }) + console.info("Hello from Lambda!") + console.warn("This is a warning message!") + + return context.logStreamName } \ No newline at end of file diff --git a/main.tf b/main.tf index 0ea3e8c..7398859 100644 --- a/main.tf +++ b/main.tf @@ -77,6 +77,17 @@ resource "aws_lambda_function" "lambda" { } } + dynamic "logging_config" { + for_each = var.logging_config == null ? [] : [var.logging_config] + content { + application_log_level = logging_config.value.application_log_level + log_format = logging_config.value.log_format + log_group = logging_config.value.log_group + system_log_level = logging_config.value.system_log_level + } + } + + // create the CloudWatch log group first so it's no create automatically // by AWS Lambda depends_on = [aws_cloudwatch_log_group.lambda] @@ -158,6 +169,16 @@ resource "aws_lambda_function" "lambda_external_lifecycle" { } } + dynamic "logging_config" { + for_each = var.logging_config == null ? [] : [var.logging_config] + content { + application_log_level = logging_config.value.application_log_level + log_format = logging_config.value.log_format + log_group = logging_config.value.log_group + system_log_level = logging_config.value.system_log_level + } + } + // create the CloudWatch log group first so it's no create automatically // by AWS Lambda depends_on = [aws_cloudwatch_log_group.lambda] diff --git a/variables.tf b/variables.tf index c0064a7..ec7e110 100644 --- a/variables.tf +++ b/variables.tf @@ -249,6 +249,21 @@ variable "vpc_config" { }) } +variable "logging_config" { + description = "The function's Amazon CloudWatch Logs configuration settings." + default = null + type = object({ + log_format = string + application_log_level = optional(string, null) + log_group = optional(string, null) + system_log_level = optional(string, null) + }) + validation { + condition = var.logging_config == null || (var.logging_config.log_format == "JSON" || var.logging_config.log_format == "Text") + error_message = "log_format must be either 'JSON' or 'Text'" + } +} + variable "iam_role_name" { description = "Override the name of the IAM role for the function. Otherwise the default will be your function name with the region as a suffix." default = null From f93c3e982259e3e554f6e061257e873c7f7d8217 Mon Sep 17 00:00:00 2001 From: Moritz Zimmer Date: Tue, 23 Sep 2025 09:58:46 +0200 Subject: [PATCH 2/7] pr feedback --- README.md | 2 +- examples/complete/main.tf | 13 +++++++------ main.tf | 1 - variables.tf | 6 +----- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7bbe42a..fc7e95c 100644 --- a/README.md +++ b/README.md @@ -415,7 +415,7 @@ No modules. | [kms\_key\_arn](#input\_kms\_key\_arn) | Amazon Resource Name (ARN) of the AWS Key Management Service (KMS) key that is used to encrypt environment variables. If this configuration is not provided when environment variables are in use, AWS Lambda uses a default service key. If this configuration is provided when environment variables are not in use, the AWS Lambda API does not save this configuration and Terraform will show a perpetual difference of adding the key. To fix the perpetual difference, remove this configuration. | `string` | `""` | no | | [lambda\_at\_edge](#input\_lambda\_at\_edge) | Enable Lambda@Edge for your Node.js or Python functions. Required trust relationship and publishing of function versions will be configured. | `bool` | `false` | no | | [layers](#input\_layers) | List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function. | `list(string)` | `[]` | no | -| [logging\_config](#input\_logging\_config) | The function's Amazon CloudWatch Logs configuration settings. |
object({
log_format = string
application_log_level = optional(string, null)
log_group = optional(string, null)
system_log_level = optional(string, null)
})
| `null` | no | +| [logging\_config](#input\_logging\_config) | Configuration block for advanced logging settings. |
object({
log_format = string
application_log_level = optional(string, null)
log_group = optional(string, null)
system_log_level = optional(string, null)
})
| `null` | no | | [memory\_size](#input\_memory\_size) | Amount of memory in MB your Lambda Function can use at runtime. | `number` | `128` | no | | [package\_type](#input\_package\_type) | The Lambda deployment package type. Valid values are Zip and Image. | `string` | `"Zip"` | no | | [publish](#input\_publish) | Whether to publish creation/change as new Lambda Function Version. | `bool` | `false` | no | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 184de52..699b3e1 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -31,18 +31,19 @@ module "lambda" { cloudwatch_lambda_insights_enabled = true layers = ["arn:aws:lambda:${local.region}:580247275435:layer:LambdaInsightsExtension-Arm64:23"] - environment = { - variables = { - key = "value" - } - } - + // Advanced logging configuration logging_config = { log_format = "JSON" application_log_level = "INFO" system_log_level = "WARN" } + environment = { + variables = { + key = "value" + } + } + // AWS Systems Manager (SSM) Parameter Store ssm = { parameter_names = ["/internal/params", "/external/params"] diff --git a/main.tf b/main.tf index 7398859..a626ddc 100644 --- a/main.tf +++ b/main.tf @@ -87,7 +87,6 @@ resource "aws_lambda_function" "lambda" { } } - // create the CloudWatch log group first so it's no create automatically // by AWS Lambda depends_on = [aws_cloudwatch_log_group.lambda] diff --git a/variables.tf b/variables.tf index ec7e110..c3b5acf 100644 --- a/variables.tf +++ b/variables.tf @@ -250,7 +250,7 @@ variable "vpc_config" { } variable "logging_config" { - description = "The function's Amazon CloudWatch Logs configuration settings." + description = "Configuration block for advanced logging settings." default = null type = object({ log_format = string @@ -258,10 +258,6 @@ variable "logging_config" { log_group = optional(string, null) system_log_level = optional(string, null) }) - validation { - condition = var.logging_config == null || (var.logging_config.log_format == "JSON" || var.logging_config.log_format == "Text") - error_message = "log_format must be either 'JSON' or 'Text'" - } } variable "iam_role_name" { From 0bcd17c9246c7f33471ba97d0e420250fafd3fe5 Mon Sep 17 00:00:00 2001 From: Moritz Zimmer Date: Tue, 23 Sep 2025 13:38:54 +0200 Subject: [PATCH 3/7] enhanced cloudwatch logs example with working producer Lambda functions --- README.md | 8 +-- examples/complete/main.tf | 6 +- examples/complete/provider.tf | 1 - .../README.md | 11 +++- .../with-cloudwatch-logs-subscription/main.tf | 61 ++++++++++++------- .../processor/index.js | 14 +++++ .../versions.tf | 4 ++ 7 files changed, 71 insertions(+), 34 deletions(-) create mode 100644 examples/with-cloudwatch-logs-subscription/processor/index.js diff --git a/README.md b/README.md index fc7e95c..8627e8d 100644 --- a/README.md +++ b/README.md @@ -238,8 +238,7 @@ The module will create a [CloudWatch Log Group](https://registry.terraform.io/pr for your Lambda function. It's retention period and [CloudWatch Logs subscription filters](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter) to stream logs to other Lambda functions (e.g. to forward logs to Amazon OpenSearch Service) can be declared inline. -The module will create the required [Lambda permissions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) automatically. -Sending logs to CloudWatch can be disabled with `cloudwatch_logs_enabled = false` +The module will create the required [Lambda permissions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) automatically. Those permissions can be removed by setting `cloudwatch_logs_enabled = false`. see [example](examples/with-cloudwatch-logs-subscription) for details @@ -253,12 +252,13 @@ module "lambda" { cloudwatch_logs_retention_in_days = 14 cloudwatch_log_subscription_filters = { - lambda_1 = { + destination_1 = { //see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter for available arguments destination_arn = module.destination_1.arn + filter_pattern = "%Lambda%" } - lambda_2 = { + destination_2 = { destination_arn = module.destination_2.arn } } diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 699b3e1..67a81b0 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -23,18 +23,18 @@ module "lambda" { snap_start = false source_code_hash = module.fixtures.output_base64sha256 timeout = 3 - tracing_config_mode = "Active" - // logs and metrics + // logs, metrics and tracing cloudwatch_logs_enabled = true cloudwatch_logs_retention_in_days = 7 cloudwatch_lambda_insights_enabled = true layers = ["arn:aws:lambda:${local.region}:580247275435:layer:LambdaInsightsExtension-Arm64:23"] + tracing_config_mode = "Active" // Advanced logging configuration logging_config = { - log_format = "JSON" application_log_level = "INFO" + log_format = "JSON" system_log_level = "WARN" } diff --git a/examples/complete/provider.tf b/examples/complete/provider.tf index aab71dc..5300107 100644 --- a/examples/complete/provider.tf +++ b/examples/complete/provider.tf @@ -4,5 +4,4 @@ provider "aws" { skip_credentials_validation = true skip_metadata_api_check = true skip_region_validation = true - } diff --git a/examples/with-cloudwatch-logs-subscription/README.md b/examples/with-cloudwatch-logs-subscription/README.md index c5c7373..e2a75f4 100644 --- a/examples/with-cloudwatch-logs-subscription/README.md +++ b/examples/with-cloudwatch-logs-subscription/README.md @@ -18,11 +18,14 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | +| [archive](#requirement\_archive) | >= 2.2 | | [aws](#requirement\_aws) | >= 6.0 | ## Providers -No providers. +| Name | Version | +|------|---------| +| [archive](#provider\_archive) | >= 2.2 | ## Modules @@ -30,12 +33,14 @@ No providers. |------|--------|---------| | [destination\_1](#module\_destination\_1) | ../../ | n/a | | [destination\_2](#module\_destination\_2) | ../../ | n/a | +| [fixtures](#module\_fixtures) | ../fixtures | n/a | | [lambda](#module\_lambda) | ../../ | n/a | -| [source](#module\_source) | ../fixtures | n/a | ## Resources -No resources. +| Name | Type | +|------|------| +| [archive_file.destination_handler](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | ## Inputs diff --git a/examples/with-cloudwatch-logs-subscription/main.tf b/examples/with-cloudwatch-logs-subscription/main.tf index b225d77..63188f5 100644 --- a/examples/with-cloudwatch-logs-subscription/main.tf +++ b/examples/with-cloudwatch-logs-subscription/main.tf @@ -1,48 +1,63 @@ -module "source" { +locals { + handler = "index.handler" + runtime = "nodejs22.x" +} + +module "fixtures" { source = "../fixtures" } module "lambda" { source = "../../" - cloudwatch_logs_retention_in_days = 14 - description = "Example usage for an AWS Lambda with a CloudWatch logs subscription filter." - filename = module.source.output_path - function_name = "example-without-cloudwatch-logs-subscription" - handler = "index.handler" - runtime = "nodejs22.x" - source_code_hash = module.source.output_base64sha256 + description = "Example usage for an AWS Lambda with a CloudWatch logs subscription filters and advanced logging." + filename = module.fixtures.output_path + function_name = module.fixtures.output_function_name + handler = local.handler + runtime = local.runtime + source_code_hash = module.fixtures.output_base64sha256 + + cloudwatch_logs_retention_in_days = 7 cloudwatch_log_subscription_filters = { - lambda_1 = { - //see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter for available arguments - destination_arn = module.destination_1.arn // required + destination_1 = { + destination_arn = module.destination_1.arn + filter_pattern = "%Lambda%" } - lambda_2 = { - destination_arn = module.destination_2.arn // required + destination_2 = { + destination_arn = module.destination_2.arn } } } +data "archive_file" "destination_handler" { + type = "zip" + source_file = "${path.module}/processor/index.js" + output_path = "${path.module}/processor.zip" + output_file_mode = "0666" +} + module "destination_1" { source = "../../" cloudwatch_logs_retention_in_days = 1 - filename = module.source.output_path - function_name = "cloudwatch-logs-subscription-destination-1" - handler = "index.handler" - runtime = "nodejs22.x" - source_code_hash = module.source.output_base64sha256 + description = "Lambda destination 1 of '${module.lambda.cloudwatch_log_group_name}'" + filename = data.archive_file.destination_handler.output_path + function_name = "${module.fixtures.output_function_name}-destination-1" + handler = local.handler + runtime = local.runtime + source_code_hash = data.archive_file.destination_handler.output_base64sha256 } module "destination_2" { source = "../../" cloudwatch_logs_retention_in_days = 1 - filename = module.source.output_path - function_name = "cloudwatch-logs-subscription-destination-2" - handler = "index.handler" - runtime = "nodejs22.x" - source_code_hash = module.source.output_base64sha256 + description = "Lambda destination 2 of '${module.lambda.cloudwatch_log_group_name}'" + filename = data.archive_file.destination_handler.output_path + function_name = "${module.fixtures.output_function_name}-destination-2" + handler = local.handler + runtime = local.runtime + source_code_hash = data.archive_file.destination_handler.output_base64sha256 } diff --git a/examples/with-cloudwatch-logs-subscription/processor/index.js b/examples/with-cloudwatch-logs-subscription/processor/index.js new file mode 100644 index 0000000..928cc68 --- /dev/null +++ b/examples/with-cloudwatch-logs-subscription/processor/index.js @@ -0,0 +1,14 @@ +var zlib = require('zlib'); + +exports.handler = function(input, context) { + var payload = Buffer.from(input.awslogs.data, 'base64'); + zlib.gunzip(payload, function(e, result) { + if (e) { + context.fail(e); + } else { + result = JSON.parse(result.toString()); + console.log("Event Data:", JSON.stringify(result, null, 2)); + context.succeed(); + } + }); +}; \ No newline at end of file diff --git a/examples/with-cloudwatch-logs-subscription/versions.tf b/examples/with-cloudwatch-logs-subscription/versions.tf index db13b0a..b8e9229 100644 --- a/examples/with-cloudwatch-logs-subscription/versions.tf +++ b/examples/with-cloudwatch-logs-subscription/versions.tf @@ -6,5 +6,9 @@ terraform { source = "hashicorp/aws" version = ">= 6.0" } + archive = { + source = "hashicorp/archive" + version = ">= 2.2" + } } } From 6a972d3d25806d3848801442cf6e5d7657b52452 Mon Sep 17 00:00:00 2001 From: Moritz Zimmer Date: Wed, 24 Sep 2025 16:51:55 +0200 Subject: [PATCH 4/7] support existing log group names and existing log groups --- README.md | 14 +-- cloudwatch_logs.tf | 21 +++- examples/cloudwatch-logs/README.md | 65 +++++++++++++ .../handler}/index.js | 0 examples/cloudwatch-logs/main.tf | 96 +++++++++++++++++++ examples/cloudwatch-logs/outputs.tf | 34 +++++++ .../provider.tf | 0 .../variables.tf | 0 .../versions.tf | 0 examples/complete/README.md | 1 + examples/complete/outputs.tf | 5 + .../README.md | 59 ------------ .../with-cloudwatch-logs-subscription/main.tf | 63 ------------ .../outputs.tf | 19 ---- iam.tf | 2 +- outputs.tf | 4 +- variables.tf | 47 +++++---- 17 files changed, 257 insertions(+), 173 deletions(-) create mode 100644 examples/cloudwatch-logs/README.md rename examples/{with-cloudwatch-logs-subscription/processor => cloudwatch-logs/handler}/index.js (100%) create mode 100644 examples/cloudwatch-logs/main.tf create mode 100644 examples/cloudwatch-logs/outputs.tf rename examples/{with-cloudwatch-logs-subscription => cloudwatch-logs}/provider.tf (100%) rename examples/{with-cloudwatch-logs-subscription => cloudwatch-logs}/variables.tf (100%) rename examples/{with-cloudwatch-logs-subscription => cloudwatch-logs}/versions.tf (100%) delete mode 100644 examples/with-cloudwatch-logs-subscription/README.md delete mode 100644 examples/with-cloudwatch-logs-subscription/main.tf delete mode 100644 examples/with-cloudwatch-logs-subscription/outputs.tf diff --git a/README.md b/README.md index 8627e8d..ce554cb 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ see [documentation](https://www.terraform.io/docs/providers/aws/r/lambda_functio ### basic -see [example](examples/complete) for other configuration options +see [example](examples/complete) for more configuration options ```hcl provider "aws" { @@ -240,16 +240,16 @@ to stream logs to other Lambda functions (e.g. to forward logs to Amazon OpenSea The module will create the required [Lambda permissions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) automatically. Those permissions can be removed by setting `cloudwatch_logs_enabled = false`. -see [example](examples/with-cloudwatch-logs-subscription) for details +see [example](examples/cloudwatch-logs) for details ```hcl module "lambda" { // see above - // disable CloudWatch logs + // remove CloudWatch logs IAM permissions // cloudwatch_logs_enabled = false - cloudwatch_logs_retention_in_days = 14 + cloudwatch_logs_retention_in_days = 7 cloudwatch_log_subscription_filters = { destination_1 = { @@ -288,7 +288,7 @@ module "lambda" { For `image` deployment packages, the Lambda Insights extension needs to be added to the [container image](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-Getting-Started-docker.html): ```dockerfile -FROM public.ecr.aws/lambda/nodejs:12 +FROM public.ecr.aws/lambda/nodejs:22 RUN curl -O https://lambda-insights-extension.s3-ap-northeast-1.amazonaws.com/amazon_linux/lambda-insights-extension.rpm && \ rpm -U lambda-insights-extension.rpm && \ @@ -312,7 +312,7 @@ see [examples](examples/deployment) for details. - [container-image](examples/container-image) - [deployment](examples/deployment) - [with-cloudwatch-event-rules](examples/with-cloudwatch-event-rules) -- [with-cloudwatch-logs-subscription](examples/with-cloudwatch-logs-subscription) +- [with-cloudwatch-logs-subscription](examples/cloudwatch-logs) - [with-event-source-mappings](examples/with-event-source-mappings) - [with-sns-subscriptions](examples/with-sns-subscriptions) - [with-vpc](examples/with-vpc) @@ -380,6 +380,7 @@ No modules. | [aws_lambda_permission.sns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | | [aws_sns_topic_subscription.subscription](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription) | resource | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_cloudwatch_log_group.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudwatch_log_group) | data source | | [aws_iam_policy.lambda_insights](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source | | [aws_iam_policy.tracing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source | | [aws_iam_policy.vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source | @@ -401,6 +402,7 @@ No modules. | [cloudwatch\_logs\_enabled](#input\_cloudwatch\_logs\_enabled) | Enables your Lambda function to send logs to CloudWatch. The IAM role of this Lambda function will be enhanced with required permissions. | `bool` | `true` | no | | [cloudwatch\_logs\_kms\_key\_id](#input\_cloudwatch\_logs\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data. | `string` | `null` | no | | [cloudwatch\_logs\_retention\_in\_days](#input\_cloudwatch\_logs\_retention\_in\_days) | Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653, and 0. If you select 0, the events in the log group are always retained and never expire. | `number` | `null` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Create and manage the CloudWatch Log Group for the Lambda function. Set to `false` to reuse an existing log group. | `bool` | `true` | no | | [description](#input\_description) | Description of what your Lambda Function does. | `string` | `""` | no | | [environment](#input\_environment) | Environment (e.g. env variables) configuration for the Lambda function enable you to dynamically pass settings to your function code and libraries |
object({
variables = map(string)
})
| `null` | no | | [ephemeral\_storage\_size](#input\_ephemeral\_storage\_size) | The size of your Lambda functions ephemeral storage (/tmp) represented in MB. Valid value between 512 MB to 10240 MB. | `number` | `512` | no | diff --git a/cloudwatch_logs.tf b/cloudwatch_logs.tf index 0204325..89ffc42 100644 --- a/cloudwatch_logs.tf +++ b/cloudwatch_logs.tf @@ -1,7 +1,22 @@ +locals { + log_group_name = coalesce(try(var.logging_config.log_group, null), "/aws/lambda/${var.lambda_at_edge ? "us-east-1." : ""}${var.function_name}") + log_group_arn = try(data.aws_cloudwatch_log_group.lambda[0].arn, aws_cloudwatch_log_group.lambda[0].arn, "") +} + +data "aws_cloudwatch_log_group" "lambda" { + count = var.create_cloudwatch_log_group ? 0 : 1 + + region = var.region + + name = local.log_group_name +} + resource "aws_cloudwatch_log_group" "lambda" { + count = var.create_cloudwatch_log_group ? 1 : 0 + region = var.region - name = "/aws/lambda/${var.lambda_at_edge ? "us-east-1." : ""}${var.function_name}" + name = local.log_group_name retention_in_days = var.cloudwatch_logs_retention_in_days kms_key_id = var.cloudwatch_logs_kms_key_id tags = var.tags @@ -15,7 +30,7 @@ resource "aws_lambda_permission" "cloudwatch_logs" { action = "lambda:InvokeFunction" function_name = lookup(each.value, "destination_arn", null) principal = "logs.${data.aws_region.current.region}.amazonaws.com" - source_arn = "${aws_cloudwatch_log_group.lambda.arn}:*" + source_arn = "${local.log_group_arn}:*" } resource "aws_cloudwatch_log_subscription_filter" "cloudwatch_logs" { @@ -27,7 +42,7 @@ resource "aws_cloudwatch_log_subscription_filter" "cloudwatch_logs" { destination_arn = lookup(each.value, "destination_arn", null) distribution = lookup(each.value, "distribution", null) filter_pattern = lookup(each.value, "filter_pattern", "") - log_group_name = aws_cloudwatch_log_group.lambda.name + log_group_name = local.log_group_name name = each.key role_arn = lookup(each.value, "role_arn", null) } diff --git a/examples/cloudwatch-logs/README.md b/examples/cloudwatch-logs/README.md new file mode 100644 index 0000000..4e07544 --- /dev/null +++ b/examples/cloudwatch-logs/README.md @@ -0,0 +1,65 @@ +# Example with CloudWatch logs configuration + +Creates an AWS Lambda functions with various CloudWatch logs configurations. + +## usage + +``` +terraform init +terraform plan +terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` to destroy those resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [archive](#requirement\_archive) | >= 2.2 | +| [aws](#requirement\_aws) | >= 6.0 | + +## Providers + +| Name | Version | +|------|---------| +| [archive](#provider\_archive) | >= 2.2 | +| [aws](#provider\_aws) | >= 6.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [custom\_log\_group\_name](#module\_custom\_log\_group\_name) | ../../ | n/a | +| [fixtures](#module\_fixtures) | ../fixtures | n/a | +| [logs\_subscription](#module\_logs\_subscription) | ../../ | n/a | +| [sub\_1](#module\_sub\_1) | ../../ | n/a | +| [sub\_2](#module\_sub\_2) | ../../ | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.existing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [archive_file.subscription_handler](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [region](#input\_region) | n/a | `string` | `"eu-west-1"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The Amazon Resource Name (ARN) identifying your Lambda Function. | +| [cloudwatch\_custom\_log\_group\_arn](#output\_cloudwatch\_custom\_log\_group\_arn) | The Amazon Resource Name (ARN) identifying the custom CloudWatch log group used by your Lambda function. | +| [cloudwatch\_custom\_log\_group\_name](#output\_cloudwatch\_custom\_log\_group\_name) | The name of the custom CloudWatch log group. | +| [cloudwatch\_existing\_log\_group\_arn](#output\_cloudwatch\_existing\_log\_group\_arn) | The Amazon Resource Name (ARN) identifying the existing CloudWatch log group used by your Lambda function. | +| [cloudwatch\_existing\_log\_group\_name](#output\_cloudwatch\_existing\_log\_group\_name) | The name of the existing CloudWatch log group. | +| [function\_name](#output\_function\_name) | The unique name of your Lambda Function. | +| [role\_name](#output\_role\_name) | The name of the IAM role attached to the Lambda Function. | + \ No newline at end of file diff --git a/examples/with-cloudwatch-logs-subscription/processor/index.js b/examples/cloudwatch-logs/handler/index.js similarity index 100% rename from examples/with-cloudwatch-logs-subscription/processor/index.js rename to examples/cloudwatch-logs/handler/index.js diff --git a/examples/cloudwatch-logs/main.tf b/examples/cloudwatch-logs/main.tf new file mode 100644 index 0000000..45ac900 --- /dev/null +++ b/examples/cloudwatch-logs/main.tf @@ -0,0 +1,96 @@ +locals { + handler = "index.handler" + runtime = "nodejs22.x" +} + +module "fixtures" { + source = "../fixtures" +} + +module "custom_log_group_name" { + source = "../../" + + description = "Example usage for an AWS Lambda using a custom log group name." + filename = module.fixtures.output_path + function_name = "${module.fixtures.output_function_name}-custom-log-group" + handler = local.handler + runtime = local.runtime + source_code_hash = module.fixtures.output_base64sha256 + + logging_config = { + log_format = "JSON" + log_group = "/custom/${module.fixtures.output_function_name}" + } +} + +module "logs_subscription" { + source = "../../" + + description = "Example usage for an AWS Lambda with a CloudWatch logs subscription filters." + filename = module.fixtures.output_path + function_name = "${module.fixtures.output_function_name}-filter-source" + handler = local.handler + runtime = local.runtime + source_code_hash = module.fixtures.output_base64sha256 + + cloudwatch_log_subscription_filters = { + sub_1 = { + destination_arn = module.sub_1.arn + filter_pattern = "%Lambda%" + } + + sub_2 = { + destination_arn = module.sub_2.arn + } + } +} + +data "archive_file" "subscription_handler" { + type = "zip" + source_file = "${path.module}/handler/index.js" + output_path = "${path.module}/handler.zip" + output_file_mode = "0666" +} + +resource "aws_cloudwatch_log_group" "existing" { + name = "/existing/${module.fixtures.output_function_name}" + retention_in_days = 1 +} + +module "sub_1" { + source = "../../" + + cloudwatch_logs_retention_in_days = 1 + description = "Subscriber function 1 using an existing log group." + filename = data.archive_file.subscription_handler.output_path + function_name = "${module.fixtures.output_function_name}-filter-sub-1" + handler = local.handler + runtime = local.runtime + source_code_hash = data.archive_file.subscription_handler.output_base64sha256 + + create_cloudwatch_log_group = false + + logging_config = { + log_format = "Text" + log_group = aws_cloudwatch_log_group.existing.name + } +} + +module "sub_2" { + source = "../../" + + cloudwatch_logs_retention_in_days = 1 + description = "Subscriber function 2 using an existing log group." + filename = data.archive_file.subscription_handler.output_path + function_name = "${module.fixtures.output_function_name}-filter-sub-2" + handler = local.handler + runtime = local.runtime + source_code_hash = data.archive_file.subscription_handler.output_base64sha256 + + create_cloudwatch_log_group = false + + logging_config = { + log_format = "Text" + log_group = aws_cloudwatch_log_group.existing.name + } +} diff --git a/examples/cloudwatch-logs/outputs.tf b/examples/cloudwatch-logs/outputs.tf new file mode 100644 index 0000000..76a0b0a --- /dev/null +++ b/examples/cloudwatch-logs/outputs.tf @@ -0,0 +1,34 @@ +output "arn" { + description = "The Amazon Resource Name (ARN) identifying your Lambda Function." + value = module.logs_subscription.arn +} + +output "cloudwatch_custom_log_group_name" { + description = "The name of the custom CloudWatch log group." + value = module.custom_log_group_name.cloudwatch_log_group_name +} + +output "cloudwatch_custom_log_group_arn" { + description = "The Amazon Resource Name (ARN) identifying the custom CloudWatch log group used by your Lambda function." + value = module.custom_log_group_name.cloudwatch_log_group_arn +} + +output "cloudwatch_existing_log_group_name" { + description = "The name of the existing CloudWatch log group." + value = module.sub_1.cloudwatch_log_group_name +} + +output "cloudwatch_existing_log_group_arn" { + description = "The Amazon Resource Name (ARN) identifying the existing CloudWatch log group used by your Lambda function." + value = module.sub_1.cloudwatch_log_group_arn +} + +output "function_name" { + description = "The unique name of your Lambda Function." + value = module.logs_subscription.function_name +} + +output "role_name" { + description = "The name of the IAM role attached to the Lambda Function." + value = module.logs_subscription.role_name +} diff --git a/examples/with-cloudwatch-logs-subscription/provider.tf b/examples/cloudwatch-logs/provider.tf similarity index 100% rename from examples/with-cloudwatch-logs-subscription/provider.tf rename to examples/cloudwatch-logs/provider.tf diff --git a/examples/with-cloudwatch-logs-subscription/variables.tf b/examples/cloudwatch-logs/variables.tf similarity index 100% rename from examples/with-cloudwatch-logs-subscription/variables.tf rename to examples/cloudwatch-logs/variables.tf diff --git a/examples/with-cloudwatch-logs-subscription/versions.tf b/examples/cloudwatch-logs/versions.tf similarity index 100% rename from examples/with-cloudwatch-logs-subscription/versions.tf rename to examples/cloudwatch-logs/versions.tf diff --git a/examples/complete/README.md b/examples/complete/README.md index c17b508..c441efe 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -46,6 +46,7 @@ No resources. | Name | Description | |------|-------------| | [arn](#output\_arn) | The Amazon Resource Name (ARN) identifying your Lambda Function. | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | The Amazon Resource Name (ARN) identifying the CloudWatch log group used by your Lambda function. | | [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | The name of the CloudWatch log group used by your Lambda function. | | [function\_name](#output\_function\_name) | The unique name of your Lambda Function. | | [role\_name](#output\_role\_name) | The name of the IAM role attached to the Lambda Function. | diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index da381f9..76acd0a 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -3,6 +3,11 @@ output "arn" { value = module.lambda.arn } +output "cloudwatch_log_group_arn" { + description = "The Amazon Resource Name (ARN) identifying the CloudWatch log group used by your Lambda function." + value = module.lambda.cloudwatch_log_group_arn +} + output "cloudwatch_log_group_name" { description = "The name of the CloudWatch log group used by your Lambda function." value = module.lambda.cloudwatch_log_group_name diff --git a/examples/with-cloudwatch-logs-subscription/README.md b/examples/with-cloudwatch-logs-subscription/README.md deleted file mode 100644 index e2a75f4..0000000 --- a/examples/with-cloudwatch-logs-subscription/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Example with CloudWatch logs configuration - -Creates an AWS Lambda function with CloudWatch logs subscription filters and configured retention time. - -## usage - -``` -terraform init -terraform plan -terraform apply -``` - -Note that this example may create resources which cost money. Run `terraform destroy` to destroy those resources. - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.5.7 | -| [archive](#requirement\_archive) | >= 2.2 | -| [aws](#requirement\_aws) | >= 6.0 | - -## Providers - -| Name | Version | -|------|---------| -| [archive](#provider\_archive) | >= 2.2 | - -## Modules - -| Name | Source | Version | -|------|--------|---------| -| [destination\_1](#module\_destination\_1) | ../../ | n/a | -| [destination\_2](#module\_destination\_2) | ../../ | n/a | -| [fixtures](#module\_fixtures) | ../fixtures | n/a | -| [lambda](#module\_lambda) | ../../ | n/a | - -## Resources - -| Name | Type | -|------|------| -| [archive_file.destination_handler](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | - -## Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [region](#input\_region) | n/a | `string` | `"eu-west-1"` | no | - -## Outputs - -| Name | Description | -|------|-------------| -| [arn](#output\_arn) | The Amazon Resource Name (ARN) identifying your Lambda Function. | -| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | The name of the CloudWatch log group used by your Lambda function. | -| [function\_name](#output\_function\_name) | The unique name of your Lambda Function. | -| [role\_name](#output\_role\_name) | The name of the IAM role attached to the Lambda Function. | - \ No newline at end of file diff --git a/examples/with-cloudwatch-logs-subscription/main.tf b/examples/with-cloudwatch-logs-subscription/main.tf deleted file mode 100644 index 63188f5..0000000 --- a/examples/with-cloudwatch-logs-subscription/main.tf +++ /dev/null @@ -1,63 +0,0 @@ -locals { - handler = "index.handler" - runtime = "nodejs22.x" -} - -module "fixtures" { - source = "../fixtures" -} - -module "lambda" { - source = "../../" - - description = "Example usage for an AWS Lambda with a CloudWatch logs subscription filters and advanced logging." - filename = module.fixtures.output_path - function_name = module.fixtures.output_function_name - handler = local.handler - runtime = local.runtime - source_code_hash = module.fixtures.output_base64sha256 - - cloudwatch_logs_retention_in_days = 7 - - cloudwatch_log_subscription_filters = { - destination_1 = { - destination_arn = module.destination_1.arn - filter_pattern = "%Lambda%" - } - - destination_2 = { - destination_arn = module.destination_2.arn - } - } -} - -data "archive_file" "destination_handler" { - type = "zip" - source_file = "${path.module}/processor/index.js" - output_path = "${path.module}/processor.zip" - output_file_mode = "0666" -} - -module "destination_1" { - source = "../../" - - cloudwatch_logs_retention_in_days = 1 - description = "Lambda destination 1 of '${module.lambda.cloudwatch_log_group_name}'" - filename = data.archive_file.destination_handler.output_path - function_name = "${module.fixtures.output_function_name}-destination-1" - handler = local.handler - runtime = local.runtime - source_code_hash = data.archive_file.destination_handler.output_base64sha256 -} - -module "destination_2" { - source = "../../" - - cloudwatch_logs_retention_in_days = 1 - description = "Lambda destination 2 of '${module.lambda.cloudwatch_log_group_name}'" - filename = data.archive_file.destination_handler.output_path - function_name = "${module.fixtures.output_function_name}-destination-2" - handler = local.handler - runtime = local.runtime - source_code_hash = data.archive_file.destination_handler.output_base64sha256 -} diff --git a/examples/with-cloudwatch-logs-subscription/outputs.tf b/examples/with-cloudwatch-logs-subscription/outputs.tf deleted file mode 100644 index da381f9..0000000 --- a/examples/with-cloudwatch-logs-subscription/outputs.tf +++ /dev/null @@ -1,19 +0,0 @@ -output "arn" { - description = "The Amazon Resource Name (ARN) identifying your Lambda Function." - value = module.lambda.arn -} - -output "cloudwatch_log_group_name" { - description = "The name of the CloudWatch log group used by your Lambda function." - value = module.lambda.cloudwatch_log_group_name -} - -output "function_name" { - description = "The unique name of your Lambda Function." - value = module.lambda.function_name -} - -output "role_name" { - description = "The name of the IAM role attached to the Lambda Function." - value = module.lambda.role_name -} diff --git a/iam.tf b/iam.tf index e001dd7..01469f2 100644 --- a/iam.tf +++ b/iam.tf @@ -100,7 +100,7 @@ data "aws_iam_policy_document" "logs" { #trivy:ignore:AVD-AWS-0057 resources = [ - "${aws_cloudwatch_log_group.lambda.arn}:*" + "${local.log_group_arn}:*" ] } } diff --git a/outputs.tf b/outputs.tf index f767d49..669b607 100644 --- a/outputs.tf +++ b/outputs.tf @@ -5,12 +5,12 @@ output "arn" { output "cloudwatch_log_group_name" { description = "The name of the CloudWatch log group used by your Lambda function." - value = aws_cloudwatch_log_group.lambda.name + value = local.log_group_name } output "cloudwatch_log_group_arn" { description = "The Amazon Resource Name (ARN) identifying the CloudWatch log group used by your Lambda function." - value = aws_cloudwatch_log_group.lambda.arn + value = local.log_group_arn } output "function_name" { diff --git a/variables.tf b/variables.tf index c3b5acf..fcdceb5 100644 --- a/variables.tf +++ b/variables.tf @@ -31,6 +31,7 @@ variable "cloudwatch_lambda_insights_enabled" { type = bool } +//FIXME: this variable should be renamed in the next major release to reflect that it attaches CloudWatch Logs permissions variable "cloudwatch_logs_enabled" { description = "Enables your Lambda function to send logs to CloudWatch. The IAM role of this Lambda function will be enhanced with required permissions." type = bool @@ -55,6 +56,12 @@ variable "cloudwatch_log_subscription_filters" { type = map(any) } +variable "create_cloudwatch_log_group" { + description = "Create and manage the CloudWatch Log Group for the Lambda function. Set to `false` to reuse an existing log group." + default = true + type = bool +} + variable "description" { description = "Description of what your Lambda Function does." default = "" @@ -87,15 +94,21 @@ variable "filename" { type = string } +variable "handler" { + description = "The function entrypoint in your code." + default = "" + type = string +} + variable "ignore_external_function_updates" { description = "Ignore updates to your Lambda function executed externally to the Terraform lifecycle. Set this to `true` if you're using CodeDeploy, aws CLI or other external tools to update your Lambda function code." default = false type = bool } -variable "handler" { - description = "The function entrypoint in your code." - default = "" +variable "iam_role_name" { + description = "Override the name of the IAM role for the function. Otherwise the default will be your function name with the region as a suffix." + default = null type = string } @@ -135,6 +148,17 @@ variable "layers" { type = list(string) } +variable "logging_config" { + description = "Configuration block for advanced logging settings." + default = null + type = object({ + log_format = string + application_log_level = optional(string, null) + log_group = optional(string, null) + system_log_level = optional(string, null) + }) +} + variable "memory_size" { description = "Amount of memory in MB your Lambda Function can use at runtime." default = 128 @@ -249,23 +273,6 @@ variable "vpc_config" { }) } -variable "logging_config" { - description = "Configuration block for advanced logging settings." - default = null - type = object({ - log_format = string - application_log_level = optional(string, null) - log_group = optional(string, null) - system_log_level = optional(string, null) - }) -} - -variable "iam_role_name" { - description = "Override the name of the IAM role for the function. Otherwise the default will be your function name with the region as a suffix." - default = null - type = string -} - variable "snap_start" { description = "Enable snap start settings for low-latency startups. This feature is currently only supported for `java11` and `java17` runtimes and `x86_64` architectures." default = false From 15b0288274e964a498abcf72476b5cc5a2dcba68 Mon Sep 17 00:00:00 2001 From: Moritz Zimmer Date: Thu, 25 Sep 2025 09:10:22 +0200 Subject: [PATCH 5/7] optimized examples and documentation --- README.md | 52 ++++++++++++++++---- examples/cloudwatch-logs/README.md | 4 +- examples/cloudwatch-logs/main.tf | 73 ++++++++++++++--------------- examples/cloudwatch-logs/outputs.tf | 4 +- main.tf | 6 +-- variables.tf | 2 +- 6 files changed, 84 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index ce554cb..b2713d3 100644 --- a/README.md +++ b/README.md @@ -234,11 +234,18 @@ module "lambda" { ### with CloudWatch Logs configuration -The module will create a [CloudWatch Log Group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) -for your Lambda function. It's retention period and [CloudWatch Logs subscription filters](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter) -to stream logs to other Lambda functions (e.g. to forward logs to Amazon OpenSearch Service) can be declared inline. +By default, the module will create and manage a [CloudWatch Log Group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) for your Lambda function. +It's possible to configure settings like retention time and [KMS encryption](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) +for this log group. -The module will create the required [Lambda permissions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) automatically. Those permissions can be removed by setting `cloudwatch_logs_enabled = false`. +In addition, the module also supports [advanced logging configuration](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs-loggroups.html) +which provides the ability to define a custom name for the module managed log group as well as specifying an existing log group to be used by the Lambda function instead. + +[CloudWatch Logs subscription filters](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter) +to stream logs to other Lambda functions (e.g. to forward logs to Amazon OpenSearch Service) can be declared inline +for the module managed log group or an existing log group. + +The module will create the required [IAM permissions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) for CloudWatch logs automatically. Those permissions can be removed by setting `cloudwatch_logs_enabled = false`. see [example](examples/cloudwatch-logs) for details @@ -249,18 +256,43 @@ module "lambda" { // remove CloudWatch logs IAM permissions // cloudwatch_logs_enabled = false + // configure retention time for the module managed log group cloudwatch_logs_retention_in_days = 7 cloudwatch_log_subscription_filters = { - destination_1 = { - //see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter for available arguments - destination_arn = module.destination_1.arn + sub_1 = { + // see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter for available arguments + destination_arn = module.sub_1.arn filter_pattern = "%Lambda%" } + } - destination_2 = { - destination_arn = module.destination_2.arn - } + // advanced logging config including a custom CloudWatch log group managed by the module + logging_config = { + application_log_level = "INFO" + log_format = "JSON" + log_group = "/custom/my_function_name" + system_log_level = "WARN" + } +} + +resource "aws_cloudwatch_log_group" "existing" { + name = "/existing/${module.fixtures.output_function_name}" + retention_in_days = 1 +} + +module "sub_1" { + source = "../../" + + // other required arguments + + // disable creation of the module managed CloudWatch log group + create_cloudwatch_log_group = false + + // advanced logging config using an external CloudWatch log group + logging_config = { + log_format = "Text" + log_group = aws_cloudwatch_log_group.existing.name } } ``` diff --git a/examples/cloudwatch-logs/README.md b/examples/cloudwatch-logs/README.md index 4e07544..467b9ec 100644 --- a/examples/cloudwatch-logs/README.md +++ b/examples/cloudwatch-logs/README.md @@ -1,6 +1,7 @@ # Example with CloudWatch logs configuration -Creates an AWS Lambda functions with various CloudWatch logs configurations. +Create AWS Lambda functions showcasing [advanced logging configuration](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs-loggroups.html) +and log [subscription filters](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Subscriptions.html). ## usage @@ -32,7 +33,6 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Source | Version | |------|--------|---------| -| [custom\_log\_group\_name](#module\_custom\_log\_group\_name) | ../../ | n/a | | [fixtures](#module\_fixtures) | ../fixtures | n/a | | [logs\_subscription](#module\_logs\_subscription) | ../../ | n/a | | [sub\_1](#module\_sub\_1) | ../../ | n/a | diff --git a/examples/cloudwatch-logs/main.tf b/examples/cloudwatch-logs/main.tf index 45ac900..18479d4 100644 --- a/examples/cloudwatch-logs/main.tf +++ b/examples/cloudwatch-logs/main.tf @@ -7,32 +7,18 @@ module "fixtures" { source = "../fixtures" } -module "custom_log_group_name" { - source = "../../" - - description = "Example usage for an AWS Lambda using a custom log group name." - filename = module.fixtures.output_path - function_name = "${module.fixtures.output_function_name}-custom-log-group" - handler = local.handler - runtime = local.runtime - source_code_hash = module.fixtures.output_base64sha256 - - logging_config = { - log_format = "JSON" - log_group = "/custom/${module.fixtures.output_function_name}" - } -} - module "logs_subscription" { source = "../../" - description = "Example usage for an AWS Lambda with a CloudWatch logs subscription filters." - filename = module.fixtures.output_path - function_name = "${module.fixtures.output_function_name}-filter-source" - handler = local.handler - runtime = local.runtime - source_code_hash = module.fixtures.output_base64sha256 + cloudwatch_logs_retention_in_days = 1 + description = "Example usage for an AWS Lambda with CloudWatch logs subscription filters and advanced log configuration using a custom log group name." + filename = module.fixtures.output_path + function_name = module.fixtures.output_function_name + handler = local.handler + runtime = local.runtime + source_code_hash = module.fixtures.output_base64sha256 + // register log subscription filters for the functions log group cloudwatch_log_subscription_filters = { sub_1 = { destination_arn = module.sub_1.arn @@ -43,6 +29,14 @@ module "logs_subscription" { destination_arn = module.sub_2.arn } } + + // advanced logging config including a custom CloudWatch log group managed by the module + logging_config = { + application_log_level = "INFO" + log_format = "JSON" + log_group = "/custom/${module.fixtures.output_function_name}" + system_log_level = "WARN" + } } data "archive_file" "subscription_handler" { @@ -60,16 +54,18 @@ resource "aws_cloudwatch_log_group" "existing" { module "sub_1" { source = "../../" - cloudwatch_logs_retention_in_days = 1 - description = "Subscriber function 1 using an existing log group." - filename = data.archive_file.subscription_handler.output_path - function_name = "${module.fixtures.output_function_name}-filter-sub-1" - handler = local.handler - runtime = local.runtime - source_code_hash = data.archive_file.subscription_handler.output_base64sha256 + description = "Example usage of a log subscription Lambda function with advanced log configuration." + filename = data.archive_file.subscription_handler.output_path + function_name = "${module.fixtures.output_function_name}-sub-1" + handler = local.handler + runtime = local.runtime + source_code_hash = data.archive_file.subscription_handler.output_base64sha256 - create_cloudwatch_log_group = false + cloudwatch_logs_retention_in_days = 1 + create_cloudwatch_log_group = false + + // advanced logging config using an external CloudWatch log group logging_config = { log_format = "Text" log_group = aws_cloudwatch_log_group.existing.name @@ -79,16 +75,17 @@ module "sub_1" { module "sub_2" { source = "../../" - cloudwatch_logs_retention_in_days = 1 - description = "Subscriber function 2 using an existing log group." - filename = data.archive_file.subscription_handler.output_path - function_name = "${module.fixtures.output_function_name}-filter-sub-2" - handler = local.handler - runtime = local.runtime - source_code_hash = data.archive_file.subscription_handler.output_base64sha256 + description = "Example usage of a log subscription Lambda function with advanced log configuration." + filename = data.archive_file.subscription_handler.output_path + function_name = "${module.fixtures.output_function_name}-sub-2" + handler = local.handler + runtime = local.runtime + source_code_hash = data.archive_file.subscription_handler.output_base64sha256 - create_cloudwatch_log_group = false + cloudwatch_logs_retention_in_days = 1 + create_cloudwatch_log_group = false + // advanced logging config using an external CloudWatch log group logging_config = { log_format = "Text" log_group = aws_cloudwatch_log_group.existing.name diff --git a/examples/cloudwatch-logs/outputs.tf b/examples/cloudwatch-logs/outputs.tf index 76a0b0a..c735fc9 100644 --- a/examples/cloudwatch-logs/outputs.tf +++ b/examples/cloudwatch-logs/outputs.tf @@ -5,12 +5,12 @@ output "arn" { output "cloudwatch_custom_log_group_name" { description = "The name of the custom CloudWatch log group." - value = module.custom_log_group_name.cloudwatch_log_group_name + value = module.logs_subscription.cloudwatch_log_group_name } output "cloudwatch_custom_log_group_arn" { description = "The Amazon Resource Name (ARN) identifying the custom CloudWatch log group used by your Lambda function." - value = module.custom_log_group_name.cloudwatch_log_group_arn + value = module.logs_subscription.cloudwatch_log_group_arn } output "cloudwatch_existing_log_group_name" { diff --git a/main.tf b/main.tf index a626ddc..c7a0057 100644 --- a/main.tf +++ b/main.tf @@ -87,8 +87,7 @@ resource "aws_lambda_function" "lambda" { } } - // create the CloudWatch log group first so it's no create automatically - // by AWS Lambda + // create the CloudWatch log group first so it's not created automatically by AWS Lambda depends_on = [aws_cloudwatch_log_group.lambda] } @@ -178,8 +177,7 @@ resource "aws_lambda_function" "lambda_external_lifecycle" { } } - // create the CloudWatch log group first so it's no create automatically - // by AWS Lambda + // create the CloudWatch log group first so it's not created automatically by AWS Lambda depends_on = [aws_cloudwatch_log_group.lambda] lifecycle { diff --git a/variables.tf b/variables.tf index fcdceb5..6547995 100644 --- a/variables.tf +++ b/variables.tf @@ -31,7 +31,7 @@ variable "cloudwatch_lambda_insights_enabled" { type = bool } -//FIXME: this variable should be renamed in the next major release to reflect that it attaches CloudWatch Logs permissions +# FIXME: this variable should be renamed in the next major release to reflect that it attaches CloudWatch Logs permissions variable "cloudwatch_logs_enabled" { description = "Enables your Lambda function to send logs to CloudWatch. The IAM role of this Lambda function will be enhanced with required permissions." type = bool From 9ecbee3677486071101d016b20e7d6af7a19df45 Mon Sep 17 00:00:00 2001 From: Moritz Zimmer Date: Thu, 25 Sep 2025 09:23:35 +0200 Subject: [PATCH 6/7] support log group class and skip destroy config --- README.md | 23 ++++++++++++--------- cloudwatch_logs.tf | 2 ++ examples/cloudwatch-logs/main.tf | 34 ++++++++++++++++++-------------- examples/complete/main.tf | 2 ++ variables.tf | 13 ++++++++++++ 5 files changed, 50 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b2713d3..ea4dca6 100644 --- a/README.md +++ b/README.md @@ -256,16 +256,10 @@ module "lambda" { // remove CloudWatch logs IAM permissions // cloudwatch_logs_enabled = false - // configure retention time for the module managed log group + // configure module managed log group + cloudwatch_logs_log_group_class = "STANDARD" cloudwatch_logs_retention_in_days = 7 - - cloudwatch_log_subscription_filters = { - sub_1 = { - // see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter for available arguments - destination_arn = module.sub_1.arn - filter_pattern = "%Lambda%" - } - } + cloudwatch_logs_skip_destroy = false // advanced logging config including a custom CloudWatch log group managed by the module logging_config = { @@ -274,6 +268,15 @@ module "lambda" { log_group = "/custom/my_function_name" system_log_level = "WARN" } + + // register log subscription filters for the functions log group + cloudwatch_log_subscription_filters = { + sub_1 = { + // see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter for available arguments + destination_arn = module.sub_1.arn + filter_pattern = "%Lambda%" + } + } } resource "aws_cloudwatch_log_group" "existing" { @@ -433,7 +436,9 @@ No modules. | [cloudwatch\_log\_subscription\_filters](#input\_cloudwatch\_log\_subscription\_filters) | CloudWatch Logs subscription filter resources. Currently supports only Lambda functions as destinations. | `map(any)` | `{}` | no | | [cloudwatch\_logs\_enabled](#input\_cloudwatch\_logs\_enabled) | Enables your Lambda function to send logs to CloudWatch. The IAM role of this Lambda function will be enhanced with required permissions. | `bool` | `true` | no | | [cloudwatch\_logs\_kms\_key\_id](#input\_cloudwatch\_logs\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data. | `string` | `null` | no | +| [cloudwatch\_logs\_log\_group\_class](#input\_cloudwatch\_logs\_log\_group\_class) | Specified the log class of the log group. Possible values are: `STANDARD`, `INFREQUENT_ACCESS`, or `DELIVERY`. | `string` | `null` | no | | [cloudwatch\_logs\_retention\_in\_days](#input\_cloudwatch\_logs\_retention\_in\_days) | Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653, and 0. If you select 0, the events in the log group are always retained and never expire. | `number` | `null` | no | +| [cloudwatch\_logs\_skip\_destroy](#input\_cloudwatch\_logs\_skip\_destroy) | Set to true if you do not wish the log group (and any logs it may contain) to be deleted at destroy time, and instead just remove the log group from the Terraform state. | `bool` | `false` | no | | [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Create and manage the CloudWatch Log Group for the Lambda function. Set to `false` to reuse an existing log group. | `bool` | `true` | no | | [description](#input\_description) | Description of what your Lambda Function does. | `string` | `""` | no | | [environment](#input\_environment) | Environment (e.g. env variables) configuration for the Lambda function enable you to dynamically pass settings to your function code and libraries |
object({
variables = map(string)
})
| `null` | no | diff --git a/cloudwatch_logs.tf b/cloudwatch_logs.tf index 89ffc42..14363f2 100644 --- a/cloudwatch_logs.tf +++ b/cloudwatch_logs.tf @@ -17,8 +17,10 @@ resource "aws_cloudwatch_log_group" "lambda" { region = var.region name = local.log_group_name + log_group_class = var.cloudwatch_logs_log_group_class retention_in_days = var.cloudwatch_logs_retention_in_days kms_key_id = var.cloudwatch_logs_kms_key_id + skip_destroy = var.cloudwatch_logs_skip_destroy tags = var.tags } diff --git a/examples/cloudwatch-logs/main.tf b/examples/cloudwatch-logs/main.tf index 18479d4..2dfb6b3 100644 --- a/examples/cloudwatch-logs/main.tf +++ b/examples/cloudwatch-logs/main.tf @@ -10,13 +10,25 @@ module "fixtures" { module "logs_subscription" { source = "../../" - cloudwatch_logs_retention_in_days = 1 - description = "Example usage for an AWS Lambda with CloudWatch logs subscription filters and advanced log configuration using a custom log group name." - filename = module.fixtures.output_path - function_name = module.fixtures.output_function_name - handler = local.handler - runtime = local.runtime - source_code_hash = module.fixtures.output_base64sha256 + description = "Example usage for an AWS Lambda with CloudWatch logs subscription filters and advanced log configuration using a custom log group name." + filename = module.fixtures.output_path + function_name = module.fixtures.output_function_name + handler = local.handler + runtime = local.runtime + source_code_hash = module.fixtures.output_base64sha256 + + // configure module managed log group + cloudwatch_logs_log_group_class = "STANDARD" + cloudwatch_logs_retention_in_days = 7 + cloudwatch_logs_skip_destroy = false + + // advanced logging config including a custom CloudWatch log group managed by the module + logging_config = { + application_log_level = "INFO" + log_format = "JSON" + log_group = "/custom/${module.fixtures.output_function_name}" + system_log_level = "WARN" + } // register log subscription filters for the functions log group cloudwatch_log_subscription_filters = { @@ -29,14 +41,6 @@ module "logs_subscription" { destination_arn = module.sub_2.arn } } - - // advanced logging config including a custom CloudWatch log group managed by the module - logging_config = { - application_log_level = "INFO" - log_format = "JSON" - log_group = "/custom/${module.fixtures.output_function_name}" - system_log_level = "WARN" - } } data "archive_file" "subscription_handler" { diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 67a81b0..7667d25 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -26,7 +26,9 @@ module "lambda" { // logs, metrics and tracing cloudwatch_logs_enabled = true + cloudwatch_logs_log_group_class = "STANDARD" cloudwatch_logs_retention_in_days = 7 + cloudwatch_logs_skip_destroy = false cloudwatch_lambda_insights_enabled = true layers = ["arn:aws:lambda:${local.region}:580247275435:layer:LambdaInsightsExtension-Arm64:23"] tracing_config_mode = "Active" diff --git a/variables.tf b/variables.tf index 6547995..b44ca70 100644 --- a/variables.tf +++ b/variables.tf @@ -44,12 +44,25 @@ variable "cloudwatch_logs_kms_key_id" { default = null } + +variable "cloudwatch_logs_log_group_class" { + description = "Specified the log class of the log group. Possible values are: `STANDARD`, `INFREQUENT_ACCESS`, or `DELIVERY`." + default = null + type = string +} + variable "cloudwatch_logs_retention_in_days" { description = "Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653, and 0. If you select 0, the events in the log group are always retained and never expire." default = null type = number } +variable "cloudwatch_logs_skip_destroy" { + description = "Set to true if you do not wish the log group (and any logs it may contain) to be deleted at destroy time, and instead just remove the log group from the Terraform state." + type = bool + default = false +} + variable "cloudwatch_log_subscription_filters" { description = "CloudWatch Logs subscription filter resources. Currently supports only Lambda functions as destinations." default = {} From 2c74e1972871422c788694a53befd8b3a2ecfd14 Mon Sep 17 00:00:00 2001 From: Moritz Zimmer Date: Thu, 25 Sep 2025 09:27:38 +0200 Subject: [PATCH 7/7] last nitpicks --- README.md | 2 +- main.tf | 4 ++-- variables.tf | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ea4dca6..c5b8347 100644 --- a/README.md +++ b/README.md @@ -436,7 +436,7 @@ No modules. | [cloudwatch\_log\_subscription\_filters](#input\_cloudwatch\_log\_subscription\_filters) | CloudWatch Logs subscription filter resources. Currently supports only Lambda functions as destinations. | `map(any)` | `{}` | no | | [cloudwatch\_logs\_enabled](#input\_cloudwatch\_logs\_enabled) | Enables your Lambda function to send logs to CloudWatch. The IAM role of this Lambda function will be enhanced with required permissions. | `bool` | `true` | no | | [cloudwatch\_logs\_kms\_key\_id](#input\_cloudwatch\_logs\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data. | `string` | `null` | no | -| [cloudwatch\_logs\_log\_group\_class](#input\_cloudwatch\_logs\_log\_group\_class) | Specified the log class of the log group. Possible values are: `STANDARD`, `INFREQUENT_ACCESS`, or `DELIVERY`. | `string` | `null` | no | +| [cloudwatch\_logs\_log\_group\_class](#input\_cloudwatch\_logs\_log\_group\_class) | Specifies the log class of the log group. Possible values are: `STANDARD`, `INFREQUENT_ACCESS`, or `DELIVERY`. | `string` | `null` | no | | [cloudwatch\_logs\_retention\_in\_days](#input\_cloudwatch\_logs\_retention\_in\_days) | Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653, and 0. If you select 0, the events in the log group are always retained and never expire. | `number` | `null` | no | | [cloudwatch\_logs\_skip\_destroy](#input\_cloudwatch\_logs\_skip\_destroy) | Set to true if you do not wish the log group (and any logs it may contain) to be deleted at destroy time, and instead just remove the log group from the Terraform state. | `bool` | `false` | no | | [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Create and manage the CloudWatch Log Group for the Lambda function. Set to `false` to reuse an existing log group. | `bool` | `true` | no | diff --git a/main.tf b/main.tf index c7a0057..c2ad039 100644 --- a/main.tf +++ b/main.tf @@ -87,7 +87,7 @@ resource "aws_lambda_function" "lambda" { } } - // create the CloudWatch log group first so it's not created automatically by AWS Lambda + // create the CloudWatch log group first so it's not automatically created by AWS Lambda depends_on = [aws_cloudwatch_log_group.lambda] } @@ -177,7 +177,7 @@ resource "aws_lambda_function" "lambda_external_lifecycle" { } } - // create the CloudWatch log group first so it's not created automatically by AWS Lambda + // create the CloudWatch log group first so it's not automatically created by AWS Lambda depends_on = [aws_cloudwatch_log_group.lambda] lifecycle { diff --git a/variables.tf b/variables.tf index b44ca70..ad625c7 100644 --- a/variables.tf +++ b/variables.tf @@ -46,7 +46,7 @@ variable "cloudwatch_logs_kms_key_id" { variable "cloudwatch_logs_log_group_class" { - description = "Specified the log class of the log group. Possible values are: `STANDARD`, `INFREQUENT_ACCESS`, or `DELIVERY`." + description = "Specifies the log class of the log group. Possible values are: `STANDARD`, `INFREQUENT_ACCESS`, or `DELIVERY`." default = null type = string }