๐Ÿ’ธ ๋ฌด๋ฃŒ๋กœ Sentry ์•Œ๋ฆผ์„ ์Šฌ๋ž™์œผ๋กœ ๋ฐ›์•„๋ณด์ž

์‹ ์›์ค€ยท2024๋…„ 10์›” 9์ผ
5

๋š๋”ฑ๋š๋”ฑ

๋ชฉ๋ก ๋ณด๊ธฐ
1/1
post-custom-banner

๋ฐฐ๊ฒฝ

์ €๋Š” ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ์ธ MUFI์—์„œ ํฌํ† ๋ถ€์Šค ํ‚ค์˜ค์Šคํฌ, ๋ชจ๋ฐ”์ผ ์›น, ์–ด๋“œ๋ฏผ ๋“ฑ ๋‹ค์–‘ํ•œ ํ”„๋กœ๋•ํŠธ๋ฅผ ๊ฐœ๋ฐœ ์ค‘์ธ๋ฐ์š”. 2๋…„ ๋ฐ˜ ์ •๋„ ํ•˜๋‹ˆ๊นŒ ๊ธฐ๊ณ„๊ฐ€ ์–ด๋Š๋ง ์—ด๋Œ€๊ฐ€ ํ›Œ์ฉ ๋„˜๊ฒŒ ๋˜์–ด์„œ ํ˜„์žฅ์—์„œ ์ด๋Ÿฐ ๋ณด์ด์Šค๊ฐ€ ๋“ค๋ ค์™”์–ด์š”.

Slack 1Slack 2

์›๋ž˜๋„ ์—๋Ÿฌ ๋ฆฌํฌํŒ… ํˆด์ธ Sentry๋ฅผ ์“ฐ๊ณ  ์žˆ์–ด์„œ ์ €์—๊ฒŒ๋Š” ์—๋Ÿฌ๊ฐ€ ๋‚ ๋•Œ๋งˆ๋‹ค ๋ฉ”์ผ์ด ๋‚ ์•„์˜ค๊ณ  ์žˆ์—ˆ๊ฑฐ๋“ ์š”. ์ด๊ฑธ ์Šฌ๋ž™ ์ฑ„๋„์œผ๋กœ๋งŒ ์ด์ฃผ๋ฉด ํ˜„์žฅ ์šด์˜์ง„๊ณผ ํ•จ๊ป˜ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๊ฒ ๋‹ค๊ณ  ๊ฐ„๋‹จํ•˜๊ฒŒ ์ƒ๊ฐํ–ˆ์–ด์š”! ํ•˜์ง€๋งŒ..

๐Ÿ’ธ ๋ˆ์ด ๋„ˆ๋ฌด ์•„๊นŒ์›Œ!

Sentry ์—๋Ÿฌ๋ฅผ ์Šฌ๋ž™์— ์ด์ฃผ๋Š” ๊ฑด ์›๋ž˜ ์ƒ๊ฐํ–ˆ๋˜ ๋Œ€๋กœ ๊ฐ„๋‹จํ•˜์ง€ ์•Š์•˜์–ด์š”.
์•„๋‹ˆ, ๊ฐ„๋‹จํ–ˆ์ง€๋งŒ ๋ˆ์ด ๋“ค์—ˆ์–ด์š” ๐Ÿซ 

Sentry Slack Integration

Sentry์—์„œ๋Š” Slack Integration์„ ์“ฐ๋ ค๋ฉด ์ตœ์†Œ ํŒ€ ํ”Œ๋žœ ์ด์ƒ์„ ๊ตฌ๋…ํ•ด์•ผ ํ–ˆ๊ณ , ์ด ๊ฐ€๊ฒฉ์€ ๋ฌด๋ ค ์›” $29(์•ฝ 39,000์›) ์˜€์–ด์š”.

Sentry Team Plan

์ด ๊ธฐ๋Šฅ ํ•˜๋‚˜๋งŒ ๋ณด๊ณ  ๋งค๋‹ฌ 4๋งŒ์›์— ๊ฐ€๊นŒ์šด ๋น„์šฉ์„ ๋‚ด๋Š” ๊ฒƒ์€ ํ•ฉ๋ฆฌ์ ์ด์ง€ ์•Š๋‹ค๊ณ  ์ƒ๊ฐํ•ด์„œ, ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด๊ธฐ๋กœ ํ–ˆ์–ด์š”.

