Selaa lähdekoodia

add system health check

shjung 1 viikko sitten
vanhempi
commit
6110eeb08c

+ 17 - 15
.idea/workspace.xml

@@ -5,18 +5,19 @@
   </component>
   <component name="ChangeListManager">
     <list default="true" id="137ea174-7fc0-49e0-a3b0-3deac854d820" name="Changes" comment="">
+      <change afterPath="$PROJECT_DIR$/utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server/health/SystemHealthService.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/utic-stat-server/src/main/java/com/utic/center/utic/stat/server/health/SystemHealthService.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/utic-traf-server/src/main/java/com/utic/center/utic/traf/server/health/SystemHealthService.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/utic-center-common/src/main/java/com/utic/center/common/utils/SystemHealth.java" beforeDir="false" afterPath="$PROJECT_DIR$/utic-center-common/src/main/java/com/utic/center/common/utils/SystemHealth.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/utic-ptis-server/Reference.TXT" beforeDir="false" afterPath="$PROJECT_DIR$/utic-ptis-server/Reference.TXT" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server/UticPtisServerApplication.java" beforeDir="false" afterPath="$PROJECT_DIR$/utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server/UticPtisServerApplication.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server/config/DatabaseConfig.java" beforeDir="false" afterPath="$PROJECT_DIR$/utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server/config/DatabaseConfig.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server/scheduler/ApplicationScheduler.java" beforeDir="false" afterPath="$PROJECT_DIR$/utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server/scheduler/ApplicationScheduler.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server/service/UticPtisServerService.java" beforeDir="false" afterPath="$PROJECT_DIR$/utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server/service/UticPtisServerService.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/utic-ptis-server/src/main/resources/mybatis/mapper/UticPtisServerMapper.xml" beforeDir="false" afterPath="$PROJECT_DIR$/utic-ptis-server/src/main/resources/mybatis/mapper/UticPtisServerMapper.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/utic-stat-server/src/main/java/com/utic/center/utic/stat/server/config/DatabaseConfig.java" beforeDir="false" afterPath="$PROJECT_DIR$/utic-stat-server/src/main/java/com/utic/center/utic/stat/server/config/DatabaseConfig.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/utic-ptis-server/src/main/resources/application.yml" beforeDir="false" afterPath="$PROJECT_DIR$/utic-ptis-server/src/main/resources/application.yml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/utic-ptis-server/src/main/resources/logback-spring.xml" beforeDir="false" afterPath="$PROJECT_DIR$/utic-ptis-server/src/main/resources/logback-spring.xml" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/utic-stat-server/src/main/java/com/utic/center/utic/stat/server/scheduler/ApplicationScheduler.java" beforeDir="false" afterPath="$PROJECT_DIR$/utic-stat-server/src/main/java/com/utic/center/utic/stat/server/scheduler/ApplicationScheduler.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/utic-traf-server/src/main/java/com/utic/center/utic/traf/server/config/DatabaseUticConfig.java" beforeDir="false" afterPath="$PROJECT_DIR$/utic-traf-server/src/main/java/com/utic/center/utic/traf/server/config/DatabaseConfig.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/utic-stat-server/src/main/resources/application.yml" beforeDir="false" afterPath="$PROJECT_DIR$/utic-stat-server/src/main/resources/application.yml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/utic-stat-server/src/main/resources/logback-spring.xml" beforeDir="false" afterPath="$PROJECT_DIR$/utic-stat-server/src/main/resources/logback-spring.xml" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/utic-traf-server/src/main/java/com/utic/center/utic/traf/server/scheduler/ApplicationScheduler.java" beforeDir="false" afterPath="$PROJECT_DIR$/utic-traf-server/src/main/java/com/utic/center/utic/traf/server/scheduler/ApplicationScheduler.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/utic-traf-server/src/main/resources/application.yml" beforeDir="false" afterPath="$PROJECT_DIR$/utic-traf-server/src/main/resources/application.yml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/utic-traf-server/src/main/resources/logback-spring.xml" beforeDir="false" afterPath="$PROJECT_DIR$/utic-traf-server/src/main/resources/logback-spring.xml" afterDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -274,7 +275,7 @@
     "ignore.virus.scanning.warn.message": "true",
     "junie.onboarding.icon.badge.shown": "true",
     "kotlin-language-version-configured": "true",
-    "last_opened_file_path": "/Users/openvalue/Projects/KoROAD/UTIC-CENTER/utic-center/utic-traf-server/src/main/java/com/utic/center/utic/traf/server/dao/repository/utic",
+    "last_opened_file_path": "/Users/openvalue/Projects/KoROAD/UTIC-CENTER/utic-center/utic-center-common/src/main/java/com/utic/center/common",
     "node.js.detected.package.eslint": "true",
     "node.js.detected.package.tslint": "true",
     "node.js.selected.package.eslint": "(autodetect)",
@@ -286,7 +287,7 @@
     "project.structure.side.proportion": "0.32302406",
     "run.configurations.included.in.services": "true",
     "settings.build.tools.auto.reload": "ALL",
-    "settings.editor.selected.configurable": "editor.preferences.fonts.default",
+    "settings.editor.selected.configurable": "editor.preferences.import",
     "show.migrate.to.gradle.popup": "false",
     "sweep.chatMode": "Chat",
     "to.speed.mode.migration.done": "true",
@@ -295,11 +296,11 @@
 }]]></component>
   <component name="RecentsManager">
     <key name="CopyFile.RECENT_KEYS">
+      <recent name="$PROJECT_DIR$/utic-center-common/src/main/java/com/utic/center/common" />
+      <recent name="$PROJECT_DIR$/utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server" />
+      <recent name="$PROJECT_DIR$/utic-stat-server/src/main/java/com/utic/center/utic/stat/server" />
+      <recent name="$PROJECT_DIR$/utic-traf-server/src/main/java/com/utic/center/utic/traf/server" />
       <recent name="$PROJECT_DIR$/utic-traf-server/src/main/java/com/utic/center/utic/traf/server/dao/repository/utic" />
