ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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: trace

     

     

    3. 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
Designed by Tistory.