001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.logging.log4j.io;
019    
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.io.InputStreamReader;
023    import java.nio.ByteBuffer;
024    import java.nio.charset.Charset;
025    
026    import org.apache.logging.log4j.Level;
027    import org.apache.logging.log4j.Marker;
028    import org.apache.logging.log4j.spi.ExtendedLogger;
029    
030    /**
031     * 
032     * @since 2.1
033     */
034    public class ByteStreamLogger {
035        private class ByteBufferInputStream extends InputStream {
036    
037            @Override
038            public int read() throws IOException {
039                ByteStreamLogger.this.buf.flip();
040                int result = -1;
041                if (ByteStreamLogger.this.buf.limit() > 0) {
042                    result = ByteStreamLogger.this.buf.get() & 0xFF;
043                }
044                ByteStreamLogger.this.buf.compact();
045                return result;
046            }
047    
048            @Override
049            public int read(final byte[] bytes, final int off, final int len) throws IOException {
050                ByteStreamLogger.this.buf.flip();
051                int result = -1;
052                if (ByteStreamLogger.this.buf.limit() > 0) {
053                    result = Math.min(len, ByteStreamLogger.this.buf.limit());
054                    ByteStreamLogger.this.buf.get(bytes, off, result);
055                }
056                ByteStreamLogger.this.buf.compact();
057                return result;
058            }
059        }
060    
061        private static final int BUFFER_SIZE = 1024;
062        private final ExtendedLogger logger;
063        private final Level level;
064        private final Marker marker;
065        private final InputStreamReader reader;
066        private final char[] msgBuf = new char[BUFFER_SIZE];
067        private final StringBuilder msg = new StringBuilder();
068        private boolean closed;
069    
070        private final ByteBuffer buf = ByteBuffer.allocate(BUFFER_SIZE);
071    
072        public ByteStreamLogger(final ExtendedLogger logger, final Level level, final Marker marker, final Charset charset) {
073            this.logger = logger;
074            this.level = level == null ? logger.getLevel() : level;
075            this.marker = marker;
076            this.reader = new InputStreamReader(new ByteBufferInputStream(),
077                charset == null ? Charset.defaultCharset() : charset);
078        }
079    
080        public void close(final String fqcn) {
081            synchronized (this.msg) {
082                this.closed = true;
083                logEnd(fqcn);
084            }
085        }
086    
087        private void extractMessages(final String fqcn) throws IOException {
088            if (this.closed) {
089                return;
090            }
091            int read = this.reader.read(this.msgBuf);
092            while (read > 0) {
093                int off = 0;
094                for (int pos = 0; pos < read; pos++) {
095                    switch (this.msgBuf[pos]) {
096                    case '\r':
097                        this.msg.append(this.msgBuf, off, pos - off);
098                        off = pos + 1;
099                        break;
100                    case '\n':
101                        this.msg.append(this.msgBuf, off, pos - off);
102                        off = pos + 1;
103                        log(fqcn);
104                        break;
105                    }
106                }
107                this.msg.append(this.msgBuf, off, read - off);
108                read = this.reader.read(this.msgBuf);
109            }
110        }
111    
112        private void log(final String fqcn) {
113            // convert to string now so async loggers work
114            this.logger.logIfEnabled(fqcn, this.level, this.marker, this.msg.toString());
115            this.msg.setLength(0);
116        }
117        
118        private void logEnd(final String fqcn) {
119            if (this.msg.length() > 0) {
120                log(fqcn);
121            }
122        }
123    
124        public void put(final String fqcn, final byte[] b, final int off, final int len) throws IOException {
125            int curOff = off;
126            int curLen = len;
127            if (curLen >= 0) {
128                synchronized (this.msg) {
129                    while (curLen > this.buf.remaining()) {
130                        final int remaining = this.buf.remaining();
131                        this.buf.put(b, curOff, remaining);
132                        curLen -= remaining;
133                        curOff += remaining;
134                        extractMessages(fqcn);
135                    }
136                    this.buf.put(b, curOff, curLen);
137                    extractMessages(fqcn);
138                }
139            } else {
140                logEnd(fqcn);
141            }
142        }
143    
144        public void put(final String fqcn, final int b) throws IOException {
145            if (b >= 0) {
146                synchronized (this.msg) {
147                    this.buf.put((byte) (b & 0xFF));
148                    extractMessages(fqcn);
149                }
150            } else {
151                logEnd(fqcn);
152            }
153        }
154    }