-      <recent name="$PROJECT_DIR$/utic-traf-server/src/main/java/com/utic/center/utic/traf/server/dao/mapper/utic" />
-      <recent name="$PROJECT_DIR$/utic-traf-server/src/main/resources/mybatis/mapper/utic" />
-      <recent name="$PROJECT_DIR$/utic-traf-server/src/main/java/com/utic/center/utic/traf/server/service" />
-      <recent name="$PROJECT_DIR$/utic-traf-server/src/main/resources/mybatis/mapper/utic/dwdb" />
     </key>
     <key name="CreateTestDialog.Recents.Supers">
       <recent name="" />
@@ -691,7 +692,8 @@
       <workItem from="1760242883220" duration="6897000" />
       <workItem from="1760327252176" duration="2139000" />
       <workItem from="1760337299130" duration="612000" />
-      <workItem from="1760406508566" duration="39395000" />
+      <workItem from="1760406508566" duration="40011000" />
+      <workItem from="1760788030757" duration="6726000" />
     </task>
     <servers />
   </component>

+ 115 - 0
utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server/health/SystemHealthService.java

@@ -0,0 +1,115 @@
+package com.utic.center.utic.ptis.server.health;
+
+import com.utic.center.common.utils.SystemHealth;
+import com.utic.center.utic.ptis.server.config.ApplicationConfig;
+import com.utic.center.utic.ptis.server.repository.ApplicationRepository;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.HikariPoolMXBean;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.text.DecimalFormat;
+import java.util.EnumMap;
+import java.util.Map;
+
+@Slf4j
+@Getter
+@Service
+@RequiredArgsConstructor
+public class SystemHealthService {
+
+    private final ApplicationConfig config;
+    private final HikariDataSource dataSource;
+    private final Map<Thread.State, Integer> stateCountMap = new EnumMap<>(Thread.State.class);
+    private final SystemHealth systemHealth = new SystemHealth();
+    private final DecimalFormat df = new DecimalFormat("#.##");
+
+    public boolean checkSystemHealth() {
+        try {
+            this.systemHealth.checkHealth(false);
+            loggingHealthCheck();
+
+            double cpuUsage = this.systemHealth.getCpuUsage();
+            if (cpuUsage > this.config.getCpuLimits()) {
+                ApplicationRepository.processState.setErrDesc("CPU 사용율이 너무 높음: " + String.format("%.2f", cpuUsage));
+                log.warn("[SKIP] SystemHealthService.checkSystemHealth: High CPU Usage, Limit({} %), Current({} %), Schedule Job SKIP...",
+                        this.config.getCpuLimits(), String.format("%.2f", cpuUsage));
+                loggingThreads();
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("SystemHealthService.checkSystemHealth: Exception {}", e.getMessage());
+        }
+        return true;
+    }
+
+    private void loggingHealthCheck() {
+        log.info("----------------------------------------------------------------------------------------------------------");
+        loggingSystemHealth();
+        logSessionStatus();
+        loggingThreadState();
+        log.info("----------------------------------------------------------------------------------------------------------");
+    }
+
+    private void loggingSystemHealth() {
+        double loadAvg = this.systemHealth.getLoadAverage();
+        String loadAvgStr = (loadAvg < 0) ? "N/A" : this.df.format(loadAvg);
+        log.info("SYSTEM HEALTH: CPU {} Cores[{} %, LoadAvg({})], Memory[{} %, Used({} MB), Max({} MB)], Threads[{}, Peak({})], GC(T/R)[{}/{} ms]",
+                this.systemHealth.getCpuCores(),
+                this.df.format(this.systemHealth.getCpuUsage()),
+                loadAvgStr,
+                this.df.format(this.systemHealth.getMemUsage()),
+                this.systemHealth.getUsedMemory(),
+                this.systemHealth.getMaxMemory(),
+                this.systemHealth.getThreadCount(),
+                this.systemHealth.getPeakThreadCount(),
+                this.systemHealth.getGcTotalAvgTime(),
+                this.systemHealth.getGcRecentAvgTime());
+    }
+
+    private void logSessionStatus() {
+        HikariPoolMXBean poolStats = this.dataSource.getHikariPoolMXBean();
+        int totalConnections = poolStats.getTotalConnections();
+        int activeConnections = poolStats.getActiveConnections();
+        int idleConnections = poolStats.getIdleConnections();
+        int threadsAwaiting = poolStats.getThreadsAwaitingConnection();
+        log.info("   DB SESSION: Total: {}, Active: {}, Idle: {}, Waiting: {}",
+                totalConnections, activeConnections, idleConnections, threadsAwaiting);
+    }
+
+    private void loggingThreadState() {
+        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
+        this.stateCountMap.clear();
+        for (ThreadInfo info : infos) {
+            Thread.State state = info.getThreadState();
+            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
+        }
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<Thread.State, Integer> entry : stateCountMap.entrySet()) {
+            sb.append(String.format("%s(%d), ", entry.getKey().name(), entry.getValue()));
+        }
+        // 마지막 쉼표 제거
+        if (sb.length() > 0) {
+            sb.setLength(sb.length() - 2);
+        }
+        log.info(" THREAD STATE: {}", sb.toString());
+    }
+
+    private void loggingThreads() {
+        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
+        this.stateCountMap.clear();
+        for (ThreadInfo info : infos) {
+            Thread.State state = info.getThreadState();
+            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
+            log.info("         [" + info.getThreadId() + "] " + info.getThreadName() + " - " + info.getThreadState());
+        }
+    }
+
+}

+ 5 - 92
utic-ptis-server/src/main/java/com/utic/center/utic/ptis/server/scheduler/ApplicationScheduler.java

@@ -1,25 +1,16 @@
 package com.utic.center.utic.ptis.server.scheduler;
 
-import com.utic.center.common.utils.SystemHealth;
 import com.utic.center.common.utils.TimeUtils;
 import com.utic.center.utic.ptis.server.config.ApplicationConfig;
 import com.utic.center.utic.ptis.server.controller.UticPtisServerController;
