skip to Main Content

I am using Terraform to build my architecture, specifically an API Gateway with CloudWatch Logs integration. However, I am encountering an issue where I cannot see the API Gateway CloudWatch logs. I have integrated the apigateway-sns and can see the Lambda logs subscribed to the SNS topic, but I am unable to view the CloudWatch logs for the API Gateway methods.

From the console, I noticed that the "CloudWatch Logs" option is unchecked. I can manually check it from the console, but I want to configure it through Terraform. I have checked my Terraform code, but I can’t figure out where I am making a mistake.

resource "aws_api_gateway_rest_api" "main" {
  name        = "gitlab-slack-api-${var.environment}"
  description = "Gitlab Slack notifications integration"

  endpoint_configuration {
    types = ["REGIONAL"]
  }
}

resource "aws_cloudwatch_log_group" "main_api_gw" {
  name              = "/aws/api-gw/${aws_api_gateway_rest_api.main.name}"
  retention_in_days = 14
}

resource "aws_api_gateway_resource" "resource" {
  path_part   = "slack"
  parent_id   = aws_api_gateway_rest_api.main.root_resource_id
  rest_api_id = aws_api_gateway_rest_api.main.id
}

resource "aws_api_gateway_method" "method" {
  rest_api_id   = aws_api_gateway_rest_api.main.id
  resource_id   = aws_api_gateway_resource.resource.id
  http_method   = "POST"
  authorization = "NONE"

  request_parameters = {
    "method.request.querystring.TopicArn" = false
    "method.request.querystring.Message"  = false
  }
}

resource "aws_api_gateway_integration" "integration" {
  rest_api_id             = aws_api_gateway_rest_api.main.id
  resource_id             = aws_api_gateway_resource.resource.id
  http_method             = aws_api_gateway_method.method.http_method
  type                    = "AWS"
  integration_http_method = "POST"
  uri                     = "arn:aws:apigateway:${data.aws_region.region.name}:sns:action/Publish"
  credentials             = aws_iam_role.api_gateway_role.arn

  request_parameters = {
    "integration.request.querystring.TopicArn" = "'${aws_sns_topic.sns_topic_name.arn}'"
    "integration.request.querystring.Message"  = "method.request.body"
  }

  request_templates = {
    "application/json" = <<EOF
   {
      "Message": "$input.body",
      "TopicArn": "$input.params('TopicArn')"
   }
   EOF
  }
}

resource "aws_api_gateway_method_response" "response_200" {
  rest_api_id = aws_api_gateway_rest_api.main.id
  resource_id = aws_api_gateway_resource.resource.id
  http_method = aws_api_gateway_method.method.http_method
  status_code = "200"
}

resource "aws_api_gateway_integration_response" "integration_response" {
  rest_api_id = aws_api_gateway_rest_api.main.id
  resource_id = aws_api_gateway_resource.resource.id
  http_method = aws_api_gateway_method.method.http_method
  status_code = aws_api_gateway_method_response.response_200.status_code
}

resource "aws_api_gateway_deployment" "deployment" {
  depends_on  = [aws_api_gateway_integration.integration]
  rest_api_id = aws_api_gateway_rest_api.main.id
  stage_name  = var.environment
}

resource "aws_api_gateway_stage" "prod" {
  depends_on    = [aws_api_gateway_deployment.deployment]
  stage_name    = var.environment
  rest_api_id   = aws_api_gateway_rest_api.main.id
  deployment_id = aws_api_gateway_deployment.deployment.id

  access_log_settings {
    destination_arn = aws_cloudwatch_log_group.main_api_gw.arn
    format = jsonencode({
      requestId        = "$context.requestId"
      requestTime      = "$context.requestTime"
      requestTimeEpoch = "$context.requestTimeEpoch"
      path             = "$context.path"
      method           = "$context.httpMethod"
      status           = "$context.status"
      responseLength   = "$context.responseLength"
    })
  }
}

resource "aws_api_gateway_method_settings" "all" {
  rest_api_id = aws_api_gateway_rest_api.test_api_gateway.id
  stage_name  = aws_api_gateway_stage.prod.stage_name
  method_path = "*/*"

  settings {
    logging_level = "INFO"
  }
}

resource "aws_iam_policy" "api_gateway_sns_policy" {
  name        = "ApiGatewaySNSAccessPolicy"
  description = "IAM policy for API Gateway to invoke SNS"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
         Effect   = "Allow"
         Action   = ["sns:Publish"]
         Resource = aws_sns_topic.sns_topic_name.arn
      }
    ]
  })
}

resource "aws_iam_role" "api_gateway_role" {
  name = "ApiGatewaySNSAccessRole"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
     Statement = [
       {
          Effect    = "Allow"
          Principal = { Service = "apigateway.amazonaws.com" }
          Action    = "sts:AssumeRole"
       }
     ]
  })
}

resource "aws_iam_role_policy_attachment" "api_gateway_role_policy_attachment" {
  role       = aws_iam_role.api_gateway_role.name
  policy_arn = aws_iam_policy.api_gateway_sns_policy.arn
}

