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}