Errors from the BQE and beyond....using Slack

How we built a dynamic library to send formatted messages to Slack

Errors from the BQE and beyond....using Slack

Recently Nick Booth wrote a blog about our long-running service the Barstool Queue Engine. This being a core service we needed active error notifications to alert the team when it runs into trouble. While there are multiple options, levels, and redundant alerts to consider, I will focus on how I built a simple dynamic system to communicate with Slack.

My main goal was to build a reusable library that would accept a recognizable format that would allow for dynamic content. Slack's webhooks accept JSON to structure the message so it seemed a natural choice. Using the Block Kit documentation I created a base set up to be used around our various applications:

{
  "attachments": [
    {
      "color": "#ff3d41",
      "blocks": [
        {
          "type": "header",
          "text": {
            "type": "plain_text",
            "text": "⚠️ {{Application}}-{{Stage}}",
            "emoji": true
          }
        },
        {
          "type": "section",
          "fields": [
            {
              "type": "mrkdwn",
              "text": "*Field 1*\n{{FIELD_1}}"
            },
            {
              "type": "mrkdwn",
              "text": "*Field 2*\n{{FIELD_2}}"
            }
          ]
        },
        {
          "type": "divider"
        },
        {
          "type": "section",
          "text": {
            "type": "mrkdwn",
            "text": "*Error Message*\n{{ERROR_MESSAGE}}"
          }
        }
      ]
    }
  ]
}

This JSON will render the following in Slack:

Base Block Kit Format

Pulling from other templating languages I utilized {{ }} to denote variable placement to support dynamic content in the messages. The core logic simply accepts a slack webhook, a path to the template and the data to be used when recursively replacing all of the placeholders in the template.

async function sendMessage({ endpoint, messageFilepath, data }) {
  const messageBody = JSON.parse(await fs.readFile(messageFilepath))
  const messageWithData = _replaceVariables({ message: messageBody, data })
  await got.post(endpoint, {
    json: {
      ...messageWithData
    }
  })
}

function _replaceVariables({ message, data }) {
  for (var key in message) {
    if (typeof message[key] == 'object' && message[key] !== null) {
      _replaceVariables({ message: message[key], data })
    } else {
      if (['text', 'color'].includes(key)) {
        Object.keys(data).map((dataKey) => {
          message[key] = message[key].replace(`{{${dataKey}}}`, data[dataKey])
        })
      }
    }
  }
  return message
}

The final product is a well-formatted error message in Slack that looks similar to the example below.

Error message as seen in Slack

We've implemented this in our BQE but plan to roll it out to many of our services to stay ahead of our users when it comes to resolving issues they may run into. The flexibility allows us to send all pertinent information to enable speedy debugging.