diff --git a/modules/infrastructure/log-writer/build.gradle b/modules/infrastructure/log-writer/build.gradle index 2eb5924..026945d 100644 --- a/modules/infrastructure/log-writer/build.gradle +++ b/modules/infrastructure/log-writer/build.gradle @@ -14,7 +14,6 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2' runtimeOnly 'com.mysql:mysql-connector-j:8.3.0' - compileOnly 'jakarta.annotation:jakarta.annotation-api:2.1.1' testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' diff --git a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/SystemValidator.java b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/SystemValidator.java index 5b89311..6107f13 100644 --- a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/SystemValidator.java +++ b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/SystemValidator.java @@ -53,7 +53,7 @@ private void checkRegularly(){ } private void checkCommandInstalled(String command) { - List results = new TransientProcess("which " + command).results(); + List results = new TransientProcess("which " + command).resultList(); if (results.isEmpty() || !results.get(0).contains("/")) { throw new IllegalStateException(command + "가 설치되지 않았습니다."); } @@ -78,7 +78,7 @@ private void checkNetworkInterfaces(List system, List getSystemNetworkInterfaces() { List interfaces = new ArrayList<>(); - List iwconfigOutput = new TransientProcess("iwconfig").results(); + List iwconfigOutput = new TransientProcess("iwconfig").resultList(); String currentName = null; String currentEssid = null; diff --git a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/common/process/ContinuousProcess.java b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/common/process/ContinuousProcess.java index 2b7e534..e7a371d 100644 --- a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/common/process/ContinuousProcess.java +++ b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/common/process/ContinuousProcess.java @@ -13,22 +13,23 @@ public class ContinuousProcess { protected Process process; protected NonBlockingBufferedReader br; + //기본 생성자를 호출했을 땐 ebr이 null일 수 있다. + @Nullable + protected NonBlockingBufferedReader ebr = null; + //sub class에서 자신만의 Process를 정의하고 싶을 때 사용 + //super()는 생성자의 첫 번째 줄에 있어야 하기 때문에 만든 것임 public ContinuousProcess() {} - public ContinuousProcess(Process process) { - this.process = process; - this.br = new NonBlockingBufferedReader(new BufferedReader(new InputStreamReader(process.getInputStream()))); - } public ContinuousProcess(String command) { this(command, null); } public ContinuousProcess(String command, @Nullable String sudoPassword) { try { this.process = new ProcessBuilder(command.split(" ")) - .redirectErrorStream(true) .start(); this.br = new NonBlockingBufferedReader(new BufferedReader(new InputStreamReader(this.process.getInputStream()))); + this.ebr = new NonBlockingBufferedReader(new BufferedReader(new InputStreamReader(this.process.getErrorStream()))); if (sudoPassword==null) return; Writer writer = new OutputStreamWriter(this.process.getOutputStream()); writer.write(sudoPassword + System.lineSeparator()); @@ -37,7 +38,6 @@ public ContinuousProcess(String command, @Nullable String sudoPassword) { throw new RuntimeException(command + " 실행 실패"); } } - /** * @return 프로세스의 출력에서 한 줄을 읽어들인다. * 읽을 줄이 없을경우 null을 출력한다. @@ -49,6 +49,30 @@ public String readLine(){ throw new RuntimeException(e); } } + + /** + * @return 프로세스의 에러 출력에서 한 줄을 읽어들인다. + * 읽을 줄이 없을경우 null을 출력한다. + */ + public String readErrorLine(){ + if (this.ebr == null) + throw new RuntimeException("error stream을 초기화하지 않았습니다!"); + try { + return this.ebr.readLine(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String readErrorLines(){ + StringBuilder sb = new StringBuilder(); + String line; + while((line = readErrorLine()) != null){ + sb.append(line).append("\n"); + } + return sb.toString(); + } + public boolean isAlive(){ return this.process.isAlive(); } diff --git a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/common/process/TransientProcess.java b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/common/process/TransientProcess.java index 2105268..391c622 100644 --- a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/common/process/TransientProcess.java +++ b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/common/process/TransientProcess.java @@ -11,16 +11,15 @@ //실행 후 종료되어 모든 출력을 얻는 프로세스 //출력 스트림을 통한 프로세스와의 상호작용은 없다. + public class TransientProcess { + protected BufferedReader br; + protected BufferedReader ebr; protected Process process; public TransientProcess() {} - public TransientProcess(Process process){ - this.process = process; - this.br = new BufferedReader(new InputStreamReader(process.getInputStream())); - } public TransientProcess(String command){ this(command, null); } @@ -30,6 +29,7 @@ public TransientProcess(String command, @Nullable String sudoPassword) { .redirectErrorStream(true) .start(); this.br = new BufferedReader(new InputStreamReader(process.getInputStream())); + this.ebr = new BufferedReader(new InputStreamReader(process.getErrorStream())); if (sudoPassword==null) return; Writer writer = new OutputStreamWriter(this.process.getOutputStream()); writer.write(sudoPassword + System.lineSeparator()); @@ -39,12 +39,28 @@ public TransientProcess(String command, @Nullable String sudoPassword) { } } + public String resultString(){ + return resultString(this.br); + } + + public List resultList(){ + return resultList(this.br); + } + + public String errorResultString(){ + return resultString(this.ebr); + } + + public List errorResultList(){ + return resultList(this.ebr); + } + //종료되었을 때 출력을 얻는다. //종료되지 않았다면 블로킹된다. //출력이 없는 프로세스의 경우 빈 리스트를 출력한다. - public List results(){ + private List resultList(BufferedReader br){ + waitTermination(); List logs = new ArrayList<>(); - try { String line; while((line = br.readLine()) != null){ @@ -53,7 +69,31 @@ public List results(){ } catch (IOException e) { throw new RuntimeException(e); } - return logs; } + + //결과를 하나의 String으로 반환 + private String resultString(BufferedReader br){ + waitTermination(); + StringBuilder sb = new StringBuilder(); + try { + int size=1024; + char[] buff = new char[size]; + int read; + while((read = br.read(buff, 0, size)) != -1){ + sb.append(buff, 0, read); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return sb.toString(); + } + + public void waitTermination(){ + try { + process.waitFor(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } } diff --git a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/arp/ArpLogProcess.java b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/arp/ArpLogProcess.java index fddf3e6..9437879 100644 --- a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/arp/ArpLogProcess.java +++ b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/arp/ArpLogProcess.java @@ -2,29 +2,10 @@ import com.whoz_in.log_writer.common.process.TransientProcess; import com.whoz_in.log_writer.managed.ManagedInfo; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; public class ArpLogProcess extends TransientProcess { public ArpLogProcess(ManagedInfo info, String password) { - try { - //TODO: error 처리 로직 수정 - super.process = new ProcessBuilder(info.command().split(" ")) - //arp-scan 실행할 때마다 아래와 같은 오류가 떠서 일단 Error Stream 출력 안하도록 했음 - // WARNING: Cannot open MAC/Vendor file ieee-oui.txt: Permission denied -// .redirectError(Redirect.INHERIT) - .start(); - super.br = new BufferedReader(new InputStreamReader(process.getInputStream())); - BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(process.getOutputStream())); - bw.write(password); - bw.newLine(); - bw.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } + super(info.command(), password); } } diff --git a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/arp/ArpLogWriter.java b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/arp/ArpLogWriter.java index a7f95f7..86d1c9a 100644 --- a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/arp/ArpLogWriter.java +++ b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/arp/ArpLogWriter.java @@ -14,6 +14,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +//TODO: 에러 로그 어떻게 관리할지 생각. 일단 TransientProcess라서 구현 안함 @Slf4j @Component public class ArpLogWriter { @@ -38,7 +39,7 @@ private void scan() { List logs= arpList.stream() .flatMap(arpInfo-> { ArpLogProcess proc = new ArpLogProcess(arpInfo, sudoPassword); //프로세스 실행 - List lines = proc.results(); //프로세스의 모든 출력 가져오기 + List lines = proc.resultList(); //프로세스의 모든 출력 가져오기 Set procLogs = lines.stream() //출력 라인들을 ManagedLog 변환하며 ssid도 넣어줌 .filter(parser::validate) .map(line->{ @@ -54,6 +55,7 @@ private void scan() { 따라서 Arp-scan의 경우 무조건 1개 이상의 결과가 나오므로 0개라면 실행 실패라고 판단한다. */ if (procLogs.isEmpty()) { + //SystemValidator가 시스템의 네트워크 인터페이스가 올바른지 검증하기 때문에 여기서는 warn으로 로깅 log.warn("[managed - arp({})] 실행 실패 : ERROR", arpInfo.ssid()); return Stream.empty(); } diff --git a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/mdns/MdnsLogProcess.java b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/mdns/MdnsLogProcess.java index d3ade93..74dfbd6 100644 --- a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/mdns/MdnsLogProcess.java +++ b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/mdns/MdnsLogProcess.java @@ -1,29 +1,11 @@ package com.whoz_in.log_writer.managed.mdns; import com.whoz_in.log_writer.common.process.ContinuousProcess; -import com.whoz_in.log_writer.common.util.NonBlockingBufferedReader; import com.whoz_in.log_writer.managed.ManagedInfo; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.lang.ProcessBuilder.Redirect; public class MdnsLogProcess extends ContinuousProcess { public MdnsLogProcess(ManagedInfo info, String sudoPassword) { - try { - super.process = new ProcessBuilder(info.command().split(" ")) - .redirectError(Redirect.INHERIT) - .start(); - super.br = new NonBlockingBufferedReader(new BufferedReader(new InputStreamReader(this.process.getInputStream()))); - Writer writer = new OutputStreamWriter(this.process.getOutputStream()); - writer.write(sudoPassword + System.lineSeparator()); - writer.flush(); - } catch (IOException e) { - - throw new RuntimeException(info.command() + " 실행 실패"); - } + super(info.command(), sudoPassword); } } diff --git a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/mdns/MdnsLogWriter.java b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/mdns/MdnsLogWriter.java index 0def7ec..d7d4296 100644 --- a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/mdns/MdnsLogWriter.java +++ b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/managed/mdns/MdnsLogWriter.java @@ -7,16 +7,17 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +//TODO: tshark가 종료되지 않더라도 오류가 발생할 수 있으려나? 이 경우에도 로깅이 필요하긴 함 @Slf4j @Component public class MdnsLogWriter { private final Map processes; + private final Map wasDead; private final MdnsLogParser parser; private final ManagedLogDAO dao; private final String sudoPassword; @@ -25,11 +26,13 @@ public MdnsLogWriter(ManagedLogDAO dao, MdnsLogParser parser, NetworkConfig conf this.dao = dao; this.parser = parser; this.sudoPassword = sudoPassword; - this.processes = config.getMdnsList().parallelStream() - .collect(Collectors.toMap( - managedInfo -> managedInfo, - managedInfo -> new MdnsLogProcess(managedInfo, sudoPassword) - )); + this.processes = new HashMap<>(); + this.wasDead = new HashMap<>(); + config.getMdnsList().parallelStream() + .forEach(managedInfo -> { + this.processes.put(managedInfo, new MdnsLogProcess(managedInfo, sudoPassword)); + this.wasDead.put(managedInfo, false); + }); } @Scheduled(initialDelay = 10000, fixedDelay = 10000) @@ -39,7 +42,10 @@ private void writeLogs() { ManagedInfo managedInfo = entry.getKey(); MdnsLogProcess process = entry.getValue(); boolean alive = process.isAlive(); - if (!alive) log.error("[managed - mdns({})] dead", managedInfo.ssid()); + if (!alive && wasDead.get(managedInfo).equals(Boolean.FALSE)) { + wasDead.put(managedInfo, true); + log.error("[managed - mdns({})] dead :\n{}", managedInfo.ssid(), process.readErrorLines()); + } return alive;}) .flatMap(entry -> { ManagedInfo managedInfo = entry.getKey(); diff --git a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/ChannelHopper.java b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/ChannelHopper.java index 16867dd..cf11c0e 100644 --- a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/ChannelHopper.java +++ b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/ChannelHopper.java @@ -25,7 +25,7 @@ public ChannelHopper(NetworkInterface monitor, @Value("${sudo_password}") String @Scheduled(initialDelay = 5000, fixedDelay = 1000) public void hop(){ if (!channelsToHop.iterator().hasNext()) { - Set channels = new TransientProcess("nmcli -f SSID,CHAN dev wifi").results() + Set channels = new TransientProcess("nmcli -f SSID,CHAN dev wifi").resultList() .stream() .map(line -> line.trim().split("\\s+")) .filter(split -> (split.length == 2) && split[1].matches("\\d+")) diff --git a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/MonitorLogProcess.java b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/MonitorLogProcess.java index 25c9365..23074b1 100644 --- a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/MonitorLogProcess.java +++ b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/MonitorLogProcess.java @@ -1,32 +1,12 @@ package com.whoz_in.log_writer.monitor; import com.whoz_in.log_writer.common.process.ContinuousProcess; -import com.whoz_in.log_writer.common.util.NonBlockingBufferedReader; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.lang.ProcessBuilder.Redirect; import lombok.extern.slf4j.Slf4j; @Slf4j public final class MonitorLogProcess extends ContinuousProcess { public MonitorLogProcess(MonitorInfo info, String sudoPassword) { - try { - super.process = new ProcessBuilder(info.command().split(" ")) - .redirectError(Redirect.INHERIT) - .start(); - super.br = new NonBlockingBufferedReader(new BufferedReader(new InputStreamReader(process.getInputStream()))); - - Writer writer = new OutputStreamWriter(process.getOutputStream()); - writer.write(sudoPassword + System.lineSeparator()); - writer.flush(); - } catch (IOException e) { - String message = info.command() + " 실행 실패"; - log.error(message); - throw new RuntimeException(message); - } + super(info.command(), sudoPassword); } } diff --git a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/MonitorLogWriter.java b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/MonitorLogWriter.java index 8b3096a..365c77c 100644 --- a/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/MonitorLogWriter.java +++ b/modules/infrastructure/log-writer/src/main/java/com/whoz_in/log_writer/monitor/MonitorLogWriter.java @@ -12,6 +12,7 @@ @Component public class MonitorLogWriter { private MonitorLogProcess process; //교체될 수 있으므로 final X + private Boolean wasDead = false; private final MonitorLogParser parser; private final MonitorLogDAO repo; private final String sudoPassword; @@ -27,7 +28,10 @@ public MonitorLogWriter(MonitorLogParser parser, MonitorLogDAO repo, NetworkConf @Scheduled(initialDelay = 10000, fixedDelay = 10000) private void saveLogs(){ if (!process.isAlive()) { - log.error("[monitor] dead"); + if (wasDead.equals(Boolean.FALSE)) { + log.error("[monitor] dead:\n{}", process.readErrorLines()); + wasDead = true; + } return; } Set macs = new HashSet<>();