Backend

<Spring> WARN ๋ ˆ๋ฒจ ์ด์ƒ ๋กœ๊ทธ Slack ์•Œ๋ฆผ ๋ณด๋‚ด๊ธฐ

wjdtkdgns 2024. 3. 17. 22:48

๐Ÿ“Œ ๋„์ž… ๋ฐฐ๊ฒฝ

์›๋ž˜ WARN ๋ ˆ๋ฒจ ์ด์ƒ ๋กœ๊ทธ๋ฅผ Sentry๋กœ ๋ณด๋ƒˆ์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ Sentry ๋ฌด๋ฃŒ ๋ผ์ด์„ ์Šค๋ฅผ ์ด์šฉ ์ค‘์ด๋ผ์„œ ๋„์ž…ํ•œ ์ง€ 10์ผ ๋งŒ์— ์šฉ๋Ÿ‰ ์ดˆ๊ณผ๋กœ ์ด๋ฉ”์ผ์ด ์™”๋‹ค.

๊ทธ๋ž˜์„œ ์ฐพ์€ ๋Œ€์•ˆ์ด ์Šฌ๋ž™์ด์—ˆ๋‹ค.

 

์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

๋จผ์ € spring์—์„œ log๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด์ž.

๐Ÿ“Œ Log

๐Ÿ”ต System.out.printIn()

์ž๋ฐ”์—์„œ ๋กœ๊ทธ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๊ธฐ๋ณธ ์ค‘์˜ ๊ธฐ๋ณธ ํ•จ์ˆ˜์ด๋‹ค.

 

ํ•˜์ง€๋งŒ ์ด๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ์ด์œ ๋กœ ์“ฐ๋ฉด ์•ˆ ๋œ๋‹ค.

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

 

๐Ÿ”ต  slf4j

Simple Logging Facade for Java์˜ ์ค„์ž„๋ง์ด๋‹ค.

๋กœ๊ทธ๋ฅผ ์ฒ˜๋ฆฌํ•ด ์ฃผ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค.

 

์Šคํ”„๋ง์„ ์ด์šฉํ•ด ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ๋งŽ์ด ๋ดค์„ ๊ฑฐ๋ผ ์ƒ๊ฐํ•œ๋‹ค.

๋กฌ๋ณต ์–ด๋…ธํ…Œ์ด์…˜ ์ค‘ @Slf4j ๊ฐ€ ๋ฐ”๋กœ ์ด๊ฒƒ์„ ๋งํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

ํ•˜์ง€๋งŒ ์ด๊ฒƒ์€ ๊ตฌํ˜„์ฒด๊ฐ€ ์•„๋‹ˆ๋‹ค.

์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค.

 

๊ทธ๋ ‡๋‹ค๋ฉด ์ด๊ฒƒ์˜ ๊ตฌํ˜„์ฒด๊ฐ€ ๋ฌด์—‡์ผ๊นŒ?

log4 j, logback, log4j2 ๊ฐ€ ์œ„์˜ ๊ตฌํ˜„์ฒด์ด๋‹ค.

 

log4j -> logback -> log4j2 ์ˆœ์„œ๋กœ ๋ฐœํ‘œ๋˜์—ˆ๋‹ค.

ํ•˜๋‚˜ํ•˜๋‚˜ ์•Œ์•„๋ณด์ž.

 

log4j

  • ์—๋Ÿฌ ๋ ˆ๋ฒจ ๊ธฐ๋Šฅ ์ง€์›
  • ๋กœ๊ทธ ์ถœ๋ ฅ ํŒŒ์ผ ์ง€์› ๋ฐ ๋‹ค์–‘ํ•œ ๋กœ๊ทธ ์ถœ๋ ฅ ํ˜•์‹ ์ง€์›

logback

  • log4j ํ›„์† ๋ฒ„์ „
  • Spring์˜ spring-boot-starter-logging์—์„œ ์ง€์›ํ•ด ์ฃผ๋Š” ๋””ํดํŠธ ๊ตฌํ˜„์ฒด
  • ํ•„ํ„ฐ ์ •์ฑ… ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • XML, groovy ์„ค์ • ์ง€์›

