Skip to content

feat(infra): alarm on ERROR-level server log lines via CloudWatch Logs metric filters - #475

Merged
haseebrabbani merged 4 commits into
mainfrom
99-log-error-alarms
Sep 15, 2026
Merged

haseebrabbani merged 4 commits into
mainfrom
99-log-error-alarms

Conversation

@haseebrabbani

@haseebrabbani haseebrabbani commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Closes #99.

Per the triage comment, the metric-based error-rate, latency, and throughput coverage landed with #300 (PR #442) and alarm routing is #443 (PR #474). The one piece of #99 still open was monitoring log-level error patterns; this PR adds it.

What

  • infra/log_alarms.tf: CloudWatch Logs metric filters on the server log group matching the JSON level field the server emits with guardian_log_format = "json". log_error_events (always) and log_warn_events (only alongside the dashboard, its sole consumer) are published under <metrics_namespace>/Logs, kept apart from the scraped metrics so "metrics arriving in metrics_namespace" stays the ADOT pipeline health check.
  • Alarm <stack>-server-log-errors: Sum > alarm_log_error_threshold (default 0) in each of two consecutive 5-minute periods. It is the absolute complement to the rate alarms: on a low-traffic stack a few failures never move a percentage that ALB health checks dominate, but every one is an ERROR line.
  • Gated by cloudwatch_log_alarms_enabled (default true), independent of guardian_metrics_enabled / cloudwatch_metrics_enabled, so it keeps working with the metrics pipeline off. A plan-time precondition on the filter rejects text/compact log formats with the flag on.
  • Dashboard gains a "Server log lines by level" widget when the filters exist. New outputs: cloudwatch_log_alarms_enabled, log_metrics_namespace, server_log_errors_alarm_name.
  • Docs: new "Log-level alarms" section and verify step in docs/SERVER_AWS_DEPLOY.md; infra/README.md, docs/architecture/infra.md, docs/PRODUCTION.md updated.

Design notes

  • A metric filter applies to the whole log group, which also carries the adot and ca-init streams. The ADOT Collector writes console-encoded (non-JSON) lines, which a JSON pattern never matches; collector faults still surface through <stack>-metrics-missing. Anything that is not a JSON object with an uppercase level (panic text from a crash-looping task, for instance) is invisible to these filters, so the alarm covers logged faults, not liveness. Documented.
  • The server logs ERROR for some client-caused rejections before mapping them to 4xx (signature/cosigner checks in metadata/auth, credential validation in configure_account), so a persistently misconfigured client can trip the default threshold. Documented with alarm_log_error_threshold as the tolerance knob; downgrading those call sites to warn! is the longer-term server-side fix.
  • Based on main. Once feat(infra): route CloudWatch alarms to per-environment Slack channels #474 lands, the new alarm's alarm_actions = var.alarm_actions becomes local.effective_alarm_actions like the others (one-line rebase).
  • default_value = "0" keeps the series continuous while log lines flow (zeros on the dashboard instead of gaps); with no lines at all the alarm falls back to treat_missing_data = notBreaching.

Summary by CodeRabbit

  • New Features

    • Added CloudWatch monitoring for server log levels, including ERROR alerts and ERROR/WARN activity metrics.
    • Added a dashboard widget displaying server log activity by severity.
    • Log-based alerting operates independently from the main metrics pipeline.
    • Added configuration options for enabling log alarms and setting the ERROR threshold.
  • Documentation

    • Expanded deployment, architecture, infrastructure, and production guidance for log-based monitoring, JSON log requirements, alarm behavior, verification, and configuration.

…s metric filters

Closes #99. The metric-based error-rate, latency, and throughput coverage
landed with #300 (PR #442) and alarm routing is #443 (PR #474); the one
piece of #99 still open was monitoring log-level error patterns.

- infra/log_alarms.tf: metric filters on the server log group matching the
  JSON level field (ERROR always, WARN only alongside the dashboard),
  publishing log_error_events / log_warn_events under
  <metrics_namespace>/Logs, and the <stack>-server-log-errors alarm
  (Sum > alarm_log_error_threshold, default 0, in two consecutive
  5-minute periods). Independent of the metrics pipeline, so it survives
  guardian_metrics_enabled = false.