์›นํ›…์€ ๋ฌด๋ฃŒ๋„ค?

Sentry Integration ๋ชฉ๋ก์„ ์‚ดํŽด๋ณด๋˜ ์ค‘, ์›นํ›…์„ ์ง€์›ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๋ฐœ๊ฒฌํ–ˆ์–ด์š”.

Sentry Webhook Integration

๊ทธ๋ฆฌ๊ณ  ์›นํ›…์„ ์“ฐ๋Š” ๊ฑด ๋ฌด๋ฃŒ ํ”Œ๋žœ์œผ๋กœ๋„ ๊ฐ€๋Šฅํ•ด์„œ, ์ด๊ฑธ IFTTT๋‚˜ Zapier ๊ฐ™์€ ์ž๋™ํ™” ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๋งŒ๋“ค์–ด์„œ ์Šฌ๋ž™์— ์ด์ฃผ๋ฉด ๋˜๊ฒ ๋‹ค ์ƒ๊ฐํ–ˆ์–ด์š”.

๊ทธ๋ž˜์„œ ์‚ฌ์šฉํ•ด๋ณธ ๊ฒฝํ—˜์ด ์žˆ๋˜ Zapier์—์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊ตฌ์„ฑํ•ด์„œ ํ…Œ์ŠคํŠธ๊นŒ์ง€ ๋˜๋Š” ๊ฑธ ํ™•์ธํ–ˆ์–ด์š”.

Zapier WorkflowZapier Test

์‹ฌ์ง€์–ด Zapier์—์„œ๋Š” AI Copilot์œผ๋กœ ์›ํ•˜๋Š” ๋™์ž‘์„ ๋งํ•˜๋ฉด ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๊ฑฐ์˜ ๋‹ค ๋งŒ๋“ค์–ด์ค˜์„œ 10๋ถ„๋„ ์ฑ„ ๊ฑธ๋ฆฌ์ง€ ์•Š์•„์„œ ๊ธˆ๋ฐฉ ๋๋‚ด๊ฒ ๋‹ค ์ƒ๊ฐํ–ˆ๋Š”๋ฐ..

Zapier Webhook ProPro PlanSlack Angry

Sentry Team Plan๋ณด๋‹ค ๋” ๋น„์‹ผ ๊ฐ’์— Zapier Pro๋ฅผ ๊ตฌ๋…ํ•ด์•ผ ์›นํ›…์„ ์“ธ ์ˆ˜ ์žˆ์–ด์„œ ํฌ๊ธฐ.. IFTTT๋„ ์ฐพ์•„๋ณด๋‹ˆ ์ƒ๋Œ€์ ์œผ๋กœ ์ €๋ ดํ•˜์ง€๋งŒ ์›” 14.99๋‹ฌ๋Ÿฌ ์š”๊ธˆ์„ ์ง€๋ถˆํ•ด์•ผ ์›นํ›…์„ ์จ์•ผ ํ–ˆ์–ด์š”. ๐Ÿ’ฐ๐Ÿฅน

๐Ÿ˜พ ๊ฑ ์ง์ ‘ ๋งŒ๋“ค๊ณ  ๋งŒ๋‹ค!

์‚ฌ์‹ค ๊ท€์ฐฎ์•„์„œ ๋ฏธ๋ค„์™”๋˜ ์„ ํƒ์ง€์ธ ์ง์ ‘ ๋งŒ๋“ค๊ธฐ๋ฅผ ๊ฒฐ๊ตญ ๊บผ๋‚ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

Slack Sentry Architecture

์œ„์™€ ๊ฐ™์ด ๊ฐ„๋‹จํ•œ ์„ค๊ณ„ ๋ฐ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ž‘์„ฑํ–ˆ๊ณ , ์ œ๊ฐ€ ์•…ํ•„์ด๋ผ ํ…์ŠคํŠธ๋กœ ์˜ฎ๊ฒจ ์ ์–ด๋ณด์ž๋ฉด

  • ์š”์•ฝ : GCP Cloud Functions๋กœ Sentry ์›นํ›…์„ ๋ฐ›์•„์„œ Slack์— ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•œ๋‹ค.
    • GCP Cloud Functions๋Š” ์ €ํฌ ํ”„๋กœ์ ํŠธ๊ฐ€ GCP ๊ธฐ๋ฐ˜์ด๋ผ์„œ ๊ทธ๋ ‡๊ณ , AWS Lambda ๊ฐ™์€ ๋‹ค๋ฅธ Serverless๋„ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.
  • Cloud Function ์š”๊ตฌ์‚ฌํ•ญ
    1. Request๋กœ๋ถ€ํ„ฐ ์—๋Ÿฌ ์ •๋ณด ํŒŒ์‹ฑ
    2. ํŒŒ์‹ฑ๋œ ์—๋Ÿฌ ์ •๋ณด๋กœ ์Šฌ๋ž™์— ๋ณด๋‚ผ Report ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ
    3. ์Šฌ๋ž™์— ๋ฆฌํฌํŠธ ๋ฐœ์†ก