log4j2

  • logback ํ›„์† ๋ฒ„์ „
  • ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ async logger ์„ฑ๋Šฅ์ด logback๋ณด๋‹ค ์ข‹์Œ
  • lambda, lazy evaluation ์ง€์›
  • ์ž„์‹œ ๊ฐœ์ฒด๊ฐ€ ๊ฐ€๋Šฅํ•œ ๋งŽ์ด ํ• ๋‹น๋˜์ง€ ์•Š๋Š” ๊ฐ€๋น„์ง€ ์—†๋Š” ๋ชจ๋“œ์—์„œ ์‹คํ–‰ ๊ฐ€๋Šฅ

์œ„๋ฅผ ๋ดค์„ ๋•Œ, log4j2๊ฐ€ ๊ฐ€์žฅ ์ข‹์•„ ๋ณด์ธ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋Š” ๋ณด์•ˆ ๊ด€๋ จ ์ด์Šˆ ์žˆ์—ˆ๋‹ค.

ํ˜„์žฌ๋Š” ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค๊ณ  ํ•œ๋‹ค.

ํ•„์ž๋Š” ์ด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  spring ๊ธฐ๋ณธ์ธ logback์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

 

๐Ÿ“Œ Logback

๐Ÿ”ต Logback

์ด๋Š” ๋กœ๊น… ์‹œ์Šคํ…œ์ด๋‹ค.

 

logback์€ ์•„๋ž˜์™€ ๊ฐ™์€ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๊ฐ€์ง„๋‹ค.

  • appender
    : logging ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์—ญํ• ์„ ๋‹ด๋‹น. Appender ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌ
  • logger
    : ์ด๋ฒคํŠธ์˜ ๋Œ€์ƒ, ์–ด๋–ค ๋‚ด์šฉ์„ log๋กœ ๋‚จ๊ธธ ๊ฒƒ์ธ์ง€ ์ •์˜ํ•˜๋ฉฐ log level์„ ์„ ํƒ์ ์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Œ
  • root
    : ์ตœ์ƒ๋‹จ์˜ logger๋ฅผ root๋ผ ์นญํ•จ

์œ„ ๊ตฌ์„ฑ์š”์†Œ ์ค‘, ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„  appender๋ฅผ ์ปค์Šคํ…€ํ•ด์•ผ ํ•œ๋‹ค.

 

๐Ÿ“Œ Appender, Slack ์—ฐ๋™

๐Ÿ”ต Appender ์ปค์Šคํ…€

class SlackAppender : AppenderBase<ILoggingEvent>() {
    private var token: String = ""

    fun setToken(token: String) {
        this.token = token
    }

    override fun append(event: ILoggingEvent) {
        val message = SlackMessageModel(
            text = """
                        $event
            """.trimIndent()
        )

        WarningLogService.sendLog(message, token)
    }
}

์ปค์Šคํ…€ appender๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„  AppenderBase์˜ ๊ตฌํ˜„์ฒด๋ฅผ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ logback.xml์— ๋“ฑ๋กํ•˜๋ฉด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ด๋•Œ, logback์€ ์Šคํ”„๋ง์ด ์‹œ์ž‘๋˜๊ธฐ ์ด์ „์— ์ดˆ๊ธฐํ™”๋œ๋‹ค.

์ฆ‰, ๋นˆ, IoC ๋“ฑ ์Šคํ”„๋ง์˜ ์žฅ์ ์„ ์ด์šฉํ•˜์ง€ ๋ชปํ•œ๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ ๋‹ค๋ฅธ ํด๋ž˜์Šค์˜ ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ํ•ด๋‹น ํด๋ž˜์Šค์˜ ์ •์  ํ•จ์ˆ˜๋กœ ์„ ์–ธํ•ด์•ผ ํ•œ๋‹ค.

 

์Šฌ๋ž™ ์ „์†ก ์ฃผ์†Œ๋Š” ์™ธ๋ถ€๋กœ ๋…ธ์ถœํ•˜๋ฉด ์•ˆ ๋˜๋ฏ€๋กœ, xml ํŒŒ์ผ์—์„œ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ๋‹ค.

 