-import com.utic.center.utic.ptis.server.repository.ApplicationRepository;
+import com.utic.center.utic.ptis.server.health.SystemHealthService;
 import com.utic.center.utic.ptis.server.service.ProcessStateService;
-import com.zaxxer.hikari.HikariDataSource;
-import com.zaxxer.hikari.HikariPoolMXBean;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
-import java.lang.management.ManagementFactory;
-import java.lang.management.ThreadInfo;
-import java.lang.management.ThreadMXBean;
-import java.text.DecimalFormat;
-import java.util.EnumMap;
-import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @Slf4j
@@ -31,13 +22,10 @@ public class ApplicationScheduler {
     private final ApplicationConfig config;
     private final UticPtisServerController controller;
     private final ProcessStateService processStateService;
+    private final SystemHealthService healthService;
 
     private final AtomicBoolean isScheduleRunning = new AtomicBoolean(false);
 
-    private final HikariDataSource dataSource;
-    private final Map<Thread.State, Integer> stateCountMap = new EnumMap<>(Thread.State.class);
-    private final SystemHealth systemHealth = new SystemHealth();
-    private final DecimalFormat df = new DecimalFormat("#.##");
     private String scheduleTime;
 
     @Scheduled(cron = "0 * * * * *")
@@ -46,20 +34,9 @@ public class ApplicationScheduler {
             return;
         }
 
-        try {
-            this.systemHealth.checkHealth(false);
-            loggingHealthCheck();
-
-            double cpuUsage = this.systemHealth.getCpuUsage();
-            if (cpuUsage > this.config.getCpuLimits()) {
-                ApplicationRepository.processState.setErrDesc("CPU 사용율이 너무 높음: " + String.format("%.2f", cpuUsage));
-                log.warn("[SKIP] ApplicationScheduler.scheduleProcess: High CPU Usage, Limit({} %), Current({} %), Schedule Job SKIP...",
-                        this.config.getCpuLimits(), String.format("%.2f", cpuUsage));
-                loggingThreads();
-                return;
-            }
-        } catch (Exception e) {
-            log.error("ApplicationScheduler.scheduleProcess: System Health Check Exception {}", e.getMessage());
+        if (!this.healthService.checkSystemHealth()) {
+            // 시스템이 과부하이면 처리하지 않는다.
+            return;
         }
 
         // 이전 작업이 아직 실행 중이면 스킵(비동기 처리로 변경할 경우 대비-보혐)
@@ -89,68 +66,4 @@ public class ApplicationScheduler {
         }
     }
 
-    private void loggingHealthCheck() {
-        log.info("----------------------------------------------------------------------------------------------------------");
-        loggingSystemHealth();
-        logSessionStatus();
-        loggingThreadState();
-        log.info("----------------------------------------------------------------------------------------------------------");
-    }
-
-    private void loggingSystemHealth() {
-        double loadAvg = this.systemHealth.getLoadAverage();
-        String loadAvgStr = (loadAvg < 0) ? "N/A" : this.df.format(loadAvg);
-        log.info("SYSTEM HEALTH: CPU {} Cores[{} %, LoadAvg({})], Memory[{} %, Used({} MB), Max({} MB)], Threads[{}, Peak({})], GC(T/R)[{}/{} ms]",
-                this.systemHealth.getCpuCores(),
-                this.df.format(this.systemHealth.getCpuUsage()),
-                loadAvgStr,
-                this.df.format(this.systemHealth.getMemUsage()),
-                this.systemHealth.getUsedMemory(),
-                this.systemHealth.getMaxMemory(),
-                this.systemHealth.getThreadCount(),
-                this.systemHealth.getPeakThreadCount(),
-                this.systemHealth.getGcTotalAvgTime(),
-                this.systemHealth.getGcRecentAvgTime());
-    }
-
-    private void logSessionStatus() {
-        HikariPoolMXBean poolStats = this.dataSource.getHikariPoolMXBean();
-        int totalConnections = poolStats.getTotalConnections();
-        int activeConnections = poolStats.getActiveConnections();
-        int idleConnections = poolStats.getIdleConnections();
-        int threadsAwaiting = poolStats.getThreadsAwaitingConnection();
-        log.info("   DB SESSION: Total: {}, Active: {}, Idle: {}, Waiting: {}",
-                totalConnections, activeConnections, idleConnections, threadsAwaiting);
-    }
-
-    private void loggingThreadState() {
-        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
-        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
-        this.stateCountMap.clear();
-        for (ThreadInfo info : infos) {
-            Thread.State state = info.getThreadState();
-            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
-        }
-        StringBuilder sb = new StringBuilder();
-        for (Map.Entry<Thread.State, Integer> entry : stateCountMap.entrySet()) {
-            sb.append(String.format("%s(%d), ", entry.getKey().name(), entry.getValue()));
-        }
-        // 마지막 쉼표 제거
-        if (sb.length() > 0) {
-            sb.setLength(sb.length() - 2);
-        }
-        log.info(" THREAD STATE: {}", sb.toString());
-    }
-
-    private void loggingThreads() {
-        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
-        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
-        this.stateCountMap.clear();
-        for (ThreadInfo info : infos) {
-            Thread.State state = info.getThreadState();
-            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
-            log.info("         [" + info.getThreadId() + "] " + info.getThreadName() + " - " + info.getThreadState());
-        }
-    }
-
 }

+ 5 - 0
utic-ptis-server/src/main/resources/application.yml

@@ -30,9 +30,14 @@ server:
   port: 9871
   shutdown: graceful
   tomcat:
+    connection-timeout: 5000         # 연결 대기 시간 5초
+    keep-alive-timeout: 0            # 응답 후 바로 연결 종료
+    max-keep-alive-requests: 1       # 한 연결당 1회 요청만 허용
     threads:
       max: 5        # 톰캣 최대 스레드 수 설정
       min-spare: 2  # 초기 여유 스레드 수
+    accesslog:
+      enabled: false            # 액세스 로그 활성화 여부
 
 management:
   endpoints:

+ 21 - 0
utic-ptis-server/src/main/resources/logback-spring.xml