์ด๋Ÿฐ ๊ตฌ์กฐ๋กœ ๊ฐ„๋‹ค๋ฉด Cloud Functions ๋น„์šฉ๋งŒ ๋“ค๊ฒŒ ๋˜๋Š”๋ฐ, ์‚ฌ์‹ค์ƒ ๋ฌด๋ฃŒ๋กœ ์šด์˜์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒฐ๋ก ์ด ๋‚ฌ์–ด์š”.

Cloud Functions

๐Ÿค– GPT์•ผ ๊ฐœ๋ฐœํ•ด์ค˜!

Cloud Functions๋Š” ํ•˜๋‚˜์˜ ์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ ์ˆ˜์ค€์œผ๋กœ๋งŒ ๊ฐœ๋ฐœํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์—, GPTํ•œํ…Œ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ „๋‹ฌํ•˜๊ณ  ์ดˆ์•ˆ์„ ๋ฐ›๊ณ  ์›ํ•˜๋Š” ๋ชจ์Šต์œผ๋กœ๋งŒ ์ฝ”๋“œ๋ฅผ ๋‹ค๋“ฌ๋Š” ์ˆ˜์ค€์œผ๋กœ ๋งˆ๋ฌด๋ฆฌํ•  ์ˆ˜ ์žˆ์—ˆ์–ด์š”.

์ œ๊ฐ€ ์–ด๋–ค ๋Œ€ํ™”๋กœ ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ–ˆ๋Š”์ง€๋Š” ์ด ๋งํฌ๋ฅผ ํ†ตํ•ด์„œ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์–ด์š”.

์ด๋ ‡๊ฒŒ ๋ฐ›์€ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฝ”๋“œ ์Šคํƒ€์ผ๊ณผ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ์„ ์ข€ ๋” ๋‹ค๋“ฌ์€ ๊ฒฐ๊ณผ, ์ตœ์ข…์ ์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋กœ ์™„์„ฑํ–ˆ์–ด์š”.

const functions = require('@google-cloud/functions-framework');
const { WebClient } = require('@slack/web-api');

functions.http('sentry-to-slack', async (req, res) => {
    // Slack Bot Token์„ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ์„ค์ • (GCP ์ฝ˜์†”์—์„œ ์„ค์ • ๊ฐ€๋Šฅ)
    const slackToken = process.env.SLACK_BOT_TOKEN;
    const slackChannel = 'YOUR_CHANNEL_ID'; // ๋ณด๋‚ผ ์ฑ„๋„์˜ ID

    // WebClient ์ดˆ๊ธฐํ™”
    const slackClient = new WebClient(slackToken);

    try {
        // Sentry์—์„œ ์›นํ›…์œผ๋กœ ์ „๋‹ฌ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์Œ
        const sentryData = req.body;
        console.log('# sentryData', sentryData)

        // Slack ๋ฉ”์‹œ์ง€ ํฌ๋งท ๊ตฌ์„ฑ
        const message = {
            channel: slackChannel,
            blocks: [
                {
                    type: "section",
                    text: {
                        type: "mrkdwn",
                        text: "๐Ÿšจ *[ํ‚ค์˜ค์Šคํฌ ์—๋Ÿฌ ๋ฐœ์ƒ]* ๐Ÿšจ",
                    },
                },
                {
                    type: "section",
                    text: {
                        type: "mrkdwn",
                        text: `*<${sentryData.url}|Sentry ๋งํฌ>*`
                    }
                },
                {
                    type: "section",
                    fields: [
                        {
                            type: "mrkdwn",
                            text: `*์—๋Ÿฌ ๋ฉ”์‹œ์ง€:*\n${sentryData.message}`
                        },
                    ],
                },
                {
                    type: "section",
                    fields: [
                        {
                            type: "mrkdwn",
                            text: `*์›์ธ:*\n${sentryData.culprit}`
                        },
                    ],
                },
                {
                    type: "section",
                    fields: [
                        {
                            type: "mrkdwn",
                            text: `*๋ฒ„์ „:*\n${sentryData.release || 'N/A'}`
                        },
                    ],
                },
                {
                    type: "section",
                    fields: [
                        {
                            type: "mrkdwn",
                            text: `*๋ฐœ์ƒ์‹œ๊ฐ:*\n${sentryData.event.timestamp ? formatTimestamp(sentryData.event.timestamp) : 'N/A'}`
                        }
                    ]
                }
            ],
        };

        // Slack์— ๋ฉ”์‹œ์ง€ ์ „์†ก
        await slackClient.chat.postMessage(message);

        // ์„ฑ๊ณต ์‘๋‹ต
        res.status(200).send('Notification sent to Slack');
    } catch (error) {
        console.error('Error sending message to Slack:', error);
        res.status(500).send('Error sending notification');
    }
});


