feat: batch logging 반영
parent
411ccb2d16
commit
5ee5c5eec3
@ -1,279 +0,0 @@
|
|||||||
package kr.xit.core.aop;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import kr.xit.core.biz.model.LoggingDTO;
|
|
||||||
import kr.xit.core.biz.service.ILoggingService;
|
|
||||||
import kr.xit.core.exception.BizRuntimeException;
|
|
||||||
import kr.xit.core.model.ApiResponseDTO;
|
|
||||||
import kr.xit.core.spring.util.error.ErrorParse;
|
|
||||||
import kr.xit.core.support.slack.SlackWebhookPush;
|
|
||||||
import kr.xit.core.support.utils.JsonUtils;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.aspectj.lang.JoinPoint;
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
|
||||||
import org.aspectj.lang.annotation.AfterThrowing;
|
|
||||||
import org.aspectj.lang.annotation.Around;
|
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
|
||||||
import org.slf4j.MDC;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <pre>
|
|
||||||
* description : logging trace aspect
|
|
||||||
* 공통 core 모듈의 LoggerAspect 상속 -> traceLogging / traceLoggingError / traceLoggingResult 구현
|
|
||||||
*
|
|
||||||
* Logging trace 구현시
|
|
||||||
* - MDC(Mapped Diagnostic Context : logback, log4j에 포함) 사용 로깅
|
|
||||||
* - ThreadLocal 사용
|
|
||||||
* - nginx : proxy_set_header X-RequestID $request_id;
|
|
||||||
* - logback log pattern : [traceId=%X{request_id}]
|
|
||||||
*
|
|
||||||
* app.slack-webhook.enabled: true인 경우 slack push
|
|
||||||
* Slack webhook : SlackWebhookPush
|
|
||||||
*
|
|
||||||
* packageName : kr.xit.core.aop
|
|
||||||
* fileName : TraceLoggerAspect
|
|
||||||
* author : julim
|
|
||||||
* date : 2023-04-28
|
|
||||||
* ======================================================================
|
|
||||||
* 변경일 변경자 변경 내용
|
|
||||||
* ----------------------------------------------------------------------
|
|
||||||
* 2023-04-28 julim 최초 생성
|
|
||||||
* 2023-06-12 julim 배치처리시 RequestContextHolder.HttpServletRequest 객체 미사용에 따른 처리 추가
|
|
||||||
* </pre>
|
|
||||||
* @see kr.xit.core.support.slack.SlackWebhookPush#sendSlackAlertLog(String, String, String)
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
//@Aspect
|
|
||||||
//@Component
|
|
||||||
public class TraceLoggerAspect {
|
|
||||||
|
|
||||||
@Value("${app.slack-webhook.enabled:false}")
|
|
||||||
private boolean isSlackEnabled;
|
|
||||||
|
|
||||||
@Value("#{'${app.log.mdc.exclude-patterns}'.split(',')}")
|
|
||||||
private String[] excludes;
|
|
||||||
|
|
||||||
private final ILoggingService loggingService;
|
|
||||||
private final SlackWebhookPush slackWebhookPush;
|
|
||||||
private static final String REQUEST_TRACE_ID = "request_trace_id";
|
|
||||||
|
|
||||||
public TraceLoggerAspect(@Lazy ILoggingService loggingService, SlackWebhookPush slackWebhookPush) {
|
|
||||||
this.loggingService = loggingService;
|
|
||||||
this.slackWebhookPush = slackWebhookPush;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pointcut("execution(public * egovframework..*.*(..)) || execution(public * kr.xit..*.*(..))")
|
|
||||||
public void errorPointCut() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Around(value = "@annotation(kr.xit.core.spring.annotation.TraceLogging)")
|
|
||||||
public Object serviceTraceLogging(final ProceedingJoinPoint pjp) throws Throwable {
|
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
|
|
||||||
HttpServletRequest request = attributes != null? attributes.getRequest(): null;
|
|
||||||
|
|
||||||
traceLogging(JsonUtils.toJson(pjp.getArgs()), request);
|
|
||||||
Object result = pjp.proceed();
|
|
||||||
|
|
||||||
//noinspection rawtypes
|
|
||||||
if(result instanceof CompletableFuture future){
|
|
||||||
while(true) {
|
|
||||||
if (future.isDone()) break;
|
|
||||||
}
|
|
||||||
traceLoggingResult(future.get());
|
|
||||||
}else{
|
|
||||||
traceLoggingResult(result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//@AfterThrowing(value = "@annotation(kr.xit.core.spring.annotation.TraceLogging)", throwing = "error")
|
|
||||||
@AfterThrowing(value = "errorPointCut()", throwing="error")
|
|
||||||
public void afterThrowingProceed(final JoinPoint jp, final Throwable error) {
|
|
||||||
log.error("+++++++++++++++>>>>>>> Batch Error log trace::{}", error.getMessage());
|
|
||||||
traceLoggingError(jp, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 배치 실행시 여기에서 set한 MDC 값이 batch 모듈에서 reading이 불가하여, 배치 tasklet에서 set한 trace_id로 set
|
|
||||||
* @param params
|
|
||||||
* @param request
|
|
||||||
*/
|
|
||||||
protected void traceLogging(final String params, final HttpServletRequest request) {
|
|
||||||
if(request != null) {
|
|
||||||
String uri = request.getRequestURI().toString();
|
|
||||||
if(Arrays.asList(excludes).stream().anyMatch(regx -> uri.matches(regx))) return;
|
|
||||||
|
|
||||||
MDC.put(REQUEST_TRACE_ID,
|
|
||||||
StringUtils.defaultString(MDC.get("request_trace_batch_id"), UUID.randomUUID().toString().replaceAll("/-/g", "")));
|
|
||||||
MDC.put("method", request.getMethod());
|
|
||||||
MDC.put("uri", uri);
|
|
||||||
MDC.put("ip", request.getRemoteAddr());
|
|
||||||
MDC.put("sessionId", request.getSession().getId());
|
|
||||||
|
|
||||||
// batch로 실행되는 경우 task에서 설정 : uri / method
|
|
||||||
}else{
|
|
||||||
// request_id는 중복 처리 되면 않되므로 여기에서 생성
|
|
||||||
MDC.put(REQUEST_TRACE_ID, StringUtils.defaultString(MDC.get("request_trace_batch_id"), UUID.randomUUID().toString().replaceAll("-", "")));
|
|
||||||
}
|
|
||||||
//TODO::systemId, reqSystemId 설정 필요
|
|
||||||
log.info("@@@@@@@@@@@@@@@@@로깅 start : [\n{}\n]",MDC.getCopyOfContextMap());
|
|
||||||
MDC.put("systemId", "ENS");
|
|
||||||
MDC.put("reqSystemId", "KAKAO");
|
|
||||||
MDC.put("param", params);
|
|
||||||
MDC.put("accessToken", "");
|
|
||||||
|
|
||||||
LoggingDTO loggingDTO = LoggingDTO.builder()
|
|
||||||
.requestId(MDC.getCopyOfContextMap().get(REQUEST_TRACE_ID))
|
|
||||||
.systemId("ENS")
|
|
||||||
.reqSystemId("KAKAO")
|
|
||||||
.method(MDC.getCopyOfContextMap().get("method"))
|
|
||||||
.uri(MDC.getCopyOfContextMap().get("uri"))
|
|
||||||
.param(params)
|
|
||||||
.ip(MDC.getCopyOfContextMap().get("ip"))
|
|
||||||
.accessToken("")
|
|
||||||
.sessionId(MDC.getCopyOfContextMap().get("sessionId"))
|
|
||||||
.build();
|
|
||||||
loggingService.saveLogging(loggingDTO);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void traceLoggingResult(final Object result) {
|
|
||||||
String success = "true";
|
|
||||||
//FIXME: slack webhook log push
|
|
||||||
if(result instanceof ApiResponseDTO<?> apiResponseDTO){
|
|
||||||
if(!apiResponseDTO.isSuccess()){
|
|
||||||
success = "false";
|
|
||||||
|
|
||||||
if(isSlackEnabled) {
|
|
||||||
|
|
||||||
if(RequestContextHolder.getRequestAttributes() != null) {
|
|
||||||
slackWebhookPush.sendSlackAlertLog(
|
|
||||||
String.format("[%s]%s", MDC.get(REQUEST_TRACE_ID), apiResponseDTO.getMessage()),
|
|
||||||
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest());
|
|
||||||
}else{
|
|
||||||
slackWebhookPush.sendSlackAlertLog(
|
|
||||||
String.format("[%s]%s", MDC.get(REQUEST_TRACE_ID), apiResponseDTO.getMessage()),
|
|
||||||
MDC.get("uri"),
|
|
||||||
"batch call");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
if(apiResponseDTO.getData() != null){
|
|
||||||
log.info("~~~~");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LoggingDTO reqDTO = LoggingDTO
|
|
||||||
.builder()
|
|
||||||
.requestId(MDC.get(REQUEST_TRACE_ID))
|
|
||||||
.success(success)
|
|
||||||
.response(getResult(result))
|
|
||||||
.message(HttpStatus.OK.name())
|
|
||||||
.build();
|
|
||||||
//}
|
|
||||||
loggingService.modifyLogging(reqDTO);
|
|
||||||
//loggingService.saveLogging(reqDTO);
|
|
||||||
|
|
||||||
log.info("@@@@@@@@@@@@@@로깅 end[\n{}\n]", MDC.getCopyOfContextMap());
|
|
||||||
|
|
||||||
//if(RequestContextHolder.getRequestAttributes() != null)
|
|
||||||
MDC.clear();
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getResult(final Object o){
|
|
||||||
//noinspection rawtypes
|
|
||||||
if(o instanceof Future future) {
|
|
||||||
try {
|
|
||||||
return JsonUtils.toJson(future.get());
|
|
||||||
} catch (InterruptedException ie){
|
|
||||||
// thread pool에 에러 상태 전송
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
throw BizRuntimeException.create(ie);
|
|
||||||
|
|
||||||
} catch (ExecutionException ee) {
|
|
||||||
throw BizRuntimeException.create(ee);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
return JsonUtils.toJson(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void traceLoggingError(final JoinPoint jp, final Throwable e) {
|
|
||||||
log.info("~~~~~~~~~~~~~~~~~~~~~~~>>>>{}", MDC.get(REQUEST_TRACE_ID));
|
|
||||||
// if(Checks.isEmpty(MDC.get(REQUEST_TRACE_ID))) return;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
ApiResponseDTO dto = ErrorParse.extractError(e);
|
|
||||||
|
|
||||||
loggingService.modifyLogging(
|
|
||||||
LoggingDTO
|
|
||||||
.builder()
|
|
||||||
.requestId(MDC.get(REQUEST_TRACE_ID))
|
|
||||||
.success("false")
|
|
||||||
.response(JsonUtils.toJson(dto))
|
|
||||||
.message(dto.getMessage())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
//FIXME :: slack webhook log push
|
|
||||||
if(isSlackEnabled){
|
|
||||||
if(RequestContextHolder.getRequestAttributes() != null) {
|
|
||||||
slackWebhookPush.sendSlackAlertLog(
|
|
||||||
String.format("[%s]%s(%s)", MDC.get(REQUEST_TRACE_ID), dto.getCode(), dto.getMessage()),
|
|
||||||
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest());
|
|
||||||
}else{
|
|
||||||
slackWebhookPush.sendSlackAlertLog(
|
|
||||||
String.format("[%s]%s(%s)", MDC.get(REQUEST_TRACE_ID), dto.getCode(), dto.getMessage()),
|
|
||||||
MDC.get("uri"),
|
|
||||||
"batch call");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("@@@@@@@@@@@@ 로깅 end[\n{}\n]", MDC.getCopyOfContextMap());
|
|
||||||
//if(RequestContextHolder.getRequestAttributes() != null)
|
|
||||||
MDC.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
enum ApiSystemId {
|
|
||||||
KAKAO("KAKAO", "카카오", "/kakao/"),
|
|
||||||
KT_BC("KT-BC", "공공알림문자", "/kt/"),
|
|
||||||
PPLUS("POST-PLUS", "Post Plus", "/pplus/"),
|
|
||||||
NICE("NICE", "NICE CI", "/nice/"),
|
|
||||||
EPOST("EPOST", "E-POST", "/ag/"),
|
|
||||||
NONE("NONE", "미정의", "/미정의/"),
|
|
||||||
;
|
|
||||||
|
|
||||||
private final String code;
|
|
||||||
private final String desc;
|
|
||||||
private final String uri;
|
|
||||||
|
|
||||||
ApiSystemId(final String code, final String desc, final String uri) {
|
|
||||||
this.code = code;
|
|
||||||
this.desc = desc;
|
|
||||||
this.uri = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ApiSystemId compareByUri(final String uri){
|
|
||||||
return Arrays.stream(ApiSystemId.values())
|
|
||||||
.filter(ssc -> uri.contains(ssc.getUri()))
|
|
||||||
.findFirst()
|
|
||||||
.orElseGet(() -> ApiSystemId.valueOf("NONE"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
||||||
|
|
||||||
<mapper namespace="kr.xit.core.biz.mapper.ILoggingMapper">
|
|
||||||
|
|
||||||
<insert id="saveLogging" parameterType="kr.xit.core.biz.model.LoggingDTO">
|
|
||||||
/** logging-mysql-mapper|saveLogging|julim */
|
|
||||||
INSERT INTO tb_cmm_api_log (
|
|
||||||
request_id
|
|
||||||
, system_id
|
|
||||||
, req_system_id
|
|
||||||
, method
|
|
||||||
, uri
|
|
||||||
, param
|
|
||||||
, ip
|
|
||||||
, access_token
|
|
||||||
, session_id
|
|
||||||
, success
|
|
||||||
, response
|
|
||||||
, message
|
|
||||||
, regist_dt
|
|
||||||
, regist_id
|
|
||||||
) VALUES (
|
|
||||||
#{requestId}
|
|
||||||
, #{systemId}
|
|
||||||
, #{reqSystemId}
|
|
||||||
, #{method}
|
|
||||||
, #{uri}
|
|
||||||
, #{param}
|
|
||||||
, #{ip}
|
|
||||||
, #{accessToken}
|
|
||||||
, #{sessionId}
|
|
||||||
, #{success}
|
|
||||||
, #{response}
|
|
||||||
, #{message}
|
|
||||||
, now(3)
|
|
||||||
, #{registId}
|
|
||||||
)
|
|
||||||
</insert>
|
|
||||||
|
|
||||||
<update id="updateLogging" parameterType="kr.xit.core.biz.model.LoggingDTO">
|
|
||||||
/** logging-mysql-mapper|updateLogging|julim */
|
|
||||||
UPDATE tb_cmm_api_log
|
|
||||||
SET success = #{success}
|
|
||||||
, response = #{response}
|
|
||||||
, message = #{message}
|
|
||||||
, updt_dt = now(3)
|
|
||||||
, updt_id = #{updtId}
|
|
||||||
WHERE request_id = #{requestId}
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<select id="selectLogging" resultType="kr.xit.core.biz.model.LoggingDTO">
|
|
||||||
/** logging-mysql-mapper|selectLogging|julim */
|
|
||||||
SELECT request_id
|
|
||||||
, system_id
|
|
||||||
, req_system_id
|
|
||||||
, method
|
|
||||||
, uri
|
|
||||||
, param
|
|
||||||
, ip
|
|
||||||
, access_token
|
|
||||||
, session_id
|
|
||||||
, regist_dt
|
|
||||||
, regist_id
|
|
||||||
FROM tb_cmm_api_log
|
|
||||||
<where>
|
|
||||||
<if test='requestId != null and requestId != ""'>
|
|
||||||
AND request_id = #{requestId}
|
|
||||||
</if>
|
|
||||||
<if test='systemId != null and systemId != ""'>
|
|
||||||
AND system_id = #{systemId}
|
|
||||||
</if>
|
|
||||||
<if test='uri != null and uri != ""'>
|
|
||||||
AND uri LIKE CONCAT('%', #{url}, '%')
|
|
||||||
</if>
|
|
||||||
</where>
|
|
||||||
|
|
||||||
</select>
|
|
||||||
</mapper>
|
|
Loading…
Reference in New Issue