@@ -13,12 +13,14 @@
 
     <property name="LOG_FILE_NAME"        value="${PROJECT_NAME}.log"/>
     <property name="LOG_FILE_NAME_ERROR"  value="${PROJECT_NAME}.err.log"/>
+    <property name="LOG_FILE_NAME_HEALTH" value="${PROJECT_PREFIX}-health.log"/>
     <property name="LOG_FILE_NAME_BACKUP" value="%d{yyyyMMdd}_%i.log.gz"/>
 
     <property name="MAX_FILESIZE" value="40MB"/>
     <property name="MAX_HISTORY"  value="10"/>
     <property name="LOG_PATTERN_FILE"        value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %msg%n"/>
     <property name="LOG_PATTERN_ERROR"       value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %msg%n"/>
+    <property name="LOG_PATTERN_HEALTH"      value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %msg%n"/>
     <property name="LOG_PATTERN_CONSOLE"     value="[%d{HH:mm:ss.SSS}] [%5level] %msg %n"/>
 
     <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
@@ -59,6 +61,20 @@
         </rollingPolicy>
     </appender>
 
+    <appender name="FILE_HEALTH" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_PATH}${LOG_FILE_NAME_HEALTH}</file>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <charset>${FILE_LOG_CHARSET}</charset>
+            <pattern>${LOG_PATTERN_HEALTH}</pattern>
+            <immediateFlush>true</immediateFlush>
+        </encoder>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_BACKUP_PATH}${LOG_FILE_NAME_HEALTH}.${LOG_FILE_NAME_BACKUP}</fileNamePattern>
+            <maxFileSize>${MAX_FILESIZE}</maxFileSize>
+            <maxHistory>${MAX_HISTORY}</maxHistory>
+        </rollingPolicy>
+    </appender>
+
     <springProfile name="!prod">
         <root level="INFO">
             <appender-ref ref="CONSOLE"/>
@@ -70,4 +86,9 @@
         <appender-ref ref="FILE_ERROR"/>
     </root>
 
+    <logger name="${APP_CLASS_PATH}.health" level="INFO" additivity="false">
+        <appender-ref ref="FILE_HEALTH"/>
+        <appender-ref ref="FILE_ERROR"/>
+    </logger>
+
 </configuration>

+ 114 - 0
utic-stat-server/src/main/java/com/utic/center/utic/stat/server/health/SystemHealthService.java

@@ -0,0 +1,114 @@
+package com.utic.center.utic.stat.server.health;
+
+import com.utic.center.utic.stat.server.config.ApplicationConfig;
+import com.utic.center.utic.stat.server.repository.ApplicationRepository;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.HikariPoolMXBean;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.text.DecimalFormat;
+import java.util.EnumMap;
+import java.util.Map;
+
+@Slf4j
+@Getter
+@Service
+@RequiredArgsConstructor
+public class SystemHealthService {
+
+    private final ApplicationConfig config;
+    private final HikariDataSource dataSource;
+    private final Map<Thread.State, Integer> stateCountMap = new EnumMap<>(Thread.State.class);
+    private final com.utic.center.common.utils.SystemHealth systemHealth = new com.utic.center.common.utils.SystemHealth();
+    private final DecimalFormat df = new DecimalFormat("#.##");
+
+    public boolean checkSystemHealth() {
+        try {
+            this.systemHealth.checkHealth(false);
+            loggingHealthCheck();
+
+            double cpuUsage = this.systemHealth.getCpuUsage();
+            if (cpuUsage > this.config.getCpuLimits()) {
+                ApplicationRepository.processState.setErrDesc("CPU 사용율이 너무 높음: " + String.format("%.2f", cpuUsage));
+                log.warn("[SKIP] SystemHealthService.checkSystemHealth: High CPU Usage, Limit({} %), Current({} %), Schedule Job SKIP...",
+                        this.config.getCpuLimits(), String.format("%.2f", cpuUsage));
+                loggingThreads();
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("SystemHealthService.checkSystemHealth: Exception {}", e.getMessage());
+        }
+        return true;
+    }
+
+    private void loggingHealthCheck() {
+        log.info("----------------------------------------------------------------------------------------------------------");
+        loggingSystemHealth();
+        logSessionStatus();
+        loggingThreadState();
+        log.info("----------------------------------------------------------------------------------------------------------");
+    }
+
+    private void loggingSystemHealth() {
+        double loadAvg = this.systemHealth.getLoadAverage();
+        String loadAvgStr = (loadAvg < 0) ? "N/A" : this.df.format(loadAvg);
+        log.info("SYSTEM HEALTH: CPU {} Cores[{} %, LoadAvg({})], Memory[{} %, Used({} MB), Max({} MB)], Threads[{}, Peak({})], GC(T/R)[{}/{} ms]",
+                this.systemHealth.getCpuCores(),
+                this.df.format(this.systemHealth.getCpuUsage()),
+                loadAvgStr,
+                this.df.format(this.systemHealth.getMemUsage()),
+                this.systemHealth.getUsedMemory(),
+                this.systemHealth.getMaxMemory(),
+                this.systemHealth.getThreadCount(),
+                this.systemHealth.getPeakThreadCount(),
+                this.systemHealth.getGcTotalAvgTime(),
+                this.systemHealth.getGcRecentAvgTime());
+    }
+
+    private void logSessionStatus() {
+        HikariPoolMXBean poolStats = this.dataSource.getHikariPoolMXBean();
+        int totalConnections = poolStats.getTotalConnections();
+        int activeConnections = poolStats.getActiveConnections();
+        int idleConnections = poolStats.getIdleConnections();
+        int threadsAwaiting = poolStats.getThreadsAwaitingConnection();
+        log.info("   DB SESSION: Total: {}, Active: {}, Idle: {}, Waiting: {}",
+                totalConnections, activeConnections, idleConnections, threadsAwaiting);
+    }
+
+    private void loggingThreadState() {
+        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
+        this.stateCountMap.clear();
+        for (ThreadInfo info : infos) {
+            Thread.State state = info.getThreadState();
+            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
+        }
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<Thread.State, Integer> entry : stateCountMap.entrySet()) {
+            sb.append(String.format("%s(%d), ", entry.getKey().name(), entry.getValue()));
+        }
+        // 마지막 쉼표 제거
+        if (sb.length() > 0) {
+            sb.setLength(sb.length() - 2);
+        }
+        log.info(" THREAD STATE: {}", sb.toString());
+    }
+
+    private void loggingThreads() {
+        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
+        this.stateCountMap.clear();
+        for (ThreadInfo info : infos) {
+            Thread.State state = info.getThreadState();
+            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
+            log.info("         [" + info.getThreadId() + "] " + info.getThreadName() + " - " + info.getThreadState());
+        }
+    }
+
+}

