mirror of
https://github.com/tiennm99/tjahzi.git
synced 2026-10-14 20:17:49 +00:00
Allocation free encoding of log event.
This commit is contained in:
1 parent
b6cb562f02
commit
700c78be8f
15 files changed
+927
-12
No files matched your search
+59
@@ -0,0 +1,59 @@
|
||||
package ch.qos.logback.core.pattern;
|
||||
|
||||
import ch.qos.logback.classic.PatternLayout;
|
||||
import ch.qos.logback.classic.pattern.EnsureExceptionHandling;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
|
||||
public class EfficientPatternLayout extends PatternLayoutBase<ILoggingEvent> {
|
||||
|
||||
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
|
||||
|
||||
public EfficientPatternLayout() {
|
||||
this.postCompileProcessor = new EnsureExceptionHandling();
|
||||
}
|
||||
|
||||
public Map<String, String> getDefaultConverterMap() {
|
||||
return PatternLayout.defaultConverterMap;
|
||||
}
|
||||
|
||||
public ByteBuffer doEfficientLayout(ILoggingEvent event) {
|
||||
if (!isStarted()) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
return efficientWriteLoopOnConverters(event);
|
||||
}
|
||||
|
||||
protected ByteBuffer efficientWriteLoopOnConverters(ILoggingEvent event) {
|
||||
StringBuilder strBuilder = StringBuilders.threadLocal();
|
||||
Converter<ILoggingEvent> c = head;
|
||||
while (c != null) {
|
||||
c.write(strBuilder, event);
|
||||
c = c.getNext();
|
||||
}
|
||||
|
||||
Encoder encoder = Encoders.threadLocal();
|
||||
encoder.encode(strBuilder);
|
||||
|
||||
return encoder.getBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPresentationHeaderPrefix() {
|
||||
return PatternLayout.HEADER_PREFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String doLayout(ILoggingEvent event) {
|
||||
ByteBuffer byteBuffer = doEfficientLayout(event);
|
||||
|
||||
return new String(
|
||||
byteBuffer.array(),
|
||||
byteBuffer.position(),
|
||||
byteBuffer.limit()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package ch.qos.logback.core.pattern;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.CoderResult;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class Encoder {
|
||||
|
||||
/**
|
||||
* 10kb should be enough for everyone. Even in case of 1000s of threads we will allocate at most 10s of MB of buffers.
|
||||
* Larger messages will be fragmented.
|
||||
*/
|
||||
private static final int ENCODER_BUFFER_SIZE = Integer.getInteger("tjahzi.logback.layout.encoder.bufferSize", 10 * 1024);
|
||||
|
||||
private final ByteBuffer buffer = ByteBuffer.allocate(ENCODER_BUFFER_SIZE);
|
||||
|
||||
private final char[] temporaryTextArray = new char[1024];
|
||||
private final CharBuffer temporaryTextBuffer = CharBuffer.wrap(temporaryTextArray);
|
||||
private final CharsetEncoder charsetEncoder = StandardCharsets.UTF_8.newEncoder();
|
||||
|
||||
public void encode(StringBuilder input) {
|
||||
int offset = 0;
|
||||
int sizeOfInput = input.length();
|
||||
while (sizeOfInput > 0) {
|
||||
int length = Math.min(sizeOfInput, temporaryTextArray.length);
|
||||
sizeOfInput -= length;
|
||||
|
||||
encodeRound(input, offset, length, sizeOfInput == 0);
|
||||
offset += length;
|
||||
}
|
||||
|
||||
buffer.flip();
|
||||
}
|
||||
|
||||
public ByteBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
buffer.clear();
|
||||
charsetEncoder.reset();
|
||||
}
|
||||
|
||||
private void encodeRound(StringBuilder input, int offset, int length, boolean endOfInput) {
|
||||
input.getChars(offset, offset + length, temporaryTextArray, 0);
|
||||
temporaryTextBuffer.clear().limit(length);
|
||||
|
||||
CoderResult coderResult = charsetEncoder.encode(
|
||||
temporaryTextBuffer,
|
||||
buffer,
|
||||
endOfInput
|
||||
);
|
||||
|
||||
if (coderResult.isError()) {
|
||||
throw new RuntimeException(coderResult.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ch.qos.logback.core.pattern;
|
||||
|
||||
public class Encoders {
|
||||
|
||||
private static final ThreadLocal<Encoder> THREAD_LOCAL = ThreadLocal.withInitial(Encoder::new);
|
||||
|
||||
public static Encoder threadLocal() {
|
||||
Encoder result = THREAD_LOCAL.get();
|
||||
result.clear();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package ch.qos.logback.core.pattern;
|
||||
|
||||
public class StringBuilders {
|
||||
|
||||
private static final ThreadLocal<StringBuilder> THREAD_LOCAL = ThreadLocal.withInitial(StringBuilder::new);
|
||||
|
||||
public static StringBuilder threadLocal() {
|
||||
StringBuilder result = THREAD_LOCAL.get();
|
||||
result.setLength(0);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package pl.tkowalcz.tjahzi.logback;
|
||||
|
||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.pattern.EfficientPatternLayout;
|
||||
import pl.tkowalcz.tjahzi.LoggingSystem;
|
||||
import pl.tkowalcz.tjahzi.TjahziLogger;
|
||||
import pl.tkowalcz.tjahzi.stats.MonitoringModule;
|
||||
@@ -9,17 +10,28 @@ import pl.tkowalcz.tjahzi.stats.MutableMonitoringModuleWrapper;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class LokiAppender extends LokiAppenderConfigurator {
|
||||
|
||||
|
||||
private EfficientPatternLayout efficientLayout;
|
||||
private PatternLayoutEncoder encoder;
|
||||
|
||||
private LoggingSystem loggingSystem;
|
||||
private Function<ILoggingEvent, ByteBuffer> actualEncoder;
|
||||
|
||||
private TjahziLogger logger;
|
||||
private String logLevelLabel;
|
||||
private MutableMonitoringModuleWrapper monitoringModuleWrapper;
|
||||
|
||||
public EfficientPatternLayout getEfficientLayout() {
|
||||
return efficientLayout;
|
||||
}
|
||||
|
||||
public void setEfficientLayout(EfficientPatternLayout efficientLayout) {
|
||||
this.efficientLayout = efficientLayout;
|
||||
}
|
||||
|
||||
public PatternLayoutEncoder getEncoder() {
|
||||
return encoder;
|
||||
}
|
||||
@@ -44,25 +56,30 @@ public class LokiAppender extends LokiAppenderConfigurator {
|
||||
@Override
|
||||
protected void append(ILoggingEvent event) {
|
||||
String logLevel = event.getLevel().toString();
|
||||
byte[] logLine = encoder.encode(event);
|
||||
ByteBuffer logLine = actualEncoder.apply(event);
|
||||
|
||||
logger.log(
|
||||
event.getTimeStamp(),
|
||||
Collections.emptyMap(),
|
||||
logLevelLabel,
|
||||
logLevel,
|
||||
ByteBuffer.wrap(logLine)
|
||||
logLine
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (encoder == null) {
|
||||
addError("No encoder set for the appender named [" + name + "].");
|
||||
return;
|
||||
}
|
||||
if (efficientLayout == null) {
|
||||
addError("No encoder set for the appender named [" + name + "]. You can also try setting efficientLayout instead.");
|
||||
return;
|
||||
}
|
||||
|
||||
encoder.start();
|
||||
actualEncoder = efficientLayout::doEfficientLayout;
|
||||
} else {
|
||||
encoder.start();
|
||||
actualEncoder = event -> ByteBuffer.wrap(encoder.encode(event));
|
||||
}
|
||||
|
||||
LokiAppenderFactory lokiAppenderFactory = new LokiAppenderFactory(this);
|
||||
loggingSystem = lokiAppenderFactory.createAppender();
|
||||
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
package ch.qos.logback.core.pattern;
|
||||
|
||||
import ch.qos.logback.classic.ClassicTestConstants;
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.classic.spi.LoggingEvent;
|
||||
import ch.qos.logback.classic.testUtil.SampleConverter;
|
||||
import ch.qos.logback.core.Context;
|
||||
import ch.qos.logback.core.joran.spi.JoranException;
|
||||
import ch.qos.logback.core.pattern.parser.AbstractPatternLayoutBaseTest;
|
||||
import ch.qos.logback.core.testUtil.StringListAppender;
|
||||
import ch.qos.logback.core.util.OptionHelper;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import static ch.qos.logback.classic.ClassicTestConstants.ISO_REGEX;
|
||||
import static ch.qos.logback.classic.ClassicTestConstants.MAIN_REGEX;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Adapted from ch.qos.logback.classic.PatternLayoutTest
|
||||
*/
|
||||
public class EfficientPatternLayoutTest extends AbstractPatternLayoutBaseTest<ILoggingEvent> {
|
||||
|
||||
private EfficientPatternLayout patternLayout;
|
||||
|
||||
private final LoggerContext loggerContext = new LoggerContext();
|
||||
private final Logger logger = loggerContext.getLogger(EfficientPatternLayoutTest.class);
|
||||
private final Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
patternLayout = getPatternLayoutBase();
|
||||
patternLayout.setContext(loggerContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EfficientPatternLayout getPatternLayoutBase() {
|
||||
return new EfficientPatternLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILoggingEvent getEventObject() {
|
||||
return new LoggingEvent(
|
||||
ch.qos.logback.core.pattern.FormattingConverter.class.getName(),
|
||||
logger,
|
||||
Level.INFO,
|
||||
"Some message",
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return loggerContext;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOK() {
|
||||
// Given
|
||||
String pattern = "%d %le [%t] %lo{30} - %m%n";
|
||||
|
||||
// When
|
||||
patternLayout.setPattern(pattern);
|
||||
patternLayout.start();
|
||||
String val = patternLayout.doLayout(getEventObject());
|
||||
|
||||
// Then
|
||||
// 2021-02-01 22:38:06,212 INFO [main] c.q.l.pattern.EfficientPatternLayoutTest - Some message
|
||||
String regex = ISO_REGEX + " INFO " + MAIN_REGEX + " c.q.l.c.p.EfficientPatternLayoutTest - Some message\\s*";
|
||||
assertThat(val).matches(regex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoExeptionHandler() {
|
||||
// Given
|
||||
String pattern = "%m%n";
|
||||
ILoggingEvent loggingEvent = new LoggingEvent(
|
||||
ch.qos.logback.core.pattern.FormattingConverter.class.getName(),
|
||||
logger,
|
||||
Level.INFO,
|
||||
"Some message",
|
||||
new Exception("Bogus exception"),
|
||||
null
|
||||
);
|
||||
|
||||
// When
|
||||
patternLayout.setPattern(pattern);
|
||||
patternLayout.start();
|
||||
String val = patternLayout.doLayout(loggingEvent);
|
||||
|
||||
// Then
|
||||
assertThat(val).contains("java.lang.Exception: Bogus exception");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompositePattern() {
|
||||
// Given
|
||||
String pattern = "%-69(%d %lo{20}) - %m%n";
|
||||
|
||||
// When
|
||||
patternLayout.setPattern(pattern);
|
||||
patternLayout.start();
|
||||
String val = patternLayout.doLayout(getEventObject());
|
||||
|
||||
// Then
|
||||
// / Ten spaces \
|
||||
// 2021-03-18 21:55:54,250 c.q.l.c.p.EfficientPatternLayoutTest - Some message
|
||||
String regex = ISO_REGEX + " c.q.l.c.p.EfficientPatternLayoutTest {10}- Some message\\s*";
|
||||
assertThat(val).matches(regex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contextProperty() {
|
||||
// Given
|
||||
String pattern = "%property{a}";
|
||||
loggerContext.putProperty("a", "b");
|
||||
|
||||
// When
|
||||
patternLayout.setPattern(pattern);
|
||||
patternLayout.start();
|
||||
String val = patternLayout.doLayout(getEventObject());
|
||||
|
||||
// Then
|
||||
assertThat(val).isEqualTo("b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNopExceptionHandler() {
|
||||
// Given
|
||||
ILoggingEvent loggingEvent = new LoggingEvent(
|
||||
ch.qos.logback.core.pattern.FormattingConverter.class.getName(),
|
||||
logger,
|
||||
Level.INFO,
|
||||
"Some message",
|
||||
new Exception("Bogus exception"),
|
||||
null
|
||||
);
|
||||
|
||||
// When
|
||||
patternLayout.setPattern("%nopex %m%n");
|
||||
patternLayout.start();
|
||||
String val = patternLayout.doLayout(loggingEvent);
|
||||
|
||||
// Then
|
||||
assertThat(val).doesNotContain("java.lang.Exception: Bogus exception");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithParenthesis() {
|
||||
// Given
|
||||
ILoggingEvent loggingEvent = new LoggingEvent(
|
||||
ch.qos.logback.core.pattern.FormattingConverter.class.getName(),
|
||||
logger,
|
||||
Level.INFO,
|
||||
"Some message",
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
// When
|
||||
patternLayout.setPattern("\\(%msg:%msg\\) %msg");
|
||||
patternLayout.start();
|
||||
String val = patternLayout.doLayout(loggingEvent);
|
||||
|
||||
// Then
|
||||
assertThat(val).isEqualTo("(Some message:Some message) Some message");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithLettersComingFromLog4j() {
|
||||
// Given
|
||||
// Letters: p = level and c = logger
|
||||
String pattern = "%d %p [%t] %c{30} - %m%n";
|
||||
|
||||
// When
|
||||
patternLayout.setPattern(pattern);
|
||||
patternLayout.start();
|
||||
String val = patternLayout.doLayout(getEventObject());
|
||||
|
||||
// Then
|
||||
// 2021-02-01 22:38:06,212 INFO [main] c.q.l.pattern.EfficientPatternLayoutTest - Some message
|
||||
String regex = ClassicTestConstants.ISO_REGEX + " INFO " + MAIN_REGEX + " c.q.l.c.p.EfficientPatternLayoutTest - Some message\\s*";
|
||||
assertThat(val).matches(regex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mdcWithDefaultValue() {
|
||||
// Given
|
||||
String pattern = "%msg %mdc{foo} %mdc{bar:-[null]}";
|
||||
MDC.put("foo", "foo");
|
||||
|
||||
// When
|
||||
patternLayout.setPattern(OptionHelper.substVars(pattern, loggerContext));
|
||||
patternLayout.start();
|
||||
String val = patternLayout.doLayout(getEventObject());
|
||||
|
||||
// Then
|
||||
assertThat(val).isEqualTo("Some message foo [null]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contextNameTest() {
|
||||
// Given
|
||||
String pattern = "%contextName";
|
||||
|
||||
// When
|
||||
patternLayout.setPattern(pattern);
|
||||
patternLayout.start();
|
||||
|
||||
loggerContext.setName("aValue");
|
||||
String val = patternLayout.doLayout(getEventObject());
|
||||
|
||||
// Then
|
||||
assertThat(val).isEqualTo("aValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cnTest() {
|
||||
// Given
|
||||
String pattern = "%cn";
|
||||
|
||||
// When
|
||||
patternLayout.setPattern(pattern);
|
||||
patternLayout.start();
|
||||
|
||||
loggerContext.setName("aValue");
|
||||
String val = patternLayout.doLayout(getEventObject());
|
||||
|
||||
// Then
|
||||
assertEquals("aValue", val);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConversionRuleSupportInPatternLayout() throws JoranException {
|
||||
// Given
|
||||
String configurationFile = "src/test/resources/joran/conversionRule/patternLayout0.xml";
|
||||
String msg = "Simon says";
|
||||
|
||||
// When
|
||||
configure(configurationFile);
|
||||
logger.debug(msg);
|
||||
|
||||
// Then
|
||||
StringListAppender<ILoggingEvent> sla = (StringListAppender<ILoggingEvent>) root.getAppender("LIST");
|
||||
assertThat(sla).isNotNull();
|
||||
assertThat(sla.strList).containsExactly(SampleConverter.SAMPLE_STR + " - " + msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void smokeReplace() {
|
||||
// Given
|
||||
String pattern = "%replace(a1234b){'\\d{4}', 'XXXX'}";
|
||||
|
||||
// When
|
||||
patternLayout.setPattern(pattern);
|
||||
patternLayout.start();
|
||||
String val = patternLayout.doLayout(getEventObject());
|
||||
|
||||
// Then
|
||||
assertThat(val).isEqualTo("aXXXXb");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceNewline() {
|
||||
// Given
|
||||
String pattern = "%replace(A\nB){'\n', '\n\t'}";
|
||||
ILoggingEvent loggingEvent = new LoggingEvent(
|
||||
ch.qos.logback.core.pattern.FormattingConverter.class.getName(),
|
||||
logger,
|
||||
Level.INFO,
|
||||
"",
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
String substPattern = OptionHelper.substVars(pattern, null, loggerContext);
|
||||
assertEquals(pattern, substPattern);
|
||||
|
||||
// When
|
||||
patternLayout.setPattern(substPattern);
|
||||
patternLayout.start();
|
||||
String val = patternLayout.doLayout(loggingEvent);
|
||||
|
||||
// Then
|
||||
assertThat(val).isEqualTo("A\n\tB");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceWithJoran() throws JoranException {
|
||||
// Given
|
||||
String configurationFile = "src/test/resources/joran/pattern/replace0.xml";
|
||||
String msg = "And the number is 4111111111110000, expiring on 12/2010";
|
||||
|
||||
// When
|
||||
configure(configurationFile);
|
||||
logger.debug(msg);
|
||||
|
||||
// Then
|
||||
StringListAppender<ILoggingEvent> sla = (StringListAppender<ILoggingEvent>) root.getAppender("LIST");
|
||||
assertThat(sla).isNotNull();
|
||||
assertThat(sla.strList).containsExactly("And the number is XXXX, expiring on 12/2010");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceWithJoran_NEWLINE() throws JoranException {
|
||||
// Given
|
||||
String configurationFile = "src/test/resources/joran/pattern/replaceNewline.xml";
|
||||
String msg = "A\nC";
|
||||
|
||||
// When
|
||||
loggerContext.putProperty("TAB", "\t");
|
||||
configure(configurationFile);
|
||||
logger.debug(msg);
|
||||
|
||||
// Then
|
||||
StringListAppender<ILoggingEvent> sla = (StringListAppender<ILoggingEvent>) root.getAppender("LIST");
|
||||
assertThat(sla).isNotNull();
|
||||
assertThat(sla.strList).containsExactly("A\n\tC");
|
||||
}
|
||||
|
||||
private void configure(String file) throws JoranException {
|
||||
JoranConfigurator jc = new JoranConfigurator();
|
||||
jc.setContext(loggerContext);
|
||||
jc.doConfigure(file);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package ch.qos.logback.core.pattern;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static java.nio.ByteBuffer.wrap;
|
||||
import static java.util.Arrays.copyOf;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class EncoderTest {
|
||||
|
||||
@Test
|
||||
void shouldHandleEmptyInput() {
|
||||
// Given
|
||||
Encoder encoder = Encoders.threadLocal();
|
||||
String expected = "ABC";
|
||||
|
||||
// When
|
||||
encoder.encode(new StringBuilder(expected));
|
||||
|
||||
// Then
|
||||
ByteBuffer actual = encoder.getBuffer();
|
||||
ByteBuffer actual1 = wrap(expected.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(actual).isEqualTo(actual1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncode() {
|
||||
// Given
|
||||
Encoder encoder = Encoders.threadLocal();
|
||||
String expected = "ABC";
|
||||
|
||||
// When
|
||||
encoder.encode(new StringBuilder(expected));
|
||||
|
||||
// Then
|
||||
ByteBuffer actual = encoder.getBuffer();
|
||||
ByteBuffer actual1 = wrap(expected.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(actual).isEqualTo(actual1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldClearInternalBuffer() {
|
||||
// Given
|
||||
Encoder encoder = new Encoder();
|
||||
|
||||
StringBuilder firstText = new StringBuilder("first");
|
||||
StringBuilder secondText = new StringBuilder("second");
|
||||
|
||||
encoder.encode(firstText);
|
||||
assertThat(encoder.getBuffer()).isEqualTo(wrap(firstText.toString().getBytes()));
|
||||
|
||||
// When
|
||||
encoder.clear();
|
||||
|
||||
// Then
|
||||
assertThat(encoder.getBuffer().position()).isZero();
|
||||
|
||||
encoder.encode(secondText);
|
||||
assertThat(encoder.getBuffer()).isEqualTo(wrap(secondText.toString().getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncodeTextLargerThanTempArray() {
|
||||
// Given
|
||||
Encoder encoder = new Encoder();
|
||||
|
||||
String toEncode = RandomStringUtils.randomAlphanumeric(4 * 1024 + 41);
|
||||
ByteBuffer expected = wrap(toEncode.getBytes());
|
||||
|
||||
// When
|
||||
encoder.encode(new StringBuilder(toEncode));
|
||||
|
||||
// Then
|
||||
assertThat(encoder.getBuffer()).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTruncateLongerMessages() {
|
||||
// Given
|
||||
Encoder encoder = new Encoder();
|
||||
|
||||
String toEncode = RandomStringUtils.randomAlphanumeric(40 * 1024 + 41);
|
||||
ByteBuffer expected = wrap(
|
||||
copyOf(
|
||||
toEncode.getBytes(),
|
||||
10 * 1024
|
||||
)
|
||||
);
|
||||
|
||||
// When
|
||||
encoder.encode(new StringBuilder(toEncode));
|
||||
|
||||
// Then
|
||||
assertThat(encoder.getBuffer()).isEqualTo(expected);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package ch.qos.logback.core.pattern;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class EncodersTest {
|
||||
|
||||
@Test
|
||||
void shouldClearBufferBeforeUse() {
|
||||
// Given
|
||||
Encoder encoder = Encoders.threadLocal();
|
||||
encoder.encode(new StringBuilder("abc"));
|
||||
|
||||
// When
|
||||
Encoder actual = Encoders.threadLocal();
|
||||
|
||||
// Then
|
||||
assertThat(actual.getBuffer().position()).isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnSameBufferInsideSameThread() {
|
||||
// When
|
||||
Encoder encoder1 = Encoders.threadLocal();
|
||||
Encoder encoder2 = Encoders.threadLocal();
|
||||
|
||||
// Then
|
||||
assertThat(encoder1).isSameAs(encoder2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnDifferentBufferInsideDifferentThreads() throws InterruptedException {
|
||||
// Given
|
||||
AtomicReference<Encoder> captor1 = new AtomicReference<>();
|
||||
AtomicReference<Encoder> captor2 = new AtomicReference<>();
|
||||
|
||||
// When
|
||||
Thread thread1 = new Thread(() -> captor1.set(Encoders.threadLocal()));
|
||||
thread1.start();
|
||||
thread1.join();
|
||||
|
||||
Thread thread2 = new Thread(() -> captor2.set(Encoders.threadLocal()));
|
||||
thread2.start();
|
||||
thread2.join();
|
||||
|
||||
// Then
|
||||
assertThat(captor1.get()).isNotNull();
|
||||
assertThat(captor2.get()).isNotNull();
|
||||
|
||||
assertThat(captor1.get()).isNotSameAs(captor2.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package ch.qos.logback.core.pattern;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class StringBuildersTest {
|
||||
|
||||
@Test
|
||||
void shouldClearBufferBeforeUse() {
|
||||
// Given
|
||||
StringBuilder stringBuilder = StringBuilders.threadLocal();
|
||||
stringBuilder.append("abc");
|
||||
|
||||
// When
|
||||
StringBuilder actual = StringBuilders.threadLocal();
|
||||
|
||||
// Then
|
||||
assertThat(actual).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnSameBufferInsideSameThread() {
|
||||
// When
|
||||
StringBuilder stringBuilder1 = StringBuilders.threadLocal();
|
||||
StringBuilder stringBuilder2 = StringBuilders.threadLocal();
|
||||
|
||||
// Then
|
||||
assertThat(stringBuilder1).isSameAs(stringBuilder2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnDifferentBufferInsideDifferentThreads() throws InterruptedException {
|
||||
// Given
|
||||
AtomicReference<StringBuilder> captor1 = new AtomicReference<>();
|
||||
AtomicReference<StringBuilder> captor2 = new AtomicReference<>();
|
||||
|
||||
// When
|
||||
Thread thread1 = new Thread(() -> captor1.set(StringBuilders.threadLocal()));
|
||||
thread1.start();
|
||||
thread1.join();
|
||||
|
||||
Thread thread2 = new Thread(() -> captor2.set(StringBuilders.threadLocal()));
|
||||
thread2.start();
|
||||
thread2.join();
|
||||
|
||||
// Then
|
||||
assertThat(captor1.get()).isNotNull();
|
||||
assertThat(captor2.get()).isNotNull();
|
||||
|
||||
assertThat(captor1.get()).isNotSameAs(captor2.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package pl.tkowalcz.tjahzi.logback;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||
import com.google.monitoring.runtime.instrumentation.AllocationRecorder;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testcontainers.containers.BindMode;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.wait.strategy.Wait;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
import org.testcontainers.shaded.com.google.common.util.concurrent.Uninterruptibles;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@Testcontainers
|
||||
public class AllocationTest {
|
||||
|
||||
@Container
|
||||
public GenericContainer loki = new GenericContainer("grafana/loki:latest")
|
||||
.withCommand("-config.file=/etc/loki-config.yaml")
|
||||
.withClasspathResourceMapping("loki-config.yaml",
|
||||
"/etc/loki-config.yaml",
|
||||
BindMode.READ_ONLY)
|
||||
.waitingFor(
|
||||
Wait.forHttp("/ready")
|
||||
.forPort(3100)
|
||||
)
|
||||
.withExposedPorts(3100);
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void shouldSendData() throws Exception {
|
||||
AtomicLong totalAllocatedMemory = new AtomicLong();
|
||||
Map<String, AtomicLong> allocatedMemory = new HashMap<>();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
allocatedMemory
|
||||
.entrySet()
|
||||
.stream()
|
||||
.sorted(Comparator.<Map.Entry<String, AtomicLong>>comparingLong(e -> e.getValue().get()).reversed())
|
||||
.limit(30)
|
||||
.forEach(System.out::println);
|
||||
|
||||
System.out.println("totalAllocatedMemory = " + totalAllocatedMemory);
|
||||
}));
|
||||
|
||||
// Given
|
||||
System.setProperty("loki.host", loki.getHost());
|
||||
System.setProperty("loki.port", loki.getFirstMappedPort().toString());
|
||||
|
||||
URI uri = getClass()
|
||||
.getClassLoader()
|
||||
.getResource("basic-appender-test-configuration.xml")
|
||||
.toURI();
|
||||
|
||||
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
|
||||
JoranConfigurator configurator = new JoranConfigurator();
|
||||
configurator.setContext(context);
|
||||
|
||||
context.reset();
|
||||
configurator.doConfigure(uri.toURL());
|
||||
|
||||
String logLine = "Cupcake ipsum dolor sit amet cake wafer. " +
|
||||
"Souffle jelly beans biscuit topping. " +
|
||||
"Danish bonbon gummies powder caramels. ";
|
||||
|
||||
Logger logger = context.getLogger(AllocationTest.class);
|
||||
|
||||
// When
|
||||
System.out.println("Warmup...");
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
logger.info(logLine);
|
||||
Uninterruptibles.sleepUninterruptibly(10, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
AllocationRecorder.addSampler((count, desc, newObj, size) -> {
|
||||
if (desc.contains("AllocationTest")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (desc.contains("instrumentation")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (desc.contains("asm")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (desc.contains("Object")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(desc.contains("LoggingEvent")) {
|
||||
return;
|
||||
}
|
||||
|
||||
allocatedMemory.computeIfAbsent(desc, __ -> new AtomicLong()).addAndGet(size);
|
||||
totalAllocatedMemory.addAndGet(size);
|
||||
});
|
||||
|
||||
System.out.println("Test...");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
logger.info(logLine);
|
||||
Uninterruptibles.sleepUninterruptibly(10, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
// Then
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@
|
||||
<host>${loki.host}</host>
|
||||
<port>${loki.port}</port>
|
||||
|
||||
<encoder>
|
||||
<efficientLayout>
|
||||
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</efficientLayout>
|
||||
|
||||
<header>
|
||||
<name>X-Org-Id</name>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
/**
|
||||
* Logback: the reliable, generic, fast and flexible logging framework.
|
||||
* Copyright (C) 1999-2015, QOS.ch. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are dual-licensed under
|
||||
* either the terms of the Eclipse Public License v1.0 as published by
|
||||
* the Eclipse Foundation
|
||||
*
|
||||
* or (per the licensee's choosing)
|
||||
*
|
||||
* under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation.
|
||||
*/
|
||||
-->
|
||||
<configuration>
|
||||
|
||||
<conversionRule conversionWord="sample"
|
||||
converterClass="ch.qos.logback.classic.testUtil.SampleConverter"/>
|
||||
|
||||
<appender name="LIST" class="ch.qos.logback.core.testUtil.StringListAppender">
|
||||
<layout class="ch.qos.logback.core.pattern.EfficientPatternLayout">
|
||||
<Pattern>%sample - %msg</Pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<root level="debug">
|
||||
<appender-ref ref="LIST"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,31 @@
|
||||
<!--
|
||||
/**
|
||||
* Logback: the reliable, generic, fast and flexible logging framework.
|
||||
* Copyright (C) 1999-2015, QOS.ch. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are dual-licensed under
|
||||
* either the terms of the Eclipse Public License v1.0 as published by
|
||||
* the Eclipse Foundation
|
||||
*
|
||||
* or (per the licensee's choosing)
|
||||
*
|
||||
* under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation.
|
||||
*/
|
||||
-->
|
||||
<configuration>
|
||||
|
||||
<conversionRule conversionWord="sample"
|
||||
converterClass="ch.qos.logback.classic.testUtil.SampleConverter"/>
|
||||
|
||||
<appender name="LIST" class="ch.qos.logback.core.testUtil.StringListAppender">
|
||||
<layout class="ch.qos.logback.core.pattern.EfficientPatternLayout">
|
||||
<Pattern>%replace(%msg){'\d{14,16}', 'XXXX'}</Pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<root level="debug">
|
||||
<appender-ref ref="LIST"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
/**
|
||||
* Logback: the reliable, generic, fast and flexible logging framework.
|
||||
* Copyright (C) 1999-2015, QOS.ch. All rights reserved.
|
||||
*
|
||||
* This program and the accompanying materials are dual-licensed under
|
||||
* either the terms of the Eclipse Public License v1.0 as published by
|
||||
* the Eclipse Foundation
|
||||
*
|
||||
* or (per the licensee's choosing)
|
||||
*
|
||||
* under the terms of the GNU Lesser General Public License version 2.1
|
||||
* as published by the Free Software Foundation.
|
||||
*/
|
||||
-->
|
||||
<configuration>
|
||||
|
||||
<conversionRule conversionWord="sample"
|
||||
converterClass="ch.qos.logback.classic.testUtil.SampleConverter"/>
|
||||
|
||||
<appender name="LIST" class="ch.qos.logback.core.testUtil.StringListAppender">
|
||||
<layout class="ch.qos.logback.core.pattern.EfficientPatternLayout">
|
||||
<Pattern>%replace(%msg){'(\n)', '$1${TAB}'}</Pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<root level="debug">
|
||||
<appender-ref ref="LIST"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -3,9 +3,7 @@
|
||||
<host>${loki.host}</host>
|
||||
<port>${loki.port}</port>
|
||||
|
||||
<encoder>
|
||||
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
|
||||
</encoder>
|
||||
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
|
||||
|
||||
<header>
|
||||
<name>X-Org-Id</name>
|
||||
|
||||
Reference in new issue
Block a user