1
2
3
4
5
6
7
8
9
10
11
12
13
14 package ch.qos.logback.classic;
15
16 import ch.qos.logback.classic.joran.JoranConfigurator;
17 import ch.qos.logback.classic.pattern.ConverterTest;
18 import ch.qos.logback.classic.pattern.ExceptionalConverter2;
19 import ch.qos.logback.classic.spi.ILoggingEvent;
20 import ch.qos.logback.classic.spi.LoggingEvent;
21 import ch.qos.logback.classic.pattern.SampleConverter;
22 import ch.qos.logback.classic.util.LogbackMDCAdapter;
23 import ch.qos.logback.core.Context;
24 import ch.qos.logback.core.joran.spi.JoranException;
25 import ch.qos.logback.core.pattern.PatternLayoutBase;
26 import ch.qos.logback.core.pattern.parser.test.AbstractPatternLayoutBaseTest;
27 import ch.qos.logback.core.spi.ScanException;
28 import ch.qos.logback.core.testUtil.RandomUtil;
29 import ch.qos.logback.core.testUtil.StringListAppender;
30 import ch.qos.logback.core.util.OptionHelper;
31 import ch.qos.logback.core.util.StatusPrinter;
32 import org.junit.jupiter.api.BeforeEach;
33 import org.junit.jupiter.api.Test;
34 import org.slf4j.MDC;
35
36 import static ch.qos.logback.classic.ClassicTestConstants.ISO_REGEX;
37 import static ch.qos.logback.classic.ClassicTestConstants.MAIN_REGEX;
38 import static org.junit.jupiter.api.Assertions.assertEquals;
39 import static org.junit.jupiter.api.Assertions.assertNotNull;
40 import static org.junit.jupiter.api.Assertions.assertTrue;
41
42 import java.time.Instant;
43
44 public class PatternLayoutTest extends AbstractPatternLayoutBaseTest<ILoggingEvent> {
45
46 private PatternLayout pl = new PatternLayout();
47 private LoggerContext loggerContext = new LoggerContext();
48
49 LogbackMDCAdapter logbackMDCAdapter = new LogbackMDCAdapter();
50 Logger logger = loggerContext.getLogger(ConverterTest.class);
51 Logger root = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
52
53 int diff = RandomUtil.getPositiveInt();
54
55 String aMessage = "Some message";
56
57 Exception ex = new Exception("Bogus exception");
58
59
60
61 @BeforeEach
62 public void setUp() {
63 loggerContext.setMDCAdapter(logbackMDCAdapter);
64 pl.setContext(loggerContext);
65
66 }
67
68
69
70
71
72
73 protected String getExceptionalConverterClassName() {
74 return ExceptionalConverter2.class.getName();
75 }
76
77 LoggingEvent makeLoggingEvent(String msg, Exception ex) {
78 return new LoggingEvent(ch.qos.logback.core.pattern.FormattingConverter.class.getName(), logger, Level.INFO,
79 msg, ex, null);
80 }
81
82 public ILoggingEvent getEventObject() {
83 return makeLoggingEvent("Some message", null);
84 }
85
86 public PatternLayoutBase<ILoggingEvent> getPatternLayoutBase() {
87 return new PatternLayout();
88 }
89
90 @Test
91 public void testOK() {
92 pl.setPattern("%d %le [%t] %lo{30} - %m%n");
93 pl.start();
94 String val = pl.doLayout(getEventObject());
95
96
97
98
99 String regex = ISO_REGEX + " INFO " + MAIN_REGEX + " c.q.l.c.pattern.ConverterTest - Some message\\s*";
100
101 assertTrue( val.matches(regex), "val=" + val);
102 }
103
104 @Test
105 public void testNoExeptionHandler() {
106 pl.setPattern("%m%n");
107 pl.start();
108 String val = pl.doLayout(makeLoggingEvent(aMessage, ex));
109 assertTrue(val.contains("java.lang.Exception: Bogus exception"));
110 }
111
112 @Test
113 public void testCompositePattern() {
114 pl.setPattern("%-56(%d %lo{20}) - %m%n");
115 pl.start();
116 String val = pl.doLayout(getEventObject());
117
118 String regex = ISO_REGEX + " c.q.l.c.p.ConverterTest - Some message\\s*";
119 assertTrue(val.matches(regex));
120 }
121
122 @Test
123 public void contextProperty() {
124 pl.setPattern("%property{a}");
125 pl.start();
126 loggerContext.putProperty("a", "b");
127
128 String val = pl.doLayout(getEventObject());
129 assertEquals("b", val);
130 }
131
132 @Test
133 public void testNopExeptionHandler() {
134 pl.setPattern("%nopex %m%n");
135 pl.start();
136 String val = pl.doLayout(makeLoggingEvent(aMessage, ex));
137 assertTrue(!val.contains("java.lang.Exception: Bogus exception"));
138 }
139
140 @Test
141 public void testWithParenthesis() {
142 pl.setPattern("\\(%msg:%msg\\) %msg");
143 pl.start();
144 LoggingEvent le = makeLoggingEvent(aMessage, null);
145 String val = pl.doLayout(le);
146 assertEquals("(Some message:Some message) Some message", val);
147 }
148
149 @Test
150 public void testWithLettersComingFromLog4j() {
151
152 pl.setPattern("%d %p [%t] %c{30} - %m%n");
153 pl.start();
154 String val = pl.doLayout(getEventObject());
155
156
157 String regex = ClassicTestConstants.ISO_REGEX + " INFO " + MAIN_REGEX
158 + " c.q.l.c.pattern.ConverterTest - Some message\\s*";
159 assertTrue(val.matches(regex));
160 }
161
162 @Test
163 public void mdcWithDefaultValue() throws ScanException {
164 String pattern = "%msg %mdc{foo1} %mdc{bar:-[null]}";
165 pl.setPattern(OptionHelper.substVars(pattern, loggerContext));
166 pl.start();
167
168 String key = "foo1";
169
170 logbackMDCAdapter.put(key, key);
171 try {
172 String val = pl.doLayout(getEventObject());
173 assertEquals("Some message foo1 [null]", val);
174 } finally {
175 logbackMDCAdapter.remove(key);
176 }
177 }
178
179 @Test
180 public void contextNameTest() {
181 pl.setPattern("%contextName");
182 loggerContext.setName("aValue");
183 pl.start();
184 String val = pl.doLayout(getEventObject());
185 assertEquals("aValue", val);
186 }
187
188 @Test
189 public void cnTest() {
190 pl.setPattern("%cn");
191 loggerContext.setName("aValue");
192 pl.start();
193 String val = pl.doLayout(getEventObject());
194 assertEquals("aValue", val);
195 }
196
197 @Test
198 public void micros() {
199 verifyMicros(122_891_479, "2011-12-03 10:15:30.122 891 Some message");
200 verifyMicros(122_091_479, "2011-12-03 10:15:30.122 091 Some message");
201 verifyMicros(122_001_479, "2011-12-03 10:15:30.122 001 Some message");
202 }
203
204 void verifyMicros(int nanos, String expected) {
205 Instant instant = Instant.parse("2011-12-03T10:15:30Z");
206 instant = instant.plusNanos(nanos);
207 LoggingEvent le = makeLoggingEvent(aMessage, null);
208 le.setInstant(instant);
209
210 pl.setPattern("%date{yyyy-MM-dd HH:mm:ss.SSS, UTC} %micros %message%nopex");
211 pl.start();
212
213 String val = pl.doLayout(le);
214 assertEquals(expected, val);
215 }
216
217 @Test
218 public void epoch() {
219 verifyEpoch(123, false, "2026-01-15 10:15:30.123 1768472130123 Some message");
220 verifyEpoch(456, false, "2026-01-15 10:15:30.456 1768472130456 Some message");
221 verifyEpoch(123, true, "2026-01-15 10:15:30.123 1768472130 Some message");
222 verifyEpoch(456, true, "2026-01-15 10:15:30.456 1768472130 Some message");
223 }
224
225 void verifyEpoch(int millis, boolean secondsNotMillis, String expected) {
226 Instant instant = Instant.parse("2026-01-15T10:15:30Z");
227 instant = instant.plusMillis(millis);
228 LoggingEvent le = makeLoggingEvent(aMessage, null);
229 le.setInstant(instant);
230
231 String option = secondsNotMillis ? "{seconds}" : "";
232 pl.setPattern("%date{yyyy-MM-dd HH:mm:ss.SSS, UTC} %epoch"+option+" %message%nopex");
233 pl.start();
234
235 String val = pl.doLayout(le);
236 assertEquals(expected, val);
237 }
238
239 @Override
240 public Context getContext() {
241 return loggerContext;
242 }
243
244 void configure(String file) throws JoranException {
245 JoranConfigurator jc = new JoranConfigurator();
246 jc.setContext(loggerContext);
247 jc.doConfigure(file);
248 }
249
250 @Test
251 public void testConversionRuleSupportInPatternLayout() throws JoranException {
252 configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "conversionRule/patternLayout0.xml");
253 root.getAppender("LIST");
254 String msg = "Simon says";
255 logger.debug(msg);
256 StringListAppender<ILoggingEvent> sla = (StringListAppender<ILoggingEvent>) root.getAppender("LIST");
257 assertNotNull(sla);
258 assertEquals(1, sla.strList.size());
259 assertEquals(SampleConverter.SAMPLE_STR + " - " + msg, sla.strList.get(0));
260 }
261
262 @Test
263 public void testConversionRuleAtEnd() throws JoranException {
264 configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "conversionRule/conversionRuleAtEnd.xml");
265 root.getAppender("LIST");
266 String msg = "testConversionRuleAtEnd";
267 logger.debug(msg);
268 StringListAppender<ILoggingEvent> sla = (StringListAppender<ILoggingEvent>) root.getAppender("LIST");
269 assertNotNull(sla);
270 assertEquals(1, sla.strList.size());
271 assertEquals(SampleConverter.SAMPLE_STR + " - " + msg, sla.strList.get(0));
272 }
273
274 @Test
275 public void testConversionRuleInIncluded() throws JoranException {
276 configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "conversionRule/conversionRuleTop0.xml");
277 StatusPrinter.print(loggerContext);
278 root.getAppender("LIST");
279 String msg = "testConversionRuleInIncluded";
280 logger.debug(msg);
281 StringListAppender<ILoggingEvent> sla = (StringListAppender<ILoggingEvent>) root.getAppender("LIST");
282 assertNotNull(sla);
283 assertEquals(1, sla.strList.size());
284 assertEquals(SampleConverter.SAMPLE_STR + " - " + msg, sla.strList.get(0));
285 }
286
287 @Test
288 public void smokeReplace() {
289 pl.setPattern("%replace(a1234b){'\\d{4}', 'XXXX'}");
290 pl.start();
291 String val = pl.doLayout(getEventObject());
292 assertEquals("aXXXXb", val);
293 }
294
295 @Test
296 public void replaceNewline() throws ScanException {
297 String pattern = "%replace(A\nB){'\n', '\n\t'}";
298 String substPattern = OptionHelper.substVars(pattern, null, loggerContext);
299 assertEquals(pattern, substPattern);
300 pl.setPattern(substPattern);
301 pl.start();
302
303 String val = pl.doLayout(makeLoggingEvent("", null));
304 assertEquals("A\n\tB", val);
305 }
306
307 @Test
308 public void replaceWithJoran() throws JoranException {
309 configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "pattern/replace0.xml");
310
311 root.getAppender("LIST");
312 String msg = "And the number is 4111111111110000, expiring on 12/2010";
313 logger.debug(msg);
314 StringListAppender<ILoggingEvent> sla = (StringListAppender<ILoggingEvent>) root.getAppender("LIST");
315 assertNotNull(sla);
316 assertEquals(1, sla.strList.size());
317 assertEquals("And the number is XXXX, expiring on 12/2010", sla.strList.get(0));
318 }
319
320 @Test
321 public void replaceWithJoran_NEWLINE() throws JoranException {
322 loggerContext.putProperty("TAB", "\t");
323 configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "pattern/replaceNewline.xml");
324
325 root.getAppender("LIST");
326 String msg = "A\nC";
327 logger.debug(msg);
328 StringListAppender<ILoggingEvent> sla = (StringListAppender<ILoggingEvent>) root.getAppender("LIST");
329 assertNotNull(sla);
330 assertEquals(1, sla.strList.size());
331 assertEquals("A\n\tC", sla.strList.get(0));
332 }
333
334 @Test
335 public void prefixConverterSmoke() {
336 String pattern = "%prefix(%logger) %message";
337 pl.setPattern(pattern);
338 pl.start();
339 String val = pl.doLayout(makeLoggingEvent("hello", null));
340 assertEquals("logger=" + logger.getName() + " hello", val);
341 }
342
343 @Test
344 public void prefixConverterWithMDC() {
345 String mdcKey = "boo";
346 String mdcVal = "moo";
347
348 String pattern = "%prefix(%level %logger %X{" + mdcKey + "}) %message";
349 pl.setPattern(pattern);
350 pl.start();
351 logbackMDCAdapter.put(mdcKey, mdcVal);
352 try {
353 String val = pl.doLayout(makeLoggingEvent("hello", null));
354
355 assertEquals("level=" + "INFO logger=" + logger.getName() + " " + mdcKey + "=" + mdcVal + " hello", val);
356
357 } finally {
358 MDC.remove(mdcKey);
359 }
360 }
361
362 @Test
363 public void prefixConverterWithProperty() {
364
365 try {
366 String propertyKey = "px1953";
367 String propertyVal = "pxVal";
368
369 System.setProperty(propertyKey, propertyVal);
370
371 String pattern = "%prefix(%logger %property{" + propertyKey + "}) %message";
372 pl.setPattern(pattern);
373 pl.start();
374
375 String val = pl.doLayout(makeLoggingEvent("hello", null));
376
377 assertEquals("logger=" + logger.getName() + " " + propertyKey + "=" + propertyVal + " hello", val);
378
379 } finally {
380 System.clearProperty("px");
381 }
382 }
383
384 }