1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    * <p>
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    * <p>
9    * or (per the licensee's choosing)
10   * <p>
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.joran;
15  
16  import ch.qos.logback.classic.*;
17  import ch.qos.logback.classic.joran.serializedModel.HardenedModelInputStream;
18  import ch.qos.logback.classic.model.ConfigurationModel;
19  import ch.qos.logback.classic.model.LoggerModel;
20  import ch.qos.logback.classic.spi.ILoggingEvent;
21  import ch.qos.logback.classic.spi.LoggingEvent;
22  import ch.qos.logback.classic.turbo.DebugUsersTurboFilter;
23  import ch.qos.logback.classic.turbo.NOPTurboFilter;
24  import ch.qos.logback.classic.turbo.TurboFilter;
25  import ch.qos.logback.classic.util.LogbackMDCAdapter;
26  import ch.qos.logback.core.ConsoleAppender;
27  import ch.qos.logback.core.CoreConstants;
28  import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
29  import ch.qos.logback.core.joran.action.ParamAction;
30  import ch.qos.logback.core.joran.spi.ActionException;
31  import ch.qos.logback.core.joran.spi.JoranException;
32  import ch.qos.logback.core.model.Model;
33  import ch.qos.logback.core.model.SerializeModelModel;
34  import ch.qos.logback.core.pattern.parser.Parser;
35  import ch.qos.logback.core.read.ListAppender;
36  import ch.qos.logback.core.spi.ErrorCodes;
37  import ch.qos.logback.core.spi.ScanException;
38  import ch.qos.logback.core.status.Status;
39  import ch.qos.logback.core.status.testUtil.StatusChecker;
40  import ch.qos.logback.core.testUtil.RandomUtil;
41  import ch.qos.logback.core.testUtil.StringListAppender;
42  import ch.qos.logback.core.util.CachingDateFormatter;
43  import ch.qos.logback.core.util.EnvUtil;
44  import ch.qos.logback.core.util.StatusPrinter;
45  import ch.qos.logback.core.util.StatusPrinter2;
46  import org.junit.jupiter.api.Disabled;
47  import org.junit.jupiter.api.Test;
48  import org.slf4j.MDC;
49  import org.slf4j.event.KeyValuePair;
50  import org.slf4j.spi.MDCAdapter;
51  
52  import java.io.Console;
53  import java.io.FileInputStream;
54  import java.io.IOException;
55  import java.nio.charset.Charset;
56  import java.text.SimpleDateFormat;
57  import java.util.Date;
58  
59  import static ch.qos.logback.core.CoreConstants.MODEL_CONFIG_FILE_EXTENSION;
60  import static ch.qos.logback.core.joran.sanity.AppenderWithinAppenderSanityChecker.NESTED_APPENDERS_WARNING;
61  import static ch.qos.logback.core.model.processor.ImplicitModelHandler.IGNORING_UNKNOWN_PROP;
62  import static ch.qos.logback.core.model.processor.ShutdownHookModelHandler.RENAME_WARNING;
63  import static ch.qos.logback.core.testUtil.CoreTestConstants.OUTPUT_DIR_PREFIX;
64  import static org.junit.jupiter.api.Assertions.*;
65  
66  public class JoranConfiguratorTest {
67  
68      LoggerContext loggerContext = new LoggerContext();
69      MDCAdapter mdcAdapter = new LogbackMDCAdapter();
70      Logger logger = loggerContext.getLogger(this.getClass().getName());
71      Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
72      StatusPrinter2 statusPrinter2 = new StatusPrinter2();
73      StatusChecker checker = new StatusChecker(loggerContext);
74      int diff = RandomUtil.getPositiveInt();
75  
76      void configure(String file) throws JoranException {
77          loggerContext.setMDCAdapter(mdcAdapter);
78          JoranConfigurator jc = new JoranConfigurator();
79          jc.setContext(loggerContext);
80          loggerContext.putProperty("diff", "" + diff);
81          jc.doConfigure(file);
82  
83      }
84  
85      @Test
86      public void simpleList() throws JoranException {
87          configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "simpleList.xml");
88          Logger logger = loggerContext.getLogger(this.getClass().getName());
89          Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
90          ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
91          assertNotNull(listAppender);
92          assertEquals(0, listAppender.list.size());
93          String msg = "hello world";
94          logger.debug(msg);
95          assertEquals(1, listAppender.list.size());
96          ILoggingEvent le = (ILoggingEvent) listAppender.list.get(0);
97          assertEquals(msg, le.getMessage());
98      }
99  
100 
101     @Test
102     public void asyncWithMultipleAppendersInRoot() throws JoranException {
103         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "async/logback_1614.xml");
104         Logger logger = loggerContext.getLogger(this.getClass().getName());
105         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
106         AsyncAppender asyncAppender = (AsyncAppender) root.getAppender("ASYNC");
107         assertNotNull(asyncAppender);
108         ConsoleAppender<ILoggingEvent> console = (ConsoleAppender<ILoggingEvent>) root.getAppender("CONSOLE");
109         assertNotNull(console);
110         assertTrue(console.isStarted());
111         //assertEquals(0, listAppender.list.size());
112         String msg = "hello world";
113         logger.warn(msg);
114     }
115 
116     @Test
117     public void simpleListWithImports() throws JoranException {
118         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "simpleListWithImports.xml");
119         Logger logger = loggerContext.getLogger(this.getClass().getName());
120         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
121         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
122         assertNotNull(listAppender);
123         assertEquals(0, listAppender.list.size());
124         String msg = "hello world";
125         logger.debug(msg);
126         assertEquals(1, listAppender.list.size());
127         ILoggingEvent le = (ILoggingEvent) listAppender.list.get(0);
128         assertEquals(msg, le.getMessage());
129     }
130 
131     @Test
132     public void level() throws JoranException {
133         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "simpleLevel.xml");
134         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
135         assertEquals(0, listAppender.list.size());
136         String msg = "hello world";
137         logger.debug(msg);
138         assertEquals(0, listAppender.list.size());
139     }
140 
141     @Test
142     public void additivity() throws JoranException {
143         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "additivity.xml");
144         Logger logger = loggerContext.getLogger("additivityTest");
145         assertFalse(logger.isAdditive());
146     }
147 
148     @Test
149     public void rootLoggerLevelSettingBySystemProperty() throws JoranException {
150         String propertyName = "logback.level";
151 
152         System.setProperty(propertyName, "INFO");
153         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "rootLevelByProperty.xml");
154         // StatusPrinter.print(loggerContext);
155         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
156         assertEquals(0, listAppender.list.size());
157         String msg = "hello world";
158         logger.debug(msg);
159         assertEquals(0, listAppender.list.size());
160         System.clearProperty(propertyName);
161     }
162 
163     @Test
164     public void loggerLevelSettingBySystemProperty() throws JoranException {
165         String propertyName = "logback.level";
166         System.setProperty(propertyName, "DEBUG");
167         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "loggerLevelByProperty.xml");
168         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
169         assertEquals(0, listAppender.list.size());
170         String msg = "hello world";
171         logger.debug(msg);
172         assertEquals(1, listAppender.list.size());
173         System.clearProperty(propertyName);
174     }
175 
176     @Test
177     public void appenderRefSettingBySystemProperty() throws JoranException {
178         final String propertyName = "logback.appenderRef";
179         System.setProperty(propertyName, "A");
180         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "appenderRefByProperty.xml");
181         final Logger logger = loggerContext.getLogger("ch.qos.logback.classic.joran");
182         final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) logger.getAppender("A");
183         assertEquals(0, listAppender.list.size());
184         final String msg = "hello world";
185         logger.info(msg);
186 
187         assertEquals(1, listAppender.list.size());
188         System.clearProperty(propertyName);
189     }
190 
191     @Test
192     public void statusListener() throws JoranException {
193         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "statusListener.xml");
194         //StatusPrinter.print(loggerContext);
195         checker.assertIsErrorFree();
196         checker.assertContainsMatch(Status.WARN,
197                 "Please use \"level\" attribute within <logger> or <root> elements instead.");
198     }
199 
200     @Test
201     public void statusListenerWithImports() throws JoranException {
202         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "statusListenerWithImports.xml");
203         //StatusPrinter.print(loggerContext);
204         checker.assertIsErrorFree();
205         checker.assertContainsMatch(Status.WARN,
206                 "Please use \"level\" attribute within <logger> or <root> elements instead.");
207     }
208 
209     @Test
210     public void contextRename() throws JoranException {
211         loggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
212         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "contextRename.xml");
213         assertEquals("wombat", loggerContext.getName());
214     }
215 
216     @Test
217     public void missingConfigurationElement() throws JoranException {
218         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/noConfig.xml");
219 
220         String msg1 = "Exception in body\\(\\) method for action \\[" + ParamAction.class.getName() + "\\]";
221         checker.assertContainsMatch(Status.ERROR, msg1);
222 
223         String msg2 = "current model is null. Is <configuration> element missing?";
224         checker.assertContainsException(ActionException.class, msg2);
225     }
226 
227     @Test
228     public void ignoreUnknownProperty() throws JoranException {
229 
230         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/unknownProperty.xml");
231         String msg = IGNORING_UNKNOWN_PROP + " \\[a\\] in \\[ch.qos.logback.classic.LoggerContext\\]";
232         checker.assertContainsMatch(Status.WARN, msg);
233     }
234 
235     // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=46995
236     @Test
237     public void complexCollectionWihhNoKnownClass() throws JoranException {
238 
239         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/nestedComplexWithNoKnownClass.xml");
240         String msg = "Could not find an appropriate class for property \\[listener\\]";
241         checker.assertContainsMatch(Status.ERROR, msg);
242     }
243 
244     @Test
245     public void turboFilter() throws JoranException {
246         // Although this test uses turbo filters, it only checks
247         // that Joran can see the xml element and create
248         // and place the relevant object correctly.
249         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "turbo.xml");
250 
251         TurboFilter filter = loggerContext.getTurboFilterList().get(0);
252         assertTrue(filter instanceof NOPTurboFilter);
253     }
254 
255     @Test
256     public void testTurboFilterWithStringList() throws JoranException {
257         // Although this test uses turbo filters, it only checks
258         // that Joran can see <user> elements, and behave correctly
259         // that is call the addUser method and pass the correct values
260         // to that method.
261         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "turbo2.xml");
262 
263         // StatusPrinter.print(loggerContext.getStatusManager());
264 
265         TurboFilter filter = loggerContext.getTurboFilterList().get(0);
266         assertTrue(filter instanceof DebugUsersTurboFilter);
267         DebugUsersTurboFilter dutf = (DebugUsersTurboFilter) filter;
268         assertEquals(2, dutf.getUsers().size());
269     }
270 
271     @Test
272     public void testLevelFilter() throws JoranException {
273         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "levelFilter.xml");
274 
275         // StatusPrinter.print(loggerContext);
276 
277         logger.warn("hello");
278         logger.error("to be ignored");
279 
280         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
281 
282         assertNotNull(listAppender);
283         assertEquals(1, listAppender.list.size());
284         ILoggingEvent back = listAppender.list.get(0);
285         assertEquals(Level.WARN, back.getLevel());
286         assertEquals("hello", back.getMessage());
287     }
288 
289 
290     @Test
291     public void testTurboDynamicThreshold() throws JoranException {
292         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "turboDynamicThreshold.xml");
293 
294         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
295         assertEquals(0, listAppender.list.size());
296 
297         // this one should be denied
298         MDC.put("userId", "user1");
299         logger.debug("hello user1");
300         // this one should log
301         MDC.put("userId", "user2");
302         logger.debug("hello user2");
303 
304         assertEquals(1, listAppender.list.size());
305         ILoggingEvent le = (ILoggingEvent) listAppender.list.get(0);
306         assertEquals("hello user2", le.getMessage());
307     }
308 
309     @Test
310     public void testTurboDynamicThreshold2() throws JoranException {
311 
312         try {
313             configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "turboDynamicThreshold2.xml");
314         } finally {
315             // StatusPrinter.print(loggerContext);
316         }
317         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
318         assertEquals(0, listAppender.list.size());
319 
320         // this one should log
321         MDC.put("userId", "user1");
322         logger.debug("hello user1");
323         // this one should log
324         MDC.put("userId", "user2");
325         logger.debug("hello user2");
326         // this one should fail
327         MDC.put("userId", "user3");
328         logger.debug("hello user3");
329 
330         assertEquals(2, listAppender.list.size());
331         ILoggingEvent le = (ILoggingEvent) listAppender.list.get(0);
332         assertEquals("hello user1", le.getMessage());
333         le = (ILoggingEvent) listAppender.list.get(1);
334         assertEquals("hello user2", le.getMessage());
335     }
336 
337     @Test
338     public void timestamp() throws JoranException, IOException, InterruptedException {
339 
340         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "timestamp-context.xml";
341         configure(configFileAsStr);
342 
343         String r = loggerContext.getProperty("testTimestamp");
344         assertNotNull(r);
345         CachingDateFormatter sdf = new CachingDateFormatter("yyyy-MM");
346         String expected = sdf.format(System.currentTimeMillis());
347         assertEquals(expected, r, "expected \"" + expected + "\" but got " + r);
348     }
349 
350     @Test
351     public void timestampLocal() throws JoranException, IOException, InterruptedException {
352 
353         String sysProp = "ch.qos.logback.classic.joran.JoranConfiguratorTest.timestampLocal";
354         System.setProperty(sysProp, "");
355 
356         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "timestamp-local.xml";
357         configure(configFileAsStr);
358 
359         // It's hard to test the local variable has been set, as it's not
360         // visible from here. But instead we test that it's not set in the
361         // context. And check that a system property has been replaced with the
362         // contents of the local variable
363 
364         String r = loggerContext.getProperty("testTimestamp");
365         assertNull(r);
366 
367         String expected = "today is " + new SimpleDateFormat("yyyy-MM").format(new Date());
368         String sysPropValue = System.getProperty(sysProp);
369         assertEquals(expected, sysPropValue);
370     }
371 
372     @Test
373     public void encoderCharset() throws JoranException, IOException, InterruptedException {
374 
375         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "encoderCharset.xml";
376         configure(configFileAsStr);
377 
378         ConsoleAppender<ILoggingEvent> consoleAppender = (ConsoleAppender<ILoggingEvent>) root.getAppender("CONSOLE");
379         assertNotNull(consoleAppender);
380         LayoutWrappingEncoder<ILoggingEvent> encoder = (LayoutWrappingEncoder<ILoggingEvent>) consoleAppender
381                 .getEncoder();
382 
383         assertEquals("UTF-8", encoder.getCharset().displayName());
384 
385         checker.assertIsErrorFree();
386     }
387 
388 
389 
390     @Disabled // because slow
391     @Test
392     public void onConsoleRetro() throws JoranException, IOException, InterruptedException {
393         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "/onConsoleRetro.xml";
394         configure(configFileAsStr);
395         Thread.sleep(400);
396 
397         loggerContext.reset();
398         configure(configFileAsStr);
399     }
400 
401     @Test
402     public void unreferencedAppenderShouldNotTriggerUnknownPropertyMessages() throws JoranException {
403         String configFileAsStr = ClassicTestConstants.ISSUES_PREFIX + "/logback1572.xml";
404         configure(configFileAsStr);
405         checker.assertContainsMatch(Status.WARN,
406                 "Appender named \\[EMAIL\\] not referenced. Skipping further processing.");
407         checker.assertNoMatch(IGNORING_UNKNOWN_PROP + " \\[evaluator\\]");
408     }
409 
410     @Test
411     public void LOGBACK_111() throws JoranException {
412         String configFileAsStr = ClassicTestConstants.ISSUES_PREFIX + "lbcore193.xml";
413         configure(configFileAsStr);
414         checker.assertContainsException(ScanException.class);
415         checker.assertContainsMatch(Status.ERROR, "Expecting RIGHT_PARENTHESIS token but got null");
416         checker.assertContainsMatch(Status.ERROR, "See also " + Parser.MISSING_RIGHT_PARENTHESIS);
417     }
418 
419     @Test
420     public void properties() throws JoranException {
421         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "properties.xml";
422         assertNull(System.getProperty("sys"));
423 
424         configure(configFileAsStr);
425         assertNotNull(loggerContext.getProperty(CoreConstants.HOSTNAME_KEY));
426         assertNull(loggerContext.getProperty("transientKey1"));
427         assertNull(loggerContext.getProperty("transientKey2"));
428         assertEquals("node0", loggerContext.getProperty("nodeId"));
429         assertEquals("tem", System.getProperty("sys"));
430         assertNotNull(loggerContext.getProperty("path"));
431         checker.assertIsErrorFree();
432     }
433 
434     @Test
435     public void hostnameProperty() throws JoranException {
436         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "hostnameProperty.xml";
437         configure(configFileAsStr);
438         assertEquals("A", loggerContext.getProperty(CoreConstants.HOSTNAME_KEY));
439     }
440 
441     // see also http://jira.qos.ch/browse/LOGBACK-134
442     @Test
443     public void sysProps() throws JoranException {
444         System.setProperty("k.lbcore254", ClassicTestConstants.ISSUES_PREFIX + "lbcore254");
445         JoranConfigurator configurator = new JoranConfigurator();
446         configurator.setContext(loggerContext);
447         configurator.doConfigure(ClassicTestConstants.ISSUES_PREFIX + "lbcore254.xml");
448 
449         checker.assertIsErrorFree();
450     }
451 
452     @Test
453     public void propsWithMissingRightCurlyBrace() throws JoranException {
454         System.setProperty("abc", "not important");
455         JoranConfigurator configurator = new JoranConfigurator();
456         configurator.setContext(loggerContext);
457         configurator.doConfigure(ClassicTestConstants.JORAN_INPUT_PREFIX + "propsMissingRightCurlyBrace.xml");
458         checker.assertContainsMatch(Status.ERROR, "Problem while parsing");
459     }
460 
461     @Test
462     public void packageDataDisabledByConfigAttribute() throws JoranException {
463         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "packagingDataDisabled.xml";
464         configure(configFileAsStr);
465         assertFalse(loggerContext.isPackagingDataEnabled());
466     }
467 
468     @Test
469     public void packageDataEnabledByConfigAttribute() throws JoranException {
470         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "packagingDataEnabled.xml";
471         try {
472             configure(configFileAsStr);
473         } finally {
474             // StatusPrinter.print(loggerContext);
475         }
476         assertTrue(loggerContext.isPackagingDataEnabled());
477     }
478 
479     @Test
480     public void valueOfConvention() throws JoranException {
481         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "valueOfConvention.xml";
482         configure(configFileAsStr);
483         checker.assertIsWarningOrErrorFree();
484     }
485 
486     @Test
487     public void shutdownHookTest() throws JoranException {
488         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "issues/logback_1162.xml";
489         loggerContext.putProperty("output_dir", ClassicTestConstants.OUTPUT_DIR_PREFIX + "logback_issue_1162/");
490         configure(configFileAsStr);
491         Thread thread = (Thread) loggerContext.getObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
492         assertNotNull(thread);
493     }
494 
495 
496     @Test
497     public void nestedAppendersDisallowed() throws JoranException {
498         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "issues/logback_1674.xml";
499         configure(configFileAsStr);
500         checker.assertContainsMatch(Status.WARN, NESTED_APPENDERS_WARNING);
501         checker.assertContainsMatch(Status.WARN, "Appender at line ");
502     }
503 
504     @Test
505     public void shutdownHookWithDelayParameter() throws JoranException {
506         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "issues/logback_1672.xml";
507         configure(configFileAsStr);
508 
509         Thread thread = (Thread) loggerContext.getObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
510         assertNotNull(thread);
511         checker.assertNoMatch(IGNORING_UNKNOWN_PROP);
512     }
513 
514     @Test
515     public void migrateShutdownHookClassName() throws JoranException {
516         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "issues/logback_1678_shutdown.xml";
517         configure(configFileAsStr);
518 
519         Thread thread = (Thread) loggerContext.getObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
520         assertNotNull(thread);
521         checker.assertContainsMatch(RENAME_WARNING);
522     }
523 
524     @Test
525     public void appenderRefBeforeAppenderTest() throws JoranException {
526         String configFileAsStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "appenderRefBeforeAppender.xml";
527         configure(configFileAsStr);
528         Logger logger = loggerContext.getLogger(this.getClass().getName());
529         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
530         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
531         assertNotNull(listAppender);
532         assertEquals(0, listAppender.list.size());
533         String msg = "hello world";
534         logger.debug(msg);
535         assertEquals(1, listAppender.list.size());
536         ILoggingEvent le = (ILoggingEvent) listAppender.list.get(0);
537         assertEquals(msg, le.getMessage());
538         checker.assertIsErrorFree();
539     }
540 
541     @Test
542     public void unreferencedAppendersShouldBeSkipped() throws JoranException {
543         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "unreferencedAppender1.xml");
544 
545         final ListAppender<ILoggingEvent> listAppenderA = (ListAppender<ILoggingEvent>) root.getAppender("A");
546         assertNotNull(listAppenderA);
547         checker.assertContainsMatch(Status.WARN, "Appender named \\[B\\] not referenced. Skipping further processing.");
548     }
549 
550     @Test
551     public void asynAppenderListFirst() throws JoranException {
552         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "asyncAppender_list_first.xml");
553 
554         final AsyncAppender asyncAppender = (AsyncAppender) root.getAppender("ASYNC");
555         assertNotNull(asyncAppender);
556         assertTrue(asyncAppender.isStarted());
557     }
558 
559     @Test
560     public void asynAppenderListAfter() throws JoranException {
561         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "asyncAppender_list_after.xml");
562 
563         final AsyncAppender asyncAppender = (AsyncAppender) root.getAppender("ASYNC");
564         assertNotNull(asyncAppender);
565         assertTrue(asyncAppender.isStarted());
566     }
567 
568     // https://jira.qos.ch/browse/LOGBACK-1570
569     @Test
570     public void missingPropertyErrorHandling() throws JoranException {
571         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "missingProperty.xml");
572 
573         final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
574         assertNotNull(listAppender);
575         assertTrue(listAppender.isStarted());
576         checker.assertContainsMatch(Status.WARN,
577                 IGNORING_UNKNOWN_PROP + " \\[inexistent\\] in \\[ch.qos.logback.core.read.ListAppender\\]");
578     }
579 
580     @Test
581     public void sequenceNumberGenerator() throws JoranException {
582         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "sequenceNumberGenerator.xml");
583         final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
584         assertNotNull(listAppender);
585 
586         logger.atDebug().setMessage("hello").log();
587         logger.atDebug().setMessage("world").log();
588 
589         ILoggingEvent le0 = listAppender.list.get(0);
590         ILoggingEvent le1 = listAppender.list.get(1);
591 
592         long se0 = le0.getSequenceNumber();
593         long se1 = le1.getSequenceNumber();
594         assertEquals(1, se1 - se0);
595     }
596 
597     @Test
598     public void sequenceNumberGenerator_missingClass() throws JoranException {
599         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "sequenceNumberGenerator-missingClass.xml");
600         //StatusPrinter.print(loggerContext);
601         final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
602         assertNotNull(listAppender);
603         checker.assertContainsMatch(Status.ERROR, "Missing attribute \\[class\\]. See element \\[sequenceNumberGenerator\\]");
604     }
605 
606     @Test
607     public void kvp() throws JoranException {
608         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "pattern/kvp.xml");
609 
610         String msg = "hello kvp";
611 
612         KeyValuePair kvp1 = new KeyValuePair("k" + diff, "v" + diff);
613         KeyValuePair kvp2 = new KeyValuePair("k" + (diff + 1), "v" + (diff + 1));
614         KeyValuePair kvpNullKey = new KeyValuePair(null, "v" + (diff + 2));
615         KeyValuePair kvpNullValue = new KeyValuePair("k" + (diff + 3), null);
616 
617         logger.atDebug().addKeyValue(kvp1.key, kvp1.value).log(msg);
618         logger.atDebug().addKeyValue(kvp2.key, kvp2.value).log(msg);
619         logger.atDebug().addKeyValue(kvpNullKey.key, kvpNullKey.value).log(msg);
620         logger.atDebug().addKeyValue(kvpNullValue.key, kvpNullValue.value).log(msg);
621 
622         StringListAppender<ILoggingEvent> slAppender = (StringListAppender<ILoggingEvent>) loggerContext
623                 .getLogger("root").getAppender("LIST");
624         assertNotNull(slAppender);
625         assertEquals(4, slAppender.strList.size());
626         assertTrue(slAppender.strList.get(0).contains(kvp1.key + "=\"" + kvp1.value + "\" " + msg));
627         assertTrue(slAppender.strList.get(1).contains(kvp2.key + "=\"" + kvp2.value + "\" " + msg));
628         assertTrue(slAppender.strList.get(2).contains("null=\"" + kvpNullKey.value + "\" " + msg));
629         assertTrue(slAppender.strList.get(3).contains(kvpNullValue.key + "=\"null\" " + msg));
630     }
631 
632 
633     // See LOGBACK-1746
634     @Test
635     public void inclusionWithVariables() throws JoranException {
636         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "include/topLevel0.xml");
637         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
638         //statusPrinter2.print(loggerContext);
639         assertEquals(Level.ERROR, root.getLevel());
640     }
641 
642     // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=46697
643     @Test
644     public void ossFuzz_46697() throws JoranException {
645         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-46697.xml");
646 
647         checker.assertContainsMatch(Status.ERROR, ErrorCodes.EMPTY_MODEL_STACK);
648     }
649 
650     // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=47093
651     // In previous versions of the code, we honored String literal
652     // escape sequences for the 'value' attribute named 'value'. After
653     // analysis this was deemed superfluous.
654     @Test
655     public void ossFuzz_47093() throws JoranException {
656         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47093.xml");
657         assertEquals("a\\t", loggerContext.getProperty("fuzz-47093-a"));
658         assertEquals("a\\\\", loggerContext.getProperty("fuzz-47093-b"));
659     }
660 
661     @Test
662     public void ossFuzz_41117() throws JoranException {
663         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47117.xml");
664         checker.assertContainsMatch(Status.ERROR, ErrorCodes.ROOT_LEVEL_CANNOT_BE_SET_TO_NULL);
665         checker.assertErrorCount(2);
666         //StatusPrinter.print(loggerContext);
667     }
668 
669     @Test
670     public void ossFuzz_41117_bis() throws JoranException {
671         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47117-bis.xml");
672         checker.assertContainsMatch(Status.ERROR, ErrorCodes.ROOT_LEVEL_CANNOT_BE_SET_TO_NULL);
673     }
674 
675     @Test
676     public void ossFuzz_41117_bis2() throws JoranException {
677         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47117-bis2.xml");
678         checker.assertContainsMatch(Status.ERROR, ErrorCodes.ROOT_LEVEL_CANNOT_BE_SET_TO_NULL);
679     }
680 
681     @Test
682     public void ossFuzz_47293() throws JoranException {
683         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47293.xml");
684         checker.assertContainsMatch(Status.ERROR, ErrorCodes.MISSING_IF_EMPTY_MODEL_STACK);
685     }
686 
687 
688     @Test
689     public void dateConverterWithLocale() throws JoranException {
690         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "dateWithLocale.xml");
691         checker.assertContainsMatch(Status.INFO, "Setting zoneId to \"Australia/Perth\"");
692         checker.assertContainsMatch(Status.INFO, "Setting locale to \"en_AU\"");
693         //StatusPrinter.print(loggerContext);
694     }
695 
696     @Test
697     public void propertyConfiguratorSmoke() throws JoranException {
698         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "propertiesConfigurator/smoke.xml");
699         Logger com_foo_Logger = loggerContext.getLogger("com.toto");
700         //StatusPrinter.print(loggerContext);
701         assertEquals(Level.WARN, com_foo_Logger.getLevel());
702 
703 
704     }
705 
706     @Test
707     public void consoleCharsetTest() throws JoranException {
708         if (EnvUtil.isJDK21OrHigher()) {
709             configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "consoleCharset.xml");
710             checker.assertContainsMatch(Status.INFO, "About to instantiate property definer of type \\[ch.qos.logback.core.property.ConsoleCharsetPropertyDefiner\\]");
711 
712             Console console = System.console();
713             if(console == null) {
714                 checker.assertContainsMatch(Status.WARN, "System.console\\(\\) returned null. Cannot compute console's charset, returning");
715             } else {
716 
717                 boolean nullCharset =  checker.containsMatch("System.console() returned null charset. Returning \"NULL\" string as defined value.");
718                 boolean foundCharset = checker.containsMatch("Found value '.*' as returned by System.console().");
719 
720             }
721             //StatusPrinter.print(loggerContext);
722         }
723     }
724 
725     @Test
726     public void levelFromAPropertyTest() throws JoranException {
727 
728 
729         String loggerASysLevelKey = "LOGGER_A_SYS_LEVEL";
730         String loggerNestedSysLevelKey = "LOGGER_NESTED_SYS_LEVEL";
731         System.setProperty(loggerASysLevelKey, "WARN");
732         System.setProperty(loggerNestedSysLevelKey, "ERROR");
733         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "levelFromAProperty.xml");
734 
735 
736         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
737 
738 
739         Logger loggerA = loggerContext.getLogger("A");
740         Logger loggerA_SYS = loggerContext.getLogger("A_SYS");
741 
742         Logger loggerNESTED = loggerContext.getLogger("NESTED");
743 
744         Logger loggerNESTED_SYS = loggerContext.getLogger("NESTED_SYS");
745 
746 
747         assertEquals(Level.TRACE, root.getLevel());
748         assertEquals(Level.WARN, loggerA.getLevel());
749         assertEquals(Level.WARN, loggerA_SYS.getLevel());
750 
751         assertEquals(Level.ERROR, loggerNESTED.getLevel());
752         assertEquals(Level.ERROR, loggerNESTED_SYS.getLevel());
753 
754         checker.assertContainsMatch(Status.INFO, "value \\\"WARN\\\" substituted for \\\"\\$\\{LOGGER_A_LEVEL\\}\\\"");
755 
756         System.clearProperty(loggerASysLevelKey);
757         System.clearProperty(loggerNestedSysLevelKey);
758 
759     }
760 
761     @Test
762     public void exceptionEventFilter() throws JoranException {
763         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "boolex/exceptionEvaluator.xml");
764         Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
765         root.info("deny");
766         root.info("allow", new RuntimeException("test"));
767 
768         ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) root.getAppender("LIST");
769         assertNotNull(listAppender);
770         assertEquals(1, listAppender.list.size());
771 
772         ILoggingEvent le = listAppender.list.get(0);
773 
774         assertNotNull(le.getThrowableProxy());
775         assertEquals(RuntimeException.class.getName(), le.getThrowableProxy().getClassName());
776 
777     }
778 
779     @Test
780     public void modelSerialization() throws JoranException, IOException, ClassNotFoundException {
781         String outputPath = OUTPUT_DIR_PREFIX + "minimal_" + diff + MODEL_CONFIG_FILE_EXTENSION;
782 
783         loggerContext.putProperty("targetModelFile", outputPath);
784         configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "model/minimal.xml");
785         StatusPrinter.print(loggerContext);
786 
787         FileInputStream fis = new FileInputStream(outputPath);
788         HardenedModelInputStream hmis = new HardenedModelInputStream(fis);
789 
790         Model model = (Model) hmis.readObject();
791 
792         assertNotNull(model);
793         assertTrue(model instanceof ConfigurationModel);
794 
795         ConfigurationModel configurationModel = (ConfigurationModel) model;
796 
797         assertEquals("false", configurationModel.getDebugStr());
798 
799         assertEquals(2, configurationModel.getSubModels().size());
800 
801         SerializeModelModel smm = (SerializeModelModel) configurationModel.getSubModels().get(0);
802         assertEquals("${targetModelFile}", smm.getFile());
803 
804 
805         LoggerModel loggerModel = (LoggerModel) configurationModel.getSubModels().get(1);
806         assertEquals("ModelSerializationTest", loggerModel.getName());
807 
808         //    <serializeModel file="${targetModelFile}"/>
809         //    <logger name="ModelSerializationTest" level="DEBUG"/>
810 
811 
812     }
813 
814 
815     // reproduction requires placing a binary properties file. Probably not worth the effort.
816 //    @Test
817 //    public void ossFuzz_47249() throws JoranException  {
818 //        configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "ossfuzz/fuzz-47249.xml");
819 //         StatusPrinter.print(loggerContext);
820 //    }
821 
822 //	@Test
823 //	public void doTest() throws JoranException {
824 //		int LIMIT = 0;
825 //		boolean oss = true;
826 //		for (int i = 0; i < LIMIT; i++) {
827 //			innerDoT(oss);
828 //		}
829 //		long start = System.currentTimeMillis();
830 //		innerDoT(oss);
831 //		long diff = System.currentTimeMillis() - start;
832 //		double average = (1.0d * diff);
833 //		System.out.println("Average time " + average + " ms. By serialization " + oss);
834 //
835 //	}
836 
837 //	private void innerDoT(boolean oss) throws JoranException {
838 //		JoranConfigurator jc = new JoranConfigurator();
839 //		jc.setContext(loggerContext);
840 //		if (oss) {
841 //			System.out.println("jc.doT");
842 //			jc.doT();
843 //		} else {
844 //			System.out.println("jc.doConfigure");
845 //			jc.doConfigure(ClassicTestConstants.JORAN_INPUT_PREFIX + "twoAppenders.xml");
846 //		}
847 //	}
848 
849 }