Back to blog
AWSBest PracticesCloud SecurityMonitoring & LoggingServerless

Lambda Has No Log Group: Why It Happens and How to Fix It

Learn why an AWS Lambda function ends up with no CloudWatch log group, the risks of running blind, and step-by-step fixes with IAM, CLI, and Terraform.

TL;DR

This check flags Lambda functions that have no CloudWatch log group, which means execution logs are either missing or silently failing to write. Without logs you lose your primary debugging and audit trail. Fix it by granting the function's execution role logs:CreateLogGroup and logs:PutLogEvents, or pre-creating the log group yourself.

When a Lambda function runs, the runtime tries to ship its output to a CloudWatch log group named /aws/lambda/<function-name>. Most of the time this happens automatically and nobody thinks about it. But the automatic creation depends on the function's execution role having the right IAM permissions. When those permissions are missing, the log group never gets created, and your function runs blind.

The Lambda Has No Log Group check (lambda_nologgroup) looks for exactly this situation: a deployed Lambda function with no associated CloudWatch log group. It is a small misconfiguration with an outsized impact, because the absence of logs only becomes obvious at the worst possible moment, usually during an incident.


What this check detects

The check inspects each Lambda function in your account and verifies that a corresponding CloudWatch log group exists. By default, the expected log group follows the naming pattern:

/aws/lambda/<function-name>

If the function has been invoked but no log group is present, or if the log group was never created, the check fails. A few common causes lead to this state:

  • The execution role lacks logs:CreateLogGroup, so the runtime cannot create the group on first invocation.
  • Someone manually deleted the log group, and the role permissions no longer allow recreation.
  • The function uses a custom logging configuration pointing at a log group that does not exist.
  • The function was deployed but has never actually been invoked (in which case the group is created lazily on the first run).

Note: Lambda creates the log group lazily. The group does not appear the moment you deploy the function, it appears on the first invocation, and only if the execution role permits it. A brand new function that has never run is a common false-positive trigger, so confirm whether the function has actually been invoked.


Why it matters

Logs are not a nice-to-have for serverless workloads, they are the main window into what your code is doing. Lambda gives you no SSH access, no persistent disk to inspect, and no long-lived process to attach a debugger to. CloudWatch logs are effectively your only built-in record of execution. Lose them and you lose a lot.

Operational blind spots

When a function starts throwing errors, your first move is usually to open its logs. If there is no log group, that workflow dead-ends. You cannot see stack traces, timeout messages, cold-start timing, or the values that triggered the failure. Mean time to resolution climbs sharply when engineers are reduced to guesswork.

Security and audit gaps

Lambda logs frequently capture security-relevant signals: unexpected input, authentication failures, calls to downstream services, and anomalous invocation patterns. If a function is compromised or abused and there are no logs, you have no forensic trail. During an incident response you cannot reconstruct what happened, which can turn a contained event into an open-ended investigation.

Warning: Many compliance frameworks (PCI DSS, SOC 2, HIPAA, ISO 27001) require audit logging for systems that process sensitive data. A Lambda function with no log group can put you out of compliance, even if the function itself is otherwise secure.

Hidden permission problems

A missing log group is often a symptom of a broader IAM issue. If the execution role cannot write logs, it may also be missing other permissions, or it may have been provisioned by a broken pipeline. Treating the missing log group as a signal rather than an isolated bug often surfaces deeper configuration drift.


How to fix it

There are two parts to a durable fix: make sure the execution role can write logs, and make sure the log group exists. You can solve both at once by pre-creating the group and granting the right permissions.

Step 1: Confirm the execution role and its permissions

Find the execution role attached to the function:

aws lambda get-function-configuration \
  --function-name my-function \
  --query 'Role' \
  --output text

The function needs, at minimum, the permissions in the AWS-managed AWSLambdaBasicExecutionRole policy. Attach it if it is missing:

aws iam attach-role-policy \
  --role-name my-function-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

If you prefer a scoped inline policy instead of the managed one, this is the minimal set of actions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/my-function:*"
    }
  ]
}

Step 2: Pre-create the log group

Rather than relying on lazy creation, create the log group explicitly. This is more reliable and lets you set a retention policy at the same time:

