001package co.codewizards.cloudstore.core.childprocess; 002 003import java.io.BufferedReader; 004import java.io.ByteArrayOutputStream; 005import java.io.IOException; 006import java.io.StringReader; 007import java.io.UnsupportedEncodingException; 008 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012import co.codewizards.cloudstore.core.util.IOUtil; 013 014public class LogDumpedStreamThread extends Thread 015{ 016 private static final Logger logger = LoggerFactory.getLogger(LogDumpedStreamThread.class); 017 018 /** 019 * If the first data which arrived here via {@link #write(byte[], int)} have not yet been written 020 * after this time (i.e. their age exceeds this time) in milliseconds, it is logged. 021 */ 022 private static final long logMaxAge = 5000L; 023 024 /** 025 * If there was no write after this many milliseconds, the current buffer is logged. 026 */ 027 private static final long logAfterNoWritePeriod = 500L; 028 029 /** 030 * If the buffer grows bigger than this size in bytes, it is logged - no matter when the last 031 * write occured. 032 */ 033 private static final int logWhenBufferExceedsSize = 50 * 1024; // 50 KB 034 035 private ByteArrayOutputStream bufferOutputStream = new ByteArrayOutputStream(); 036 private volatile boolean forceInterrupt = false; 037 private Long firstNonLoggedWriteTimestamp = null; 038 private long lastWriteTimestamp = 0; 039 040 private volatile StringBuffer outputStringBuffer; 041 private volatile int outputStringBufferMaxLength = 1024 * 1024; 042 043 private Logger childProcessLogger; 044 045 public LogDumpedStreamThread(String childProcessLoggerName) 046 { 047 if (childProcessLoggerName == null) 048 childProcessLogger = logger; 049 else 050 childProcessLogger = LoggerFactory.getLogger(childProcessLoggerName); 051 } 052 053 public void write(byte[] data, int length) 054 { 055 if (data == null) 056 throw new IllegalArgumentException("data == null"); //$NON-NLS-1$ 057 058 synchronized (bufferOutputStream) { 059 bufferOutputStream.write(data, 0, length); 060 lastWriteTimestamp = System.currentTimeMillis(); 061 if (firstNonLoggedWriteTimestamp == null) 062 firstNonLoggedWriteTimestamp = lastWriteTimestamp; 063 } 064 } 065 066 public void setOutputStringBuffer(StringBuffer outputStringBuffer) { 067 this.outputStringBuffer = outputStringBuffer; 068 } 069 public StringBuffer getOutputStringBuffer() { 070 return outputStringBuffer; 071 } 072 public void setOutputStringBufferMaxLength(int outputStringBufferMaxLength) { 073 this.outputStringBufferMaxLength = outputStringBufferMaxLength; 074 } 075 public int getOutputStringBufferMaxLength() { 076 return outputStringBufferMaxLength; 077 } 078 079 @Override 080 public void interrupt() { 081 forceInterrupt = true; 082 super.interrupt(); 083 } 084 085 @Override 086 public boolean isInterrupted() { 087 return forceInterrupt || super.isInterrupted(); 088 } 089 090 @Override 091 public void run() { 092 while (!isInterrupted()) { 093 try { 094 synchronized (bufferOutputStream) { 095 try { 096 bufferOutputStream.wait(500L); 097 } catch (InterruptedException x) { 098 doNothing(); 099 } 100 101 processBuffer(false); 102 } 103 } catch (Throwable e) { 104 logger.error("run: " + e, e); //$NON-NLS-1$ 105 } 106 } 107 processBuffer(true); 108 } 109 110 public void flushBuffer() 111 { 112 processBuffer(true); 113 } 114 115 protected void processBuffer(boolean force) 116 { 117 synchronized (bufferOutputStream) { 118 if (bufferOutputStream.size() > 0) { 119 long firstNonLoggedWriteAge = firstNonLoggedWriteTimestamp == null ? 0 : System.currentTimeMillis() - firstNonLoggedWriteTimestamp; 120 long noWritePeriod = System.currentTimeMillis() - lastWriteTimestamp; 121 if (force || firstNonLoggedWriteAge > logMaxAge || noWritePeriod > logAfterNoWritePeriod || bufferOutputStream.size() > logWhenBufferExceedsSize) { 122 String currentBufferString; 123 try { 124 currentBufferString = bufferOutputStream.toString(IOUtil.CHARSET_NAME_UTF_8); 125 } catch (UnsupportedEncodingException e) { 126 throw new RuntimeException(e); 127 } 128 129 StringBuffer outputStringBuffer = getOutputStringBuffer(); 130 if (outputStringBuffer != null) { 131 int newOutputStringBufferLength = outputStringBuffer.length() + currentBufferString.length(); 132 if (newOutputStringBufferLength > outputStringBufferMaxLength) { 133 int lastCharPositionToDelete = newOutputStringBufferLength - outputStringBufferMaxLength; 134 // search for first line-break 135 while (outputStringBuffer.length() > lastCharPositionToDelete && outputStringBuffer.charAt(lastCharPositionToDelete) != '\n') 136 ++lastCharPositionToDelete; 137 138 lastCharPositionToDelete = Math.min(lastCharPositionToDelete, outputStringBuffer.length() - 1); 139 outputStringBuffer.delete(0, lastCharPositionToDelete + 1); 140 } 141 142 outputStringBuffer.append(currentBufferString); 143 } 144 145 childProcessLogger.info( 146 '\n' + prefixEveryLine(currentBufferString) 147 ); 148 149 bufferOutputStream.reset(); 150 } 151 } 152 } 153 } 154 155 private static final void doNothing() { } 156 157 private String prefixEveryLine(String s) 158 { 159 try { 160 StringBuilder result = new StringBuilder(); 161 final String prefix = " >>> "; //$NON-NLS-1$ 162 BufferedReader r = new BufferedReader(new StringReader(s)); 163 String line; 164 while (null != (line = r.readLine())) 165 result.append(prefix).append(line).append('\n'); 166 167 return result.toString(); 168 } catch (IOException x) { 169 throw new RuntimeException(x); 170 } 171 } 172}