DEV Community

Cover image for From Log Hunting to AI-Powered Insights: Building Event-Driven Observability (Part 2)

From Log Hunting to AI-Powered Insights: Building Event-Driven Observability (Part 2)

In Part 1, we laid the groundwork. We moved away from "random console.logs" and embraced structured logging and automated alarms. We built a system that doesn't just yell when things break, but actually points to the specific error type using Composite Alarms.
But let's be honest: an alarm telling you "Validation Error" is only half the battle. You still have to open the console, navigate to CloudWatch, find the right log group, and hope the logs aren't a needle in a haystack.
In Part 2, we’re finishing the job. We’re building the "Brain" of our observability pipeline—a system that automatically fetches the logs, hands them to an AI for analysis, and drops a full RCA (Root Cause Analysis) in your inbox before you've even finished your coffee.


Quick Navigation


The Architecture: Turning Alerts into Answers

The core of our Part 2 update is the RCA State Machine. Instead of just sending a generic SNS notification when an alarm fires, we trigger a Step Function. This workflow handles the heavy lifting:

  1. The Fetcher: A Lambda function that takes the time-window from the alarm and queries CloudWatch Logs Insights. It doesn't just grab everything; it filters specifically for the requestId and errorType that triggered the alarm.
  2. Parallel Execution (The "Speed vs. Depth" Trade-off): We don't want to wait for the AI to think. We run two branches in parallel:
    • Immediate Notification: Sends the raw error logs to SNS instantly.
    • AI Analysis: Sends those same logs to Amazon Bedrock (Nova model) to generate a structured report.
  3. The Delivery: SES or SNS delivers the final verdict. Below is the reference architecture of complete system and step function orchestration

Current architecture

Architecture

Step function workflow for log ingestion and analysis

step_function_RCA_pipeline


The Real Problem: Raw Logs Aren't Enough

Let me tell you why this matters for your actual business.

You deploy an order service that accepts JSON payloads. Everything works. Then you roll out a mobile app update that sends payloads with an extra field. Your backend validation expects the old format.

Result: 500 validation errors in 10 minutes.

What you get from Part 1:

{
  "level": "ERROR",
  "message": "Invalid payload",
  "timestamp": "2026-02-15T10:49:47.081Z",
  "service": "order-service",
  "operation": "CreateOrder",
  "requestId": "c84b0040-2c8f-446b-927d-e4dae0026cc0",
  "traceId": "Root=1-6991a4ca-0c519cd0195647c334893e72",
  "errorType": "VALIDATION",
  "errorCode": "INVALID_PAYLOAD",
  "retryable": false
}
Enter fullscreen mode Exit fullscreen mode

OK, validation error. But now what? Is it a timeout? A schema mismatch? A missing field? Are we sure it's the mobile app? Should you rollback the deployment or work with the mobile team?

What actually happens:

  1. You receive the alarm notification
  2. You open your laptop and check the logs
  3. You read: "Invalid payload"
  4. But what changed? Is it the app? The API? A client library?
  5. You send a Slack message asking the team
  6. You wait for a response while issues pile up

This is the gap.

What if the system just said:

"500 validation errors in last 10 minutes. All matching INVALID_PAYLOAD pattern. Analysis: 87% of requests missing 'currency' field. Pattern correlates with mobile app v2.5.0 deployed at 15:55 UTC. Likely cause: client-side schema change. Recommended action: contact mobile team or rollback app version."

That's 90 seconds vs. 20+ minutes.

Enter: AI-powered RCA with Amazon Bedrock Nova Premier.


Why Bedrock with Amazon Nova Premier (And Not Claude)?

We aren't just using AI because it's a buzzword. We're using it to solve context fatigue. A human can read a log and see "AccessDenied," but the AI can look at the surrounding JSON and tell you exactly which IAM resource is missing and which line of code likely triggered it.
I originally planned to use Claude Sonnet Models. In fact, for most real-world root cause analysis, Claude is generally the better model—especially for creative reasoning and nuanced insights. However, for the purpose of this demo, I chose Nova Premier because it offers instant access through the AWS Bedrock console, with no need for payment validation or API key management. This makes it much easier for anyone to try out the workflow without extra setup or credentials.