+ 5 - 92
utic-stat-server/src/main/java/com/utic/center/utic/stat/server/scheduler/ApplicationScheduler.java

@@ -1,25 +1,16 @@
 package com.utic.center.utic.stat.server.scheduler;
 
-import com.utic.center.common.utils.SystemHealth;
 import com.utic.center.common.utils.TimeUtils;
 import com.utic.center.utic.stat.server.config.ApplicationConfig;
 import com.utic.center.utic.stat.server.controller.UticStatServerController;
-import com.utic.center.utic.stat.server.repository.ApplicationRepository;
+import com.utic.center.utic.stat.server.health.SystemHealthService;
 import com.utic.center.utic.stat.server.service.ProcessStateService;
-import com.zaxxer.hikari.HikariDataSource;
-import com.zaxxer.hikari.HikariPoolMXBean;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
-import java.lang.management.ManagementFactory;
-import java.lang.management.ThreadInfo;
-import java.lang.management.ThreadMXBean;
-import java.text.DecimalFormat;
-import java.util.EnumMap;
-import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @Slf4j
@@ -31,13 +22,10 @@ public class ApplicationScheduler {
     private final ApplicationConfig config;
     private final ProcessStateService processStateService;
     private final UticStatServerController controller;
+    private final SystemHealthService healthService;
 
     private final AtomicBoolean isScheduleRunning = new AtomicBoolean(false);
 
-    private final HikariDataSource dataSource;
-    private final Map<Thread.State, Integer> stateCountMap = new EnumMap<>(Thread.State.class);
-    private final SystemHealth systemHealth = new SystemHealth();
-    private final DecimalFormat df = new DecimalFormat("#.##");
     private String scheduleTime;
 
     @Scheduled(cron = "0 * * * * *")
@@ -46,20 +34,9 @@ public class ApplicationScheduler {
             return;
         }
 
-        try {
-            this.systemHealth.checkHealth(false);
-            loggingHealthCheck();
-
-            double cpuUsage = this.systemHealth.getCpuUsage();
-            if (cpuUsage > this.config.getCpuLimits()) {
-                ApplicationRepository.processState.setErrDesc("CPU 사용율이 너무 높음: " + String.format("%.2f", cpuUsage));
-                log.warn("[SKIP] ApplicationScheduler.scheduleProcess: High CPU Usage, Limit({} %), Current({} %), Schedule Job SKIP...",
-                        this.config.getCpuLimits(), String.format("%.2f", cpuUsage));
-                loggingThreads();
-                return;
-            }
-        } catch (Exception e) {
-            log.error("ApplicationScheduler.scheduleProcess: System Health Check Exception {}", e.getMessage());
+        if (!this.healthService.checkSystemHealth()) {
+            // 시스템이 과부하이면 처리하지 않는다.
+            return;
         }
 
         // 이전 작업이 아직 실행 중이면 스킵(비동기 처리로 변경할 경우 대비-보혐)
@@ -81,68 +58,4 @@ public class ApplicationScheduler {
         }
     }
 
-    private void loggingHealthCheck() {
-        log.info("----------------------------------------------------------------------------------------------------------");
-        loggingSystemHealth();
-        logSessionStatus();
-        loggingThreadState();
-        log.info("----------------------------------------------------------------------------------------------------------");
-    }
-
-    private void loggingSystemHealth() {
-        double loadAvg = this.systemHealth.getLoadAverage();
-        String loadAvgStr = (loadAvg < 0) ? "N/A" : this.df.format(loadAvg);
-        log.info("SYSTEM HEALTH: CPU {} Cores[{} %, LoadAvg({})], Memory[{} %, Used({} MB), Max({} MB)], Threads[{}, Peak({})], GC(T/R)[{}/{} ms]",
-                this.systemHealth.getCpuCores(),
-                this.df.format(this.systemHealth.getCpuUsage()),
-                loadAvgStr,
-                this.df.format(this.systemHealth.getMemUsage()),
-                this.systemHealth.getUsedMemory(),
-                this.systemHealth.getMaxMemory(),
-                this.systemHealth.getThreadCount(),
-                this.systemHealth.getPeakThreadCount(),
-                this.systemHealth.getGcTotalAvgTime(),
-                this.systemHealth.getGcRecentAvgTime());
-    }
-
-    private void logSessionStatus() {
-        HikariPoolMXBean poolStats = this.dataSource.getHikariPoolMXBean();
-        int totalConnections = poolStats.getTotalConnections();
-        int activeConnections = poolStats.getActiveConnections();
-        int idleConnections = poolStats.getIdleConnections();
-        int threadsAwaiting = poolStats.getThreadsAwaitingConnection();
-        log.info("   DB SESSION: Total: {}, Active: {}, Idle: {}, Waiting: {}",
-                totalConnections, activeConnections, idleConnections, threadsAwaiting);
-    }
-
-    private void loggingThreadState() {
-        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
-        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
-        this.stateCountMap.clear();
-        for (ThreadInfo info : infos) {
-            Thread.State state = info.getThreadState();
-            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
-        }
-        StringBuilder sb = new StringBuilder();
-        for (Map.Entry<Thread.State, Integer> entry : stateCountMap.entrySet()) {
-            sb.append(String.format("%s(%d), ", entry.getKey().name(), entry.getValue()));
-        }
-        // 마지막 쉼표 제거
-        if (sb.length() > 0) {
-            sb.setLength(sb.length() - 2);
-        }
-        log.info(" THREAD STATE: {}", sb.toString());
-    }
-
-    private void loggingThreads() {
-        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
-        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
-        this.stateCountMap.clear();
-        for (ThreadInfo info : infos) {
-            Thread.State state = info.getThreadState();
-            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
-            log.info("         [" + info.getThreadId() + "] " + info.getThreadName() + " - " + info.getThreadState());
-        }
-    }
-
 }