๐Ÿ”ต Slack ์ „์†ก ์„œ๋น„์Šค

class WarningLogService {
    companion object {
        private const val SLACK_WEBHOOKS_DOMAIN = "https://hooks.slack.com/services"
        private val webClient = WebClientFactory.generate(baseUrl = SLACK_WEBHOOKS_DOMAIN)

        fun sendLog(message: SlackMessageModel, token: String) {
            CoroutineScope(Dispatchers.IO).launch {
                webClient.post()
                    .uri("/$token")
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(message)
                    .retrieve()
                    .awaitBody()
            }
        }
    }
}

์œ„์—์„œ ์„ค๋ช…ํ•œ ์ด์œ ๋กœ ์ •์  ํ•จ์ˆ˜๋กœ ์Šฌ๋ž™ ๋ฉ”์‹œ์ง€ ์ „์†ก ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

webclient๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด์ฃผ๋Š” ์—ญํ• ์„ ํ•ด์ค€๋‹ค.

 

๋กœ๊น…์€ ๋™๊ธฐ์ ์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค.

์ด๋•Œ, ํ•œ ๋กœ๊น… ํ”„๋กœ์„ธ์Šค๊ฐ€ slack๊ณผ์˜ ํ†ต์‹ ์„ ์ด์œ ๋กœ block ํ•˜๋ฉด,

๋‹ค์Œ ๋ฉ”์‹œ์ง€๋Š” ์•ž์„  ๋ฉ”์‹œ์ง€์˜ ํ†ต์‹ ์ด ๋๋‚˜๋Š” ์‹œ์ ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ค์•ผ ํ•œ๋‹ค.

๊ทธ๋ž˜์„œ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ๊ผญ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

AsyncAppender๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์œผ๋‚˜, ํ˜„ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ฝ”๋ฃจํ‹ด์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ AsyncAppender๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋‹ค.

 

๐Ÿ”ต Slack ์ „์†ก ์„œ๋น„์Šค

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
    <include resource="org/springframework/boot/logging/logback/base.xml"/>

    <springProperty scope="context" name="logLevel" source="logging.level.root" defaultValue="INFO"/>
    <springProperty scope="context" name="warningLogToken" source="slack.warning-log-token" defaultValue=""/>

	...

    <appender name="SLACK_WARNING_LOG" class="com.oksusu.susu.client.appender.SlackAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
        <token>${warningLogToken}</token>
    </appender>

    <springProfile name="default, dev">
        <root level="${logLevel}">
        </root>
    </springProfile>

    <springProfile name="prod">
        <root level="${logLevel}">
            <appender-ref ref="SLACK_WARNING_LOG"/>
            <!--            <appender-ref ref="SENTRY"/>-->
        </root>
    </springProfile>
</configuration>

logback์˜ ์„ค์ • ํŒŒ์ผ์ด๋‹ค. 

์—ฌ๊ธฐ์— ์œ„์—์„œ ๋งŒ๋“  appender๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

 

์œ„์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ, slack ์ฃผ์†Œ๋ฅผ ๊ฐ€์ ธ์™€์„œ appender์— ์ฃผ์ž…ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ <springProperty...>๋ฅผ ์ด์šฉํ•˜์—ฌ ์ฃผ์†Œ๋ฅผ ๊ฐ€์ ธ์™€์„œ appender์— ์ฃผ์ž…ํ•ด ์คฌ๋‹ค.

 

์ด ํ”„๋กœ์ ํŠธ์—์„œ๋Š” prod ํ™˜๊ฒฝ์—์„œ๋งŒ ์ž‘๋™ํ•˜๋„๋ก ํ•  ์˜ˆ์ •์ด๋ฏ€๋กœ, ํ”„๋กœํŒŒ์ผ์ด prod์ผ ๊ฒฝ์šฐ ์‹คํ–‰๋˜๋Š” ๋ถ€๋ถ„์— ์ ์–ด๋’€๋‹ค.

 

๐Ÿ”ต ๊ฒฐ๊ณผ

์ด ๊ธ€์„ ์“ฐ๋Š” ์ง€๊ธˆ๊นŒ์ง€ ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•˜๊ณ  ์žˆ๋‹ค.