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}