+ 5 - 4
utic-stat-server/src/main/resources/application.yml

@@ -33,13 +33,14 @@ server:
 #  compression:
 #    enabled: false  # 응답 압축 여부
   tomcat:
+    connection-timeout: 5000         # 연결 대기 시간 5초
+    keep-alive-timeout: 0            # 응답 후 바로 연결 종료
+    max-keep-alive-requests: 1       # 한 연결당 1회 요청만 허용
     threads:
       max: 5        # 톰캣 최대 스레드 수 설정
       min-spare: 2  # 초기 여유 스레드 수
-#    keep-alive-timeout: 0 # 커넥션 타임아웃 (ms 단위)
-#    max-keep-alive-requests: 1  # Keep-Alive 유지 시간
-#    accesslog:
-#      enabled: false            # 액세스 로그 활성화 여부
+    accesslog:
+      enabled: false            # 액세스 로그 활성화 여부
 
 management:
   endpoints:

+ 21 - 0
utic-stat-server/src/main/resources/logback-spring.xml

@@ -14,10 +14,12 @@
 
     <property name="LOG_FILE_NAME"        value="${PROJECT_NAME}.log"/>
     <property name="LOG_FILE_NAME_ERROR"  value="${PROJECT_NAME}.err.log"/>
+    <property name="LOG_FILE_NAME_HEALTH" value="${PROJECT_PREFIX}-health.log"/>
     <property name="LOG_FILE_NAME_BACKUP" value="%d{yyyyMMdd}_%i.log.gz"/>
 
     <property name="LOG_PATTERN_FILE"        value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %msg%n"/>
     <property name="LOG_PATTERN_ERROR"       value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %msg%n"/>
+    <property name="LOG_PATTERN_HEALTH"      value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %msg%n"/>
     <property name="LOG_PATTERN_CONSOLE"     value="[%d{HH:mm:ss.SSS}] [%5level] %msg %n"/>
 
     <property name="MAX_FILESIZE" value="80MB"/>
@@ -64,6 +66,20 @@
         </rollingPolicy>
     </appender>
 
+    <appender name="FILE_HEALTH" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_PATH}${LOG_FILE_NAME_HEALTH}</file>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <charset>${FILE_LOG_CHARSET}</charset>
+            <pattern>${LOG_PATTERN_HEALTH}</pattern>
+            <immediateFlush>true</immediateFlush>
+        </encoder>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_BACKUP_PATH}${LOG_FILE_NAME_HEALTH}.${LOG_FILE_NAME_BACKUP}</fileNamePattern>
+            <maxFileSize>${MAX_FILESIZE}</maxFileSize>
+            <maxHistory>${MAX_HISTORY}</maxHistory>
+        </rollingPolicy>
+    </appender>
+
     <springProfile name="!prod">
         <root level="INFO">
             <appender-ref ref="CONSOLE"/>
@@ -75,4 +91,9 @@
         <appender-ref ref="FILE_ERROR"/>
     </root>
 
+    <logger name="${APP_CLASS_PATH}.health" level="INFO" additivity="false">
+        <appender-ref ref="FILE_HEALTH"/>
+        <appender-ref ref="FILE_ERROR"/>
+    </logger>
+
 </configuration>

+ 123 - 0
utic-traf-server/src/main/java/com/utic/center/utic/traf/server/health/SystemHealthService.java

