在開發專案時,我們經常使用 Sentry 這類錯誤監控服務來即時捕捉線上環境發生的問題。Sentry 的一大優點是可以在錯誤發生時,立即通知開發團隊。然而,若想直接將通知整合到 Discord,需要升級到付費的「Team Plan」方案
但別擔心!今天我們將利用自動化工具 n8n,繞個彎路,實作一個免費且高效的解決方案:當 Sentry 寄出錯誤通知信時,自動解析信件內容,並將精美的通知訊息發送到指定的 Discord 頻道

來到儀表板,新增一個流程「Create Workflow」

初始節點選擇「GMAIL」的「On message received」

同樣要設定憑證,之前有介紹過就不重複惹

「Filters」選擇「Sender」並設定為「sentry」

由於 Sentry 寄來的 Email 是 HTML 格式,我們需要從中提取有用的資訊
下個節點選擇「HTML」的「Extract HTML Content」

「JSON Property」設定為「html」,並使用「Add Value」來新增變數資料
Key:errorTitle
Key:errorUrl
CSS Selector:.btn.view-on-sentry
Return Value:Attribute
Attribute:href
Key:exceptionDetails

下個節點選擇「Edit Fields」來組合要傳遞給 Discord 的變數資料

點選「Add Field」來新增變數
Name: projectName
{{ $('Gmail Trigger').item.json.headers['x-sentry-project'].split(': ')[1] }}
Name: environment
{{ $('Gmail Trigger').item.json.text.match(/environment = (.*)/)[1] }}
Name: level
{{ $('Gmail').item.json.text.match(/level = (.*)/)[1] }}

下個節點選擇「Discord」的「Send a message」來傳送訊息

「Connection Type」選擇「Webhook」,憑證的串接在之前的文章有撰寫過,這邊就不重複惹

可以在「Embeds」選擇「Raw JSON」來傳送具有樣式的訊息

JSON 內容如下
{
  "title": "🚨 Sentry 錯誤通知 (來自 Gmail)",
  "color": 15548997,
  "url": "{{ $('HTML').item.json.errorUrl }}",
  "description": "**錯誤訊息:**\\n{{ $('HTML').item.json.errorTitle.replace(/"/g, '\\"').replace(/\n/g, '\\n') }}\\n\\n**詳細堆疊資訊:**\\n```\\n{{ $('HTML').item.json.exceptionDetails.slice(0, 1500).replace(/"/g, '\\"').replace(/\n/g, '\\n') }}\\n```",
  "fields": [
    {
      "name": "專案",
      "value": "{{ $json.projectName.replace(/"/g, '\\"') }}",
      "inline": true
    },
    {
      "name": "環境",
      "value": "{{ $json.environment.replace(/"/g, '\\"') }}",
      "inline": true
    },
    {
      "name": "等級",
      "value": "`{{ $json.level }}`",
      "inline": true
    }
  ],
  "footer": {
    "text": "由 n8n 轉發"
  },
  "timestamp": "{{ new Date().toISOString() }}"
}

接著點選「Execute step」來試跑,看到「true」代表傳送成功囉

看起來的訊息會長得像這樣

畫布上的流程如下

恭喜,現在已經擁有一個自動化、免費且客製化的 Sentry 錯誤通知系統了
最後也附上流程的 JSON 內容
{
  "nodes": [
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "simple": false,
        "filters": {
          "q": "\"error\"",
          "sender": "sentry"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1.2,
      "position": [0, 0],
      "id": "f23db1a9-bf57-442f-a12a-bf1daa5fc38b",
      "name": "Gmail Trigger"
    },
    {
      "parameters": {
        "operation": "extractHtmlContent",
        "dataPropertyName": "html",
        "extractionValues": {
          "values": [
            {
              "key": "errorTitle",
              "cssSelector": ".event-type.error small"
            },
            {
              "key": "errorUrl",
              "cssSelector": ".btn.view-on-sentry",
              "returnValue": "attribute",
              "attribute": "href"
            },
            {
              "key": "exceptionDetails",
              "cssSelector": "div.interface > pre"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.html",
      "typeVersion": 1.2,
      "position": [220, 0],
      "id": "612bef6a-3072-4d2e-a8d9-59e9c9b413df",
      "name": "HTML"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "b94bbf0a-e651-4495-87d2-6f3cdc995a96",
              "name": "projectName",
              "value": "={{ $('Gmail Trigger').item.json.headers['x-sentry-project'].split(': ')[1] }}",
              "type": "string"
            },
            {
              "id": "c841e270-89e2-4274-af3c-9c6ce79d75a0",
              "name": "environment",
              "value": "={{ $('Gmail Trigger').item.json.text.match(/environment = (.*)/)[1] }}",
              "type": "string"
            },
            {
              "id": "c02b409a-8ed4-4c59-ba31-38a7c794bce9",
              "name": "level",
              "value": "={{ $('Gmail Trigger').item.json.text.match(/level = (.*)/)[1] }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [440, 0],
      "id": "fe591d12-d3b7-4b7a-b43e-73a35610d80d",
      "name": "Edit Fields"
    },
    {
      "parameters": {
        "authentication": "webhook",
        "options": {},
        "embeds": {
          "values": [
            {
              "inputMethod": "json",
              "json": "={\n  \"title\": \"🚨 Sentry 錯誤通知 (來自 Gmail)\",\n  \"color\": 15548997,\n  \"url\": \"{{ $('HTML').item.json.errorUrl }}\",\n  \"description\": \"**錯誤訊息:**\\\\n{{ $('HTML').item.json.errorTitle.replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n') }}\\\\n\\\\n**詳細堆疊資訊:**\\\\n```\\\\n{{ $('HTML').item.json.exceptionDetails.slice(0, 1500).replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n') }}\\\\n```\",\n  \"fields\": [\n    {\n      \"name\": \"專案\",\n      \"value\": \"{{ $json.projectName.replace(/\"/g, '\\\\\"') }}\",\n      \"inline\": true\n    },\n    {\n      \"name\": \"環境\",\n      \"value\": \"{{ $json.environment.replace(/\"/g, '\\\\\"') }}\",\n      \"inline\": true\n    },\n    {\n      \"name\": \"等級\",\n      \"value\": \"`{{ $json.level }}`\",\n      \"inline\": true\n    }\n  ],\n  \"footer\": {\n    \"text\": \"由 n8n 轉發\"\n  },\n  \"timestamp\": \"{{ new Date().toISOString() }}\"\n}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.discord",
      "typeVersion": 2,
      "position": [660, 0],
      "id": "e9639ccc-9f8d-46eb-b0a2-8f5238452e3a",
      "name": "Discord"
    }
  ],
  "connections": {
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTML": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Discord",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": true
  }
}