로그 포맷팅 재정의

dev
박성영 4 months ago
parent 363094233d
commit 1745c979fb

@ -137,7 +137,7 @@ dependencies {
// ===== DataSource Proxy =====
// datasource-proxy -
implementation 'net.ttddyy:datasource-proxy:1.8.1'
implementation 'net.ttddyy:datasource-proxy:1.10.1'
// ===== Mail =====
implementation 'org.springframework.boot:spring-boot-starter-mail'

@ -0,0 +1,730 @@
package egovframework.config;
import com.zaxxer.hikari.HikariDataSource;
import net.sf.jsqlparser.expression.CaseExpression;
import net.sf.jsqlparser.expression.CastExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.WhenClause;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import net.ttddyy.dsproxy.ExecutionInfo;
import net.ttddyy.dsproxy.QueryInfo;
import net.ttddyy.dsproxy.listener.QueryExecutionListener;
import net.ttddyy.dsproxy.listener.logging.DefaultQueryLogEntryCreator;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener;
import net.ttddyy.dsproxy.proxy.ParameterSetOperation;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import javax.sql.DataSource;
import java.util.List;
/**
* DataSource Proxy
*
* datasource-proxy SQL
* MyBatis include, foreach
*
* Environment DataSource
*
* @author XIT Framework
*/
@Configuration
public class DataSourceProxyConfig {
@Autowired
private Environment environment;
/**
*
* Environment HikariDataSource
*/
@Bean
public DataSource actualDataSource() {
HikariDataSource dataSource = new HikariDataSource();
// application.yml에서 설정값 읽기
dataSource.setJdbcUrl(environment.getProperty("spring.datasource.url"));
dataSource.setUsername(environment.getProperty("spring.datasource.username"));
dataSource.setPassword(environment.getProperty("spring.datasource.password"));
dataSource.setDriverClassName(environment.getProperty("spring.datasource.driver-class-name"));
// HikariCP 설정
dataSource.setMaximumPoolSize(environment.getProperty("spring.datasource.hikari.maximum-pool-size", Integer.class, 10));
dataSource.setMinimumIdle(environment.getProperty("spring.datasource.hikari.minimum-idle", Integer.class, 5));
dataSource.setConnectionTimeout(environment.getProperty("spring.datasource.hikari.connection-timeout", Long.class, 30000L));
dataSource.setIdleTimeout(environment.getProperty("spring.datasource.hikari.idle-timeout", Long.class, 600000L));
dataSource.setMaxLifetime(environment.getProperty("spring.datasource.hikari.max-lifetime", Long.class, 1800000L));
dataSource.setValidationTimeout(environment.getProperty("spring.datasource.hikari.validation-timeout", Long.class, 60000L));
return dataSource;
}
/**
* (Primary)
* actualDataSource SQL
*
* @param actualDataSource
* @return
*/
@Bean
@Primary
public DataSource dataSource(@Qualifier("actualDataSource") DataSource actualDataSource) {
// SLF4J 쿼리 로깅 리스너 생성
SLF4JQueryLoggingListener loggingListener = new SLF4JQueryLoggingListener();
// 로그 레벨 설정 (DEBUG 레벨로 출력)
loggingListener.setLogLevel(SLF4JLogLevel.DEBUG);
// 로거 이름 설정 (쿼리 로그 식별을 위함)
loggingListener.setLogger("go.kr.project.sql");
// 쿼리 로그 엔트리 생성자 설정
DefaultQueryLogEntryCreator logEntryCreator = new DefaultQueryLogEntryCreator();
logEntryCreator.setMultiline(true); // 멀티라인으로 보기 좋게 출력
loggingListener.setQueryLogEntryCreator(logEntryCreator);
// 커스텀 파라미터 바인딩 리스너 생성
CustomParameterBindingListener customListener = new CustomParameterBindingListener();
// 프록시 데이터소스 빌더를 사용하여 프록시 데이터소스 생성
return ProxyDataSourceBuilder
.create(actualDataSource)
.name("XIT-Framework-DB") // 데이터소스 이름 설정
.listener(loggingListener) // 기본 로깅 리스너 추가
.listener(customListener) // 커스텀 파라미터 바인딩 리스너 추가
.asJson() // JSON 형태로 파라미터 바인딩된 쿼리 출력
.build();
}
/**
* SQL
*/
private static class CustomParameterBindingListener implements QueryExecutionListener {
private static final Logger logger = LoggerFactory.getLogger("go.kr.project.sql.binding");
@Override
public void beforeQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
// 쿼리 실행 전 처리 (필요시 구현)
}
@Override
public void afterQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
// 쿼리 실행 후 파라미터가 바인딩된 SQL 출력
for (QueryInfo queryInfo : queryInfoList) {
String query = queryInfo.getQuery();
List<List<ParameterSetOperation>> parametersList = queryInfo.getParametersList();
// Mapper 경로 및 메서드명 추출
String mapperInfo = extractMapperInfo();
logger.info(" ========================== Mapper: {} ========================== ", mapperInfo);
// 쿼리 실행 시간 및 기본 정보
long executionTime = execInfo.getElapsedTime();
logger.debug("실행 시간: {}ms", executionTime);
// 파라미터 값 추출
List<Object> parameterValues = extractParameterValues(parametersList);
// 파라미터 개수 정보
int questionMarkCount = countQuestionMarks(query);
logger.debug("파라미터 정보: ? 플레이스홀더 {}개, 바인딩 값 {}개", questionMarkCount, parameterValues.size());
// 파라미터 값들 전체 출력 (인덱스 포함)
if (!parameterValues.isEmpty()) {
logger.debug("파라미터 값 목록 (총 {}개):", parameterValues.size());
for (int i = 0; i < parameterValues.size(); i++) {
Object param = parameterValues.get(i);
String paramValue;
if (param == null) {
paramValue = "NULL";
} else if (param instanceof String) {
paramValue = "'" + param.toString() + "'";
} else {
paramValue = param.toString();
}
// 파라미터 인덱스와 함께 출력
logger.debug(" (parameter {}) = {}", i + 1, paramValue);
}
}
// 파라미터 바인딩된 SQL 생성
String boundQuery = bindParameters(query, parameterValues);
// SQL 포맷팅 적용 (jsqlparser 사용)
String formattedQuery = formatSql(boundQuery);
logger.info("\n{}\n", formattedQuery);
}
}
/**
* SQL ?
* ?
*
* @param query SQL
* @return ?
*/
private int countQuestionMarks(String query) {
int count = 0;
boolean inBlockComment = false; // /* */ 블록 주석 내부인지 여부
boolean inLineComment = false; // -- 라인 주석 내부인지 여부
boolean inStringLiteral = false; // ' ' 문자열 리터럴 내부인지 여부
for (int i = 0; i < query.length(); i++) {
char currentChar = query.charAt(i);
char nextChar = (i + 1 < query.length()) ? query.charAt(i + 1) : '\0';
// 문자열 리터럴 처리 (홑따옴표로 둘러싸인 문자열)
if (currentChar == '\'' && !inBlockComment && !inLineComment) {
inStringLiteral = !inStringLiteral;
continue;
}
// 블록 주석 시작 /* 검사
if (!inStringLiteral && !inLineComment && currentChar == '/' && nextChar == '*') {
inBlockComment = true;
i++; // 다음 문자도 건너뛰기
continue;
}
// 블록 주석 끝 */ 검사
if (!inStringLiteral && inBlockComment && currentChar == '*' && nextChar == '/') {
inBlockComment = false;
i++; // 다음 문자도 건너뛰기
continue;
}
// 라인 주석 시작 -- 검사
if (!inStringLiteral && !inBlockComment && currentChar == '-' && nextChar == '-') {
inLineComment = true;
i++; // 다음 문자도 건너뛰기
continue;
}
// 라인 주석 끝 (줄바꿈) 검사
if (inLineComment && (currentChar == '\n' || currentChar == '\r')) {
inLineComment = false;
continue;
}
// ? 플레이스홀더 카운트 (주석이나 문자열 리터럴 내부가 아닌 경우에만)
if (currentChar == '?' && !inBlockComment && !inLineComment && !inStringLiteral) {
count++;
}
}
return count;
}
/**
* MyBatis Interceptor Mapper
* SqlLoggingInterceptor ThreadLocal
*
* @return Mapper (: go.kr.project.login.mapper.LoginMapper.selectMenusByRoleIds)
*/
private String extractMapperInfo() {
try {
// SqlLoggingInterceptor에서 제공하는 정확한 Mapper 정보 사용
String mapperInfo = SqlLoggingInterceptor.getCurrentMapperInfo();
return mapperInfo != null ? mapperInfo : "Unknown Mapper";
} catch (Exception e) {
logger.warn("Mapper 정보 추출 실패: {}", e.getMessage());
return "Unknown Mapper";
}
}
/**
* ParameterSetOperation
*/
private List<Object> extractParameterValues(List<List<ParameterSetOperation>> parametersList) {
java.util.Map<Integer, Object> parameterMap = new java.util.TreeMap<>();
if (parametersList != null && !parametersList.isEmpty()) {
// 첫 번째 파라미터 세트를 사용 (일반적으로 PreparedStatement는 하나의 파라미터 세트를 가짐)
List<ParameterSetOperation> operations = parametersList.get(0);
if (operations != null) {
for (ParameterSetOperation operation : operations) {
Object[] args = operation.getArgs();
if (args != null && args.length >= 2) {
// [0]은 파라미터 인덱스 (1-based), [1]은 실제 값
Integer paramIndex = (Integer) args[0];
Object paramValue = args[1];
parameterMap.put(paramIndex, paramValue);
}
}
}
}
// TreeMap을 사용했으므로 자동으로 인덱스 순서대로 정렬됨
return new java.util.ArrayList<>(parameterMap.values());
}
/**
* jsqlparser SQL .
* SELECT AST(Abstract Syntax Tree) .
* CASE, JOIN, .
*
* @param sql SQL
* @return SQL
*/
private String formatSql(String sql) {
try {
Statement statement = CCJSqlParserUtil.parse(sql);
if (statement instanceof Select) {
return formatSelectStatement((Select) statement);
} else if (statement instanceof Insert) {
return formatInsertStatement((Insert) statement);
} else if (statement instanceof Update) {
return formatUpdateStatement((Update) statement);
} else {
// SELECT, INSERT, UPDATE 외 다른 구문(DELETE 등)은 기본 포맷팅 적용
return applyBasicFormatting(statement.toString());
}
} catch (Exception e) {
// SQL 파싱 실패 시 원본 SQL 반환
logger.warn("SQL 파싱 실패, 원본 SQL 반환: {}", e.getMessage());
return sql;
}
}
/**
* UPDATE AST .
*/
private String formatUpdateStatement(Update update) {
StringBuilder sb = new StringBuilder();
sb.append("UPDATE ").append(update.getTable());
// SET clause
if (update.getUpdateSets() != null && !update.getUpdateSets().isEmpty()) {
sb.append("\nSET");
List<UpdateSet> updateSets = update.getUpdateSets();
for (int i = 0; i < updateSets.size(); i++) {
if (i > 0) {
sb.append(",");
}
UpdateSet set = updateSets.get(i);
sb.append("\n ");
if (set.getColumns().size() > 1) sb.append("(");
for (int j = 0; j < set.getColumns().size(); j++) {
if (j > 0) sb.append(", ");
sb.append(set.getColumns().get(j));
}
if (set.getColumns().size() > 1) sb.append(")");
sb.append(" = ");
if (set.getExpressions().size() > 1) sb.append("(");
for (int j = 0; j < set.getExpressions().size(); j++) {
if (j > 0) sb.append(", ");
sb.append(formatComplexExpression(set.getExpressions().get(j), 2));
}
if (set.getExpressions().size() > 1) sb.append(")");
}
}
// FROM clause (for some dialects)
if (update.getFromItem() != null) {
sb.append("\nFROM ").append(update.getFromItem());
}
// JOIN clause (for some dialects)
if (update.getJoins() != null) {
for (Join join : update.getJoins()) {
sb.append("\n").append(formatJoin(join));
}
}
// WHERE clause
if (update.getWhere() != null) {
sb.append("\nWHERE");
appendExpression(update.getWhere(), sb, 1, false);
}
return sb.toString();
}
/**
* INSERT AST .
* .
*/
private String formatInsertStatement(Insert insert) {
StringBuilder sb = new StringBuilder();
sb.append("INSERT INTO ").append(insert.getTable());
// 컬럼 목록 포맷팅
if (insert.getColumns() != null && !insert.getColumns().isEmpty()) {
sb.append("(");
for (int i = 0; i < insert.getColumns().size(); i++) {
sb.append("\n ").append(insert.getColumns().get(i).getColumnName());
if (i < insert.getColumns().size() - 1) {
sb.append(",");
}
}
sb.append("\n)");
}
// 값 목록 포맷팅
if (insert.getItemsList() != null) {
if (insert.getItemsList() instanceof SubSelect) {
sb.append("\n");
formatSelectBody(((SubSelect) insert.getItemsList()).getSelectBody(), sb, 0);
} else {
sb.append("\nVALUES\n(");
if (insert.getItemsList() instanceof ExpressionList) {
List<Expression> values = ((ExpressionList) insert.getItemsList()).getExpressions();
for (int i = 0; i < values.size(); i++) {
sb.append("\n ").append(formatComplexExpression(values.get(i), 2));
if (i < values.size() - 1) {
sb.append(",");
}
}
} else {
sb.append("\n ").append(insert.getItemsList().toString());
}
sb.append("\n)");
}
}
return sb.toString();
}
/**
* SELECT AST .
*
* @param select JSqlParser Select
* @return SELECT
*/
private String formatSelectStatement(Select select) {
StringBuilder sb = new StringBuilder();
SelectBody selectBody = select.getSelectBody();
// 재귀적으로 SelectBody를 처리 (UNION 등 복합 쿼리 지원)
formatSelectBody(selectBody, sb, 0);
return sb.toString();
}
/**
* SelectBody UNION .
*
* @param selectBody SelectBody
* @param sb StringBuilder
* @param indentLevel
*/
private void formatSelectBody(SelectBody selectBody, StringBuilder sb, int indentLevel) {
String indent = getIndent(indentLevel);
if (selectBody instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) selectBody;
// SELECT 절
sb.append(indent).append("SELECT");
if (plainSelect.getDistinct() != null) {
sb.append(" ").append(plainSelect.getDistinct());
}
appendSelectItems(plainSelect.getSelectItems(), sb, indentLevel + 1);
// FROM 절
if (plainSelect.getFromItem() != null) {
sb.append("\n").append(indent).append("FROM ").append(plainSelect.getFromItem());
}
// JOIN 절
if (plainSelect.getJoins() != null) {
for (Join join : plainSelect.getJoins()) {
sb.append("\n").append(indent).append(formatJoin(join));
}
}
// WHERE 절
if (plainSelect.getWhere() != null) {
sb.append("\n").append(indent).append("WHERE");
appendExpression(plainSelect.getWhere(), sb, indentLevel + 1, false);
}
// GROUP BY 절
if (plainSelect.getGroupBy() != null) {
sb.append("\n").append(indent).append("GROUP BY ").append(plainSelect.getGroupBy().toString());
}
// HAVING 절
if (plainSelect.getHaving() != null) {
sb.append("\n").append(indent).append("HAVING");
appendExpression(plainSelect.getHaving(), sb, indentLevel + 1, false);
}
// ORDER BY 절
if (plainSelect.getOrderByElements() != null) {
sb.append("\n").append(indent).append("ORDER BY ").append(plainSelect.getOrderByElements().toString());
}
// LIMIT 절
if (plainSelect.getLimit() != null) {
sb.append("\n").append(indent).append(plainSelect.getLimit());
}
} else if (selectBody instanceof SetOperationList) {
SetOperationList setOpList = (SetOperationList) selectBody;
for (int i = 0; i < setOpList.getSelects().size(); i++) {
if (i > 0) {
sb.append("\n").append(indent).append(setOpList.getOperations().get(i - 1)).append("\n");
}
formatSelectBody(setOpList.getSelects().get(i), sb, indentLevel);
}
} else {
// 기타 SelectBody 타입은 기본 toString() 사용
sb.append(indent).append(selectBody.toString());
}
}
/**
* SELECT .
*/
private void appendSelectItems(List<SelectItem> selectItems, StringBuilder sb, int indentLevel) {
String indent = getIndent(indentLevel);
for (int i = 0; i < selectItems.size(); i++) {
sb.append("\n").append(indent);
if (i > 0) {
sb.append(", ");
}
SelectItem item = selectItems.get(i);
if (item instanceof SelectExpressionItem) {
SelectExpressionItem exprItem = (SelectExpressionItem) item;
// CASE 표현식 등을 포함한 복잡한 표현식 포맷팅
sb.append(formatComplexExpression(exprItem.getExpression(), indentLevel));
if (exprItem.getAlias() != null) {
sb.append(" AS ").append(exprItem.getAlias().getName());
}
} else {
sb.append(item.toString());
}
}
}
/**
* (WHERE, HAVING) .
* AND, OR .
*/
private void appendExpression(Expression expression, StringBuilder sb, int indentLevel, boolean isNested) {
String indent = getIndent(indentLevel);
if (expression instanceof AndExpression) {
AndExpression and = (AndExpression) expression;
appendExpression(and.getLeftExpression(), sb, indentLevel, isNested);
sb.append("\n").append(indent).append("AND ");
appendExpression(and.getRightExpression(), sb, indentLevel, true);
} else if (expression instanceof OrExpression) {
OrExpression or = (OrExpression) expression;
appendExpression(or.getLeftExpression(), sb, indentLevel, isNested);
sb.append("\n").append(indent).append("OR ");
appendExpression(or.getRightExpression(), sb, indentLevel, true);
} else {
if (!isNested) {
sb.append("\n").append(indent);
}
sb.append(formatComplexExpression(expression, indentLevel));
}
}
/**
* CASE .
*/
private String formatComplexExpression(Expression expression, int indentLevel) {
if (expression instanceof CaseExpression) {
return formatCaseExpression((CaseExpression) expression, indentLevel);
}
if (expression instanceof CastExpression) {
return formatCastExpression((CastExpression) expression, indentLevel);
}
// 다른 복잡한 표현식들도 여기에 추가 가능
return expression.toString();
}
/**
* CASE .
*/
private String formatCaseExpression(CaseExpression caseExpr, int indentLevel) {
String indent = getIndent(indentLevel);
String innerIndent = getIndent(indentLevel + 1);
StringBuilder sb = new StringBuilder("CASE");
if (caseExpr.getSwitchExpression() != null) {
sb.append(" ").append(caseExpr.getSwitchExpression().toString());
}
for (WhenClause when : caseExpr.getWhenClauses()) {
sb.append("\n").append(innerIndent).append("WHEN ").append(when.getWhenExpression().toString());
sb.append(" THEN ").append(when.getThenExpression().toString());
}
if (caseExpr.getElseExpression() != null) {
sb.append("\n").append(innerIndent).append("ELSE ").append(caseExpr.getElseExpression().toString());
}
sb.append("\n").append(indent).append("END");
return sb.toString();
}
/**
* CAST .
*/
private String formatCastExpression(CastExpression castExpr, int indentLevel) {
return "CAST(" + formatComplexExpression(castExpr.getLeftExpression(), indentLevel) + " AS " + castExpr.getType().toString() + ")";
}
/**
* JOIN .
*/
private String formatJoin(Join join) {
String joinType = "";
if (join.isSimple()) joinType += " ";
if (join.isCross()) joinType += "CROSS ";
if (join.isFull()) joinType += "FULL ";
if (join.isInner()) joinType += "INNER ";
if (join.isLeft()) joinType += "LEFT ";
if (join.isNatural()) joinType += "NATURAL ";
if (join.isOuter()) joinType += "OUTER ";
if (join.isRight()) joinType += "RIGHT ";
if (join.isSemi()) joinType += "SEMI ";
return joinType + "JOIN " + join.getRightItem() + (join.getOnExpression() != null ? " ON " + join.getOnExpression() : "");
}
/**
* INSERT, UPDATE, DELETE .
*/
private String applyBasicFormatting(String sql) {
return sql.replaceAll("(?i)\\b(SET|VALUES|WHERE)\\b", "\\n$1");
}
/**
* .
*/
private String getIndent(int level) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < level; i++) {
sb.append(" "); // 4 spaces for each level
}
return sb.toString();
}
/**
* SQL ?
* ?
*/
private String bindParameters(String query, List<Object> parameters) {
if (parameters == null || parameters.isEmpty()) {
return query;
}
StringBuilder result = new StringBuilder();
int paramIndex = 0;
int queryIndex = 0;
boolean inBlockComment = false; // /* */ 블록 주석 내부인지 여부
boolean inLineComment = false; // -- 라인 주석 내부인지 여부
boolean inStringLiteral = false; // ' ' 문자열 리터럴 내부인지 여부
while (queryIndex < query.length()) {
char currentChar = query.charAt(queryIndex);
char nextChar = (queryIndex + 1 < query.length()) ? query.charAt(queryIndex + 1) : '\0';
// 문자열 리터럴 처리 (홑따옴표로 둘러싸인 문자열)
if (currentChar == '\'' && !inBlockComment && !inLineComment) {
inStringLiteral = !inStringLiteral;
result.append(currentChar);
queryIndex++;
continue;
}
// 블록 주석 시작 /* 검사
if (!inStringLiteral && !inLineComment && currentChar == '/' && nextChar == '*') {
inBlockComment = true;
result.append(currentChar);
result.append(nextChar);
queryIndex += 2;
continue;
}
// 블록 주석 끝 */ 검사
if (!inStringLiteral && inBlockComment && currentChar == '*' && nextChar == '/') {
inBlockComment = false;
result.append(currentChar);
result.append(nextChar);
queryIndex += 2;
continue;
}
// 라인 주석 시작 -- 검사
if (!inStringLiteral && !inBlockComment && currentChar == '-' && nextChar == '-') {
inLineComment = true;
result.append(currentChar);
result.append(nextChar);
queryIndex += 2;
continue;
}
// 라인 주석 끝 (줄바꿈) 검사
if (inLineComment && (currentChar == '\n' || currentChar == '\r')) {
inLineComment = false;
result.append(currentChar);
queryIndex++;
continue;
}
// ? 파라미터 플레이스홀더 처리 (주석이나 문자열 리터럴 내부가 아닌 경우에만)
if (currentChar == '?' && !inBlockComment && !inLineComment && !inStringLiteral) {
if (paramIndex < parameters.size()) {
// ? 플레이스홀더를 실제 파라미터 값으로 치환
Object param = parameters.get(paramIndex);
String paramValue;
if (param == null) {
paramValue = "NULL";
} else if (param instanceof String) {
paramValue = "'" + param.toString().replace("'", "''") + "'";
} else if (param instanceof java.util.Date || param instanceof java.time.LocalDateTime || param instanceof java.time.LocalDate) {
paramValue = "'" + param.toString() + "'";
} else {
paramValue = param.toString();
}
result.append(paramValue);
paramIndex++;
} else {
// 파라미터가 부족한 경우 ? 그대로 유지
result.append('?');
}
} else {
// 그 외의 모든 문자는 그대로 복사
result.append(currentChar);
}
queryIndex++;
}
return result.toString();
}
}
}

