1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, 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 v2.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  
15  package ch.qos.logback.classic.issue.github1010;
16  
17  import ch.qos.logback.classic.Level;
18  import ch.qos.logback.classic.Logger;
19  import ch.qos.logback.classic.LoggerContext;
20  import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
21  import ch.qos.logback.classic.net.SimpleSocketServer;
22  import ch.qos.logback.classic.net.SocketAppender;
23  import ch.qos.logback.classic.spi.ILoggingEvent;
24  import ch.qos.logback.classic.spi.LoggingEvent;
25  import ch.qos.logback.classic.spi.LoggingEventVO;
26  import ch.qos.logback.classic.util.LogbackMDCAdapter;
27  import ch.qos.logback.core.ConsoleAppender;
28  import ch.qos.logback.core.read.ListAppender;
29  import ch.qos.logback.core.status.OnConsoleStatusListener;
30  import ch.qos.logback.core.status.Status;
31  import ch.qos.logback.core.status.StatusManager;
32  import ch.qos.logback.core.testUtil.RandomUtil;
33  import ch.qos.logback.core.util.StatusListenerConfigHelper;
34  import org.junit.jupiter.api.Test;
35  import org.junit.jupiter.api.Timeout;
36  
37  import java.util.List;
38  import java.util.Map;
39  import java.util.function.Predicate;
40  
41  import static java.util.concurrent.TimeUnit.MILLISECONDS;
42  import static java.util.concurrent.TimeUnit.SECONDS;
43  import static org.junit.jupiter.api.Assertions.assertEquals;
44  import static org.junit.jupiter.api.Assertions.assertNotNull;
45  import static org.junit.jupiter.api.Assertions.assertTrue;
46  import static org.junit.jupiter.api.Assertions.fail;
47  
48  public class SocketAppender1010Test {
49  
50  
51      private static final String HOST = "localhost";
52      private final int port = RandomUtil.getRandomServerPort();
53      private final String mdcKey = "moo" + RandomUtil.getPositiveInt();
54      private final String mdcVal = "mdcVal" + RandomUtil.getPositiveInt();
55      static final String LIST_APPENDER = "LIST_APPENDER";
56  
57  
58      LogbackMDCAdapter mdcAdapterForClient = new LogbackMDCAdapter();
59  
60      @Test
61      @Timeout(value = 2000, unit = MILLISECONDS)
62      public void smoke() {
63          System.out.println("Running on port " + port);
64          LoggerContext serverLoggerContext = buildAndConfigureContextForServer();
65          SimpleSocketServer simpleSocketServer = new SimpleSocketServer(serverLoggerContext, port);
66          simpleSocketServer.start();
67  
68          // wait until server is up
69          yieldLoop(Thread::isAlive, simpleSocketServer);
70  
71          LoggerContext clientLoggerContext = buildAndConfigureContextForClient(mdcAdapterForClient);
72          Logger clientLogger = clientLoggerContext.getLogger(SocketAppender1010Test.class);
73          Logger serverRoot = serverLoggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
74          mdcAdapterForClient.put(mdcKey, mdcVal);
75          clientLogger.info("hello");
76  
77          StatusManager clientStatusManager = clientLoggerContext.getStatusManager();
78  
79          // wait until connection is established
80          yieldLoop(csm -> csm.getCopyOfStatusList().stream().anyMatch(s -> s.getMessage().contains("connection established")), clientStatusManager);
81  
82          ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) serverRoot.getAppender(LIST_APPENDER);
83          assertNotNull(listAppender);
84          assertNotNull(listAppender.list);
85          yieldLoop(( list -> !list.isEmpty()), listAppender.list);
86  
87  
88          assertEquals(1, listAppender.list.size());
89          ILoggingEvent loggingEvent = listAppender.list.get(0);
90          assertNotNull(loggingEvent);
91          assertTrue(loggingEvent instanceof LoggingEventVO);
92          LoggingEventVO loggingEventVO = (LoggingEventVO) loggingEvent;
93          Map<String, String> mdcMap = loggingEventVO.getMdc();
94          assertNotNull(mdcMap);
95          assertEquals(mdcVal, mdcMap.get(mdcKey));
96  
97      }
98  
99  
100     <T> void yieldLoop(Predicate<T> predicate, T arg) {
101         while(true) {
102             if(predicate.test(arg)) {
103                 break;
104             }
105             Thread.yield();
106         }
107     }
108 
109     private static void sleep(int millis) {
110         try {
111             Thread.sleep(millis);
112         } catch (InterruptedException e) {
113             throw new RuntimeException(e);
114         }
115     }
116 
117     LoggerContext buildAndConfigureContextForServer() {
118         LoggerContext context = new LoggerContext();
119         context.setName("serverContext");
120         LogbackMDCAdapter mdcAdapter = new LogbackMDCAdapter();
121         context.setMDCAdapter(mdcAdapter);
122 
123         StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());
124 
125         Logger root = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
126         root.setLevel(Level.DEBUG);
127 
128 
129         ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
130         listAppender.setName(LIST_APPENDER);
131         listAppender.setContext(context);
132         listAppender.start();
133         root.addAppender(listAppender);
134 
135         ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
136         consoleAppender.setContext(context);
137         consoleAppender.setName("STDOUT");
138 
139 //        PatternLayoutEncoder patternLayoutEncoder = new PatternLayoutEncoder();
140 //        patternLayoutEncoder.setContext(context);
141 //        patternLayoutEncoder.setPattern("%-4relative [%thread] %level %logger{35} =%mdc= -%kvp- %msg %n");
142 //        patternLayoutEncoder.setParent(consoleAppender);
143 //        patternLayoutEncoder.start();
144 //        consoleAppender.setEncoder(patternLayoutEncoder);
145 //        consoleAppender.start();
146 //        root.addAppender(consoleAppender);
147 
148         return context;
149     }
150 
151 
152     LoggerContext buildAndConfigureContextForClient(LogbackMDCAdapter mdcAdapter) {
153         LoggerContext context = new LoggerContext();
154         context.setName("clientContext");
155 
156         context.setMDCAdapter(mdcAdapter);
157 
158         StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());
159 
160         SocketAppender socketAppender = buildSocketAppender(context);
161 
162         Logger root = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
163         root.setLevel(Level.DEBUG);
164         root.addAppender(socketAppender);
165 
166         return context;
167     }
168 
169 
170     SocketAppender buildSocketAppender(LoggerContext context) {
171         SocketAppender socketAppender = new SocketAppender();
172         socketAppender.setContext(context);
173         socketAppender.setName("socketAppender");
174         socketAppender.setRemoteHost(HOST);
175         socketAppender.setPort(port);
176         socketAppender.setReconnectionDelay(ch.qos.logback.core.util.Duration.valueOf("100"));
177         socketAppender.setIncludeCallerData(true);
178 
179         socketAppender.start();
180         return socketAppender;
181     }
182 
183 }