- cloudwatch_log_alarms_enabled (default true) gates it; a plan-time
  precondition requires guardian_log_format = json.
- Dashboard gains a 'Server log lines by level' widget when the filters
  exist; new outputs cloudwatch_log_alarms_enabled, log_metrics_namespace,
  server_log_errors_alarm_name.
- Docs: SERVER_AWS_DEPLOY.md log-level alarms section and verify step,
  infra/README.md, docs/architecture/infra.md, docs/PRODUCTION.md.
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: cbcfe440-2c5f-4306-90bf-c168a86a0949

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: aec2534c-2c32-4bd5-9b0f-9b57df5ad1a2

📥 Commits

Reviewing files that changed from the base of the PR and between a5bd6cb and 176d669.

📒 Files selected for processing (11)
  • docs/PRODUCTION.md
  • docs/SERVER_AWS_DEPLOY.md
  • docs/architecture/infra.md
  • infra/README.md
  • infra/data.tf
  • infra/ecs.tf
  • infra/log_alarms.tf
  • infra/observability.tf
  • infra/outputs.tf
  • infra/terraform.tfvars.example
  • infra/variables.tf

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

CloudWatch Logs now provide independent ERROR and WARN metric filters. Terraform provisions a sustained ERROR alarm, an optional dashboard widget, configuration variables, outputs, deployment verification, and documentation for JSON log requirements.

Changes

CloudWatch log monitoring