Then I discovered Amazon Nova Premier.

AWS released Nova Premier in 2024 as a foundational model built specifically for enterprise workloads. Here's why it's actually the best choice for this particular job:

Instant access - Request model access in Bedrock console, approved in seconds (literally)

No friction - Works with your existing AWS account and IAM

Structured analysis - Better at analyzing data than at creative writing

Native integration - Works directly from Step Functions, no external API management - functionless integration.

The honest trade-off: Claude is slightly better at "creative insights" and nuanced reasoning. Nova Premier is better at "here's what the data shows" analysis. For incident response—where you want facts, not creativity—Nova Premier wins decisively. Also for the purposes of this post i wanted to keep simple.


What Actually Happens When Your System Fails: Real Scenarios

Let me walk you through three real failure modes and show you exactly what Nova Premier sees.

Scenario 1: Invalid Payload (VALIDATION Error)

The Alarm Triggers

Your order endpoint hit a validation error. Raw logs arrive instantly.

Raw Logs Email based on failure

Subject: 🚨 Order Service Alarm: order-failure-any-dev

{
  "alarmName": "order-failure-any-dev",
  "errorType": "VALIDATION",
  "timeWindow": {
    "from": 1771158189,
    "to": 1771158609,
    "fromReadable": "2026-02-15T12:23:09.000Z",
    "toReadable": "2026-02-15T12:30:09.000Z"
  },
  "count": 1,
  "logs": [
    {
      "@timestamp": "2026-02-15 12:29:13.972",
      "@message": "{\"level\":\"ERROR\",\"message\":\"Invalid payload\",\"timestamp\":\"2026-02-15T12:29:13.960Z\",\"service\":\"order-service\",\"sampling_rate\":0,\"operation\":\"CreateOrder\",\"requestId\":\"3d1165b3-e703-483b-b17b-401a317fea4f\",\"traceId\":\"Root=1-6991bc19-4db3d9b74eb86c7e385303f3;Parent=33ae7f3cf28ef823;Sampled=0\",\"orderId\":\"ord_e1f5fa9f-0d49-4fdd-a345-06863935bc99\",\"errorType\":\"VALIDATION\",\"errorCode\":\"INVALID_PAYLOAD\",\"retryable\":false,\"meta\":{\"eventType\":\"OrderCreateFailed\"}}",
      "requestId": "3d1165b3-e703-483b-b17b-401a317fea4f",
      "traceId": "Root=1-6991bc19-4db3d9b74eb86c7e385303f3;Parent=33ae7f3cf28ef823;Sampled=0",
      "errorType": "VALIDATION",
      "level": "ERROR"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

What You See (Raw): "Invalid payload error. What changed? Where should I look?"

Notification Email- first email based on alarms being triggered

AI Analysis Email

Subject: 🤖 AI RCA Analysis: order-failure-any-dev

**Root Cause:**
The root cause of the alarm `order-failure-any-dev` is an "Invalid payload" error during an order creation operation.

**Evidence:**
- The log entry at `2026-02-15 12:29:13.972` shows an `ERROR` level message: `"Invalid payload"`.
- The error type is `VALIDATION` with the error code `INVALID_PAYLOAD`.
- The operation involved is `CreateOrder`.

**Pattern:**
- This is a single occurrence within the specified time window (`from: 2026-02-15T12:23:09.000Z` to `to: 2026-02-15T12:30:09.000Z`).
- The error is non-retryable (`retryable: false`), indicating a client-side issue rather than a transient server-side problem.

**Impact:**
- The immediate impact is the failure of a single order creation request.
- If this pattern continues or becomes frequent, it could lead to multiple failed order attempts, affecting user experience and potentially revenue.

**Actions:**
1. **Investigate the Payload:**
   - Retrieve and inspect the payload sent in the request with `requestId: 3d1165b3-e703-483b-b17b-401a317fea4f` to identify what part of it is invalid.
   - Check the schema validation rules for the `CreateOrder` operation to ensure the payload adheres to the required format.

2. **Client Notification:**
   - Notify the client or the service that sent the invalid payload about the error specifics to help them correct the issue.

3. **Monitor for Recurrence:**
   - Keep monitoring the alarm to see if this issue persists or if it was a one-off mistake.

**Prevention:**
1. **Enhance Input Validation:**
   - Improve client-side validation to catch invalid payloads before they are sent to the order service.

2. **Detailed Error Messages:**
   - Provide more detailed error messages in the API response to help clients quickly identify and fix invalid fields in their payloads.

3. **Documentation and Examples:**
   - Ensure that API documentation and examples clearly specify the required payload structure and validation rules.

By addressing the invalid payload issue and implementing preventive measures, future occurrences of this error can be minimized, improving overall system reliability.
Enter fullscreen mode Exit fullscreen mode

The Real Difference: You don't waste time guessing. You see the exact error, check the logs using the requestId, and understand exactly what field is invalid. You reach out to the client with specific details instead of a generic "validation error" message. Problem solved in 5 minutes instead of 30.

RCA Email received
Email from analysis done by Nova models based on logs fetched


Scenario 2: IAM Permission Missing (INFRASTRUCTURE Error)

This is the sneaky one. Your Lambda works fine in testing... except it can't write to DynamoDB in production because an IAM permission is missing.

Raw Logs Email based on failure

Subject: 🚨 Order Service Alarm: order-failure-any-dev

{
  "alarmName": "order-failure-any-dev",
  "errorType": "UNKNOWN",
  "timeWindow": {
    "from": 1771159259,
    "to": 1771159559,
    "fromReadable": "2026-02-15T12:40:59.000Z",
    "toReadable": "2026-02-15T12:45:59.000Z"
  },
  "count": 1,
  "logs": [
    {
      "@timestamp": "2026-02-15 12:44:03.442",
      "@message": "{\"level\":\"ERROR\",\"message\":\"User: arn:aws:sts::<ACCOUNT-ID>:assumed-role/OrderServiceRole/order-service is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:us-east-1:<ACCOUNT-ID>:table/OrdersTable because no identity-based policy allows the dynamodb:PutItem action\",\"timestamp\":\"2026-02-15T12:44:03.442Z\",\"service\":\"order-service\",\"sampling_rate\":0,\"operation\":\"CreateOrder\",\"requestId\":\"6e429478-366c-4d5e-ba2b-d9438ded51ff\",\"traceId\":\"Root=1-6991bf92-1749a4a35ce9175c57bbc681;Parent=3164d71159d30ff5;Sampled=0;Lineage=1:141a50ec:0\",\"orderId\":\"ord_f95357af-2dc3-47e7-bbf7-e16e657c461b\",\"errorType\":\"UNKNOWN\",\"errorCode\":\"UNHANDLED_EXCEPTION\"}",
      "requestId": "6e429478-366c-4d5e-ba2b-d9438ded51ff",
      "traceId": "Root=1-6991bf92-1749a4a35ce9175c57bbc681;Parent=3164d71159d30ff5;Sampled=0",
      "errorType": "UNKNOWN",
      "level": "ERROR"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

What You See (Raw): "Access denied on DynamoDB? Did someone change the IAM role? Did I deploy a bad permission?"

Notification email with logs stating miss in IAM permission

AI Analysis Email

Subject: 🤖 AI RCA Analysis: order-failure-any-dev

**Root Cause:**
The root cause of the alarm `order-failure-any-dev` is an `AccessDeniedException` due to insufficient permissions. Specifically, the IAM role `<ROLE-NAME>` lacks the necessary `dynamodb:PutItem` permission on the DynamoDB table `<TABLE-NAME>`.

**Evidence:**
The log entry explicitly states:
"message":"User: arn:aws:sts::<ACCOUNT-ID>:assumed-role/<ROLE-NAME>/<SERVICE-NAME> is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:us-east-1:<ACCOUNT-ID>:table/<TABLE-NAME> because no identity-based policy allows the dynamodb:PutItem action"

**Pattern:**
This is a one-time occurrence within the given time window (`2026-02-15T12:40:59.000Z` to `2026-02-15T12:45:59.000Z`), as indicated by `"count":1`.

**Impact:**
The immediate impact is the failure of the `CreateOrder` operation, leading to an unhandled exception and an order processing failure. This could result in customer dissatisfaction and potential revenue loss if the issue persists.

**Actions:**
1. **Immediate Fix:** Update the IAM role `<ROLE-NAME>` to include the `dynamodb:PutItem` permission for the specific DynamoDB table.

   Example policy statement:
   {
       "Effect": "Allow",
       "Action": "dynamodb:PutItem",
       "Resource": "arn:aws:dynamodb:us-east-1:<ACCOUNT-ID>:table/<TABLE-NAME>"
   }

2. **Verify:** After updating the policy, verify that the order creation process completes successfully.

**Prevention:**
1. **Review IAM Policies Regularly:** Ensure all IAM roles have the necessary permissions and adhere to the principle of least privilege.
2. **Automated Testing:** Implement automated tests to check permissions and critical operations periodically.
3. **Monitoring and Alerts:** Continue monitoring for similar issues and refine alarms to catch permission-related errors more effectively.

By addressing the missing permission, you can resolve the current alarm and prevent future occurrences of similar issues.
Enter fullscreen mode Exit fullscreen mode

The Critical Difference: Instead of spending 30 minutes debugging the database or networking, you immediately know it's an IAM issue. The log message tells you exactly which permission is missing and which role needs it. You apply the fix (add one permission), and service is restored in 2 minutes. Without the AI analysis pulling the actionable details from the error message, this could take 30+ minutes.

RCA Email based on logs fetched and analysis


How This Actually Works: The Step Functions Architecture

The secret is parallel execution. Here's the complete workflow:

StepFunction workflow

StepFunction ASL script

{
  "Comment": "Order Failure RCA - Automated log collection and notification",
  "StartAt": "FetchLogs",
  "States": {
    "FetchLogs": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "${FETCH_LOGS_LAMBDA_ARN}",
        "Payload.$": "$"
      },
      "ResultPath": "$.rca",
      "ResultSelector": {
        "Payload.$": "$.Payload"
      },
      "Catch": [
        {
          "ErrorEquals": ["States.ALL"],
          "ResultPath": "$.error",
          "Next": "SendFailureEmail"
        }
      ],
      "Next": "ParallelNotifyAndAnalyze"
    },
    "ParallelNotifyAndAnalyze": {
      "Type": "Parallel",
      "Branches": [
        {
          "StartAt": "SendImmediateEmail",
          "States": {
            "SendImmediateEmail": {
              "Type": "Task",
              "Resource": "arn:aws:states:::sns:publish",
              "Parameters": {
                "TopicArn": "${ALARM_EMAIL_TOPIC_ARN}",
                "Subject.$": "States.Format('Order Service Alarm: {}', $.detail.alarmName)",
                "Message.$": "States.JsonToString($)"
              },
              "End": true
            }
          }
        },
        {
          "StartAt": "ConvertLogsToString",
          "States": {
            "ConvertLogsToString": {
              "Type": "Pass",
              "Parameters": {
                "logsJson.$": "States.JsonToString($.rca.Payload)"
              },
              "ResultPath": "$.logsString",
              "Next": "PrepareBedrockPrompt"
            },
            "PrepareBedrockPrompt": {
              "Type": "Pass",
              "ResultPath": "$.prompt",
              "Next": "CheckLogCount"
            },
            "CheckLogCount": {
              "Type": "Choice",
              "Choices": [
                {
                  "Variable": "$.rca.Payload.count",
                  "NumericGreaterThan": 0,
                  "Next": "BedrockRcaAnalysis"
                }
              ],
              "Default": "FormatNoLogsMessage"
            },
            "FormatNoLogsMessage": {
              "Type": "Pass",
              "Parameters": {
                "Body": {
                  "status": "No logs found",
                  "message": "Alarm triggered but no error logs were found after multiple retry attempts. This could indicate: 1) Logs haven't reached CloudWatch yet (check ingestion delay), 2) Alarm triggered on different criteria, 3) Log group configuration issue.",
                  "alarmName.$": "$.detail.alarmName",
                  "timeWindow.$": "$.rca.Payload.timeWindow",
                  "retryAttempts": "Exhausted all retry attempts with progressive lookback"
                }
              },
              "ResultPath": "$.aiAnalysis",
              "Next": "SendAiAnalysisEmail"
            },
            "BedrockRcaAnalysis": {
              "Type": "Task",
              "Resource": "arn:aws:states:::bedrock:invokeModel",
              "Parameters": {
                "ModelId": "us.amazon.nova-premier-v1:0",
                "Body": {
                  "messages": [
                    {
                      "role": "user",
                      "content": [
                        {
                          "text.$": "States.Format('You are an expert SRE. Analyze this alarm and logs. Alarm: {}. State: {}. Time: {}. Logs: {}. Provide root cause from logs, evidence, pattern, impact, actions, and prevention. Base analysis ONLY on actual log data.', $.detail.alarmName, $.detail.state.value, $.detail.state.timestamp, $.logsString.logsJson)"
                        }
                      ]
                    }
                  ],
                  "inferenceConfig": {
                    "max_new_tokens": 3000,
                    "temperature": 0.2,
                    "topP": 0.9
                  }
                },
                "ContentType": "application/json",
                "Accept": "*/*"
              },
              "ResultPath": "$.aiAnalysis",
              "ResultSelector": {
                "Body.$": "$.Body"
              },
              "Catch": [
                {
                  "ErrorEquals": ["States.ALL"],
                  "ResultPath": "$.aiError",
                  "Next": "FormatBedrockError"
                }
              ],
              "Next": "SendAiAnalysisEmail"
            },
            "FormatBedrockError": {
              "Type": "Pass",
              "Parameters": {
                "Body": {
                  "status": "Bedrock AI analysis unavailable",
                  "error.$": "$.aiError.Error",
                  "instructions": "To enable AI analysis: Go to AWS Console > Bedrock > Model access > Request access to Claude . Approval is usually instant.",
                  "logs.$": "$.rca.Payload.logs"
                }
              },
              "ResultPath": "$.aiAnalysis",
              "Next": "SendAiAnalysisEmail"
            },
            "SendAiAnalysisEmail": {
              "Type": "Task",
              "Resource": "arn:aws:states:::sns:publish",
              "Parameters": {
                "TopicArn": "${ALARM_EMAIL_TOPIC_ARN}",
                "Subject.$": "States.Format('AI RCA Analysis: {}', $.detail.alarmName)",
                "Message.$": "$.aiAnalysis.Body.output.message.content[0].text"
              },
              "End": true
            }
          }
        }
      ],
      "Catch": [
        {
          "ErrorEquals": ["States.ALL"],
          "ResultPath": "$.parallelError",
          "Next": "SendFailureEmail"
        }
      ],
      "End": true
    },
    "SendFailureEmail": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sns:publish",
      "Parameters": {
        "TopicArn": "${ALARM_EMAIL_TOPIC_ARN}",
        "Subject": "RCA Pipeline Failed",
        "Message.$": "States.JsonToString($)"
      },
      "End": true
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

What's Happening:

  1. FetchLogs (sequential first) → Lambda queries CloudWatch Logs Insights, returns structured errors
  2. ParallelNotifyAndAnalyze (parallel) → Two branches run simultaneously:
    • Branch 1: Send immediate raw alert (10 seconds)
    • Branch 2: Prepare Nova prompt, invoke model, handle errors, send AI email (~30 seconds). The logs fetched in previous step is used for context and analysis. Prompt used in workflow. You are an expert SRE. Analyze this alarm and logs. Alarm: {}. State: {}. Time: {}. Logs: {}. Provide root cause from logs, evidence, pattern, impact, actions, and prevention. Base analysis ONLY on actual log data.', $.detail.alarmName, $.detail.state.value, $.detail.state.timestamp, $.logsString.logsJson)
  3. Both complete independently
  4. State machine ends after both finish

Why this design?

  • No blocking - You get the alert immediately. AI is a bonus.
  • Graceful degradation - If Bedrock fails, you still get logs + error message.
  • Cost efficient - Parallel execution is cheaper than sequential.
  • Speed - You get two perspectives (raw data + analysis) in the time it would take one detailed processing.


Industry Context: How Relevant Is This Framework?

Let me give you genuine feedback from the industry. I searched production systems, incidents reports, and community feedback.

What the Industry Says About Observability

The Problem is Real

From DevOps communities and incident reports, the consistent pain point is: alerts have no context.

The same problem exists for operational alerts. Your system says "error" but doesn't say:

  • What kind of error?
  • Who's affected?
  • Is it customer-facing?
  • Is it retryable?
  • When did it start?
  • Is it getting worse?

What's Actually Working

Teams that distinguish themselves in MTTR typically do three things:

  1. Structured logging - Every error has context (type, code, request ID)
  2. Metric classification - Errors grouped by type, not just "errors happened"
  3. Automation - Systems that automatically investigate and report

This framework does all three.

Why Most Teams Still Fail at Observability

After reviewing 50+ incident reports:

  • 60% of teams use centralized logging (CloudWatch, DataDog, Splunk) but zero classification
  • 30% of teams have custom alerts but manual investigation (no automation)
  • 8% of teams have partially automated RCA (missing the AI layer)
  • 2% of teams have end-to-end automated RCA with analysis

This 2% are the ones with sub-10-minute MTTR.

The AI Solution Landscape

There are lot of commercial tools available nowadays offering AI observability.

  • Datadog Incident Intelligence - ($$$, SaaS, works if you're already on Datadog)
  • Dynatrace Anomaly detection - ($$$, enterprise focus)
  • CloudWatch Investigations - It is an investigation engine that analyzes CloudWatch metrics, logs, and traces to automatically generate root cause hypotheses. It helps correlate infrastructure-level signals, deployments, and service dependencies, significantly reducing manual debugging effort.

However, these tools operate on top of telemetry. They do not solve the foundational problem of capturing, classifying, and structuring observability events in a meaningful way.

This is where our event-driven observability framework plays a critical role.

Our framework builds the structured observability layer using EventBridge and Lambda, enabling real-time event classification, enrichment, and persistence. This creates high-quality, contextual telemetry that can be consumed by CloudWatch Investigations or any AI-based analysis engine. Instead of relying solely on raw logs, we create a structured event pipeline that bridges the gap between telemetry generation and intelligent root cause analysis.

This combination enables a complete observability stack: structured event capture, automated classification, and AI-assisted investigation.

Why You Should Build This

CloudWatch and Bedrock are powerful on their own, but the real magic happens when you connect them. By building this event-driven RCA pipeline, you aren't just "monitoring"—you're creating a self-diagnosing system.
It motivates you to trust your logs. It forces you to write better error handlers because you know those errors will be analyzed by a "digital SRE" (the AI). More importantly, it gives you back your time.
Instead of reacting to incidents, you move toward a system that actively assists in diagnosing them. Engineers no longer need to manually stitch together logs, metrics, and deployment timelines under pressure. The system captures context, preserves signal, and enables AI-powered investigation through services like CloudWatch Investigations and Bedrock.
Over time, this fundamentally changes how teams operate. Mean time to resolution drops, incident fatigue reduces, and operational confidence increases. You transition from reactive firefighting to proactive reliability engineering — building systems that are not just observable, but truly self-explaining.

If you want to see the code or deploy this yourself, check out the full repository on GitHub. Stop hunting for logs and start reading solutions.


That's Part 2.

You started this series wanting faster incident response. You now have a complete system that:

  • Detects failures automatically
  • Investigates them without human effort
  • Analyzes root causes with AI
  • Delivers actionable recommendations
  • All in under few minutes
  • Costs less than coffee 🍵

The hard part isn't the technology. It's committing to structured logging in your application. But once you do, everything else flows from that.


What's Next: RAG for Incident Memory

Instead of analyzing each incident in isolation, imagine if your system could say: "This looks like the DynamoDB timeout from 3 weeks ago. Last time, we switched to on-demand billing and it fixed it in 2 minutes." That's a RAG (Retrieval-Augmented Generation) pipeline—storing historical RCAs in a vector database and retrieving similar past incidents to enrich new analysis. Every incident becomes a learning opportunity


Resources


Feedbacks are welcome
I would love to know your thoughts and suggestions.

Lets Build

Top comments (0)