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
018package org.apache.logging.log4j.io;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.InputStreamReader;
023import java.nio.ByteBuffer;
024import java.nio.charset.Charset;
025
026import org.apache.logging.log4j.Level;
027import org.apache.logging.log4j.Marker;
028import org.apache.logging.log4j.spi.ExtendedLogger;
029
030/**
031 *
032 * @since 2.1
033 */
034public 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}