Creación de una integración personalizada de CloudWatch Log para Slack con AWS Lambda

Existen integraciones de Slack para todo pero a veces necesitas algo más específico así que esta vez te voy a mostrar una forma de crear tu propia integración personalizable para recibir notificaciones cuando tu aplicación registre algo en CloudWatch.

Una integración a medida, ¿qué es?

Los webhooks son una forma sencilla de compartir información de fuentes externas con tu espacio de trabajo. Slack Help Center

La API para crear una integración es muy completa, por supuesto, depende de las cosas que quieras crear pero cubre lo suficiente para resolver todas las necesidades. Por otro lado, es una forma de tener control total de tu información y del formato utilizado para mostrarla en tu espacio de trabajo de Slack.

¿Qué es una función Lambda?

Básicamente es un fragmento de código que solo se ejecuta bajo demanda y el precio es más barato que otras integraciones de Slack que tienes que pagar aunque no se usen. Usando esta función puedes reducir un poco tus costes eliminando integraciones innecesarias ya que si solo necesitas enviar alertas de error, el uso de esta función debería ser muy poca porque claro, no queremos que nuestras aplicaciones en producción estén llenas de errores.

Creando la integración personalizada

  1. Primero debes acceder a AWS Management Console

  2. Crear un nuevo IAM role; se utilizará para acceder a los eventos de CloudWatchLogs. 1

    2

    3

  3. Ahora necesitas crear 2 grupos de registro en CloudWatch, /aws/lambda/Log2Slack y production-api o cualquier otro nombre que desees probar. Nota: La configuración de retención se utiliza para asignar una vida útil al registro.

    4

    5

  4. Para crear la función lambda hay que ir a Lambda y hacer clic en el botón Crear, luego asigna un nombre, selecciona el lenguaje de ejecución (Node.js para este ejemplo) y asigna el rol creado previamente.

    6

    7

  5. Añade un disparador para escuchar los eventos de CloudWatch.

    8

    9

  6. Antes de añadir el código lambda es necesario crear una integración personalizada utilizando esta URL https://{your-slack-workspace}.slack.com/apps/manage/custom-integrations y, a continuación, hacer clic en Incoming Webhooks

  7. Ahora haz clic en Add to Slack. 10

    • Seleccione un canal para publicar las alertas.
    • Da clic en Add Incoming Webhook integration.
    • Copia el valor de Webhook URL.

    11

    Nota: En Ajustes de integración puedes asignar un nombre y un logotipo para darle un aspecto más agradable

  8. Ha sido un largo camino hasta ahora, pero ahora estás muy cerca de completar la integración, da clic en el nombre de tu función para abrir el editor de código y, a continuación doble clic en el icono index.js y sustituye el contenido actual por el código siguiente.

    12

    const zlib = require('zlib')
    const https = require('https')
    const SLACK_ENDPOINT = '/services/T1N6FE97Y/B01NK2BR2CR/TrCYV2mkCIRaaxopcXYF3jyc' // don't use this endpoint, I removed it after publish this post
    const SLACK_BOT = 'Cloudwatch'
    
    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,
        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
    
  9. Da clic en el botón Deploy para completar la función.

  10. Probar, probar y probar. Hay dos maneras de probar la función:

Este es el resultado final tras el envío de un log de Cloudwatch a Slack 🎉 18

Recursos de Slack:

Reflexiones finales

Crear tu propia integración para registrar la información relevante sobre sus aplicaciones es muy fácil y si lo deseas puede personalizar cada tipo de nivel de registro para mostrar como success, info, warning o error proporcionando una manera fácil de solucionar los problemas sin perder tiempo comprobando los registros directamente en Cloudwatch.