@ -1,88 +0,0 @@
package egovframework.config;
import lombok.extern.slf4j.Slf4j;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
/**
* MyBatis Configuration with DataSource Proxy
*
* datasource-proxy include
*
*/
@Slf4j
@Configuration
//@Profile({"local", "dev"}) // local과 dev 프로파일에서만 활성화
public class MyBatisConfig {
/**
* DataSource Properties
*/
@Bean
@Primary
@ConfigurationProperties("spring.datasource")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
/**
* DataSource ( )
*/
@Bean
@ConfigurationProperties("spring.datasource.hikari")
public DataSource actualDataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
/**
* DataSource Proxy
*
* datasource-proxy
* - (if, choose )
* - include
* -
*/
@Bean
@Primary
public DataSource dataSource(@Qualifier("actualDataSource") DataSource actualDataSource) {
return ProxyDataSourceBuilder
.create(actualDataSource)
.name("XIT-Framework-DataSource")
// 쿼리 로깅 리스너 설정 - 실행된 모든 쿼리와 파라미터 바인딩 결과 로깅
.logQueryBySlf4j(SLF4JLogLevel.INFO, "go.kr.project.sql.query")
// 멀티라인으로 쿼리 포맷팅하여 가독성 향상
.multiline()
.build();
}
/**
* MyBatis Query Interceptor
*/
@Autowired
private MyBatisQueryInterceptor myBatisQueryInterceptor;
/**
* MyBatis Configuration Customizer
*
* MyBatis
* datasource-proxy
*/
@Bean
public ConfigurationCustomizer mybatisConfigurationCustomizer() {
return configuration -> {
// 커스텀 쿼리 인터셉터 추가
configuration.addInterceptor(myBatisQueryInterceptor);
log.info("MyBatis Query Interceptor가 등록되었습니다 - 상세 쿼리 분석 활성화");
};
}
}

@ -1,450 +0,0 @@
package egovframework.config;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* MyBatis Query Interceptor
*
* datasource-proxy
* -
* - include
* -
*/
@Component
//@Profile({"local", "dev"}) // local과 dev 프로파일에서만 활성화
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Slf4j
public class MyBatisQueryInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
BoundSql boundSql = ms.getBoundSql(parameter);
String sql = boundSql.getSql();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 실제 실행될 쿼리 추출 (MyBatis 내부에서 처리된 결과)
String actualSql = getActualSql(boundSql, parameter);
// 파라미터 정보 추출
Map<String, Object> paramMap = extractDetailedParameters(parameter, parameterMappings);
logDetailedQueryInfo(ms.getId(), sql, actualSql, paramMap);
// 원래 쿼리 실행
long startTime = System.currentTimeMillis();
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
// 쿼리 실행 시간 로깅
log.debug("Query execution time: {} ms", (endTime - startTime));
return result;
}
private String getActualSql(BoundSql boundSql, Object parameter) {
String sql = boundSql.getSql();
if (parameter == null) {
return sql;
}
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings.isEmpty()) {
return sql;
}
// SQL 복사본 생성
String actualSql = sql;
try {
// 파라미터 값 추출
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
Object value;
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameter instanceof Map) {
value = ((Map<?, ?>) parameter).get(propertyName);
} else if (parameter instanceof String) {
// String 타입 파라미터 처리
value = parameter;
} else if (parameter instanceof Number) {
// Number 타입 파라미터 처리 (Integer, Long, Double 등)
value = parameter;
} else if (parameter instanceof Boolean) {
// Boolean 타입 파라미터 처리
value = parameter;
} else {
value = getParameterValue(parameter, propertyName);
}
String valueStr = value != null ? value.toString() : "null";
// SQL 인젝션 방지를 위한 문자열 이스케이프 처리
valueStr = valueStr.replace("'", "''");
// 다양한 데이터 타입에 대한 처리
if (value instanceof String) {
// 문자열 타입은 따옴표로 감싸기
actualSql = actualSql.replaceFirst("\\?", "'" + valueStr + "'");
} else if (value instanceof Number) {
// 숫자 타입 (Integer, Long, Double 등)은 그대로 사용
actualSql = actualSql.replaceFirst("\\?", valueStr);
} else if (value instanceof Boolean) {
// Boolean 타입은 그대로 사용
actualSql = actualSql.replaceFirst("\\?", valueStr);
} else if (value instanceof java.util.Date) {
// 표준 타임스탬프 포맷 사용
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
actualSql = actualSql.replaceFirst("\\?", "'" + sdf.format(value) + "'");
} else if (value instanceof java.time.LocalDateTime) {
// Java 8 LocalDateTime 처리
actualSql = actualSql.replaceFirst("\\?", "'" + value.toString().replace('T', ' ') + "'");
} else {
// 기타 타입은 null이 아닌 경우 따옴표로 감싸기
actualSql = actualSql.replaceFirst("\\?", value != null ? "'" + valueStr + "'" : valueStr);
}
}
} catch (Exception e) {
log.warn("Failed to get actual SQL with parameters", e);
return sql;
}
return actualSql;
}
private Object getParameterValue(Object parameter, String propertyName) {
try {
// 현재 클래스부터 상위 클래스까지 순회하면서 필드 찾기
Class<?> currentClass = parameter.getClass();
while (currentClass != null) {
try {
Field field = currentClass.getDeclaredField(propertyName);
field.setAccessible(true);
return field.get(parameter);
} catch (NoSuchFieldException e) {
// 현재 클래스에서 필드를 찾지 못하면 상위 클래스로 이동
currentClass = currentClass.getSuperclass();
}
}
throw new NoSuchFieldException(propertyName);
} catch (Exception e) {
log.warn("필드 값 추출 실패: {} ({})", propertyName, e.getMessage());
return null;
}
}
private Map<String, Object> extractDetailedParameters(Object parameter, List<ParameterMapping> parameterMappings) {
Map<String, Object> paramMap = new HashMap<>();
if (parameter == null) {
return paramMap;
}
if (parameter instanceof Map) {
paramMap.putAll((Map<String, Object>) parameter);
} else {
// 객체의 필드 정보도 추출
paramMap.put("param", parameter);
extractObjectFields(parameter, paramMap);
}
return paramMap;
}
private void extractObjectFields(Object obj, Map<String, Object> paramMap) {
try {
Class<?> currentClass = obj.getClass();
while (currentClass != null) {
Field[] fields = currentClass.getDeclaredFields();
for (Field field : fields) {
try {
// String 타입의 객체는 건너뛰기
if (obj instanceof String) {
continue;
}
// 시스템 클래스의 필드는 건너뛰기
if (field.getDeclaringClass().getName().startsWith("java.")) {
continue;
}
field.setAccessible(true);
Object value = field.get(obj);
paramMap.put(field.getName(), value);
} catch (IllegalAccessException | SecurityException e) {
// 개별 필드 접근 실패는 무시하고 계속 진행
log.debug("필드 접근 실패: {} ({})", field.getName(), e.getMessage());
}
}
currentClass = currentClass.getSuperclass();
}
} catch (Exception e) {
log.warn("객체 필드 추출 실패", e);
}
}
private void logDetailedQueryInfo(String mapperMethod, String originalSql, String actualSql, Map<String, Object> paramMap) {
StringBuilder logMessage = new StringBuilder();
logMessage.append("\n");
logMessage.append("┌─────────────── MyBatis Query Details ───────────────\n");
logMessage.append("│ Mapper Method: ").append(mapperMethod).append("\n");
logMessage.append("│ Parameters: ").append(paramMap).append("\n");
// 원본 SQL 포맷팅, prd, 운영
//logMessage.append("│ Original SQL:\n");
//formatSqlInLog(logMessage, originalSql);
// 실제 실행 SQL 포맷팅, local, dev 로컬 개발
logMessage.append("│ Actual SQL:\n");
formatSqlInLog(logMessage, actualSql);
logMessage.append("└──────────────────────────────────────────────────────");
log.info(logMessage.toString());
}
private void formatSqlInLog(StringBuilder logMessage, String sql) {
// SQL 키워드 하이라이트 및 들여쓰기
String formattedSql = formatSql(sql);
String[] lines = formattedSql.split("\n");
for (String line : lines) {
//logMessage.append("│ ").append(line).append("\n");
logMessage.append(line).append("\n");
}
}
private String formatSql(String sql) {
try {
// SQL 파서를 사용한 포맷팅
Statement statement = CCJSqlParserUtil.parse(sql);
return formatStatement(statement, 0);
} catch (JSQLParserException e) {
log.debug("SQL 파싱 실패. 기본 포맷팅으로 대체합니다.", e);
log.info("SQL 파싱 실패. 기본 포맷팅으로 대체합니다.");
// 파싱 실패 시 기본 포맷팅 사용
return sql.replaceAll("\\s+", " ").trim();
}
}
private String formatStatement(Statement statement, int indent) {
StringBuilder result = new StringBuilder();
String indentation = String.join("", java.util.Collections.nCopies(indent, " "));
if (statement instanceof Select) {
formatSelect((Select) statement, result, indent);
} else if (statement instanceof Insert) {
formatInsert((Insert) statement, result, indent);
} else if (statement instanceof Update) {
formatUpdate((Update) statement, result, indent);
} else if (statement instanceof Delete) {
formatDelete((Delete) statement, result, indent);
}
return result.toString();
}
private void formatSelect(Select select, StringBuilder result, int indent) {
String indentation = String.join("", Collections.nCopies(indent, " "));
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
// SELECT 절
result.append(indentation).append("SELECT\n");
formatSelectItems(plainSelect.getSelectItems(), result, indent + 2);
// FROM 절
if (plainSelect.getFromItem() != null) {
result.append(indentation).append("FROM\n");
result.append(indentation).append(" ").append(plainSelect.getFromItem()).append("\n");
}
// JOIN 절
if (plainSelect.getJoins() != null) {
for (Join join : plainSelect.getJoins()) {
result.append(indentation).append(join.isLeft() ? "LEFT JOIN " : "JOIN ")
.append(join.getRightItem()).append("\n");
if (join.getOnExpression() != null) {
result.append(indentation).append(" ON ").append(join.getOnExpression()).append("\n");
}
}
}
// WHERE 절
if (plainSelect.getWhere() != null) {
result.append(indentation).append("WHERE\n");
result.append(indentation).append(" ").append(plainSelect.getWhere()).append("\n");
}
// GROUP BY 절
if (plainSelect.getGroupBy() != null) {
result.append(indentation).append("GROUP BY\n");
result.append(indentation).append(" ")
.append(plainSelect.getGroupBy().getGroupByExpressions().stream()
.map(Object::toString)
.collect(Collectors.joining(", ")))
.append("\n");
}
// HAVING 절
if (plainSelect.getHaving() != null) {
result.append(indentation).append("HAVING\n");
result.append(indentation).append(" ").append(plainSelect.getHaving()).append("\n");
}
// ORDER BY 절
if (plainSelect.getOrderByElements() != null) {
result.append(indentation).append("ORDER BY\n");
result.append(indentation).append(" ")
.append(plainSelect.getOrderByElements().stream()
.map(Object::toString)
.collect(Collectors.joining(", ")))
.append("\n");
}
}
private void formatSelectItems(List<SelectItem> items, StringBuilder result, int indent) {
String indentation = String.join("", Collections.nCopies(indent, " "));
for (int i = 0; i < items.size(); i++) {
result.append(indentation).append(items.get(i));
if (i < items.size() - 1) {
result.append(",");
}
result.append("\n");
}
}
private void formatInsert(Insert insert, StringBuilder result, int indent) {
String indentation = String.join("", Collections.nCopies(indent, " "));
result.append(indentation).append("INSERT INTO ").append(insert.getTable()).append("\n");
// 컬럼 목록
if (insert.getColumns() != null) {
result.append(indentation).append("(")
.append(insert.getColumns().stream()
.map(Column::getColumnName)
.collect(Collectors.joining(", ")))
.append(")\n");
}
result.append(indentation).append("VALUES\n");
// VALUES 절 포맷팅
if (insert.getItemsList() != null) {
result.append(indentation).append(" ").append(insert.getItemsList()).append("\n");
}
}
private void formatUpdate(Update update, StringBuilder result, int indent) {
String indentation = String.join("", Collections.nCopies(indent, " "));
// UPDATE 절
result.append(indentation).append("UPDATE\n");
result.append(indentation).append(" ").append(update.getTable()).append("\n");
// SET 절
result.append(indentation).append("SET\n");
List<UpdateSet> updateSets = update.getUpdateSets();
for (int i = 0; i < updateSets.size(); i++) {
UpdateSet updateSet = updateSets.get(i);
result.append(indentation).append(" ")
.append(updateSet.getColumns().get(0))
.append(" = ")
.append(updateSet.getExpressions().get(0));
if (i < updateSets.size() - 1) {
result.append(",");
}
result.append("\n");
}
// WHERE 절
if (update.getWhere() != null) {
result.append(indentation).append("WHERE\n");
result.append(indentation).append(" ").append(update.getWhere()).append("\n");
}
// ORDER BY 절 (일부 데이터베이스에서 지원)
if (update.getOrderByElements() != null) {
result.append(indentation).append("ORDER BY\n");
result.append(indentation).append(" ")
.append(update.getOrderByElements().stream()
.map(Object::toString)
.collect(Collectors.joining(", ")))
.append("\n");
}
// LIMIT 절 (일부 데이터베이스에서 지원)
if (update.getLimit() != null) {
result.append(indentation).append("LIMIT ")
.append(update.getLimit()).append("\n");
}
}
private void formatDelete(Delete delete, StringBuilder result, int indent) {
String indentation = String.join("", Collections.nCopies(indent, " "));
// DELETE 절
result.append(indentation).append("DELETE FROM\n");
result.append(indentation).append(" ").append(delete.getTable()).append("\n");
// WHERE 절
if (delete.getWhere() != null) {
result.append(indentation).append("WHERE\n");
result.append(indentation).append(" ").append(delete.getWhere()).append("\n");
}
// ORDER BY 절 (일부 데이터베이스에서 지원)
if (delete.getOrderByElements() != null) {
result.append(indentation).append("ORDER BY\n");
result.append(indentation).append(" ")
.append(delete.getOrderByElements().stream()
.map(Object::toString)
.collect(Collectors.joining(", ")))
.append("\n");
}
// LIMIT 절 (일부 데이터베이스에서 지원)
if (delete.getLimit() != null) {
result.append(indentation).append("LIMIT ")
.append(delete.getLimit()).append("\n");
}
}
}

@ -0,0 +1,117 @@
package egovframework.config;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
/**
* MyBatis Interceptor SQL Mapper
*
* MappedStatement namespace(Mapper ) id()
* ThreadLocal DataSourceProxy .
*
* @author XIT Framework
*/
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class SqlLoggingInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(SqlLoggingInterceptor.class);
/**
* ThreadLocal Mapper
* DataSourceProxy Mapper
*/
private static final ThreadLocal<String> CURRENT_MAPPER_INFO = new ThreadLocal<>();
/**
* Mapper
*
* @return Mapper . (: go.kr.project.login.mapper.LoginMapper.selectMenusByRoleIds)
*/
public static String getCurrentMapperInfo() {
String mapperInfo = CURRENT_MAPPER_INFO.get();
return mapperInfo != null ? mapperInfo : "Unknown Mapper";
}
/**
* Mapper
*
* @param mapperInfo Mapper
*/
public static void setCurrentMapperInfo(String mapperInfo) {
CURRENT_MAPPER_INFO.set(mapperInfo);
}
/**
* Mapper
*/
public static void clearCurrentMapperInfo() {
CURRENT_MAPPER_INFO.remove();
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
// MappedStatement에서 Mapper 정보 추출
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
String statementId = mappedStatement.getId();
// statementId는 "namespace.methodId" 형태 (예: go.kr.project.login.mapper.LoginMapper.selectMenusByRoleIds)
String mapperInfo = statementId;
// ThreadLocal에 Mapper 정보 저장
setCurrentMapperInfo(mapperInfo);
logger.debug("MyBatis Interceptor - Mapper 정보 설정: {}", mapperInfo);
// 실제 쿼리 실행
return invocation.proceed();
} catch (Exception e) {
logger.warn("MyBatis Interceptor에서 Mapper 정보 추출 실패: {}", e.getMessage());
throw e;
} finally {
// 쿼리 실행 완료 후 ThreadLocal 정리 (메모리 누수 방지)
// 단, DataSourceProxy에서 사용할 수 있도록 바로 정리하지 않고 약간의 지연 후 정리
scheduleCleanup();
}
}
/**
* ThreadLocal
* DataSourceProxy Mapper
*/
private void scheduleCleanup() {
// 별도 스레드에서 약간의 지연 후 ThreadLocal 정리
new Thread(() -> {
try {
Thread.sleep(100); // 100ms 대기 (DataSourceProxy에서 사용할 시간 제공)
clearCurrentMapperInfo();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
clearCurrentMapperInfo();
}
}).start();
}
@Override
public Object plugin(Object target) {
// 모든 Executor에 대해 인터셉터 적용
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 설정 프로퍼티가 있다면 여기서 처리
// 현재는 특별한 설정이 필요없음
}
}

@ -89,11 +89,11 @@ logging:
max-file-size: 10MB
max-history: 30
level:
org.springframework: INFO
go.kr.project: DEBUG
go.kr.project.sql.query: OFF
egovframework: DEBUG
org.mybatis: INFO
org.springframework: WARN
go.kr.project: INFO
go.kr.project.sql: INFO # datasource-proxy
go.kr.project.sql.binding: INFO # datasource-proxy 파라미터 바인딩된 쿼리 로그
egovframework: INFO
# File upload configuration
file:

@ -94,11 +94,11 @@ logging:
max-file-size: 10MB
max-history: 30
level:
org.springframework: INFO
org.springframework: WARN
go.kr.project: DEBUG
go.kr.project.sql.query: OFF
go.kr.project.sql: INFO # datasource-proxy
go.kr.project.sql.binding: DEBUG # datasource-proxy 파라미터 바인딩된 쿼리 로그
egovframework: DEBUG
org.mybatis: INFO
# File upload configuration
file:

@ -89,11 +89,18 @@ logging:
max-file-size: 10MB
max-history: 30
level:
org.springframework: INFO
go.kr.project: INFO
go.kr.project.sql.query: OFF
egovframework: INFO
org.mybatis: INFO
org.springframework: WARN
go.kr.project: WARN
go.kr.project.sql: WARN # datasource-proxy
go.kr.project.sql.binding: INFO # datasource-proxy 파라미터 바인딩된 쿼리 로그
egovframework: WARN
# 실 운영, 안정화 이후 적용
# org.springframework: WARN
# go.kr.project: INFO
# go.kr.project.sql: WARN # datasource-proxy
# go.kr.project.sql.binding: INFO # datasource-proxy 파라미터 바인딩된 쿼리 로그
# egovframework: WARN
# org.mybatis: INFO
# File upload configuration
file:

@ -5,9 +5,6 @@
<springProperty scope="context" name="LOG_FILE" source="logging.file.name" defaultValue="application" />
<springProperty scope="context" name="MAX_FILE_SIZE" source="logging.logback.rollingpolicy.max-file-size" defaultValue="10MB" />
<springProperty scope="context" name="MAX_HISTORY" source="logging.logback.rollingpolicy.max-history" defaultValue="30" />
<!-- 현재 년월을 변수로 정의 (yyyyMM 형식) -->
<timestamp key="CURRENT_MONTH" datePattern="yyyyMM"/>
<!-- 콘솔 출력 설정 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
@ -24,14 +21,13 @@
<!-- 파일 출력 설정 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 로그 경로에 년월(yyyymm) 형식 추가 -->
<file>${LOG_PATH}/${CURRENT_MONTH}/${LOG_FILE}</file>
<file>${LOG_PATH}/${LOG_FILE}</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 일별 로그 파일 생성 -->
<fileNamePattern>${LOG_PATH}/%d{yyyyMM}/${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<fileNamePattern>${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- 파일당 최고 용량 kb, mb, gb -->
<maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
@ -54,24 +50,6 @@
<!-- 로컬 환경에서만 적용되는 설정 -->
<springProfile name="local">
<!-- datasource-proxy 쿼리 로깅 설정 -->
<logger name="go.kr.project.sql.query" level="INFO" additivity="false">
<appender-ref ref="CONSOLE_COLOR" />
<appender-ref ref="ASYNC_FILE" />
</logger>
<!-- MyBatis 쿼리 인터셉터 로깅 설정 -->
<logger name="egovframework.config.MyBatisQueryInterceptor" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE_COLOR" />
<appender-ref ref="ASYNC_FILE" />
</logger>
<!-- MyBatis SQL 로깅 설정 -->
<logger name="org.mybatis" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE_COLOR" />
<appender-ref ref="ASYNC_FILE" />
</logger>
<!-- 로컬 환경에서는 컬러 콘솔 사용 -->
<root level="INFO">
<appender-ref ref="CONSOLE_COLOR" />
@ -81,24 +59,6 @@
<!-- 개발 환경에서만 적용되는 설정 -->
<springProfile name="dev">
<!-- datasource-proxy 쿼리 로깅 설정 -->
<logger name="go.kr.project.sql.query" level="INFO" additivity="false">
<appender-ref ref="CONSOLE_COLOR" />
<appender-ref ref="ASYNC_FILE" />
</logger>
<!-- MyBatis 쿼리 인터셉터 로깅 설정 -->
<logger name="egovframework.config.MyBatisQueryInterceptor" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE_COLOR" />
<appender-ref ref="ASYNC_FILE" />
</logger>
<!-- MyBatis SQL 로깅 설정 -->
<logger name="org.mybatis" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE_COLOR" />
<appender-ref ref="ASYNC_FILE" />
</logger>
<!-- 개발 환경에서는 컬러 콘솔 사용 -->
<root level="INFO">
<appender-ref ref="CONSOLE_COLOR" />
@ -108,7 +68,7 @@
<!-- 운영 환경에서만 적용되는 설정 -->
<springProfile name="prd">
<!-- 개발 환경에서는 컬러 콘솔 사용 -->
<!-- 운영 환경에서는 일반 콘솔 사용 -->
<root level="WARN">
<appender-ref ref="CONSOLE" />
<appender-ref ref="ASYNC_FILE" />

@ -13,8 +13,15 @@
<!-- Log SQL statements -->
<setting name="logImpl" value="SLF4J"/>
</settings>
<typeAliases>
<!-- Type aliases will be defined in the type-aliases-package in application.yml -->
</typeAliases>
<!-- MyBatis Interceptors for SQL logging and Mapper information extraction -->
<plugins>
<plugin interceptor="egovframework.config.SqlLoggingInterceptor">
<!-- SQL 로깅 및 정확한 Mapper 정보 추출을 위한 인터셉터 -->
</plugin>
</plugins>
</configuration>
Loading…
Cancel
Save