View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.logging.log4j.io;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.nio.ByteBuffer;
24  import java.nio.charset.Charset;
25  
26  import org.apache.logging.log4j.Level;
27  import org.apache.logging.log4j.Marker;
28  import org.apache.logging.log4j.spi.ExtendedLogger;
29  
30  /**
31   * 
32   * @since 2.1
33   */
34  public class ByteStreamLogger {
35      private class ByteBufferInputStream extends InputStream {
36  
37          @Override
38          public int read() throws IOException {
39              ByteStreamLogger.this.buf.flip();
40              int result = -1;
41              if (ByteStreamLogger.this.buf.limit() > 0) {
42                  result = ByteStreamLogger.this.buf.get() & 0xFF;
43              }
44              ByteStreamLogger.this.buf.compact();
45              return result;
46          }
47  
48          @Override
49          public int read(final byte[] bytes, final int off, final int len) throws IOException {
50              ByteStreamLogger.this.buf.flip();
51              int result = -1;
52              if (ByteStreamLogger.this.buf.limit() > 0) {
53                  result = Math.min(len, ByteStreamLogger.this.buf.limit());
54                  ByteStreamLogger.this.buf.get(bytes, off, result);
55              }
56              ByteStreamLogger.this.buf.compact();
57              return result;
58          }
59      }
60  
61      private static final int BUFFER_SIZE = 1024;
62      private final ExtendedLogger logger;
63      private final Level level;
64      private final Marker marker;
65      private final InputStreamReader reader;
66      private final char[] msgBuf = new char[BUFFER_SIZE];
67      private final StringBuilder msg = new StringBuilder();
68      private boolean closed;
69  
70      private final ByteBuffer buf = ByteBuffer.allocate(BUFFER_SIZE);
71  
72      public ByteStreamLogger(final ExtendedLogger logger, final Level level, final Marker marker, final Charset charset) {
73          this.logger = logger;
74          this.level = level == null ? logger.getLevel() : level;
75          this.marker = marker;
76          this.reader = new InputStreamReader(new ByteBufferInputStream(),
77              charset == null ? Charset.defaultCharset() : charset);
78      }
79  
80      public void close(final String fqcn) {
81          synchronized (this.msg) {
82              this.closed = true;
83              logEnd(fqcn);
84          }
85      }
86  
87      private void extractMessages(final String fqcn) throws IOException {
88          if (this.closed) {
89              return;
90          }
91          int read = this.reader.read(this.msgBuf);
92          while (read > 0) {
93              int off = 0;
94              for (int pos = 0; pos < read; pos++) {
95                  switch (this.msgBuf[pos]) {
96                  case '\r':
97                      this.msg.append(this.msgBuf, off, pos - off);
98                      off = pos + 1;
99                      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 }