Spring Boot에서 Logback으로 slack에 에러 로그 남기는 방법

콜트·2021년 11월 10일
5

개요

프로젝트를 진행하다보면 여러 가지 에러를 마주하게 된다. 처리해준 예외들에 대해서는 괜찮지만, 미처 처리하지 못한 부분에서 에러가 발생하면 그건 곧 서비스 전체의 장애로 이어질 수 있다. 이런 상황을 피할 수는 없다. 인간은 완벽한 존재가 아니니까. 다만, 빠르게 대응할 수는 있다. 그래서 어떻게 하면 빠르게 대응할 수 있을까 고민하다가, 협업툴로 사용하는 slack과 연동하여 에러 로그를 남기는 설정을 진행했다(연동한지는 꽤 시간이 지났지만 이제서야 글을 쓴다). 그리고 그 과정을 공유하려고 한다.

Slack 설정

위 사진처럼 Spring Boot 서버와 연동할 채널(본 글에서는 error-log-sample)을 하나 만들거나 선정한다.

위 사진에 표시된 부분의 앱 추가 버튼을 누르면,

요런 화면이 나오는데, 우리가 사용할 것은 Incoming WebHooks이다.

클릭 클릭...하면 어렵지 않게 해당 앱을 slack에 추가할 수 있다. 이때 웹훅을 통해 메시지를 받을 채널을 알맞게 선택해주면 된다.

다음으로는 이런 화면을 마주하게 될텐데, 여기서 WebHooks URL을 이어서 있을 Spring Boot의 logback url에 설정해주면 된다. 잘 복사해두도록 하고 설정을 저장하도록 한다.

Spring Boot 설정

먼저, 위 사진과 같이 build.gradle에 logback의 slack 관련 의존성을 추가해준다.

그 다음으로는, application.yml에 slack 설정을 진행하면서 복사해둔 WebHook Url을 설정해준다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <springProperty name="filePath" source="logging.file.path"/>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %-5level [File:%F] [Func:%M] [Line:%L] [Message:%m] %n</pattern>
        </encoder>
    </appender>

    <appender name="APP_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/app.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %-5level [File:%F] [Func:%M] [Line:%L] [Message:%m] %n</pattern>
        </encoder>
    </appender>

    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/warn.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/warn.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %-5level [File:%F] [Func:%M] [Line:%L] [Message:%m] %n</pattern>
        </encoder>
    </appender>

    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/error.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/error-%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} %-5level [File:%F] [Func:%M] [Line:%L] [Message:%m] %n</pattern>
        </encoder>
    </appender>

    <!-- slack -->
    <springProperty name="SLACK_WEBHOOK_URI" source="logging.slack.webhook-url"/>
    <appender name="SLACK" class="com.github.maricn.logback.SlackAppender">
        <webhookUri>${SLACK_WEBHOOK_URI}</webhookUri>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %msg %n</pattern>
        </layout>
        <username>server-error-log</username>
        <iconEmoji>:anger:</iconEmoji>
        <colorCoding>true</colorCoding>
    </appender>

    <appender name="ASYNC_SLACK" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="SLACK"/>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>

    <logger name="com.example.demo" level="INFO">
        <appender-ref ref="APP_FILE"/>
        <appender-ref ref="WARN_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
        <appender-ref ref="ASYNC_SLACK"/>
    </logger>

</configuration>

이제 resources 디렉토리 내에 logback-spring.xml 파일을 만들고 logback 설정을 진행한다(설정이 미흡해도 이해해주길 바란다). 각각의 설정에 대해 자세한 내용은 참고자료를 살펴보거나 구글링해보길 바란다.
slack appender 설정에 대한 자세한 내용은 logback-slack-appender를 참고하도록 한다(아래의 코드 블록은 logback-slack-appender repository의 README.md에 있는 것을 그대로 가져온 것이다).

	<?xml version="1.0" encoding="UTF-8" ?>
	<configuration>
		...
		<appender name="SLACK" class="com.github.maricn.logback.SlackAppender">
			<!-- Slack API token -->
			<token>1111111111-1111111-11111111-111111111</token>
			<!-- Slack incoming webhook uri. Uncomment the lines below to use incoming webhook uri instead of API token. -->
			<!--
			<webhookUri>https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX</webhookUri>
			-->
			<!-- Channel that you want to post - default is #general -->
			<channel>#api-test</channel>
			<!-- Formatting (you can use Slack formatting - URL links, code formatting, etc.) -->
			<layout class="ch.qos.logback.classic.PatternLayout">
				<pattern>%-4relative [%thread] %-5level %class - %msg%n</pattern>
			</layout>
			<!-- Username of the messages sender -->
			<username>${HOSTNAME}</username>
			<!-- Emoji to be used for messages -->
			<iconEmoji>:stuck_out_tongue_winking_eye:</iconEmoji>
			<!-- If color coding of log levels should be used -->
			<colorCoding>true</colorCoding>
		</appender>

		<!-- Currently recommended way of using Slack appender -->
		<appender name="ASYNC_SLACK" class="ch.qos.logback.classic.AsyncAppender">
			<appender-ref ref="SLACK" />
			<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
				<level>ERROR</level>
			</filter>
		</appender>

		<root>
			<level value="ALL" />
			<appender-ref ref="ASYNC_SLACK" />
		</root>

	</configuration>

로그 메시지 전송 동작 확인

이제 Controller를 간단하게 작성해서 제대로 동작하는지 확인해본다.

두번째 사진은 인텔리제이에서 제공해주는 http-client-in-product-code-editor라는 것으로, 간단하게 HTTP 요청을 만들고 보낼 수 있다.

요청을 보내면!

위 사진과 같이 log가 채널에 메시지로 날아오는 것을 확인할 수 있다. 현재는 error 로그만 메시지로 날아오게끔 설정해두었기 때문에 error 로그만 보인다.

참고자료

profile
개발 블로그이지만 꼭 개발 이야기만 쓰라는 법은 없으니, 그냥 쓰고 싶은 내용이면 뭐든 쓰려고 합니다. 코드는 깃허브에다 작성할 수도 있으니까요.

0개의 댓글