Layer / File(s) Summary
Log monitoring configuration contract
infra/variables.tf, infra/data.tf, infra/ecs.tf
Adds enablement and threshold variables. Normalizes the log format and defines the log metrics namespace and metric names.
Metric filters, alarm, dashboard, and outputs
infra/log_alarms.tf, infra/observability.tf, infra/outputs.tf
Creates gated ERROR and WARN filters. Adds a two-period ERROR alarm, a conditional dashboard widget, and log-monitoring outputs.
Deployment and architecture documentation
docs/*, infra/README.md, infra/terraform.tfvars.example
Documents configuration, JSON validation, alarm behavior, verification commands, resources, outputs, and pipeline independence.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Server
  participant CloudWatchLogs
  participant CloudWatchMetricAlarm
  participant CloudWatchDashboard
  Server->>CloudWatchLogs: Emit JSON ERROR and WARN log lines
  CloudWatchLogs->>CloudWatchMetricAlarm: Count ERROR lines
  CloudWatchMetricAlarm->>CloudWatchMetricAlarm: Evaluate two 5-minute periods
  CloudWatchLogs->>CloudWatchDashboard: Publish ERROR and WARN counts
Loading

Merge Risk: ⚪ Minimal · up to 176d6

The log alarm configuration, dashboard behavior, and JSON log matching are consistent across the enabled configurations reviewed. The change is ready to merge.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding CloudWatch Logs metric filters and alarms for ERROR-level server log lines.
Linked Issues check ✅ Passed Issue #99 requires CloudWatch Logs metrics and alarms for error monitoring, actionable thresholds, notification routing, and documented behavior. This PR adds JSON ERROR and dashboard-only WARN me…
Out of Scope Changes check ✅ Passed The Terraform variables, outputs, dashboard widget, logging-format normalization, metric filters, alarm, and documentation all support the CloudWatch Logs monitoring objective in #99. The changes do n…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 99-log-error-alarms

A rabbit checks the logs at night
ERROR counts hop into sight
WARN lines warm the dashboard glow
Two quiet periods make alarms go
JSON keeps the signals clear
CloudWatch listens, far and near

Comment @coderabbitai help to get the list of available commands.

Comment thread docs/SERVER_AWS_DEPLOY.md Outdated

# 5. Log metric filters are attached and counting (skipped when
# cloudwatch_log_alarms_enabled = false: LOG_ALARM is empty then).
# Datapoints appear only while log lines flow, i.e. once the service is

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ALB health checks do not emit span-close lines: status_root is not instrumented, and track_http only records metrics. A healthy but quiet service can therefore have missing datapoints. Please remove the health-check claim and clarify that default_value = "0" publishes zeros only while logs are being ingested without matching ERROR events, otherwise missing data is expected and handled by treat_missing_data = "notBreaching".

@zeljkoX zeljkoX left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Seems like this PR slightly depend on 474 and its changes that should be incorporated into this PR once it lands.

Resolves the additive conflicts with the alarm-notification stack and, as
requested in the #474 review, wires the server-log-errors alarm to
local.effective_alarm_actions and appends local.alarm_description_links
to its description like every other alarm. Documents that the managed SNS
topic cascades off with the metrics pipeline, so in the metrics-off mode
the log alarm reaches only operator-supplied alarm_actions.
…lters

ALB health checks hit status_root, which is not instrumented, and the
HTTP metrics middleware records metrics only, so a healthy but quiet
service ingests no log lines. default_value = "0" publishes zeros only
while lines are being ingested without matching ERROR events; otherwise
the data is missing and treat_missing_data = notBreaching keeps the
alarm OK.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The alarm can include a dead dashboard link when metrics are disabled, and documentation/comment corrections remain.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds independent CloudWatch log-level monitoring with ERROR/WARN metric filters, an ERROR alarm, dashboard visibility, configuration, outputs, and documentation.

Changes:

  • Adds Terraform log filters, alarm configuration, and outputs.
  • Adds a server log-level dashboard widget.
  • Updates deployment, production, and architecture documentation.
File summaries
File Description
infra/variables.tf Adds log alarm configuration variables.
infra/terraform.tfvars.example Documents new settings.
infra/README.md Updates infrastructure monitoring documentation.
infra/outputs.tf Exposes log monitoring outputs.
infra/observability.tf Adds the log-level dashboard widget.
infra/log_alarms.tf Defines log metric filters and the ERROR alarm.
infra/ecs.tf Uses normalized log format handling.
infra/data.tf Adds log monitoring locals.
docs/SERVER_AWS_DEPLOY.md Documents setup and verification.
docs/PRODUCTION.md Updates production observability guidance.
docs/architecture/infra.md Maps the new infrastructure resources.
Review details

Suppressed comments (2)

docs/SERVER_AWS_DEPLOY.md:854

  • The log check is guarded on an empty LOG_ALARM, but the earlier metrics notification check is unconditional. In the supported cloudwatch_metrics_enabled = false mode, metrics_missing_alarm_name is empty, so copying this verification block fails at step 4 before reaching the independent log-alarm check; guard the metrics-specific test on $ALARM or explicitly tell operators to skip it when the metrics pipeline is disabled.
# 5. Log metric filters are attached and counting (skipped when
#    cloudwatch_log_alarms_enabled = false: LOG_ALARM is empty then).
#    default_value = "0" publishes zeros only while log lines are being
#    ingested without matching ERROR events; a healthy but quiet service
#    (ALB health checks log nothing at the default filter) ingests no
#    lines, so an empty Datapoints list is expected and is handled by
#    treat_missing_data = "notBreaching", not a sign the filter is missing.

infra/log_alarms.tf:24

  • default_value is emitted once for a period in which the log group has events but none match; it is not emitted for every non-matching event. The current comment overstates the datapoint frequency, which can lead to incorrect expectations about metric sums and dashboard continuity.
# default_value = "0" makes each filter emit 0 for every non-matching
# event, so the series is continuous while log lines are being ingested:
# the dashboard draws zeros instead of gaps and the alarm clears on real
  • Files reviewed: 11/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread infra/log_alarms.tf Outdated
count = var.cloudwatch_log_alarms_enabled ? 1 : 0

alarm_name = "${var.stack_name}-server-log-errors"
alarm_description = "Guardian server logged more than ${var.alarm_log_error_threshold} ERROR-level line(s) per 5-minute period in two consecutive periods (absolute count, independent of request volume; query the server log group for level = \"ERROR\")${local.alarm_description_links}"
The alarm exists with the metrics pipeline off, where the <stack>-server
dashboard and the ADOT sidecar are not provisioned, so its description
now uses a variant suffix without the dashboard link and sidecar stream
note in that mode. Also notes in the verify block that steps 1-4 need the
metrics pipeline while step 5 does not.
@haseebrabbani
haseebrabbani merged commit 383bc1d into main Sep 15, 2026
26 checks passed
@haseebrabbani
haseebrabbani deleted the 99-log-error-alarms branch September 15, 2026 13:05
@github-project-automation github-project-automation Bot moved this from Review to Done in OZ Development for Miden Sep 15, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Add metrics and alarms for cloudwatch logs

3 participants