1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2022, QOS.ch. All rights reserved.
4    *
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    *
9    *   or (per the licensee's choosing)
10   *
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.pattern;
15  
16  import ch.qos.logback.classic.*;
17  import ch.qos.logback.classic.spi.ILoggingEvent;
18  import ch.qos.logback.classic.spi.LoggingEvent;
19  import ch.qos.logback.classic.util.LogbackMDCAdapter;
20  import ch.qos.logback.core.CoreConstants;
21  import ch.qos.logback.core.net.SyslogConstants;
22  import ch.qos.logback.core.pattern.DynamicConverter;
23  import ch.qos.logback.core.pattern.FormatInfo;
24  import ch.qos.logback.core.util.EnvUtil;
25  import ch.qos.logback.core.util.StatusPrinter;
26  import org.junit.jupiter.api.BeforeEach;
27  import org.junit.jupiter.api.Test;
28  import org.slf4j.MarkerFactory;
29  
30  import java.time.Instant;
31  import java.util.ArrayList;
32  import java.util.List;
33  import java.util.regex.Pattern;
34  
35  import static org.junit.jupiter.api.Assertions.*;
36  
37  public class ConverterTest {
38  
39      LoggerContext loggerContext = new LoggerContext();
40      LogbackMDCAdapter logbackMDCAdapter = new LogbackMDCAdapter();
41      Logger logger = loggerContext.getLogger(ConverterTest.class);
42      LoggingEvent le;
43      //List<String> optionList = new ArrayList<String>();
44  
45      // The LoggingEvent is massaged with an FCQN of FormattingConverter. This
46      // forces the returned caller information to match the caller stack for
47      // this particular test.
48      LoggingEvent makeLoggingEvent(Exception ex) {
49          return new LoggingEvent(ch.qos.logback.core.pattern.FormattingConverter.class.getName(), logger, Level.INFO,
50                  "Some message", ex, null);
51      }
52  
53      Exception getException(String msg, Exception cause) {
54          return new Exception(msg, cause);
55      }
56  
57      @BeforeEach
58      public void setUp() throws Exception {
59          loggerContext.setMDCAdapter(logbackMDCAdapter);
60          Exception rootEx = getException("Innermost", null);
61          Exception nestedEx = getException("Nested", rootEx);
62  
63          Exception ex = new Exception("Bogus exception", nestedEx);
64  
65          le = makeLoggingEvent(ex);
66      }
67  
68      @Test
69      public void testLineOfCaller() {
70          {
71              DynamicConverter<ILoggingEvent> converter = new LineOfCallerConverter();
72              StringBuilder buf = new StringBuilder();
73              converter.write(buf, le);
74              // the number below should be the line number of the previous line
75              assertEquals("73", buf.toString());
76          }
77      }
78  
79      @Test
80      public void testLevel() {
81          {
82              DynamicConverter<ILoggingEvent> converter = new LevelConverter();
83              StringBuilder buf = new StringBuilder();
84              converter.write(buf, le);
85              assertEquals("INFO", buf.toString());
86          }
87          {
88              DynamicConverter<ILoggingEvent> converter = new LevelConverter();
89              converter.setFormattingInfo(new FormatInfo(1, 1, true, false));
90              StringBuilder buf = new StringBuilder();
91              converter.write(buf, le);
92              assertEquals("I", buf.toString());
93          }
94      }
95  
96      @Test
97      public void testThread() {
98          DynamicConverter<ILoggingEvent> converter = new ThreadConverter();
99          StringBuilder buf = new StringBuilder();
100         converter.write(buf, le);
101         System.out.println(buf.toString());
102         String regex = ClassicTestConstants.NAKED_MAIN_REGEX;
103         assertTrue(buf.toString().matches(regex));
104     }
105 
106     @Test
107     public void testMessage() {
108         DynamicConverter<ILoggingEvent> converter = new MessageConverter();
109         StringBuilder buf = new StringBuilder();
110         converter.write(buf, le);
111         assertEquals("Some message", buf.toString());
112     }
113 
114     @Test
115     public void testLineSeparator() {
116         DynamicConverter<ILoggingEvent> converter = new LineSeparatorConverter();
117         StringBuilder buf = new StringBuilder();
118         converter.write(buf, le);
119         assertEquals(CoreConstants.LINE_SEPARATOR, buf.toString());
120     }
121 
122     @Test
123     public void testException() {
124         {
125             DynamicConverter<ILoggingEvent> converter = new ThrowableProxyConverter();
126             StringBuilder buf = new StringBuilder();
127             converter.write(buf, le);
128         }
129 
130         {
131             DynamicConverter<ILoggingEvent> converter = new ThrowableProxyConverter();
132             converter.setOptionList(List.of("3"));
133             StringBuilder buf = new StringBuilder();
134             converter.write(buf, le);
135         }
136     }
137 
138     @Test
139     public void testLogger() {
140         {
141             ClassicConverter converter = new LoggerConverter();
142             StringBuilder buf = new StringBuilder();
143             converter.write(buf, le);
144             assertEquals(this.getClass().getName(), buf.toString());
145         }
146 
147         {
148             ClassicConverter converter = new LoggerConverter();
149             converter.setOptionList(List.of("20"));
150             converter.start();
151             StringBuilder buf = new StringBuilder();
152             converter.write(buf, le);
153             assertEquals("c.q.l.c.p.ConverterTest", buf.toString());
154         }
155 
156         {
157             DynamicConverter<ILoggingEvent> converter = new LoggerConverter();
158             converter.setOptionList(List.of("0"));
159             converter.start();
160             StringBuilder buf = new StringBuilder();
161             converter.write(buf, le);
162             assertEquals("ConverterTest", buf.toString());
163         }
164     }
165 
166     @Test
167     public void testVeryLongLoggerName() {
168         ClassicConverter converter = new LoggerConverter();
169         converter.setOptionList(List.of("5"));
170         converter.start();
171         StringBuilder buf = new StringBuilder();
172 
173         char c = 'a';
174         int extraParts = 3;
175         int totalParts = ClassicConstants.MAX_DOTS + extraParts;
176         StringBuilder loggerNameBuf = new StringBuilder();
177         StringBuilder witness = new StringBuilder();
178 
179         for (int i = 0; i < totalParts; i++) {
180             loggerNameBuf.append(c).append(c).append(c);
181             witness.append(c);
182             loggerNameBuf.append('.');
183             witness.append('.');
184         }
185         loggerNameBuf.append("zzzzzz");
186         witness.append("zzzzzz");
187 
188         le.setLoggerName(loggerNameBuf.toString());
189         converter.write(buf, le);
190         assertEquals(witness.toString(), buf.toString());
191     }
192 
193     @Test
194     public void testClass() {
195         DynamicConverter<ILoggingEvent> converter = new ClassOfCallerConverter();
196         StringBuilder buf = new StringBuilder();
197         converter.write(buf, le);
198         assertEquals(this.getClass().getName(), buf.toString());
199     }
200 
201     @Test
202     public void testMethodOfCaller() {
203         DynamicConverter<ILoggingEvent> converter = new MethodOfCallerConverter();
204         StringBuilder buf = new StringBuilder();
205         converter.write(buf, le);
206         assertEquals("testMethodOfCaller", buf.toString());
207     }
208 
209     @Test
210     public void testFileOfCaller() {
211         DynamicConverter<ILoggingEvent> converter = new FileOfCallerConverter();
212         StringBuilder buf = new StringBuilder();
213         converter.write(buf, le);
214         assertEquals("ConverterTest.java", buf.toString());
215     }
216 
217     @Test
218     public void testCallerData() {
219         {
220             DynamicConverter<ILoggingEvent> converter = new CallerDataConverter();
221             converter.start();
222 
223             StringBuilder buf = new StringBuilder();
224             converter.write(buf, le);
225             if (buf.length() < 10) {
226                 fail("buf is too short");
227             }
228         }
229 
230         {
231             DynamicConverter<ILoggingEvent> converter = new CallerDataConverter();
232             converter.setOptionList(List.of("2", "XXX"));
233             converter.start();
234 
235             StringBuilder buf = new StringBuilder();
236             LoggingEvent event = makeLoggingEvent(null);
237             event.addMarker(MarkerFactory.getMarker("XXX"));
238             converter.write(buf, event);
239             if (buf.length() < 10) {
240                 fail("buf is too short");
241             }
242         }
243 
244         {
245             DynamicConverter<ILoggingEvent> converter = new CallerDataConverter();
246             converter.setOptionList(List.of("2", "XXX", "*"));
247             converter.start();
248 
249             StringBuilder buf = new StringBuilder();
250             LoggingEvent event = makeLoggingEvent(null);
251             event.addMarker(MarkerFactory.getMarker("YYY"));
252             converter.write(buf, event);
253             if (buf.length() < 10) {
254                 fail("buf is too short");
255             }
256         }
257         {
258             DynamicConverter<ILoggingEvent> converter = new CallerDataConverter();
259             converter.setOptionList(List.of("2", "XXX", "*"));
260             converter.start();
261 
262             StringBuilder buf = new StringBuilder();
263             LoggingEvent event = makeLoggingEvent(null);
264             event.addMarker(MarkerFactory.getMarker("YYY"));
265             converter.write(buf, event);
266             if (buf.length() < 10) {
267                 fail("buf is too short");
268             }
269         }
270 
271         {
272             DynamicConverter<ILoggingEvent> converter = new CallerDataConverter();
273             converter.setOptionList(List.of("2", "XXX", "*"));
274             converter.start();
275 
276             StringBuilder buf = new StringBuilder();
277             converter.write(buf, le);
278             if (buf.length() < 10) {
279                 fail("buf is too short");
280             }
281             // System.out.println(buf);
282         }
283 
284         {
285             DynamicConverter<ILoggingEvent> converter = new CallerDataConverter();
286 
287             boolean jdk18 = EnvUtil.isJDK18OrHigher();
288             // jdk 18EA creates a different stack trace
289             converter.setOptionList(jdk18 ? List.of("2..3") : List.of("4..5"));
290             converter.start();
291 
292             StringBuilder buf = new StringBuilder();
293             converter.write(buf, le);
294             assertTrue( buf.length() >= 10, "buf is too short");
295 
296             String expectedRegex = "Caller\\+4";
297             if(jdk18) {
298                 expectedRegex = "Caller\\+2";
299             }
300             expectedRegex+="\t at (java.base\\/)?java.lang.reflect.Method.invoke.*$";
301             String actual = buf.toString();
302             assertTrue( Pattern.compile(expectedRegex).matcher(actual).find(), "actual: " + actual);
303 
304         }
305     }
306 
307     @Test
308     public void testRelativeTime() throws Exception {
309         DynamicConverter<ILoggingEvent> converter = new RelativeTimeConverter();
310         StringBuilder buf0 = new StringBuilder();
311         StringBuilder buf1 = new StringBuilder();
312         long timestamp = System.currentTimeMillis();
313         LoggingEvent e0 = makeLoggingEvent(null);
314         e0.setTimeStamp(timestamp);
315         LoggingEvent e1 = makeLoggingEvent(null);
316         e1.setTimeStamp(timestamp);
317         converter.write(buf0, e0);
318         converter.write(buf1, e1);
319         assertEquals(buf0.toString(), buf1.toString());
320     }
321 
322     @Test
323     public void testSyslogStart() throws Exception {
324         DynamicConverter<ILoggingEvent> converter = new SyslogStartConverter();
325         converter.setOptionList(List.of("MAIL"));
326         converter.start();
327 
328         ILoggingEvent event = makeLoggingEvent(null);
329 
330         StringBuilder buf = new StringBuilder();
331         converter.write(buf, event);
332 
333         String expected = "<" + (SyslogConstants.LOG_MAIL + SyslogConstants.INFO_SEVERITY) + ">";
334         assertTrue(buf.toString().startsWith(expected));
335     }
336 
337     @Test
338     public void testMDCConverter() throws Exception {
339         logbackMDCAdapter.clear();
340         logbackMDCAdapter.put("someKey", "someValue");
341         MDCConverter converter = new MDCConverter();
342         converter.setOptionList(List.of("someKey"));
343         converter.start();
344 
345         ILoggingEvent event = makeLoggingEvent(null);
346 
347         String result = converter.convert(event);
348         assertEquals("someValue", result);
349     }
350 
351     @Test
352     public void contextNameConverter() {
353         ClassicConverter converter = new ContextNameConverter();
354         // see http://jira.qos.ch/browse/LBCLASSIC-149
355         LoggerContext lcOther = new LoggerContext();
356         lcOther.setName("another");
357         converter.setContext(lcOther);
358 
359         loggerContext.setName("aValue");
360         ILoggingEvent event = makeLoggingEvent(null);
361 
362         String result = converter.convert(event);
363         assertEquals("aValue", result);
364     }
365 
366     @Test
367     public void contextProperty() {
368         PropertyConverter converter = new PropertyConverter();
369         converter.setContext(loggerContext);
370         converter.setOptionList(List.of("k"));
371         converter.start();
372         loggerContext.setName("aValue");
373         loggerContext.putProperty("k", "v");
374         ILoggingEvent event = makeLoggingEvent(null);
375 
376         String result = converter.convert(event);
377         assertEquals("v", result);
378     }
379     
380     @Test
381     public void testSequenceNumber() {
382         //lc.setSequenceNumberGenerator(new BasicSequenceNumberGenerator());
383         SequenceNumberConverter converter = new SequenceNumberConverter();
384         converter.setContext(loggerContext);
385         converter.start();
386 
387         assertTrue(converter.isStarted());
388         LoggingEvent event = makeLoggingEvent(null);
389 
390         event.setSequenceNumber(123);
391         assertEquals("123", converter.convert(event));
392         StatusPrinter.print(loggerContext);
393     }
394 
395     @Test
396     void dateConverterTest() {
397         // 2024-08-14T1Z:29:25,956 GMT
398         long millis = 1_723_649_365_956L;
399         dateConverterChecker(millis, List.of("STRICT", "GMT"), "2024-08-14T15:29:25,956");
400         dateConverterChecker(millis, List.of("ISO8601", "GMT"), "2024-08-14 15:29:25,956");
401         dateConverterChecker(millis, List.of("ISO8601", "UTC"), "2024-08-14 15:29:25,956");
402         dateConverterChecker(millis, List.of("yyyy-MM-EE", "UTC", "fr-CH"), "2024-08-mer.");
403 
404     }
405 
406     void dateConverterChecker(long millis, List<String> options, String expected) {
407         DateConverter dateConverter = new DateConverter();
408         dateConverter.setOptionList(options) ;
409         dateConverter.setContext(loggerContext);
410         dateConverter.start();
411 
412         assertTrue(dateConverter.isStarted());
413         LoggingEvent event = makeLoggingEvent(null);
414 
415         Instant now = Instant.ofEpochMilli(millis);
416         event.setInstant(now);
417         String result = dateConverter.convert(event);
418         assertEquals(expected, result);
419     }
420 }