output "api_gateway_endpoint_url" {
  value = aws_api_gateway_deployment.deployment.invoke_url
}

output "api_gateway_endpoint_arn" {
  value = aws_api_gateway_rest_api.main.arn
}

2

Answers


  1. Chosen as BEST ANSWER

    Found solution: ApiGate needs a role to allow CloudWatch to publish logs, some how Marko's policy did not work(don't why). I have used the AWS managed role AmazonAPIGatewayPushToCloudWatchLogs and the resource aws_api_gateway_method_settings for enabling the logs(INFO, ERROR,OFF). Now I can see the API Gateway logs in CloudWatch.

    Thank you Marko, put some effort on the issue.

    Here is Terraform:

    resource "aws_api_gateway_deployment" "deployment" {
      depends_on  = [aws_api_gateway_integration.integration]
      rest_api_id = aws_api_gateway_rest_api.main.id
      lifecycle {
        create_before_destroy = true
      }
    }
    
    resource "aws_api_gateway_stage" "prod" {
      depends_on    = [aws_cloudwatch_log_group.main_api_gw]
      stage_name    = var.environment
      rest_api_id   = aws_api_gateway_rest_api.main.id
      deployment_id = aws_api_gateway_deployment.deployment.id
    
      access_log_settings {
        destination_arn = aws_cloudwatch_log_group.main_api_gw.arn
        format = jsonencode({
          requestId        = "$context.requestId"
          requestTime      = "$context.requestTime"
          requestTimeEpoch = "$context.requestTimeEpoch"
          path             = "$context.path"
          method           = "$context.httpMethod"
          status           = "$context.status"
          responseLength   = "$context.responseLength"
        })
      }
    }
    
    resource "aws_iam_role" "api_gateway_cloud_watch_role" {
      name = "AmazonAPIGatewayPushToCloudWatchLogs"
    
      assume_role_policy = <<-EOF
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": "sts:AssumeRole",
              "Principal": {
                "Service": "apigateway.amazonaws.com"
              },
              "Effect": "Allow"
            }
          ]
        }
      EOF
    }
    
    
    resource "aws_iam_role_policy_attachment" "api_gateway_cloudwatchlogs" {
      role       = aws_iam_role.api_gateway_cloud_watch_role.name
      policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
    }
    
    resource "aws_api_gateway_account" "api_gateway_account" {
      cloudwatch_role_arn = aws_iam_role.api_gateway_cloud_watch_role.arn
    }
    
    
    resource "aws_api_gateway_method_settings" "api_gateway_log_settings" {
      rest_api_id = aws_api_gateway_rest_api.main.id
      stage_name  = var.environment
      method_path = "*/*"
    
      settings {
        metrics_enabled = true
        logging_level   = "INFO"
      }
    }

    Ref:

    Took Ref from [here]/https://github.com/darren-reddick/terraform-aws-transfer/blob/master/apigateway.tf) as well


  2. You can enable logging for API Gateway in the Stage settings, for which a terraform resource exists. Based on the example from the documentation and your code, the following should enable you to configure logging:

    resource "aws_cloudwatch_log_group" "main_api_gw" {
      name              = "API-Gateway-Execution-Logs_${aws_api_gateway_rest_api.main.id}/${var.environment}"
      retention_in_days = 14
    }
    
    resource "aws_api_gateway_stage" "main" {
      stage_name = var.environment
    
      access_log_settings {
        destination_arn = aws_cloudwatch_log_group.main_api_gw.arn
        format = "JSON" # or CLF/XML/CSV
      }
    }
    

    In order to avoid circular dependency, you would need to define the stage name as a variable, which in your case seems to be var.environment. This is a part of the log group name argument and stage_name argument of the API Gateway stage resource. Additionally, the name of the log group has to be like this, as this is the default API Gateway naming convention:

    To manage the CloudWatch Log Group when this feature is enabled, the aws_cloudwatch_log_group resource can be used where the name matches the API Gateway naming convention.

    The format argument is required and can take in one of "JSON"/"XML"/"CLF"/"CSV", which you can read more about in the documentation.


    EDIT: Since the original post didn’t show all the code, and the stage resource was present, referencing the log group properly, what would need to change is the log group name (as described above) and the IAM role permissions. Besides having SNS allowed in the permissions policy, CloudWatch logs also need to be added:

    resource "aws_iam_policy" "api_gateway_sns_policy" {
      name        = "ApiGatewaySNSAccessPolicy"
      description = "IAM policy for API Gateway to invoke SNS"
    
      policy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          {
             Effect   = "Allow"
             Action   = ["sns:Publish"]
             Resource = aws_sns_topic.sns_topic_name.arn
          },
          {
             Effect   = "Allow"
             Action   = [
               "logs:CreateLogGroup",
               "logs:CreateLogStream",
               "logs:PutLogEvents"
             ]
             Resource = "*"
          }
        ]
      })
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search