@@ -0,0 +1,123 @@
+package com.utic.center.utic.traf.server.health;
+
+import com.utic.center.common.utils.SystemHealth;
+import com.utic.center.utic.traf.server.config.ApplicationConfig;
+import com.utic.center.utic.traf.server.repository.ApplicationRepository;
+import com.zaxxer.hikari.HikariDataSource;
+import com.zaxxer.hikari.HikariPoolMXBean;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+
+import javax.sql.DataSource;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.text.DecimalFormat;
+import java.util.EnumMap;
+import java.util.Map;
+
+@Slf4j
+@Getter
+@Service
+public class SystemHealthService {
+
+    private final ApplicationConfig config;
+    private final DataSource dataSource;
+    private final Map<Thread.State, Integer> stateCountMap = new EnumMap<>(Thread.State.class);
+    private final SystemHealth systemHealth = new SystemHealth();
+    private final DecimalFormat df = new DecimalFormat("#.##");
+
+    public SystemHealthService(ApplicationConfig config,
+                               @Qualifier("dataSource") DataSource dataSource
+                               ) {
+        this.config = config;
+        this.dataSource = dataSource;
+    }
+
+    public boolean checkSystemHealth() {
+        try {
+            this.systemHealth.checkHealth(false);
+            loggingHealthCheck();
+
+            double cpuUsage = this.systemHealth.getCpuUsage();
+            if (cpuUsage > this.config.getCpuLimits()) {
+                ApplicationRepository.processState.setErrDesc("CPU 사용율이 너무 높음: " + String.format("%.2f", cpuUsage));
+                log.warn("[SKIP] SystemHealthService.checkSystemHealth: High CPU Usage, Limit({} %), Current({} %), Schedule Job SKIP...",
+                        this.config.getCpuLimits(), String.format("%.2f", cpuUsage));
+                loggingThreads();
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("SystemHealthService.checkSystemHealth: Exception {}", e.getMessage());
+        }
+        return true;
+    }
+
+    private void loggingHealthCheck() {
+        log.info("----------------------------------------------------------------------------------------------------------");
+        loggingSystemHealth();
+        logSessionStatus();
+        loggingThreadState();
+        log.info("----------------------------------------------------------------------------------------------------------");
+    }
+
+    private void loggingSystemHealth() {
+        double loadAvg = this.systemHealth.getLoadAverage();
+        String loadAvgStr = (loadAvg < 0) ? "N/A" : this.df.format(loadAvg);
+        log.info("SYSTEM HEALTH: CPU {} Cores[{} %, LoadAvg({})], Memory[{} %, Used({} MB), Max({} MB)], Threads[{}, Peak({})], GC(T/R)[{}/{} ms]",
+                this.systemHealth.getCpuCores(),
+                this.df.format(this.systemHealth.getCpuUsage()),
+                loadAvgStr,
+                this.df.format(this.systemHealth.getMemUsage()),
+                this.systemHealth.getUsedMemory(),
+                this.systemHealth.getMaxMemory(),
+                this.systemHealth.getThreadCount(),
+                this.systemHealth.getPeakThreadCount(),
+                this.systemHealth.getGcTotalAvgTime(),
+                this.systemHealth.getGcRecentAvgTime());
+    }
+
+    private void logSessionStatus() {
+        HikariDataSource hikariDataSource = (HikariDataSource) this.dataSource;
+        HikariPoolMXBean poolStats = hikariDataSource.getHikariPoolMXBean();
+        int totalConnections = poolStats.getTotalConnections();
+        int activeConnections = poolStats.getActiveConnections();
+        int idleConnections = poolStats.getIdleConnections();
+        int threadsAwaiting = poolStats.getThreadsAwaitingConnection();
+        log.info("   DB SESSION: UTIS, Total: {}, Active: {}, Idle: {}, Waiting: {}",
+                totalConnections, activeConnections, idleConnections, threadsAwaiting);
+    }
+
+    private void loggingThreadState() {
+        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
+        this.stateCountMap.clear();
+        for (ThreadInfo info : infos) {
+            Thread.State state = info.getThreadState();
+            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
+        }
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<Thread.State, Integer> entry : stateCountMap.entrySet()) {
+            sb.append(String.format("%s(%d), ", entry.getKey().name(), entry.getValue()));
+        }
+        // 마지막 쉼표 제거
+        if (sb.length() > 0) {
+            sb.setLength(sb.length() - 2);
+        }
+        log.info(" THREAD STATE: {}", sb.toString());
+    }
+
+    private void loggingThreads() {
+        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
+        this.stateCountMap.clear();
+        for (ThreadInfo info : infos) {
+            Thread.State state = info.getThreadState();
+            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
+            log.info("         [" + info.getThreadId() + "] " + info.getThreadName() + " - " + info.getThreadState());
+        }
+    }
+
+}

+ 7 - 107
utic-traf-server/src/main/java/com/utic/center/utic/traf/server/scheduler/ApplicationScheduler.java

@@ -1,32 +1,23 @@
 package com.utic.center.utic.traf.server.scheduler;
 
-import com.utic.center.common.utils.SystemHealth;
 import com.utic.center.common.utils.TimeUtils;
 import com.utic.center.utic.traf.server.config.ApplicationConfig;
 import com.utic.center.utic.traf.server.config.TraceConfig;
 import com.utic.center.utic.traf.server.controller.UticTrafServerController;
-import com.utic.center.utic.traf.server.repository.ApplicationRepository;
+import com.utic.center.utic.traf.server.health.SystemHealthService;
 import com.utic.center.utic.traf.server.service.ProcessStateService;
-import com.zaxxer.hikari.HikariDataSource;
-import com.zaxxer.hikari.HikariPoolMXBean;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
-import javax.sql.DataSource;
-import java.lang.management.ManagementFactory;
-import java.lang.management.ThreadInfo;
-import java.lang.management.ThreadMXBean;
-import java.text.DecimalFormat;
 import java.time.LocalDateTime;
-import java.util.EnumMap;
-import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @Slf4j
 @EnableScheduling