aws logs create-log-group \
  --log-group-name /aws/lambda/my-function

aws logs put-retention-policy \
  --log-group-name /aws/lambda/my-function \
  --retention-in-days 30

Tip: Always set a retention policy. By default CloudWatch log groups never expire, which means they accumulate cost forever. Thirty to ninety days is a reasonable default for most application logs, and longer for anything you need for compliance.

Step 3: Verify logs are flowing

Invoke the function and confirm a log stream appears:

aws lambda invoke \
  --function-name my-function \
  --payload '{}' \
  /tmp/out.json

aws logs describe-log-streams \
  --log-group-name /aws/lambda/my-function \
  --order-by LastEventTime \
  --descending \
  --max-items 1

Fixing it with Infrastructure as Code

The cleanest approach is to define the log group alongside the function so the two are always provisioned together. Here is a Terraform example:

resource "aws_cloudwatch_log_group" "my_function" {
  name              = "/aws/lambda/${aws_lambda_function.my_function.function_name}"
  retention_in_days = 30
}

resource "aws_lambda_function" "my_function" {
  function_name = "my-function"
  role          = aws_iam_role.lambda_exec.arn
  handler       = "index.handler"
  runtime       = "python3.12"
  filename      = "function.zip"

  # Ensure the log group exists before the function runs
  depends_on = [aws_cloudwatch_log_group.my_function]
}

resource "aws_iam_role_policy_attachment" "lambda_logs" {
  role       = aws_iam_role.lambda_exec.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

Defining the log group in code also means Terraform owns its retention setting, so the value will not silently drift back to "never expire."

Note: If you let Lambda auto-create the log group and then later add an aws_cloudwatch_log_group resource with the same name, Terraform will fail because the resource already exists. Import it first with terraform import aws_cloudwatch_log_group.my_function /aws/lambda/my-function to bring it under management.


How to prevent it from happening again

One-off fixes do not stick. The goal is to make "no log group" impossible to ship in the first place.

Bundle the log group with every function

Make the CloudWatch log group a required part of your Lambda module. If you use a shared Terraform module or a CDK construct for functions, embed the log group, the retention policy, and the basic execution permissions directly into it. Engineers should not be able to deploy a function without these.

Gate deployments with policy-as-code

Add a check to your CI/CD pipeline that rejects any Terraform plan defining an aws_lambda_function without a matching aws_cloudwatch_log_group. A short OPA/Conftest rule does the job:

package lambda

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_lambda_function"
  fn := resource.change.after.function_name

  not log_group_exists(fn)

  msg := sprintf("Lambda function '%s' has no associated CloudWatch log group", [fn])
}

log_group_exists(fn) {
  lg := input.resource_changes[_]
  lg.type == "aws_cloudwatch_log_group"
  contains(lg.change.after.name, fn)
}

Continuously scan running infrastructure

IaC gates only catch what flows through the pipeline. Manual changes, deleted log groups, and resources created outside your pipeline still slip through. This is where Lensix runs the lambda_nologgroup check continuously across your accounts and alerts you when a function ends up without a log group, regardless of how it got there.

Tip: Pair this check with a CloudWatch alarm on the Errors metric for each function. The log group gives you the detail, the alarm gives you the early warning. Together they close the loop between detection and diagnosis.


Best practices

  • Set retention on every log group. Unlimited retention is the default and it quietly drives up cost. Choose a value that matches your operational and compliance needs.
  • Use least-privilege execution roles, but never strip logging permissions. The three logs: actions are the floor, not something to optimize away.
  • Emit structured logs. JSON-formatted log lines are far easier to query with CloudWatch Logs Insights than free-form text, which makes the logs you are now collecting genuinely useful.
  • Centralize logs for fleets. For large estates, forward Lambda logs to a central account or a SIEM so that a single deleted log group cannot erase your only copy.
  • Consider advanced logging controls. Lambda now supports setting log format, log level, and a custom log group directly in the function configuration, which gives you tighter control without code changes.

A missing log group is one of those issues that looks trivial until the day you need the logs and they are not there. Making log groups a first-class, automatically provisioned part of every Lambda deployment costs almost nothing and saves you from debugging in the dark.