diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/Logger.java b/logback-classic/src/main/java/ch/qos/logback/classic/Logger.java index 757a970126..7793cc39cb 100644 --- a/logback-classic/src/main/java/ch/qos/logback/classic/Logger.java +++ b/logback-classic/src/main/java/ch/qos/logback/classic/Logger.java @@ -72,7 +72,7 @@ public final class Logger /** * It is assumed that once the 'aai' variable is set to a non-null value, it * will never be reset to null. it is further assumed that only place where the - * 'aai'ariable is set is within the addAppender method. This method is + * 'aai variable is set is within the addAppender method. This method is * synchronized on 'this' (Logger) protecting against simultaneous * re-configuration of this logger (a very unlikely scenario). * diff --git a/logback-classic/src/main/java/ch/qos/logback/classic/pattern/MaskedKeyValuePairConverter.java b/logback-classic/src/main/java/ch/qos/logback/classic/pattern/MaskedKeyValuePairConverter.java index 9277943497..5409e1da20 100644 --- a/logback-classic/src/main/java/ch/qos/logback/classic/pattern/MaskedKeyValuePairConverter.java +++ b/logback-classic/src/main/java/ch/qos/logback/classic/pattern/MaskedKeyValuePairConverter.java @@ -30,6 +30,8 @@ * Assuming the specified key is k2, and the kvp list of an event contains {k1, v1}, {k2, v2}, the String output * will be "k1=v1 k2=XXX", without the quotes. * + * Value quotes can be specified as the first option, e.g %maskedKvp{SINGLE, k1} + * * @author Ceki Gülcü * @since 1.5.7 */ diff --git a/logback-classic/src/test/input/joran/conversionRule/conversionRuleAtEnd.xml b/logback-classic/src/test/input/joran/conversionRule/conversionRuleAtEnd.xml new file mode 100644 index 0000000000..4e06dac0de --- /dev/null +++ b/logback-classic/src/test/input/joran/conversionRule/conversionRuleAtEnd.xml @@ -0,0 +1,16 @@ + + + + + + %sample - %msg + + + + + + + + + \ No newline at end of file diff --git a/logback-classic/src/test/input/joran/conversionRule/conversionRuleIncluded.xml b/logback-classic/src/test/input/joran/conversionRule/conversionRuleIncluded.xml new file mode 100644 index 0000000000..a28eba2e08 --- /dev/null +++ b/logback-classic/src/test/input/joran/conversionRule/conversionRuleIncluded.xml @@ -0,0 +1,16 @@ + + + + + + %sample - %msg + + + + + + + + + \ No newline at end of file diff --git a/logback-classic/src/test/input/joran/conversionRule/conversionRuleTop0.xml b/logback-classic/src/test/input/joran/conversionRule/conversionRuleTop0.xml new file mode 100644 index 0000000000..3ed44138ac --- /dev/null +++ b/logback-classic/src/test/input/joran/conversionRule/conversionRuleTop0.xml @@ -0,0 +1,15 @@ + + + + + + %sample - %msg + + + + + + + + + \ No newline at end of file diff --git a/logback-classic/src/test/java/ch/qos/logback/classic/PatternLayoutTest.java b/logback-classic/src/test/java/ch/qos/logback/classic/PatternLayoutTest.java index 00238c61e2..56fcf00ecd 100644 --- a/logback-classic/src/test/java/ch/qos/logback/classic/PatternLayoutTest.java +++ b/logback-classic/src/test/java/ch/qos/logback/classic/PatternLayoutTest.java @@ -236,6 +236,30 @@ public void testConversionRuleSupportInPatternLayout() throws JoranException { assertEquals(SampleConverter.SAMPLE_STR + " - " + msg, sla.strList.get(0)); } + @Test + public void testConversionRuleAtEnd() throws JoranException { + configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "conversionRule/conversionRuleAtEnd.xml"); + root.getAppender("LIST"); + String msg = "testConversionRuleAtEnd"; + logger.debug(msg); + StringListAppender sla = (StringListAppender) root.getAppender("LIST"); + assertNotNull(sla); + assertEquals(1, sla.strList.size()); + assertEquals(SampleConverter.SAMPLE_STR + " - " + msg, sla.strList.get(0)); + } + + @Test + public void testConversionRuleInIncluded() throws JoranException { + configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "conversionRule/conversionRuleTop0.xml"); + root.getAppender("LIST"); + String msg = "testConversionRuleInIncluded"; + logger.debug(msg); + StringListAppender sla = (StringListAppender) root.getAppender("LIST"); + assertNotNull(sla); + assertEquals(1, sla.strList.size()); + assertEquals(SampleConverter.SAMPLE_STR + " - " + msg, sla.strList.get(0)); + } + @Test public void smokeReplace() { pl.setPattern("%replace(a1234b){'\\d{4}', 'XXXX'}"); diff --git a/logback-core/src/test/java/ch/qos/logback/core/util/COWArrayListConcurrencyTest.java b/logback-core/src/test/java/ch/qos/logback/core/util/COWArrayListConcurrencyTest.java new file mode 100644 index 0000000000..8d8a2e7261 --- /dev/null +++ b/logback-core/src/test/java/ch/qos/logback/core/util/COWArrayListConcurrencyTest.java @@ -0,0 +1,188 @@ +/* + * Logback: the reliable, generic, fast and flexible logging framework. + * Copyright (C) 1999-2024, 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. + */ + +package ch.qos.logback.core.util; + +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.AppenderBase; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.ContextBase; +import ch.qos.logback.core.spi.AppenderAttachableImpl; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.locks.ReentrantLock; + +import static org.assertj.core.api.Fail.fail; + +@Disabled +public class COWArrayListConcurrencyTest { + + //private static final int LIST_SIZE = 1_000_000; + private static final int LOOP_LEN = 1_0; + private static final int RECONFIGURE_DELAY = 1; + + ReentrantLock reconfigureLock = new ReentrantLock(true); + ReentrantLock writeLock = new ReentrantLock(true); + + private static int THREAD_COUNT = 200; //Runtime.getRuntime().availableProcessors()*200; + //private static int THREAD_COUNT = 5000; + + private final ExecutorService tasksExecutor = Executors.newVirtualThreadPerTaskExecutor(); + LoopingRunnable[] loopingThreads = new LoopingRunnable[THREAD_COUNT]; + ReconfiguringThread[] reconfiguringThreads = new ReconfiguringThread[THREAD_COUNT]; + Future[] futures = new Future[THREAD_COUNT]; + + AppenderAttachableImpl aai = new AppenderAttachableImpl<>(); + Context context = new ContextBase(); + + void reconfigureWithDelay(AppenderAttachableImpl aai) { + try { + reconfigureLock.lock(); + aai.addAppender(makeNewNOPAppender()); + aai.addAppender(makeNewNOPAppender()); + delay(RECONFIGURE_DELAY); + aai.detachAndStopAllAppenders(); + } finally { + reconfigureLock.unlock(); + } + } + + private Appender makeNewNOPAppender() { + List longList = new ArrayList<>(); +// for (int j = 0; j < LIST_SIZE; j++) { +// longList.add(0L); +// } + Appender nopAppenderWithDelay = new NOPAppenderWithDelay<>(longList); + nopAppenderWithDelay.setContext(context); + nopAppenderWithDelay.start(); + return nopAppenderWithDelay; + } + + private void delay(int delay) { + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Test + void smoke() throws InterruptedException, ExecutionException { + + for (int i = 0; i < THREAD_COUNT; i++) { + System.out.println("i="+i); + ReconfiguringThread rt = new ReconfiguringThread(aai); + futures[i] = tasksExecutor.submit(rt); + reconfiguringThreads[i] = rt; + } + + for (int i = 0; i < THREAD_COUNT; i++) { + LoopingRunnable loopingThread = new LoopingRunnable(i, aai); + tasksExecutor.submit(loopingThread); + loopingThreads[i] = loopingThread; + } + + for (int i = 0; i < THREAD_COUNT; i++) { + futures[i].get(); + } + + //reconfiguringThread.join(); + Arrays.stream(loopingThreads).forEach(lt -> lt.active = false); + + } + + public class NOPAppenderWithDelay extends AppenderBase { + + List longList; + + NOPAppenderWithDelay(List longList) { + this.longList = new ArrayList<>(longList); + } + + int i = 0; + + @Override + protected void append(E eventObject) { + i++; + try { + writeLock.lock(); + if ((i & 0xF) == 0) { + delay(1); + } else { + //longList.stream().map(x-> x+1); + } + } finally { + writeLock.unlock(); + } + + } + + } + + class ReconfiguringThread extends Thread { + + AppenderAttachableImpl aai; + + ReconfiguringThread(AppenderAttachableImpl aai) { + this.aai = aai; + } + + public void run() { + Thread.yield(); + for (int i = 0; i < LOOP_LEN; i++) { + reconfigureWithDelay(aai); + } + } + + + } + + + class LoopingRunnable implements Runnable { + + int num; + AppenderAttachableImpl aai; + public boolean active = true; + + LoopingRunnable(int num, AppenderAttachableImpl aai) { + this.num = num; + this.aai = aai; + } + + public void run() { + System.out.println("LoopingRunnable.run.num="+num); + int i = 0; + while (active) { + if ((i & 0xFFFFF) == 0) { + long id = Thread.currentThread().threadId(); + System.out.println("thread=" + id + " reconfigure=" + i); + } + aai.appendLoopOnAppenders(Integer.toString(i)); + i++; + //Thread.yield(); + } + } + } + + +}