/* * ============LICENSE_START======================================================= * Integrity Audit * ================================================================================ * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============LICENSE_END========================================================= */ package org.onap.policy.common.utils.test.log.logback; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; public class ExtractAppenderTest { /** * Milliseconds to wait for a thread to terminate. */ private static final long THREAD_WAIT_MS = 5000l; private static Logger logger; private List threads; @BeforeClass public static void setUpBeforeClass() throws Exception { logger = (Logger) LoggerFactory.getLogger(ExtractAppenderTest.class); logger.setLevel(Level.INFO); } @Before public void setUp() throws Exception { threads = new LinkedList<>(); } @After public void tearDown() throws Exception { logger.detachAndStopAllAppenders(); for (Thread p : threads) { p.interrupt(); p.join(THREAD_WAIT_MS); } } @Test public void testExtractAppender() { AtomicInteger count = new AtomicInteger(0); ExtractAppender p = new ExtractAppender() { @Override protected void append(ILoggingEvent event) { count.incrementAndGet(); super.append(event); } }; addAppender(p); logger.info("hello"); logger.info("world"); // "append" should always be called assertEquals(2, count.get()); // appender with no patterns - everything should match assertEquals(strList("hello", "world"), p.getExtracted()); // add a pattern and verify match p.setPattern("abc[0-9]"); logger.info("hello abc1"); // this should not match logger.info("hello def2"); assertEquals(4, count.get()); assertEquals(strList("hello", "world", "abc1"), p.getExtracted()); } @Test public void testExtractAppenderStringArray() { AtomicInteger count = new AtomicInteger(0); ExtractAppender p = new ExtractAppender("abc[0-9]", "def[0-9]") { @Override protected void append(ILoggingEvent event) { count.incrementAndGet(); super.append(event); } }; addAppender(p); logger.info("hello abc1 world"); logger.info("world ghi2 world"); // no match logger.info("world def3 world"); logger.info("hello abc4"); logger.info("abc5 world"); logger.info("hello def6"); logger.info("ghi7 world"); // no match logger.info("def8 world"); // "append" should always be called assertEquals(8, count.get()); assertEquals(strList("abc1", "def3", "abc4", "abc5", "def6", "def8"), p.getExtracted()); p.setPattern("ghi[0-9]"); logger.info("hello abc9"); logger.info("hello ghi9"); // this should not match logger.info("hello xyz"); assertEquals(11, count.get()); assertEquals(strList("abc1", "def3", "abc4", "abc5", "def6", "def8", "abc9", "ghi9"), p.getExtracted()); } @Test public void testExtractAppenderQueueStringArray() { // no. of matches allowed in the list int nallowed = 3; AtomicInteger count = new AtomicInteger(0); LinkedList queue = new LinkedList() { private static final long serialVersionUID = 1L; @Override public boolean offer(String e) { if(count.incrementAndGet() <= nallowed) { return super.offer(e); } else { return false; } } }; ExtractAppender p = new ExtractAppender(queue, "abc[0-9]"); addAppender(p); // these shouldn't match for(int x = 0; x < 10; ++x) { logger.info("xyz"); } int nmatches = 10; LinkedList expected = new LinkedList<>(); for(int x = 0; x < nmatches; ++x) { String msg = "abc" + x; logger.info(msg + " world"); if(x < nallowed) { expected.add(msg); } } // "offer" should always be called for a match assertEquals(nmatches, count.get()); assertEquals(expected, p.getExtracted()); } @Test public void testAppendILoggingEvent_NoPatterns() { ExtractAppender p = makeAppender(); logger.info("hello"); logger.info("world"); assertEquals(strList("hello", "world"), p.getExtracted()); } @Test public void testAppendILoggingEvent_MatchFirstPattern() { ExtractAppender p = makeAppender("abc[0-9]", "def[0-9]"); logger.info("hello abc1"); logger.info("world xyz2"); assertEquals(strList("abc1"), p.getExtracted()); } @Test public void testAppendILoggingEvent_MatchLastPattern() { ExtractAppender p = makeAppender("abc[0-9]", "def[0-9]"); logger.info("hello def1"); logger.info("world xyz2"); assertEquals(strList("def1"), p.getExtracted()); } @Test public void testAppendILoggingEvent_Group1() { ExtractAppender p = makeAppender("hello (abc)|(xyz)", "def[0-9]"); logger.info("hello abc, world!"); logger.info("world abc"); assertEquals(strList("abc"), p.getExtracted()); } @Test public void testAppendILoggingEvent_Group3() { ExtractAppender p = makeAppender("hello (abc)|(pdq)|(xyz)", "def[0-9]"); logger.info("say hello xyz, world!"); logger.info("world abc"); assertEquals(strList("xyz"), p.getExtracted()); } @Test public void testAppendILoggingEvent_NoGroup() { ExtractAppender p = makeAppender("hello abc"); logger.info("say hello abc, world!"); logger.info("world abc"); assertEquals(strList("hello abc"), p.getExtracted()); } @Test public void testGetExtracted() { ExtractAppender p = makeAppender("abc[1-9]"); logger.info("hello abc1 world"); logger.info("world ghi2 world"); // no match logger.info("hello abc3"); List oldlst = p.getExtracted(); assertEquals(strList("abc1", "abc3"), oldlst); assertEquals(oldlst, p.getExtracted()); logger.info("abc9"); assertEquals(strList("abc1", "abc3", "abc9"), p.getExtracted()); } @Test public void testClearExtractions() { ExtractAppender p = makeAppender("abc[1-9]"); logger.info("hello abc1 world"); logger.info("world ghi2 world"); logger.info("hello abc3"); assertEquals(strList("abc1", "abc3"), p.getExtracted()); p.clearExtractions(); // list should be empty now assertEquals(strList(), p.getExtracted()); logger.info("hello abc4 world"); logger.info("world ghi5 world"); logger.info("hello abc6"); // list should only contain the new items assertEquals(strList("abc4", "abc6"), p.getExtracted()); } @Test public void testSetPattern() { ExtractAppender p = makeAppender("abc[1-9]"); logger.info("hello abc1 world"); logger.info("world ghi2 world"); // no match logger.info("hello abc3"); assertEquals(strList("abc1", "abc3"), p.getExtracted()); p.setPattern("ghi[0-9]"); logger.info("world ghi4 world"); // this should match now logger.info("hello abc5"); // this should still match logger.info("hello xyz5"); // no match assertEquals(strList("abc1", "abc3", "ghi4", "abc5"), p.getExtracted()); } /** * Launches threads doing everything in parallel to ensure nothing crashes. * * @throws Exception */ @Test public void test_MultiThreaded() throws Exception { // when to stop long tend = System.currentTimeMillis() + 250; // maximum number of items allowed in the extraction list int maxItems = 10; // this will be set if one of the threads generates an error AtomicBoolean err = new AtomicBoolean(false); // extracted messages go here - this is a finite-length queue since // we don't know how many messages may actually be logged LinkedList queue = new LinkedList() { private static final long serialVersionUID = 1L; @Override public boolean offer(String e) { if (size() < maxItems) { return super.offer(e); } else { return false; } } }; ExtractAppender app = new ExtractAppender(queue, "abc[1-9]"); addAppender(app); // create some threads to add another pattern addThread(tend, err, xtxt -> { app.setPattern("def[0-9]"); }); // create some threads to log "abc" messages addThread(tend, err, xtxt -> { logger.info("hello abc" + xtxt + "world!"); }); // create some threads to log "def" messages addThread(tend, err, xtxt -> { logger.info("hello def" + xtxt + "world!"); }); // create some threads to get extractions addThread(tend, err, xtxt -> { app.getExtracted(); }); // create some threads to clear extractions addThread(tend, err, xtxt -> { app.clearExtractions(); // don't want to clear the list too frequently // so sleep a bit in between try { Thread.sleep(10 + Integer.valueOf(xtxt)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw e; } }); /* * Finally ready to start. */ // start all of the threads for (Thread t : threads) { t.setDaemon(true); t.start(); } // wait for each thread to stop for (Thread t : threads) { t.join(THREAD_WAIT_MS); assertFalse(t.isAlive()); } // ensure none of the threads threw an exception assertFalse(err.get()); } /** * Adds multiple threads to perform some function repeatedly until the given * time is reached. * * @param tend * time, in milliseconds, when the test should terminate * @param haderr * this will be set to {@code true} if the function throws an * exception other than an InterruptedException * @param func * function to be repeatedly invoked */ private void addThread(long tend, AtomicBoolean haderr, VoidFunction func) { // number of threads of each type to create int neach = 3; for (int x = 0; x < neach; ++x) { String xtxt = String.valueOf(x); threads.add(new Thread() { @Override public void run() { try { while (System.currentTimeMillis() < tend) { func.apply(xtxt); } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } catch (Exception ex) { haderr.set(true); } } }); } } /** * Makes an appender that recognizes the given set of strings. * * @param strings * regular expressions to be matched * @return a new appender */ private ExtractAppender makeAppender(String... strings) { ExtractAppender p = new ExtractAppender(strings); addAppender(p); return p; } /** * Adds an appender to the logger. * * @param app * appender to be added */ private void addAppender(ExtractAppender app) { app.setContext(logger.getLoggerContext()); app.start(); logger.addAppender(app); } /** * Converts an array of strings into a list of strings. * * @param strings * array of strings * @return a list of the strings */ private List strList(String... strings) { return Arrays.asList(strings); } @FunctionalInterface public interface VoidFunction { public void apply(String text) throws InterruptedException; } }