Skip to content

Commit a5dd33a

Browse files
feat: advanced logging config (#166)
Co-authored-by: Moritz Zimmer <moritzzimmer@users.noreply.github.com>
1 parent 47c856f commit a5dd33a

File tree

21 files changed

+380
-163
lines changed

21 files changed

+380
-163
lines changed

README.md

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ see [documentation](https://www.terraform.io/docs/providers/aws/r/lambda_functio
3737

3838
### basic
3939

40-
see [example](examples/complete) for other configuration options
40+
see [example](examples/complete) for more configuration options
4141

4242
```hcl
4343
provider "aws" {
@@ -234,33 +234,68 @@ module "lambda" {
234234

235235
### with CloudWatch Logs configuration
236236

237-
The module will create a [CloudWatch Log Group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group)
238-
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)
239-
to stream logs to other Lambda functions (e.g. to forward logs to Amazon OpenSearch Service) can be declared inline.
237+
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.
238+
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)
239+
for this log group.
240240

241-
The module will create the required [Lambda permissions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) automatically.
242-
Sending logs to CloudWatch can be disabled with `cloudwatch_logs_enabled = false`
241+
In addition, the module also supports [advanced logging configuration](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs-loggroups.html)
242+
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.
243+
244+
[CloudWatch Logs subscription filters](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter)
245+
to stream logs to other Lambda functions (e.g. to forward logs to Amazon OpenSearch Service) can be declared inline
246+
for the module managed log group or an existing log group.
247+
248+
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`.
243249

244-
see [example](examples/with-cloudwatch-logs-subscription) for details
250+
see [example](examples/cloudwatch-logs) for details
245251

246252
```hcl
247253
module "lambda" {
248254
// see above
249255
250-
// disable CloudWatch logs
256+
// remove CloudWatch logs IAM permissions
251257
// cloudwatch_logs_enabled = false
252258
253-
cloudwatch_logs_retention_in_days = 14
259+
// configure module managed log group
260+
cloudwatch_logs_log_group_class = "STANDARD"
261+
cloudwatch_logs_retention_in_days = 7
262+
cloudwatch_logs_skip_destroy = false
263+
264+
// advanced logging config including a custom CloudWatch log group managed by the module
265+
logging_config = {
266+
application_log_level = "INFO"
267+
log_format = "JSON"
268+
log_group = "/custom/my_function_name"
269+
system_log_level = "WARN"
270+
}
254271
272+
// register log subscription filters for the functions log group
255273
cloudwatch_log_subscription_filters = {
256-
lambda_1 = {
257-
//see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter for available arguments
258-
destination_arn = module.destination_1.arn
274+
sub_1 = {
275+
// see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_subscription_filter for available arguments
276+
destination_arn = module.sub_1.arn
277+
filter_pattern = "%Lambda%"
259278
}
279+
}
280+
}
260281
261-
lambda_2 = {
262-
destination_arn = module.destination_2.arn
263-
}
282+
resource "aws_cloudwatch_log_group" "existing" {
283+
name = "/existing/${module.fixtures.output_function_name}"
284+
retention_in_days = 1
285+
}
286+
287+
module "sub_1" {
288+
source = "../../"
289+
290+
// other required arguments
291+
292+
// disable creation of the module managed CloudWatch log group
293+
create_cloudwatch_log_group = false
294+
295+
// advanced logging config using an external CloudWatch log group
296+
logging_config = {
297+
log_format = "Text"
298+
log_group = aws_cloudwatch_log_group.existing.name
264299
}
265300
}
266301
```
@@ -288,7 +323,7 @@ module "lambda" {
288323
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):
289324

290325
```dockerfile
291-
FROM public.ecr.aws/lambda/nodejs:12
326+
FROM public.ecr.aws/lambda/nodejs:22
292327

293328
RUN curl -O https://lambda-insights-extension.s3-ap-northeast-1.amazonaws.com/amazon_linux/lambda-insights-extension.rpm && \
294329
rpm -U lambda-insights-extension.rpm && \
@@ -312,7 +347,7 @@ see [examples](examples/deployment) for details.
312347
- [container-image](examples/container-image)
313348
- [deployment](examples/deployment)
314349
- [with-cloudwatch-event-rules](examples/with-cloudwatch-event-rules)
315-
- [with-cloudwatch-logs-subscription](examples/with-cloudwatch-logs-subscription)
350+
- [with-cloudwatch-logs-subscription](examples/cloudwatch-logs)
316351
- [with-event-source-mappings](examples/with-event-source-mappings)
317352
- [with-sns-subscriptions](examples/with-sns-subscriptions)
318353
- [with-vpc](examples/with-vpc)
@@ -380,6 +415,7 @@ No modules.
380415
| [aws_lambda_permission.sns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource |
381416
| [aws_sns_topic_subscription.subscription](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription) | resource |
382417
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
418+
| [aws_cloudwatch_log_group.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/cloudwatch_log_group) | data source |
383419
| [aws_iam_policy.lambda_insights](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source |
384420
| [aws_iam_policy.tracing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source |
385421
| [aws_iam_policy.vpc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source |
@@ -400,7 +436,10 @@ No modules.
400436
| <a name="input_cloudwatch_log_subscription_filters"></a> [cloudwatch\_log\_subscription\_filters](#input\_cloudwatch\_log\_subscription\_filters) | CloudWatch Logs subscription filter resources. Currently supports only Lambda functions as destinations. | `map(any)` | `{}` | no |
401437
| <a name="input_cloudwatch_logs_enabled"></a> [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 |
402438
| <a name="input_cloudwatch_logs_kms_key_id"></a> [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 |
439+
| <a name="input_cloudwatch_logs_log_group_class"></a> [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 |
403440
| <a name="input_cloudwatch_logs_retention_in_days"></a> [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 |
441+
| <a name="input_cloudwatch_logs_skip_destroy"></a> [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 |
442+
| <a name="input_create_cloudwatch_log_group"></a> [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 |
404443
| <a name="input_description"></a> [description](#input\_description) | Description of what your Lambda Function does. | `string` | `""` | no |
405444
| <a name="input_environment"></a> [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 | <pre>object({<br/> variables = map(string)<br/> })</pre> | `null` | no |
406445
| <a name="input_ephemeral_storage_size"></a> [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 |
@@ -415,6 +454,7 @@ No modules.
415454
| <a name="input_kms_key_arn"></a> [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 |
416455
| <a name="input_lambda_at_edge"></a> [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 |
417456
| <a name="input_layers"></a> [layers](#input\_layers) | List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function. | `list(string)` | `[]` | no |
457+
| <a name="input_logging_config"></a> [logging\_config](#input\_logging\_config) | Configuration block for advanced logging settings. | <pre>object({<br/> log_format = string<br/> application_log_level = optional(string, null)<br/> log_group = optional(string, null)<br/> system_log_level = optional(string, null)<br/> })</pre> | `null` | no |
418458
| <a name="input_memory_size"></a> [memory\_size](#input\_memory\_size) | Amount of memory in MB your Lambda Function can use at runtime. | `number` | `128` | no |
419459
| <a name="input_package_type"></a> [package\_type](#input\_package\_type) | The Lambda deployment package type. Valid values are Zip and Image. | `string` | `"Zip"` | no |
420460
| <a name="input_publish"></a> [publish](#input\_publish) | Whether to publish creation/change as new Lambda Function Version. | `bool` | `false` | no |

cloudwatch_logs.tf

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
1+
locals {
2+
log_group_name = coalesce(try(var.logging_config.log_group, null), "/aws/lambda/${var.lambda_at_edge ? "us-east-1." : ""}${var.function_name}")
3+
log_group_arn = try(data.aws_cloudwatch_log_group.lambda[0].arn, aws_cloudwatch_log_group.lambda[0].arn, "")
4+
}
5+
6+
data "aws_cloudwatch_log_group" "lambda" {
7+
count = var.create_cloudwatch_log_group ? 0 : 1
8+
9+
region = var.region
10+
11+
name = local.log_group_name
12+
}
13+
114
resource "aws_cloudwatch_log_group" "lambda" {
15+
count = var.create_cloudwatch_log_group ? 1 : 0
16+
217
region = var.region
318

4-
name = "/aws/lambda/${var.lambda_at_edge ? "us-east-1." : ""}${var.function_name}"
19+
name = local.log_group_name
20+
log_group_class = var.cloudwatch_logs_log_group_class
521
retention_in_days = var.cloudwatch_logs_retention_in_days
622
kms_key_id = var.cloudwatch_logs_kms_key_id
23+
skip_destroy = var.cloudwatch_logs_skip_destroy
724
tags = var.tags
825
}
926

@@ -15,7 +32,7 @@ resource "aws_lambda_permission" "cloudwatch_logs" {
1532
action = "lambda:InvokeFunction"
1633
function_name = lookup(each.value, "destination_arn", null)
1734
principal = "logs.${data.aws_region.current.region}.amazonaws.com"
18-
source_arn = "${aws_cloudwatch_log_group.lambda.arn}:*"
35+
source_arn = "${local.log_group_arn}:*"
1936
}
2037

2138
resource "aws_cloudwatch_log_subscription_filter" "cloudwatch_logs" {
@@ -27,7 +44,7 @@ resource "aws_cloudwatch_log_subscription_filter" "cloudwatch_logs" {
2744
destination_arn = lookup(each.value, "destination_arn", null)
2845
distribution = lookup(each.value, "distribution", null)
2946
filter_pattern = lookup(each.value, "filter_pattern", "")
30-
log_group_name = aws_cloudwatch_log_group.lambda.name
47+
log_group_name = local.log_group_name
3148
name = each.key
3249
role_arn = lookup(each.value, "role_arn", null)
3350
}

examples/cloudwatch-logs/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Example with CloudWatch logs configuration
2+
3+
Create AWS Lambda functions showcasing [advanced logging configuration](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs-loggroups.html)
4+
and log [subscription filters](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Subscriptions.html).
5+
6+
## usage
7+
8+
```
9+
terraform init
10+
terraform plan
11+
terraform apply
12+
```
13+
14+
Note that this example may create resources which cost money. Run `terraform destroy` to destroy those resources.
15+
16+
<!-- BEGIN_TF_DOCS -->
17+
## Requirements
18+
19+
| Name | Version |
20+
|------|---------|
21+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5.7 |
22+
| <a name="requirement_archive"></a> [archive](#requirement\_archive) | >= 2.2 |
23+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 6.0 |
24+
25+
## Providers
26+
27+
| Name | Version |
28+
|------|---------|
29+
| <a name="provider_archive"></a> [archive](#provider\_archive) | >= 2.2 |
30+
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 6.0 |
31+
32+
## Modules
33+
34+
| Name | Source | Version |
35+
|------|--------|---------|
36+
| <a name="module_fixtures"></a> [fixtures](#module\_fixtures) | ../fixtures | n/a |
37+
| <a name="module_logs_subscription"></a> [logs\_subscription](#module\_logs\_subscription) | ../../ | n/a |
38+
| <a name="module_sub_1"></a> [sub\_1](#module\_sub\_1) | ../../ | n/a |
39+
| <a name="module_sub_2"></a> [sub\_2](#module\_sub\_2) | ../../ | n/a |
40+
41+
## Resources
42+
43+
| Name | Type |
44+
|------|------|
45+
| [aws_cloudwatch_log_group.existing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
46+
| [archive_file.subscription_handler](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source |
47+
48+
## Inputs
49+
50+
| Name | Description | Type | Default | Required |
51+
|------|-------------|------|---------|:--------:|
52+
| <a name="input_region"></a> [region](#input\_region) | n/a | `string` | `"eu-west-1"` | no |
53+
54+
## Outputs
55+
56+
| Name | Description |
57+
|------|-------------|
58+
| <a name="output_arn"></a> [arn](#output\_arn) | The Amazon Resource Name (ARN) identifying your Lambda Function. |
59+
| <a name="output_cloudwatch_custom_log_group_arn"></a> [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. |
60+
| <a name="output_cloudwatch_custom_log_group_name"></a> [cloudwatch\_custom\_log\_group\_name](#output\_cloudwatch\_custom\_log\_group\_name) | The name of the custom CloudWatch log group. |
61+
| <a name="output_cloudwatch_existing_log_group_arn"></a> [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. |
62+
| <a name="output_cloudwatch_existing_log_group_name"></a> [cloudwatch\_existing\_log\_group\_name](#output\_cloudwatch\_existing\_log\_group\_name) | The name of the existing CloudWatch log group. |
63+
| <a name="output_function_name"></a> [function\_name](#output\_function\_name) | The unique name of your Lambda Function. |
64+
| <a name="output_role_name"></a> [role\_name](#output\_role\_name) | The name of the IAM role attached to the Lambda Function. |
65+
<!-- END_TF_DOCS -->
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
var zlib = require('zlib');
2+
3+
exports.handler = function(input, context) {
4+
var payload = Buffer.from(input.awslogs.data, 'base64');
5+
zlib.gunzip(payload, function(e, result) {
6+
if (e) {
7+
context.fail(e);
8+
} else {
9+
result = JSON.parse(result.toString());
10+
console.log("Event Data:", JSON.stringify(result, null, 2));
11+
context.succeed();
12+
}
13+
});
14+
};

examples/cloudwatch-logs/main.tf

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
locals {
2+
handler = "index.handler"
3+
runtime = "nodejs22.x"
4+
}
5+
6+
module "fixtures" {
7+
source = "../fixtures"
8+
}
9+
10+
module "logs_subscription" {
11+
source = "../../"
12+
13+
description = "Example usage for an AWS Lambda with CloudWatch logs subscription filters and advanced log configuration using a custom log group name."
14+
filename = module.fixtures.output_path
15+
function_name = module.fixtures.output_function_name
16+
handler = local.handler
17+
runtime = local.runtime
18+
source_code_hash = module.fixtures.output_base64sha256
19+
20+
// configure module managed log group
21+
cloudwatch_logs_log_group_class = "STANDARD"
22+
cloudwatch_logs_retention_in_days = 7
23+
cloudwatch_logs_skip_destroy = false
24+
25+
// advanced logging config including a custom CloudWatch log group managed by the module
26+
logging_config = {
27+
application_log_level = "INFO"
28+
log_format = "JSON"
29+
log_group = "/custom/${module.fixtures.output_function_name}"
30+
system_log_level = "WARN"
31+
}
32+
33+
// register log subscription filters for the functions log group
34+
cloudwatch_log_subscription_filters = {
35+
sub_1 = {
36+
destination_arn = module.sub_1.arn
37+
filter_pattern = "%Lambda%"
38+
}
39+
40+
sub_2 = {
41+
destination_arn = module.sub_2.arn
42+
}
43+
}
44+
}
45+
46+
data "archive_file" "subscription_handler" {
47+
type = "zip"
48+
source_file = "${path.module}/handler/index.js"
49+
output_path = "${path.module}/handler.zip"
50+
output_file_mode = "0666"
51+
}
52+
53+
resource "aws_cloudwatch_log_group" "existing" {
54+
name = "/existing/${module.fixtures.output_function_name}"
55+
retention_in_days = 1
56+
}
57+
58+
module "sub_1" {
59+
source = "../../"
60+
61+
description = "Example usage of a log subscription Lambda function with advanced log configuration."
62+
filename = data.archive_file.subscription_handler.output_path
63+
function_name = "${module.fixtures.output_function_name}-sub-1"
64+
handler = local.handler
65+
runtime = local.runtime
66+
source_code_hash = data.archive_file.subscription_handler.output_base64sha256
67+
68+
69+
cloudwatch_logs_retention_in_days = 1
70+
create_cloudwatch_log_group = false
71+
72+
// advanced logging config using an external CloudWatch log group
73+
logging_config = {
74+
log_format = "Text"
75+
log_group = aws_cloudwatch_log_group.existing.name
76+
}
77+
}
78+
79+
module "sub_2" {
80+
source = "../../"
81+
82+
description = "Example usage of a log subscription Lambda function with advanced log configuration."
83+
filename = data.archive_file.subscription_handler.output_path
84+
function_name = "${module.fixtures.output_function_name}-sub-2"
85+
handler = local.handler
86+
runtime = local.runtime
87+
source_code_hash = data.archive_file.subscription_handler.output_base64sha256
88+
89+
cloudwatch_logs_retention_in_days = 1
90+
create_cloudwatch_log_group = false
91+
92+
// advanced logging config using an external CloudWatch log group
93+
logging_config = {
94+
log_format = "Text"
95+
log_group = aws_cloudwatch_log_group.existing.name
96+
}
97+
}

0 commit comments

Comments
 (0)