+@RequiredArgsConstructor
 @Component
 public class ApplicationScheduler {
 
@@ -34,26 +25,11 @@ public class ApplicationScheduler {
     private final TraceConfig traceConfig;
     private final ProcessStateService processStateService;
     private final UticTrafServerController controller;
+    private final SystemHealthService healthService;
 
     private final AtomicBoolean isScheduleRunning = new AtomicBoolean(false);
-
-    private final DataSource dataSource;
-    private final Map<Thread.State, Integer> stateCountMap = new EnumMap<>(Thread.State.class);
-    private final SystemHealth systemHealth = new SystemHealth();
-    private final DecimalFormat df = new DecimalFormat("#.##");
     private String scheduleTime;
 
-    public ApplicationScheduler(@Qualifier("dataSource") DataSource dataSource,
-                                ApplicationConfig config,
-                                TraceConfig traceConfig,
-                                ProcessStateService processStateService,
-                                UticTrafServerController controller) {
-        this.dataSource = dataSource;
-        this.config = config;
-        this.traceConfig = traceConfig;
-        this.processStateService = processStateService;
-        this.controller = controller;
-    }
 
     @Scheduled(cron = "0 * * * * *")
     public void scheduleProcess() {
@@ -61,20 +37,9 @@ public class ApplicationScheduler {
             return;
         }
 
-        try {
-            this.systemHealth.checkHealth(false);
-            loggingHealthCheck();
-
-            double cpuUsage = this.systemHealth.getCpuUsage();
-            if (cpuUsage > this.config.getCpuLimits()) {
-                ApplicationRepository.processState.setErrDesc("CPU 사용율이 너무 높음: " + String.format("%.2f", cpuUsage));
-                log.warn("[SKIP] ApplicationScheduler.scheduleProcess: High CPU Usage, Limit({} %), Current({} %), Schedule Job SKIP...",
-                        this.config.getCpuLimits(), String.format("%.2f", cpuUsage));
-                loggingThreads();
-                return;
-            }
-        } catch (Exception e) {
-            log.error("ApplicationScheduler.scheduleProcess: System Health Check Exception {}", e.getMessage());
+        if (!this.healthService.checkSystemHealth()) {
+            // 시스템이 과부하이면 처리하지 않는다.
+            return;
         }
 
         // 이전 작업이 아직 실행 중이면 스킵(비동기 처리로 변경할 경우 대비-보혐)
@@ -106,69 +71,4 @@ public class ApplicationScheduler {
         }
     }
 
-    private void loggingHealthCheck() {
-        log.info("----------------------------------------------------------------------------------------------------------");
-        loggingSystemHealth();
-        logSessionStatus();
-        loggingThreadState();
-        log.info("----------------------------------------------------------------------------------------------------------");
-    }
-
-    private void loggingSystemHealth() {
-        double loadAvg = this.systemHealth.getLoadAverage();
-        String loadAvgStr = (loadAvg < 0) ? "N/A" : this.df.format(loadAvg);
-        log.info("SYSTEM HEALTH: CPU {} Cores[{} %, LoadAvg({})], Memory[{} %, Used({} MB), Max({} MB)], Threads[{}, Peak({})], GC(T/R)[{}/{} ms]",
-                this.systemHealth.getCpuCores(),
-                this.df.format(this.systemHealth.getCpuUsage()),
-                loadAvgStr,
-                this.df.format(this.systemHealth.getMemUsage()),
-                this.systemHealth.getUsedMemory(),
-                this.systemHealth.getMaxMemory(),
-                this.systemHealth.getThreadCount(),
-                this.systemHealth.getPeakThreadCount(),
-                this.systemHealth.getGcTotalAvgTime(),
-                this.systemHealth.getGcRecentAvgTime());
-    }
-
-    private void logSessionStatus() {
-        HikariDataSource hikariDataSource = (HikariDataSource) this.dataSource;
-        HikariPoolMXBean poolStats = hikariDataSource.getHikariPoolMXBean();
-        int totalConnections = poolStats.getTotalConnections();
-        int activeConnections = poolStats.getActiveConnections();
-        int idleConnections = poolStats.getIdleConnections();
-        int threadsAwaiting = poolStats.getThreadsAwaitingConnection();
-        log.info("   DB SESSION: UTIS, Total: {}, Active: {}, Idle: {}, Waiting: {}",
-                totalConnections, activeConnections, idleConnections, threadsAwaiting);
-    }
-
-    private void loggingThreadState() {
-        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
-        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
-        this.stateCountMap.clear();
-        for (ThreadInfo info : infos) {
-            Thread.State state = info.getThreadState();
-            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
-        }
-        StringBuilder sb = new StringBuilder();
-        for (Map.Entry<Thread.State, Integer> entry : stateCountMap.entrySet()) {
-            sb.append(String.format("%s(%d), ", entry.getKey().name(), entry.getValue()));
-        }
-        // 마지막 쉼표 제거
-        if (sb.length() > 0) {
-            sb.setLength(sb.length() - 2);
-        }
-        log.info(" THREAD STATE: {}", sb.toString());
-    }
-
-    private void loggingThreads() {
-        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
-        ThreadInfo[] infos = threadBean.dumpAllThreads(false, false);
-        this.stateCountMap.clear();
-        for (ThreadInfo info : infos) {
-            Thread.State state = info.getThreadState();
-            this.stateCountMap.put(state, this.stateCountMap.getOrDefault(state, 0) + 1);
-            log.info("         [" + info.getThreadId() + "] " + info.getThreadName() + " - " + info.getThreadState());
-        }
-    }
-
 }

+ 5 - 0
utic-traf-server/src/main/resources/application.yml

@@ -51,9 +51,14 @@ server:
   port: 9871
   shutdown: graceful
   tomcat:
+    connection-timeout: 5000         # 연결 대기 시간 5초
+    keep-alive-timeout: 0            # 응답 후 바로 연결 종료
+    max-keep-alive-requests: 1       # 한 연결당 1회 요청만 허용
     threads:
       max: 5        # 톰캣 최대 스레드 수 설정
       min-spare: 2  # 초기 여유 스레드 수
+    accesslog:
+      enabled: false            # 액세스 로그 활성화 여부
 
 management:
   endpoints:

+ 21 - 0
utic-traf-server/src/main/resources/logback-spring.xml

@@ -14,10 +14,12 @@
 
     <property name="LOG_FILE_NAME"        value="${PROJECT_NAME}.log"/>
     <property name="LOG_FILE_NAME_ERROR"  value="${PROJECT_NAME}.err.log"/>
+    <property name="LOG_FILE_NAME_HEALTH" value="${PROJECT_PREFIX}-health.log"/>
     <property name="LOG_FILE_NAME_BACKUP" value="%d{yyyyMMdd}_%i.log.gz"/>
 
     <property name="LOG_PATTERN_FILE"        value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %msg%n\n"/>
     <property name="LOG_PATTERN_ERROR"       value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %msg%n\n"/>
+    <property name="LOG_PATTERN_HEALTH"      value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %msg%n"/>
     <property name="LOG_PATTERN_CONSOLE"     value="[%d{HH:mm:ss.SSS}] [%5level] %msg %n"/>
 
     <property name="MAX_FILESIZE" value="80MB"/>
@@ -64,6 +66,20 @@
         </rollingPolicy>
     </appender>
 
+    <appender name="FILE_HEALTH" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_PATH}${LOG_FILE_NAME_HEALTH}</file>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <charset>${FILE_LOG_CHARSET}</charset>
+            <pattern>${LOG_PATTERN_HEALTH}</pattern>
+            <immediateFlush>true</immediateFlush>
+        </encoder>
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_BACKUP_PATH}${LOG_FILE_NAME_HEALTH}.${LOG_FILE_NAME_BACKUP}</fileNamePattern>
+            <maxFileSize>${MAX_FILESIZE}</maxFileSize>
+            <maxHistory>${MAX_HISTORY}</maxHistory>
+        </rollingPolicy>
+    </appender>
+
     <springProfile name="!prod">
         <root level="INFO">
             <appender-ref ref="CONSOLE"/>
@@ -75,4 +91,9 @@
         <appender-ref ref="FILE_ERROR"/>
     </root>
 
+    <logger name="${APP_CLASS_PATH}.health" level="INFO" additivity="false">
+        <appender-ref ref="FILE_HEALTH"/>
+        <appender-ref ref="FILE_ERROR"/>
+    </logger>
+
 </configuration>