    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    }