Skip to content

Commit

Permalink
[#461] feat: 로그 모니터링 위한 slack 연동
Browse files Browse the repository at this point in the history
  • Loading branch information
Te-H0 committed Oct 25, 2024
1 parent 65473de commit be1409d
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 80 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ TestController.java

data.sql

## slack 로그 모니터링
logback-spring.xml

### STS ###
.apt_generated
.classpath
Expand Down
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ dependencies {

// AOP - for logging
implementation 'org.springframework.boot:spring-boot-starter-aop'

implementation 'com.github.maricn:logback-slack-appender:1.4.0'
}
test {
systemProperty "spring.profiles.active", "test"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.jeju.nanaland.global.component;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Map;
import lombok.Setter;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
@Setter
public class SlackErrorLogAppender extends AppenderBase<ILoggingEvent> {

// webhookurl, 슬랙메시지에 찍힐 서버 돌아가는 환경 xml에서 yml 값 불러와서 저장
private String url;

private String env;

@Override
protected void append(final ILoggingEvent eventObject) {

final RestTemplate restTemplate = new RestTemplate();
final Map<String, Object> body = createSlackErrorBody(eventObject);
restTemplate.postForEntity(url, body, String.class);
}

private Map<String, Object> createSlackErrorBody(final ILoggingEvent eventObject) {
final String message = createMessage(eventObject);
return Map.of(
"username", "윤아 석희 태호 비상",
"icon_emoji", ":shocked_face_with_exploding_head",
"attachments", List.of(
Map.of(
"fallback", ":rotating_light: 에러 발생 :rotating_light:",
"color", "#2eb886",
"pretext", "에러가 발생했어요 확인해주세요 :cry:",
"author_name", "Error 발생",
"text", message,
"fields", List.of(
Map.of(
"title", "서버 환경",
"value", env,
"short", false
)
),
"ts", eventObject.getTimeStamp()
)
)
);
}

private String createMessage(final ILoggingEvent eventObject) {
final String baseMessage = "에러가 발생했습니다.\n";
final String pattern = baseMessage + "```%s %s %s [%s] - %s```";
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
return String.format(pattern,
simpleDateFormat.format(eventObject.getTimeStamp()),
eventObject.getLevel(),
eventObject.getThreadName(),
eventObject.getLoggerName(),
eventObject.getFormattedMessage());
}
}
100 changes: 20 additions & 80 deletions src/main/java/com/jeju/nanaland/global/util/LogAspect.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,30 @@
@Component
public class LogAspect {

// 모든 파일
@Pointcut(
"execution(* com.jeju.nanaland..*.*(..))")
public void all() {
}

// 컨트롤러
@Pointcut("execution(* com.jeju.nanaland..*Controller*.*(..))")
public void controllerPointcut() {
}

// 서비스
@Pointcut("execution(* com.jeju.nanaland..*Service*.*(..))")
public void servicePointcut() {
}

// 레포지토리
@Pointcut("execution(* com.jeju.nanaland..*Repository*.*(..))")
public void repositoryPointcut() {
}

// 컨트롤러단에서 로그 찍기.
// 모든 메서드 (레포,서비스에 있는)가 단일 로그로 찍히게 되면 동시 요청이 있을 때 사용자 별 로그가 겹침
// request에 stringBuilder를 포함시켜서 로그 저장함
// 메서드 호출 전: HTTP 메서드, 함수명, 파라미터 출력 및 시간 측정 시작
@Before("controllerPointcut()")
public void logBeforeController(JoinPoint joinPoint) {
Expand All @@ -64,7 +71,7 @@ public void logBeforeController(JoinPoint joinPoint) {
.append(" / Parameters: ").append(params).append("\n");
}

// 서비스 및 레포지토리 메서드의 개별 실행 시간 측정
// 서비스 및 레포지토리 메서드의 개별 실행 시간 측정해서 stringbuilder에 저장
@Around("repositoryPointcut()||servicePointcut()")
public Object logServiceAndRepositoryExecutionTime(ProceedingJoinPoint joinPoint)
throws Throwable {
Expand All @@ -83,12 +90,13 @@ public Object logServiceAndRepositoryExecutionTime(ProceedingJoinPoint joinPoint
} finally {
long executionTime = System.currentTimeMillis() - start;
if (logBuilder != null) {
logBuilder.append("Execution time of ").append(joinPoint.getSignature())
logBuilder.append("\nExecution time of ").append(joinPoint.getSignature())
.append(" : ").append(executionTime).append(" ms\n");
}
}
}

// 정상적으로 controller에서 return이 되면
// 메서드 실행 후 총 시간 계산
@AfterReturning("controllerPointcut()")
public void logAfterController(JoinPoint joinPoint) {
Expand All @@ -102,16 +110,19 @@ public void logAfterController(JoinPoint joinPoint) {

StringBuilder logBuilder = (StringBuilder) request.getAttribute("logBuilder");
if (logBuilder != null && !isExceptionHandlerMethod(joinPoint)) {
logBuilder.append("Total execution time of controller method ")
logBuilder.append("\nTotal execution time of controller method ")
.append(joinPoint.getSignature().getName()).append(" : ")
.append(totalTime).append(" ms");
.append(totalTime).append(" ms\n");

// 최종 로그 한 번에 출력
log.info(logBuilder.toString());

}
}

// 에러 발생 시
// 컨트롤러 단도 포함하면 에러 로그가 두번 찍힘
// 에러 발생 (로그 한번) -> controllerAdvice에 의해 에러 값 return(로그 한번 더 찍힘)
@AfterThrowing(pointcut = "all() && !controllerPointcut()", throwing = "exception")
public void logException(JoinPoint joinPoint, Throwable exception) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Expand All @@ -121,10 +132,13 @@ public void logException(JoinPoint joinPoint, Throwable exception) {

// 로깅
if (logBuilder != null) {
logBuilder.append("\nException Occurred From : ").append(signature)
.append(", Exception Message : ").append(exception.toString());
log.error(logBuilder.toString());
} else {
log.error("\nException Occurred From : {}, Exception Message : {}",
signature, exception.toString());
}
log.error("Exception Occurred From : {}, Exception Message : {}",
signature, exception.toString());
}

private boolean isExceptionHandlerMethod(JoinPoint joinPoint) {
Expand All @@ -143,79 +157,5 @@ private void containUserInfoInLogBuilder(JoinPoint joinPoint, StringBuilder logB
}
}
}
// // 컨트롤러 메서드 호출 시 개별 메서드 실행 시간 측정
// @Around("controllerPointcut()")
// public Object logControllerExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
// long start = System.currentTimeMillis();
// try {
// return joinPoint.proceed();
// } finally {
// long executionTime = System.currentTimeMillis() - start;
// log.info("Execution time of Controller {} : {} ms", joinPoint.getSignature(), executionTime);
// }
// }

// @Around("all()")
// public Object logging(ProceedingJoinPoint joinPoint) throws Throwable {
// long start = System.currentTimeMillis();
// try {
// Object result = joinPoint.proceed();
// return result;
// } finally {
// long end = System.currentTimeMillis();
// long timeinMs = end - start;
// log.info("!!!{} | time = {}ms", joinPoint.getSignature(), timeinMs);
// }
// }
//
// @Around("controllerPointcut()")
// public Object logging2(ProceedingJoinPoint joinPoint) throws Throwable {
// long start = System.currentTimeMillis();
// try {
// Object result = joinPoint.proceed();
// return result;
// } finally {
// long end = System.currentTimeMillis();
// long timeinMs = end - start;
// log.info("????{} | time = {}ms", joinPoint.getSignature(), timeinMs);
// }
// }

// @Before("controllerPointcut()")
// public void before(JoinPoint joinPoint) {
// // HTTP 요청 정보를 가져오기 위해 사용
// HttpServletRequest request =
// ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//
// // HTTP 메서드(GET, POST, 등)
// String httpMethod = request.getMethod();
//
// // 호출된 메서드명
// String methodName = joinPoint.getSignature().getName();
//
// // 파라미터들
// Object[] args = joinPoint.getArgs();
// String params = Arrays.toString(args);
//
// // 로깅
// log.info("-----------------");
// log.info("HTTP Method: {}", httpMethod);
// log.info("Before Method Execution: {}", methodName);
// log.info("Parameters: {}", params);
// log.info("-----------------");
// }

// @AfterReturning("controllerPointcut()")
// public void afterReturning(JoinPoint joinPoint) {
// }
//

// @After("controllerPointcut()")
// public void after(JoinPoint joinPoint) {
// }
// @Around 메서드가 빈 상태로 있거나 proceed()를 호출하지 않으면 컨트롤러가 응답을 반환하지 않게 됩니다.
// @Around("controllerPointcut()")
// public void around(ProceedingJoinPoint joinPoint) throws Throwable {
// }
}

0 comments on commit be1409d

Please sign in to comment.