001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.sift;
015
016import static org.junit.Assert.assertEquals;
017import static org.junit.Assert.assertFalse;
018import static org.junit.Assert.assertNotNull;
019import static org.junit.Assert.assertNull;
020import static org.junit.Assert.assertTrue;
021import static ch.qos.logback.core.testUtil.CoreTestConstants.OUTPUT_DIR_PREFIX;
022import static org.assertj.core.api.Assertions.*;
023
024import java.util.List;
025
026import org.junit.After;
027import org.junit.Before;
028import org.junit.Ignore;
029import org.junit.Test;
030import org.slf4j.MDC;
031
032import ch.qos.logback.classic.ClassicConstants;
033import ch.qos.logback.classic.ClassicTestConstants;
034import ch.qos.logback.classic.Level;
035import ch.qos.logback.classic.Logger;
036import ch.qos.logback.classic.LoggerContext;
037import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
038import ch.qos.logback.classic.joran.JoranConfigurator;
039import ch.qos.logback.classic.spi.ILoggingEvent;
040import ch.qos.logback.classic.spi.LoggingEvent;
041import ch.qos.logback.core.Appender;
042import ch.qos.logback.core.Context;
043import ch.qos.logback.core.FileAppender;
044import ch.qos.logback.core.helpers.NOPAppender;
045import ch.qos.logback.core.joran.spi.JoranException;
046import ch.qos.logback.core.read.ListAppender;
047import ch.qos.logback.core.rolling.RollingFileAppender;
048import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP;
049import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
050import ch.qos.logback.core.sift.AppenderFactory;
051import ch.qos.logback.core.sift.AppenderTracker;
052import ch.qos.logback.core.spi.AbstractComponentTracker;
053import ch.qos.logback.core.spi.ComponentTracker;
054import ch.qos.logback.core.status.ErrorStatus;
055import ch.qos.logback.core.testUtil.CoreTestConstants;
056import ch.qos.logback.core.testUtil.RandomUtil;
057import ch.qos.logback.core.testUtil.StatusChecker;
058import ch.qos.logback.core.testUtil.StringListAppender;
059import ch.qos.logback.core.util.FileSize;
060import ch.qos.logback.core.util.StatusPrinter;
061
062public class SiftingAppenderTest {
063
064    static String SIFT_FOLDER_PREFIX = ClassicTestConstants.JORAN_INPUT_PREFIX + "sift/";
065
066    LoggerContext loggerContext = new LoggerContext();
067    Logger logger = loggerContext.getLogger(this.getClass().getName());
068    Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
069    StatusChecker statusChecker = new StatusChecker(loggerContext);
070    int diff = RandomUtil.getPositiveInt();
071    String randomOutputDir = OUTPUT_DIR_PREFIX + diff + "/";
072    int now = 0;
073
074    protected void configure(String file) throws JoranException {
075        JoranConfigurator jc = new JoranConfigurator();
076        jc.setContext(loggerContext);
077        jc.doConfigure(file);
078    }
079
080    @Before
081    public void setUp() {
082        MDC.clear();
083    }
084
085    @After
086    public void tearDown() {
087        MDC.clear();
088    }
089
090    @Test
091    public void unsetDefaultValueProperty() throws JoranException {
092        configure(SIFT_FOLDER_PREFIX + "unsetDefaultValueProperty.xml");
093        logger.debug("hello");
094        SiftingAppender sa = (SiftingAppender) root.getAppender("SIFT");
095        assertFalse(sa.isStarted());
096    }
097
098    @Test
099    public void smoke() throws JoranException {
100        configure(SIFT_FOLDER_PREFIX + "smoke.xml");
101        logger.debug("smoke");
102        Appender<ILoggingEvent> appender = getAppenderTracker().find("smokeDefault");
103        assertNotNull(appender);
104        ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) appender;
105        List<ILoggingEvent> eventList = listAppender.list;
106        assertEquals(1, listAppender.list.size());
107        assertEquals("smoke", eventList.get(0).getMessage());
108    }
109
110    private AppenderTracker<ILoggingEvent> getAppenderTracker() {
111        SiftingAppender ha = (SiftingAppender) root.getAppender("SIFT");
112        return ha.getAppenderTracker();
113    }
114
115    @Test
116    public void zeroNesting() throws JoranException {
117        configure(SIFT_FOLDER_PREFIX + "zeroNesting.xml");
118        logger.debug("hello");
119        logger.debug("hello");
120        logger.debug("hello");
121        logger.debug("hello");
122        logger.debug("hello");
123
124        Appender<ILoggingEvent> nopa = getAppenderTracker().find("zeroDefault");
125        assertNotNull(nopa);
126        assertThat(nopa).isInstanceOf(NOPAppender.class);
127        StatusPrinter.printInCaseOfErrorsOrWarnings(loggerContext);
128
129        statusChecker.assertContainsMatch(ErrorStatus.ERROR, "No nested appenders found");
130    }
131
132    @Test
133    public void multipleNesting() throws JoranException {
134        configure(SIFT_FOLDER_PREFIX + "multipleNesting.xml");
135        logger.debug("hello");
136        logger.debug("hello");
137        logger.debug("hello");
138
139        Appender<ILoggingEvent> listAppender = getAppenderTracker().find("multipleDefault");
140        StatusPrinter.printInCaseOfErrorsOrWarnings(loggerContext);
141
142        assertNotNull(listAppender);
143        statusChecker.assertContainsMatch(ErrorStatus.ERROR, "Only and only one appender can be nested");
144    }
145
146    @Test
147    public void defaultLayoutRule() throws JoranException {
148        configure(SIFT_FOLDER_PREFIX + "defaultLayoutRule.xml");
149        logger.debug("hello");
150        SiftingAppender ha = (SiftingAppender) root.getAppender("SIFT");
151        StringListAppender<ILoggingEvent> listAppender = (StringListAppender<ILoggingEvent>) ha.getAppenderTracker().find("default");
152
153        assertNotNull(listAppender);
154        List<String> strList = listAppender.strList;
155        assertEquals(1, strList.size());
156        assertEquals("DEBUG hello", strList.get(0));
157    }
158
159    @Test
160    public void fileAppenderCollision() throws JoranException, InterruptedException {
161        loggerContext.putProperty("DIR_PREFIX", randomOutputDir);
162        String key = "collision";
163        configure(SIFT_FOLDER_PREFIX + "fileAppender.xml");
164        SiftingAppender sa = (SiftingAppender) root.getAppender("SIFT");
165
166        long timestamp = System.currentTimeMillis();
167
168        MDC.put(key, "A-"+diff);
169        logNewEventViaSiftingAppender(sa, timestamp);
170        FileAppender<ILoggingEvent> fileAppenderA = (FileAppender<ILoggingEvent>) sa.getAppenderTracker().find("A-"+diff);
171        assertNotNull(fileAppenderA);
172        assertTrue(fileAppenderA.isStarted());
173        timestamp += ComponentTracker.DEFAULT_TIMEOUT + 1;
174        MDC.put(key, "B-"+diff);
175        logNewEventViaSiftingAppender(sa, timestamp);
176        assertFalse(fileAppenderA.isStarted());
177        
178        MDC.put(key, "A-"+diff);
179        timestamp += 1;
180        logNewEventViaSiftingAppender(sa, timestamp);
181        FileAppender<ILoggingEvent> fileAppenderA_2 = (FileAppender<ILoggingEvent>) sa.getAppenderTracker().find("A-"+diff);
182        assertTrue(fileAppenderA_2.isStarted());
183        
184    }
185
186    private void logNewEventViaSiftingAppender(SiftingAppender sa, long timestamp) {
187        LoggingEvent le = new LoggingEvent("x", logger, Level.INFO, "hello", null, null);
188        le.setTimeStamp(timestamp + ComponentTracker.DEFAULT_TIMEOUT + 1);
189        sa.doAppend(le);
190    }
191
192    @Test
193    public void testWholeCycle() throws JoranException {
194        String mdcKey = "cycle";
195        configure(SIFT_FOLDER_PREFIX + "completeCycle.xml");
196        MDC.put(mdcKey, "a");
197        logger.debug("smoke");
198        long timestamp = System.currentTimeMillis();
199        SiftingAppender sa = (SiftingAppender) root.getAppender("SIFT");
200        ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) sa.getAppenderTracker().find("a");
201        assertNotNull(listAppender);
202        List<ILoggingEvent> eventList = listAppender.list;
203        assertEquals(1, listAppender.list.size());
204        assertEquals("smoke", eventList.get(0).getMessage());
205
206        MDC.remove(mdcKey);
207        logNewEventViaSiftingAppender(sa, timestamp);
208        assertFalse(listAppender.isStarted());
209        assertEquals(1, sa.getAppenderTracker().allKeys().size());
210        assertTrue(sa.getAppenderTracker().allKeys().contains("cycleDefault"));
211    }
212
213    @Test
214    public void sessionFinalizationShouldCauseLingering() throws JoranException {
215        String mdcKey = "linger";
216        String mdcVal = "session" + diff;
217        configure(SIFT_FOLDER_PREFIX + "lingering.xml");
218        MDC.put(mdcKey, mdcVal);
219        logger.debug("linger 1");
220        logger.debug(ClassicConstants.FINALIZE_SESSION_MARKER, "linger 2");
221        long now = System.currentTimeMillis();
222        SiftingAppender sa = (SiftingAppender) root.getAppender("SIFT");
223        AppenderTracker<ILoggingEvent> tracker = sa.getAppenderTracker();
224
225        assertEquals(1, tracker.allKeys().size());
226        Appender<ILoggingEvent> appender = tracker.find(mdcVal);
227        assertTrue(appender.isStarted());
228
229        tracker.removeStaleComponents(now + AppenderTracker.LINGERING_TIMEOUT + 1);
230        // previously lingering appenders should be closed upon timeout
231        assertFalse(appender.isStarted());
232        // and they should be gone
233        assertEquals(0, tracker.allKeys().size());
234    }
235
236    @Test
237    public void localPropertiesShouldBeVisible() throws JoranException {
238        String mdcKey = "localProperty";
239        String mdcVal = "" + diff;
240        String msg = "localPropertiesShouldBeVisible";
241        String prefix = "Y";
242        configure(SIFT_FOLDER_PREFIX + "propertyPropagation.xml");
243        MDC.put(mdcKey, mdcVal);
244        logger.debug(msg);
245        SiftingAppender sa = (SiftingAppender) root.getAppender("SIFT");
246        StringListAppender<ILoggingEvent> listAppender = (StringListAppender<ILoggingEvent>) sa.getAppenderTracker().find(mdcVal);
247        assertNotNull(listAppender);
248        List<String> strList = listAppender.strList;
249        assertEquals(1, listAppender.strList.size());
250        assertEquals(prefix + msg, strList.get(0));
251    }
252
253    @Test
254    public void propertyDefinedWithinSiftElementShouldBeVisible() throws JoranException {
255        String mdcKey = "propertyDefinedWithinSift";
256        String mdcVal = "" + diff;
257        String msg = "propertyDefinedWithinSiftElementShouldBeVisible";
258        String prefix = "Y";
259        configure(SIFT_FOLDER_PREFIX + "propertyDefinedInSiftElement.xml");
260        MDC.put(mdcKey, mdcVal);
261        logger.debug(msg);
262        SiftingAppender sa = (SiftingAppender) root.getAppender("SIFT");
263        StringListAppender<ILoggingEvent> listAppender = (StringListAppender<ILoggingEvent>) sa.getAppenderTracker().find(mdcVal);
264        assertNotNull(listAppender);
265        List<String> strList = listAppender.strList;
266        assertEquals(1, listAppender.strList.size());
267        assertEquals(prefix + msg, strList.get(0));
268    }
269
270    @Test
271    public void compositePropertyShouldCombineWithinAndWithoutSiftElement() throws JoranException {
272        String mdcKey = "compositeProperty";
273        String mdcVal = "" + diff;
274        String msg = "compositePropertyShouldCombineWithinAndWithoutSiftElement";
275        String prefix = "composite";
276        configure(SIFT_FOLDER_PREFIX + "compositeProperty.xml");
277        MDC.put(mdcKey, mdcVal);
278        logger.debug(msg);
279        SiftingAppender sa = (SiftingAppender) root.getAppender("SIFT");
280        StringListAppender<ILoggingEvent> listAppender = (StringListAppender<ILoggingEvent>) sa.getAppenderTracker().find(mdcVal);
281        assertNotNull(listAppender);
282        List<String> strList = listAppender.strList;
283        assertEquals(1, listAppender.strList.size());
284        assertEquals(prefix + msg, strList.get(0));
285    }
286
287    @Test
288    public void maxAppendersCountPropertyShouldBeHonored() throws JoranException {
289        configure(SIFT_FOLDER_PREFIX + "maxAppenderCount.xml");
290        int max = 5;
291        SiftingAppender sa = (SiftingAppender) root.getAppender("SIFT");
292        String mdcKey = "max";
293        for (int i = 0; i <= max; i++) {
294            MDC.put(mdcKey, "" + (diff + i));
295            LoggingEvent event = new LoggingEvent("", logger, Level.DEBUG, "max" + i, null, null);
296            event.setTimeStamp(now);
297            sa.doAppend(event);
298            now += AbstractComponentTracker.WAIT_BETWEEN_SUCCESSIVE_REMOVAL_ITERATIONS;
299        }
300        AppenderTracker<ILoggingEvent> tracker = sa.getAppenderTracker();
301        assertEquals(max, tracker.allKeys().size());
302        assertNull(tracker.find("" + (diff + 0)));
303        for (int i = 1; i <= max; i++) {
304            assertNotNull(tracker.find("" + (diff + i)));
305        }
306    }
307
308    @Test
309    public void timeoutPropertyShouldBeHonored() throws JoranException, InterruptedException {
310        configure(SIFT_FOLDER_PREFIX + "timeout.xml");
311        long timeout = 30 * 1000;
312        SiftingAppender sa = (SiftingAppender) root.getAppender("SIFT");
313
314        LoggingEvent event = new LoggingEvent("", logger, Level.DEBUG, "timeout", null, null);
315        event.setTimeStamp(now);
316        sa.doAppend(event);
317
318        AppenderTracker<ILoggingEvent> tracker = sa.getAppenderTracker();
319
320        assertEquals(1, tracker.getComponentCount());
321
322        now += timeout + 1;
323        tracker.removeStaleComponents(now);
324        assertEquals(0, tracker.getComponentCount());
325        statusChecker.assertIsErrorFree();
326    }
327
328    // LOGBACK-1127
329    @Ignore
330    @Test
331    public void programmicSiftingAppender() {
332
333        SiftingAppender connectorAppender = new SiftingAppender();
334        connectorAppender.setContext(loggerContext);
335        connectorAppender.setName("SIFTING_APPENDER");
336
337        MDCBasedDiscriminator discriminator = new MDCBasedDiscriminator();
338        discriminator.setKey("SKEY");
339        discriminator.setDefaultValue("DEF_KEY");
340        discriminator.start();
341        connectorAppender.setDiscriminator(discriminator);
342
343        connectorAppender.setAppenderFactory(new AppenderFactory<ILoggingEvent>() {
344
345            @Override
346            public Appender<ILoggingEvent> buildAppender(Context context, String discriminatingValue) throws JoranException {
347
348                RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<ILoggingEvent>();
349                appender.setName("ROLLING_APPENDER_" + discriminatingValue);
350                appender.setContext(context);
351                appender.setFile("/var/logs/active_" + discriminatingValue + ".log");
352
353                TimeBasedRollingPolicy<ILoggingEvent> policy = new TimeBasedRollingPolicy<ILoggingEvent>();
354                policy.setContext(context);
355                policy.setMaxHistory(365);
356                policy.setFileNamePattern(CoreTestConstants.OUTPUT_DIR_PREFIX + "/logback1127/" + discriminatingValue + "_%d{yyyy_MM_dd}_%i.log");
357                policy.setParent(appender);
358                policy.setCleanHistoryOnStart(true);
359
360                SizeAndTimeBasedFNATP<ILoggingEvent> innerpolicy = new SizeAndTimeBasedFNATP<ILoggingEvent>();
361                innerpolicy.setContext(context);
362                innerpolicy.setMaxFileSize(FileSize.valueOf("5KB"));
363                innerpolicy.setTimeBasedRollingPolicy(policy);
364
365                policy.setTimeBasedFileNamingAndTriggeringPolicy(innerpolicy);
366                policy.start();
367                appender.setRollingPolicy(policy);
368
369                PatternLayoutEncoder pl = new PatternLayoutEncoder();
370                pl.setContext(context);
371                pl.setPattern("%d{yyyy/MM/dd'T'HH:mm:ss} %-5level - %msg\n");
372
373                pl.start();
374                appender.setEncoder(pl);
375
376                appender.start();
377                return appender;
378            }
379        });
380        connectorAppender.start();
381
382        ch.qos.logback.classic.Logger logger = loggerContext.getLogger("org.test");
383        logger.addAppender(connectorAppender);
384        logger.setLevel(Level.DEBUG);
385        logger.setAdditive(false);
386
387        MDC.put("SKEY", "K1");
388        logger.info("bla1");
389        MDC.clear();
390
391        MDC.put("SKEY", "K2");
392        logger.info("bla2");
393        MDC.clear();
394
395        StatusPrinter.print(loggerContext);
396
397    }
398}