amazon-web-servicesaws-lambdaamazon-cloudwatchaws-codepipelineaws-codebuild

Send entire cloudwatch logs to slack when aws codebuild is finished


I have created a lambda function that send codebuild cloudwatch logs to slack. Its working but everytime the logs is generated while codebuild is running. It is throwing messages to slack like a spam.

I would want to send entire codebuild logs only once when the codebuild is finished.

I am not sure how to add trigger because in lambda I used cloudwatch trigger for lambda to work.

cloudwatch logs trigger

Nodejs Lambda function

const zlib = require("zlib");
const https = require("https");
const SLACK_ENDPOINT ="/services/000000000000000000000000000000000000"
const SLACK_BOT = "deploy-notifications";

function doRequest(content) {
  // formatting the message according Slack API
  const payload = {
    username: SLACK_BOT,
    blocks: [
      {
        type: "header",
        text: {
          type: "plain_text",
          text: "Whoops, looks like something went wrong 😞🤕",
          emoji: true,
        },
      },
      {
        type: "section",
        fields: [
          {
            type: "mrkdwn",
            text: "<!here> the API is running into an issue",
          },
        ],
      },
      {
        type: "section",
        fields: [
          {
            type: "mrkdwn",
            text: "*Environment: * Production",
          },
        ],
      },
      {
        type: "section",
        fields: [
          {
            type: "mrkdwn",
            text: "*Message:* _" + content.message + "_",
          },
        ],
      },
      {
        type: "section",
        fields: [
          {
            type: "mrkdwn",
            text: "*Stacktrace:*",
          },
        ],
      },
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text:
            "```" +
            JSON.stringify(content.original ? content.original : content) +
            "```",
        },
      },
      {
        type: "divider",
      },
    ],
  };

  const payloadStr = JSON.stringify(payload);
  const options = {
    hostname: "hooks.slack.com",
    port: 443,
    path: SLACK_ENDPOINT,
    channel: "#deploy-notifications",
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Content-Length": Buffer.byteLength(payloadStr),
    },
  };

  const postReq = https.request(options, function (res) {
    const chunks = [];
    res.setEncoding("utf8");
    res.on("data", function (chunk) {
      return chunks.push(chunk);
    });
    res.on("end", function () {
      if (res.statusCode < 400) {
        console.log("sent!!!");
      } else if (res.statusCode < 500) {
        console.error(
          "Error posting message to Slack API: " +
            res.statusCode +
            " - " +
            res.statusMessage
        );
      } else {
        console.error(
          "Server error when processing message: " +
            res.statusCode +
            " - " +
            res.statusMessage
        );
      }
    });
    return res;
  });
  postReq.write(payloadStr);
  postReq.end();
}

function main(event, context) {
  context.callbackWaitsForEmptyEventLoop = true;
  // always returns the last event
  const payload = Buffer.from(event.awslogs.data, "base64");
  const log = JSON.parse(zlib.gunzipSync(payload).toString("utf8"));
  // the log is an object that contains an array of events called `logEvents` and we need access it bypassing the index 0
  doRequest(log.logEvents[0]);
  const response = {
    statusCode: 200,
    body: JSON.stringify("Event sent to Slack!"),
  };
  return response;
}

exports.handler = main;


Solution

  • STEP 1

    You should change your trigger for AWS CloudWatch Events / AWS EventBridge rule to achieve this. Instead of using AWS CloudWatch logs event, you should use AWS CodeBuild Build State Change event as detail-type and aws.codebuild as source.

    Below is a sample for that:

    {
      "source": ["aws.codebuild"],
      "detail-type": ["CodeBuild Build State Change"],
      "detail": {
        "project-name": ["<PROJECT_NAME>"],
        "build-status": ["SUCCEEDED", "FAILED", "STOPPED"]
      }
    }
    

    This will trigger the AWS Lambda function when the AWS CodeBuild project build has finished with any of the above mentioned 4 states. It will also send AWS CodeBuild project build ID in event payload.

    STEP 2

    You need to update your AWS Lambda function code accordingly to fetch AWS CloudWatch logs using AWS CodeBuild project build id received in event payload. Your AWS CloudWatch log group name and stream for an AWS CodeBuild project build may look like this /aws/codebuild/<PROJECT_NAME>/<BUILD_ID>. It depends upon how you configured it on AWS CodeBuild project.

    Below is a sample code for AWS Lambda function for this in Python:

    import boto3
    import requests
    import json
    
    def lambda_handler(event, context):
        build_id = event['detail']['build-id'].split(':')[-1]
    
        logs_client = boto3.client('logs')
    
        log_group_name = "/aws/codebuild/" + event['detail']['project-name']
        log_stream_name = build_id
    
        response = logs_client.get_log_events(
            logGroupName=log_group_name,
            logStreamName=log_stream_name,
            startFromHead=True
        )
    
        message = {
            'text': 'Logs for build {0}:\n'.format(build_id)
        }
    
        for event in response['events']:
            message['text'] += event['message'] + '\n'
    
        slack_webhook_url = <SLACK_WEBHOOK_URL>
    
        response = requests.post(slack_webhook_url, json=message)
    
        if response.status_code == 200:
            return {
                'statusCode': 200,
                'body': 'Message is sent to Slack successfully.'
            }
        else:
            return {
                'statusCode': response.status_code,
                'body': 'Failed to send message to Slack.'
            }
    

    Here is the documentation by Slack regarding Webhook URL: Sending messages using Incoming Webhooks

    Notes: