-
P6Spy 적용하기이커머스 devops/국비교육 2025. 11. 22. 15:19
1. build.gradle 추가
dependencies { ... implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' ... }2. application.yml 추가
# Hibernate SQL 로깅 logging: level: # P6spy 로깅 레벨 설정 p6spy: debug org.hibernate.SQL: debug org.hibernate.type.descriptor.sql.BasicBinder: trace3. P6spyConfig 생성
@Configuration public class P6spyConfig { @PostConstruct public void setLogMessageFormat() { P6SpyOptions.getActiveInstance().setLogMessageFormat(P6spyPrettySqlFormatter.class.getName()); } }public class P6spyPrettySqlFormatter implements MessageFormattingStrategy { @Override public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) { sql = formatSql(category, sql); Date currentDate = new Date(); SimpleDateFormat format = new SimpleDateFormat("yy.MM.dd HH:mm:ss"); return format.format(currentDate) + " | OperationTime : " + elapsed + "ms | " + sql; } private String formatSql(String category, String sql) { if (sql == null || sql.trim().isEmpty()) { return sql; } // DDL은 format 적용 안함 if (category.contains("statement") && sql.trim().toLowerCase(Locale.ROOT).startsWith("create")) { return sql; } // Hibernate SQL 포맷 적용 if (category.equals("statement")) { String trimmedSQL = sql.trim().toLowerCase(Locale.ROOT); if (trimmedSQL.startsWith("select") || trimmedSQL.startsWith("insert") || trimmedSQL.startsWith("update") || trimmedSQL.startsWith("delete")) { sql = FormatStyle.BASIC.getFormatter().format(sql); return "\nHeFormatSql(P6Spy sql,Hibernate format):\n" + sql; } } return "\nP6Spy sql:\n" + sql; } }- 적용 전

- 적용 후

4. 테이블형식으로 포멧팅
public class P6spyPrettySqlFormatter implements MessageFormattingStrategy { private static final String NEW_LINE = System.lineSeparator(); private static final String SEPARATOR = "=".repeat(120); private static final String SUB_SEPARATOR = "-".repeat(120); private static final String TAB = " "; @Override public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) { sql = formatSql(category, sql); Date currentDate = new Date(); SimpleDateFormat format = new SimpleDateFormat("yy.MM.dd HH:mm:ss"); // 표 형식 포맷팅 StringBuilder sb = new StringBuilder(); sb.append(NEW_LINE); sb.append(SEPARATOR).append(NEW_LINE); sb.append("SQL EXECUTION REPORT").append(NEW_LINE); sb.append(SEPARATOR).append(NEW_LINE); sb.append("Timestamp : ").append(format.format(currentDate)).append(NEW_LINE); sb.append("Execution Time: ").append(formatElapsedTime(elapsed)).append(NEW_LINE); sb.append("Connection ID : ").append(connectionId).append(NEW_LINE); sb.append("Category : ").append(category).append(NEW_LINE); sb.append(SUB_SEPARATOR).append(NEW_LINE); sb.append(sql).append(NEW_LINE); sb.append(SEPARATOR).append(NEW_LINE); return sb.toString(); } private String formatElapsedTime(long elapsed) { if (elapsed > 1000) { return String.format("%d ms [SLOW QUERY]", elapsed); } else if (elapsed > 500) { return String.format("%d ms [WARNING]", elapsed); } else if (elapsed > 100) { return String.format("%d ms [CAUTION]", elapsed); } else { return String.format("%d ms [OK]", elapsed); } } private String formatSql(String category, String sql) { if (sql == null || sql.trim().isEmpty()) { return sql; } // DDL은 format 적용 안함 if (category.contains("statement") && sql.trim().toLowerCase(Locale.ROOT).startsWith("create")) { return "DDL Statement:" + NEW_LINE + TAB + sql; } // Hibernate SQL 포맷 적용 if (category.equals("statement")) { String trimmedSQL = sql.trim().toLowerCase(Locale.ROOT); if (trimmedSQL.startsWith("select") || trimmedSQL.startsWith("insert") || trimmedSQL.startsWith("update") || trimmedSQL.startsWith("delete")) { sql = FormatStyle.BASIC.getFormatter().format(sql); return "HeFormatSql(P6Spy sql, Hibernate format):" + NEW_LINE + addIndentation(sql); } } return "P6Spy sql:" + NEW_LINE + TAB + sql; } private String addIndentation(String sql) { return sql.lines() .map(line -> TAB + line) .reduce((a, b) -> a + NEW_LINE + b) .orElse(sql); } }
실무에서 처럼 깔끔한 표 형식을 원해서 더 수정했다
실무에서 봤던 것 처럼 (실행 SQL + 파라미터) + (결괏값) 이렇게 포맷팅 하고 싶었는데 P6spy는 쿼리 결과를 볼 수 없다고 한다
p6spy는 JDBC Statement/PreparedStatment의 실행 정보를 가로채는 것이지 ResultSet 내부를 끝까지 읽어 출력하는 기능이 없다고 하는데...
실무에서는 마이바티스를 사용하고 다른 라이브러리를 사용해서 가능했나 보다...
cf. 필요없는 로그 깔끔하게 정리하기
# src/main/resources/application-dev.yml logging: level: p6spy: debug com.p6spy: debug org.hibernate.SQL: info org.hibernate.type.descriptor.sql.BasicBinder: warn# src/main/resources/spy.properties appender=com.p6spy.engine.spy.appender.Slf4JLogger logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat dateformat=yyyy-MM-dd HH:mm:ss excludecategories=info,debug,result,resultset,batch,commit,rollback filter=true exclude=performance_schema,information_schema,flyway_schema_history,GET_LOCK,RELEASE_LOCK executionThreshold=10나에게 필요없는 로그들이 어플리케이션 실행할 때 너무 많이 떴다
spy.properties를 생성하고 yml 파일들을 조금씩 수정했다

# src/main/resources/application.yml spring: profiles: active: dev # 기본은 dev로 띄우기 flyway: enabled: true locations: classpath:db/migration jpa: show-sql: false hibernate: ddl-auto: none properties: hibernate: default_batch_fetch_size: 20 dialect: org.hibernate.dialect.MySQL8Dialect format_sql: false springdoc: swagger-ui: enabled: true logging: level: org.hibernate.SQL: debug org.hibernate.type.descriptor.sql.BasicBinder: trace server: port: 8080# src/main/resources/application-dev.yml spring: datasource: driver-class-name: com.p6spy.engine.spy.P6SpyDriver url: jdbc:p6spy:mysql://localhost:3306/spring_db?useSSL=false&allowPublicKeyRetrieval=true username: root password: root hikari: maximum-pool-size: 10 connection-timeout: 30000 max-lifetime: 1800000 logging: level: p6spy: debug com.p6spy: debug org.hibernate.SQL: debug org.hibernate.type.descriptor.sql.BasicBinder: trace
어차피 혼자 하는 토이프로젝트라 상관은 없지만 그냥 개발에서만 사용되도록 설정해봤다
(참고)
https://notavoid.tistory.com/124
728x90'이커머스 devops > 국비교육' 카테고리의 다른 글
스프링시큐리티 적용 로그인 구현 및 비회원 주문 로직 추가 (0) 2025.12.14 Redis - 카테고리 조회 적용 (1) 2025.12.06 Resolved [org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: (0) 2025.11.22 실무 활용 - 스프링 데이터 JPA와 Querydsl (0) 2025.11.19 동적 쿼리와 성능 최적화 조회 (0) 2025.11.19