안녕하세요. 스프링 부트 기반 서버에서 에러 발생 시 LogBack 을 통해서 디스코드 봇이 해당 에러를 채널로 전달하는 기능을 구현해보려고 합니다. 또한 디스코드 뿐만 아니라 슬랙과 텔레그램에 대해서도 다음 시간에 정리할 예정입니다 :)
HTTP REQUEST 가공 필터 구현
제가 디스코드 봇에서 채널로 보낼 메세지에 양식은 아래와 같습니다. 후에 진행할 슬랙과 텔레그램에서도 유사하니 참고하시기 바랍니다.
위 메세지 내용을 살펴보면 http request에 대한 정보를 필요로 합니다. 그러나 http body 정보는 inputstream으로 한 번 읽으면 다시 사용할 수 없기 때문에 필터에서 한 번 사용하고도 이후에도 사용할 수 있도록 HttpServletRequest 객체를 변경하려고 합니다.
필터에서는 request 객체와 response 객체를 건드릴 수 있기 때문에 필터에서 해당 작업을 진행한다고 생각하시면 될 것 같습니다. 자세한 내용은 필터와 인터셉터 차이에 대해서 공부해 보시면 좋을 것 같습니다 :)
public class HttpFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(HttpFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
CustomHttpRequest customHttpRequest = new CustomHttpRequest(request);
MDCUtil.put(customHttpRequest);
filterChain.doFilter(customHttpRequest, response);
MDC.clear();
}
}`
`public class CustomHttpRequest extends HttpServletRequestWrapper {
private final byte[] body;
*/**
* Create a new {@code HttpRequest} wrapping the given request object.
*
* @param request the request object to be wrapped
*/*
public CustomHttpRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream inputStream = request.getInputStream();
this.body = StreamUtils.copyToByteArray(inputStream);
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new CachedServletInputStream(body);
}
}
필터와 request 구현은 위와 같습니다. 특별히 복잡한 구현은 없고 단순히 body 부분을 위와 같이 byte[]배열에 캐싱해놓고 계속 사용할 수 있도록 바꿔준다고 생각하면 될 것 같습니다.
그리고 MDCUtil에서는 request 내 정보를 꺼내서 MDC에 넣어줍니다. 이때 MDC는 각각의 요청 즉, 쓰레드마다 독립적으로 관리되기 때문에 정보가 섞일 우려는 하지 않으셔도 됩니다 :)
Appender 구현
이번에는 스프링 애플리케이션에서 에러 이벤트가 발생하면 에러 내용이 appender를 통해 디스코드로 전달될 수 있도록 appender 를 구현하도록 하겠습니다.
@RequiredArgsConstructor
public class ErrorAppender extends AppenderBase<ILoggingEvent> {
private final static boolean LOGGING_DISCORD = true;
private final static boolean LOGGING_SLACK = true;
private final static boolean LOGGING_TELEGRAM = true;
@Override
protected void append(ILoggingEvent eventObject) {
if (eventObject.getLevel().isGreaterOrEqual(Level.ERROR)) {
IThrowableProxy throwableProxy = eventObject.getThrowableProxy();
if (LOGGING_DISCORD)
DiscordWebhook.generate(throwableProxy);
if (LOGGING_SLACK)
SlackWebhook.generate(throwableProxy);
if (LOGGING_TELEGRAM)
TelegramWebhook.generate(throwableProxy);
}
}
}
<configuration>
<appender name="DiscordAppender" class="com.giggal.initask.config.appender.ErrorAppender">
<!-- myCustomAppender 설정 --></appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}[%-5level] : %msg%n</pattern>
</encoder>
</appender>
<!-- root logger 설정 --><root level="DEBUG">
<appender-ref ref="DiscordAppender" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
appender를 스프링에서 정상적으로 인식하기 위해서는 logback.xml 혹은 logback-spring.xml 내에 설정이 되어있어야 합니다. 위 설정 코드를 보면 기본적으로 logback에 구현되어 있는 콘솔어펜더와 제가 구현한 어펜더 총 두개의 어펜더가 존재하는데요. 제 디스코드 어펜더는 ERROR로그만 처리하게 되어 있고, 콘솔 어펜더는 디버그 레벨 이상의 로그는 모두 처리하게 됩니다.
디스코드 웹 훅 설정
이번에는 디스코드 앱에서 메세지를 정상적으로 수신받기 위해 설정을 해보도록 하겠습니다.
위와 같이 서버를 만들고 서버 설정 > 연동 > 웹후크 로 이동해줍니다.
새 웹후크를 누르면 위와 같이 웹훅을 보낼 수 있는 URL이 생성됩니다!
디스코드 메세지 보내기
이제 메세지를 보내기 위한 준비가 거의 다 됐습니다. 필터에서 MDC 내에 저장해두었던 정보들과 디스코드 웹훅 URL을 통해 메세지를 생성하고 보내보도록 하겠습니다.
public class DiscordWebhook {
private static Logger logger = (Logger)LoggerFactory.getLogger(DiscordWebhook.class);
private static final int DISCORD_MAX_LENGTH = 1000;
private static final String url = "https://discord.com/api/webhooks/1096011716480991262/pARnmPlUEIwDIJ_gfoJQoHnTRyLltnP1aAbiSf7Ht4EIFKZwu-CGend2I2VcpkMhk_-Q";
public static void generate(IThrowableProxy throwableProxy) {
DiscordMessage discordMessage;
DiscordMessage discordError;
try {
discordMessage = new DiscordMessage(throwableProxy);
discordError = new DiscordMessage(
ThrowableProxyUtil.asString(throwableProxy).substring(0, DISCORD_MAX_LENGTH));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
AppenderUtil.send(url, discordMessage, logger);
AppenderUtil.send(url, discordError, logger);
}
}
디스코드 웹 훅에 보낼 양식은 이 곳에서 확인할 수 있습니다.에러 로그의 내용이 길기도 하고, 디스코드 메세지 자체 길이 제한도 있다보니 두 번에 나누어 전송하도록 구현했습니다.
public class AppenderUtil {
public static void send(String url, Object object, Logger logger) {
WebClient.create(url)
.post()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(object)
.retrieve()
.toBodilessEntity()
.subscribe((e) -> {
}, (error) -> {
logger.error(error.getMessage());
});
}
}
메세지를 생성한 후 디스코드에서 만든 url과 양식에 맞게 만든 메세지를 스프링 WebFlux에서 제공하는 WebClient를 통해 메세지를 전달할 수 있도록 했습니다.
결과
이제 결과를 확인해 보겠습니다.
@GetMapping("/test")
public String test() throws IOException {
String s = null;
s.charAt(0);
return ":/";
}
위 method를 통해 NullPointer 예외가 발생하도록 했습니다.
스프링 애플리케이션 콘솔에 위와 같이 웹 훅을 보내고 response를 받았고,
위와 같이 디스코드에 정상적으로 발송이 된 것을 확인할 수 있습니다.
참고자료
**🔗 - https://medium.com/chequer/springboot-slack-logback을-이용한-실시간-에러-모니터링-구현하기-7f231812d3fc**
**🔗 - https://junspapa-itdev.tistory.com/34**
'개발 > 스프링부트' 카테고리의 다른 글
스프링 시큐리티로 회원가입 로그인 구현하기 (0) | 2023.06.28 |
---|---|
LogBack과 슬랙, 텔레그램 봇 사용하기 with Springboot (0) | 2023.06.27 |