SocketAppender1010Test.java

/*
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v2.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */

package ch.qos.logback.classic.issue.github1010;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.net.SimpleSocketServer;
import ch.qos.logback.classic.net.SocketAppender;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.classic.spi.LoggingEventVO;
import ch.qos.logback.classic.util.LogbackMDCAdapter;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.read.ListAppender;
import ch.qos.logback.core.status.OnConsoleStatusListener;
import ch.qos.logback.core.status.Status;
import ch.qos.logback.core.status.StatusManager;
import ch.qos.logback.core.testUtil.RandomUtil;
import ch.qos.logback.core.util.StatusListenerConfigHelper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;

import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

public class SocketAppender1010Test {


    private static final String HOST = "localhost";
    private final int port = RandomUtil.getRandomServerPort();
    private final String mdcKey = "moo" + RandomUtil.getPositiveInt();
    private final String mdcVal = "mdcVal" + RandomUtil.getPositiveInt();
    static final String LIST_APPENDER = "LIST_APPENDER";


    LogbackMDCAdapter mdcAdapterForClient = new LogbackMDCAdapter();

    @Test
    @Timeout(value = 2000, unit = MILLISECONDS)
    public void smoke() {
        System.out.println("Running on port " + port);
        LoggerContext serverLoggerContext = buildAndConfigureContextForServer();
        SimpleSocketServer simpleSocketServer = new SimpleSocketServer(serverLoggerContext, port);
        simpleSocketServer.start();

        // wait until server is up
        yieldLoop(Thread::isAlive, simpleSocketServer);

        LoggerContext clientLoggerContext = buildAndConfigureContextForClient(mdcAdapterForClient);
        Logger clientLogger = clientLoggerContext.getLogger(SocketAppender1010Test.class);
        Logger serverRoot = serverLoggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
        mdcAdapterForClient.put(mdcKey, mdcVal);
        clientLogger.info("hello");

        StatusManager clientStatusManager = clientLoggerContext.getStatusManager();

        // wait until connection is established
        yieldLoop(csm -> csm.getCopyOfStatusList().stream().anyMatch(s -> s.getMessage().contains("connection established")), clientStatusManager);

        ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) serverRoot.getAppender(LIST_APPENDER);
        assertNotNull(listAppender);
        assertNotNull(listAppender.list);
        yieldLoop(( list -> !list.isEmpty()), listAppender.list);


        assertEquals(1, listAppender.list.size());
        ILoggingEvent loggingEvent = listAppender.list.get(0);
        assertNotNull(loggingEvent);
        assertTrue(loggingEvent instanceof LoggingEventVO);
        LoggingEventVO loggingEventVO = (LoggingEventVO) loggingEvent;
        Map<String, String> mdcMap = loggingEventVO.getMdc();
        assertNotNull(mdcMap);
        assertEquals(mdcVal, mdcMap.get(mdcKey));

    }


    <T> void yieldLoop(Predicate<T> predicate, T arg) {
        while(true) {
            if(predicate.test(arg)) {
                break;
            }
            Thread.yield();
        }
    }

    private static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    LoggerContext buildAndConfigureContextForServer() {
        LoggerContext context = new LoggerContext();
        context.setName("serverContext");
        LogbackMDCAdapter mdcAdapter = new LogbackMDCAdapter();
        context.setMDCAdapter(mdcAdapter);

        StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());

        Logger root = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
        root.setLevel(Level.DEBUG);


        ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
        listAppender.setName(LIST_APPENDER);
        listAppender.setContext(context);
        listAppender.start();
        root.addAppender(listAppender);

        ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
        consoleAppender.setContext(context);
        consoleAppender.setName("STDOUT");

//        PatternLayoutEncoder patternLayoutEncoder = new PatternLayoutEncoder();
//        patternLayoutEncoder.setContext(context);
//        patternLayoutEncoder.setPattern("%-4relative [%thread] %level %logger{35} =%mdc= -%kvp- %msg %n");
//        patternLayoutEncoder.setParent(consoleAppender);
//        patternLayoutEncoder.start();
//        consoleAppender.setEncoder(patternLayoutEncoder);
//        consoleAppender.start();
//        root.addAppender(consoleAppender);

        return context;
    }


    LoggerContext buildAndConfigureContextForClient(LogbackMDCAdapter mdcAdapter) {
        LoggerContext context = new LoggerContext();
        context.setName("clientContext");

        context.setMDCAdapter(mdcAdapter);

        StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());

        SocketAppender socketAppender = buildSocketAppender(context);

        Logger root = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
        root.setLevel(Level.DEBUG);
        root.addAppender(socketAppender);

        return context;
    }


    SocketAppender buildSocketAppender(LoggerContext context) {
        SocketAppender socketAppender = new SocketAppender();
        socketAppender.setContext(context);
        socketAppender.setName("socketAppender");
        socketAppender.setRemoteHost(HOST);
        socketAppender.setPort(port);
        socketAppender.setReconnectionDelay(ch.qos.logback.core.util.Duration.valueOf("100"));
        socketAppender.setIncludeCallerData(true);

        socketAppender.start();
        return socketAppender;
    }

}