function formatTimestamp(unixTimestamp) {
    // ์ดˆ ๋‹จ์œ„ Unix ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ๋ฐ€๋ฆฌ์ดˆ๋กœ ๋ณ€ํ™˜
    const date = new Date(unixTimestamp * 1000);

    // UTC ์‹œ๊ฐ„์—์„œ 9์‹œ๊ฐ„์„ ๋”ํ•ด KST๋กœ ๋ณ€ํ™˜
    const KSTOffset = 9 * 60 * 60 * 1000; // 9์‹œ๊ฐ„์„ ๋ฐ€๋ฆฌ์ดˆ๋กœ ๋ณ€ํ™˜
    const KSTDate = new Date(date.getTime() + KSTOffset);

    // ์—ฐ๋„, ์›”, ์ผ ๊ฐ€์ ธ์˜ค๊ธฐ
    const year = KSTDate.getFullYear();
    const month = String(KSTDate.getMonth() + 1).padStart(2, '0'); // ์›”์€ 0๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋ฏ€๋กœ 1 ๋”ํ•จ
    const day = String(KSTDate.getDate()).padStart(2, '0');

    // ์‹œ, ๋ถ„, ์ดˆ ๊ฐ€์ ธ์˜ค๊ธฐ
    const hours = String(KSTDate.getHours()).padStart(2, '0');
    const minutes = String(KSTDate.getMinutes()).padStart(2, '0');
    const seconds = String(KSTDate.getSeconds()).padStart(2, '0');

    // ์›ํ•˜๋Š” ํฌ๋งท์œผ๋กœ ๋ฐ˜ํ™˜
    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}

Slack SDK๋ฅผ ์“ฐ๊ธฐ ์œ„ํ•œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์ถ”๊ฐ€์™€ Sentry ์›นํ›… ๋“ฑ๋ก, Slack ์•ฑ ๋“ฑ๋ก ์ •๋„์˜ ๋ถ€๊ฐ€ ์ž‘์—…์„ ๋งˆ์น˜๋‹ˆ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋ˆ์„ ๊ฑฐ์˜ ๋“ค์ด์ง€ ์•Š๊ณ  Sentry ์Šฌ๋ž™๋ด‡์„ ์™„์„ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์–ด์š”.

Slack Bot Final

๋งบ์Œ๋ง

Zapier, IFTTT ์‚ฝ์งˆ์„ ์ œ์™ธํ•˜๋ฉด ์‹ค์งˆ์ ์ธ ์ž‘์—… ์‹œ๊ฐ„์€ 1์‹œ๊ฐ„ ์ด๋‚ด์˜€๋Š”๋ฐ, ์ด๋Ÿฐ ๊ฐ„๋‹จํ•œ ์ˆ˜์ค€์˜ ์Šคํฌ๋ฆฝํŒ…์€ GPT์—๊ฒŒ ๊ฑฐ์˜ ์œ„์ž„์‹œํ‚ฌ ์ˆ˜ ์žˆ์—ˆ๋‹ค๋Š”๊ฒŒ ์ข‹์€ ๋ ˆ์Šจ์ด ๋˜์—ˆ๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค!

profile
๋š๋”ฑ๋š๋”ฑ
post-custom-banner

0๊ฐœ์˜ ๋Œ“๊ธ€