001package com.streamconverter.command; 002 003import java.io.IOException; 004import java.io.InputStream; 005import java.io.OutputStream; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * IStreamCommandのログ出力デコレーター 011 * 012 * <p>このクラスは既存のIStreamCommandをラップして、詳細なログ出力機能を追加します。 デコレーターパターンを使用することで、既存のコマンドを変更せずに 013 * 追加のログ機能を提供できます。 014 * 015 * <p>使用例: 016 * 017 * <pre> 018 * IStreamCommand originalCommand = new CsvNavigateCommand("name"); 019 * IStreamCommand loggedCommand = new LoggingDecorator(originalCommand); 020 * loggedCommand.execute(inputStream, outputStream); 021 * </pre> 022 */ 023public class LoggingDecorator implements IStreamCommand { 024 private final IStreamCommand delegate; 025 private final Logger log; 026 private final String commandName; 027 028 /** 029 * コンストラクタ 030 * 031 * @param delegate ラップ対象のコマンド 032 */ 033 public LoggingDecorator(IStreamCommand delegate) { 034 this.delegate = delegate; 035 this.commandName = delegate.getClass().getSimpleName(); 036 this.log = LoggerFactory.getLogger(delegate.getClass()); 037 } 038 039 /** 040 * カスタムログ名を指定するコンストラクタ 041 * 042 * @param delegate ラップ対象のコマンド 043 * @param loggerName ログ名 044 */ 045 public LoggingDecorator(IStreamCommand delegate, String loggerName) { 046 this.delegate = delegate; 047 this.commandName = delegate.getClass().getSimpleName(); 048 this.log = LoggerFactory.getLogger(loggerName); 049 } 050 051 @Override 052 public void execute(InputStream inputStream, OutputStream outputStream) throws IOException { 053 long startTime = System.currentTimeMillis(); 054 055 // AbstractStreamCommandの場合は重複ログを避けるため、簡潔なログ出力 056 boolean isAbstractCommand = delegate instanceof AbstractStreamCommand; 057 058 if (isAbstractCommand) { 059 // AbstractStreamCommandの場合は追加的な情報のみログ出力 060 log.info("=== Enhanced logging for {} ===", commandName); 061 } else { 062 // 非AbstractStreamCommandの場合は詳細なログ出力 063 log.info("=== {} Execution Started ===", commandName); 064 } 065 066 log.debug("Input stream type: {}", inputStream.getClass().getSimpleName()); 067 log.debug("Output stream type: {}", outputStream.getClass().getSimpleName()); 068 log.debug("Available input bytes: {}", safeAvailable(inputStream)); 069 070 // スレッド情報 071 Thread currentThread = Thread.currentThread(); 072 log.debug("Executing on thread: {} (ID: {})", currentThread.getName(), currentThread.getId()); 073 074 // システムリソース情報 075 Runtime runtime = Runtime.getRuntime(); 076 long totalMemory = runtime.totalMemory(); 077 long freeMemory = runtime.freeMemory(); 078 long usedMemory = totalMemory - freeMemory; 079 080 log.debug( 081 "Memory before execution: used={}KB, free={}KB, total={}KB", 082 usedMemory / 1024, 083 freeMemory / 1024, 084 totalMemory / 1024); 085 086 try { 087 // 実際のコマンド実行 088 delegate.execute(inputStream, outputStream); 089 090 // 成功時のログ 091 long duration = System.currentTimeMillis() - startTime; 092 093 if (isAbstractCommand) { 094 // AbstractStreamCommandの場合は追加的な情報のみ 095 log.info("=== Enhanced logging completed for {} ({} ms) ===", commandName, duration); 096 } else { 097 // 非AbstractStreamCommandの場合は詳細なログ 098 log.info("=== {} Execution Completed Successfully ({} ms) ===", commandName, duration); 099 } 100 101 // 実行後のメモリ情報 102 long usedMemoryAfter = (runtime.totalMemory() - runtime.freeMemory()); 103 long memoryDiff = usedMemoryAfter - usedMemory; 104 105 log.debug( 106 "Memory after execution: used={}KB (diff: {}KB)", 107 usedMemoryAfter / 1024, 108 memoryDiff / 1024); 109 110 // パフォーマンス分析 111 analyzePerformance(duration, memoryDiff); 112 113 } catch (Exception e) { 114 // 例外発生時のログ 115 long duration = System.currentTimeMillis() - startTime; 116 117 if (isAbstractCommand) { 118 // AbstractStreamCommandの場合は追加的な情報のみ 119 log.error("=== Enhanced logging failed for {} ({} ms) ===", commandName, duration); 120 } else { 121 // 非AbstractStreamCommandの場合は詳細なログ 122 log.error("=== {} Execution Failed ({} ms) ===", commandName, duration); 123 } 124 125 log.error("Exception type: {}", e.getClass().getSimpleName()); 126 log.error("Exception message: {}", e.getMessage()); 127 128 // スタックトレースの最初の数行のみを記録(デバッグレベル) 129 if (log.isDebugEnabled()) { 130 StackTraceElement[] stackTrace = e.getStackTrace(); 131 log.debug("Stack trace (first 5 lines):"); 132 for (int i = 0; i < Math.min(5, stackTrace.length); i++) { 133 log.debug(" at {}", stackTrace[i]); 134 } 135 } 136 137 throw e; 138 } 139 } 140 141 /** 142 * パフォーマンス分析を実行 143 * 144 * @param duration 実行時間(ミリ秒) 145 * @param memoryDiff メモリ使用量の変化(バイト) 146 */ 147 private void analyzePerformance(long duration, long memoryDiff) { 148 // パフォーマンス警告 149 if (duration > 10000) { // 10秒以上 150 log.warn("PERFORMANCE: {} took {} ms - consider optimization", commandName, duration); 151 } else if (duration > 5000) { // 5秒以上 152 log.info("PERFORMANCE: {} took {} ms - slower than expected", commandName, duration); 153 } 154 155 // メモリ使用量警告 156 long memoryDiffMB = memoryDiff / 1024 / 1024; 157 if (memoryDiffMB > 100) { // 100MB以上 158 log.warn("MEMORY: {} used {} MB - high memory consumption", commandName, memoryDiffMB); 159 } else if (memoryDiffMB > 50) { // 50MB以上 160 log.info("MEMORY: {} used {} MB - moderate memory consumption", commandName, memoryDiffMB); 161 } 162 163 // 効率性分析 164 if (duration > 1000 && memoryDiffMB > 10) { 165 log.info( 166 "EFFICIENCY: {} may benefit from optimization (time: {}ms, memory: {}MB)", 167 commandName, 168 duration, 169 memoryDiffMB); 170 } 171 } 172 173 /** 174 * InputStreamのavailable()メソッドを安全に呼び出し 175 * 176 * @param inputStream 入力ストリーム 177 * @return 利用可能バイト数(取得失敗時は-1) 178 */ 179 private int safeAvailable(InputStream inputStream) { 180 try { 181 return inputStream.available(); 182 } catch (IOException e) { 183 log.debug("Failed to get available bytes: {}", e.getMessage()); 184 return -1; 185 } 186 } 187 188 /** 189 * ラップされているコマンドを取得 190 * 191 * @return ラップされているコマンド 192 */ 193 public IStreamCommand getDelegate() { 194 return delegate; 195 } 196 197 /** 198 * コマンド名を取得 199 * 200 * @return コマンド名 201 */ 202 public String getCommandName() { 203 return commandName; 204 } 205}