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 */ 017package org.apache.logging.log4j.core.layout; 018 019import java.nio.ByteBuffer; 020import java.nio.CharBuffer; 021import java.nio.charset.Charset; 022import java.nio.charset.CharsetEncoder; 023import java.nio.charset.CodingErrorAction; 024import java.util.Objects; 025 026import org.apache.logging.log4j.core.util.Constants; 027import org.apache.logging.log4j.status.StatusLogger; 028 029/** 030 * Encoder for StringBuilders that uses ThreadLocals to avoid locking as much as possible. 031 */ 032public class StringBuilderEncoder implements Encoder<StringBuilder> { 033 034 private static final int DEFAULT_BYTE_BUFFER_SIZE = 8 * 1024; 035 /** 036 * This ThreadLocal uses raw and inconvenient Object[] to store three heterogeneous objects (CharEncoder, CharBuffer 037 * and ByteBuffer) instead of a custom class, because it needs to contain JDK classes, no custom (Log4j) classes. 038 * Where possible putting only JDK classes in ThreadLocals is preferable to avoid memory leaks in web containers: 039 * the Log4j classes may be loaded by a separate class loader which cannot be garbage collected if a thread pool 040 * threadlocal still has a reference to it. 041 * 042 * Using just one ThreadLocal instead of three separate ones is an optimization: {@link ThreadLocal.ThreadLocalMap} 043 * is polluted less, {@link ThreadLocal.ThreadLocalMap#get()} is called only once on each call to {@link #encode} 044 * instead of three times. 045 */ 046 private final ThreadLocal<Object[]> threadLocal = new ThreadLocal<>(); 047 private final Charset charset; 048 private final int charBufferSize; 049 private final int byteBufferSize; 050 051 public StringBuilderEncoder(final Charset charset) { 052 this(charset, Constants.ENCODER_CHAR_BUFFER_SIZE, DEFAULT_BYTE_BUFFER_SIZE); 053 } 054 055 public StringBuilderEncoder(final Charset charset, final int charBufferSize, final int byteBufferSize) { 056 this.charBufferSize = charBufferSize; 057 this.byteBufferSize = byteBufferSize; 058 this.charset = Objects.requireNonNull(charset, "charset"); 059 } 060 061 @Override 062 public void encode(final StringBuilder source, final ByteBufferDestination destination) { 063 try { 064 final Object[] threadLocalState = getThreadLocalState(); 065 final CharsetEncoder charsetEncoder = (CharsetEncoder) threadLocalState[0]; 066 final CharBuffer charBuffer = (CharBuffer) threadLocalState[1]; 067 final ByteBuffer byteBuffer = (ByteBuffer) threadLocalState[2]; 068 TextEncoderHelper.encodeText(charsetEncoder, charBuffer, byteBuffer, source, destination); 069 } catch (final Exception ex) { 070 logEncodeTextException(ex, source, destination); 071 TextEncoderHelper.encodeTextFallBack(charset, source, destination); 072 } 073 } 074 075 private Object[] getThreadLocalState() { 076 Object[] threadLocalState = threadLocal.get(); 077 if (threadLocalState == null) { 078 threadLocalState = new Object[] { 079 charset.newEncoder().onMalformedInput(CodingErrorAction.REPLACE) 080 .onUnmappableCharacter(CodingErrorAction.REPLACE), 081 CharBuffer.allocate(charBufferSize), 082 ByteBuffer.allocate(byteBufferSize) 083 }; 084 threadLocal.set(threadLocalState); 085 } else { 086 ((CharsetEncoder) threadLocalState[0]).reset(); 087 ((CharBuffer) threadLocalState[1]).clear(); 088 ((ByteBuffer) threadLocalState[2]).clear(); 089 } 090 return threadLocalState; 091 } 092 093 private void logEncodeTextException(final Exception ex, final StringBuilder text, 094 final ByteBufferDestination destination) { 095 StatusLogger.getLogger().error("Recovering from StringBuilderEncoder.encode('{}') error: {}", text, ex, ex); 096 } 097}