All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.binjr.common.logging.TextFlowAppender Maven / Gradle / Ivy

There is a newer version: 3.20.1
Show newest version
/*
 *    Copyright 2017-2021 Frederic Thevenet
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package eu.binjr.common.logging;

import eu.binjr.core.dialogs.Dialogs;
import eu.binjr.core.preferences.AppEnvironment;
import eu.binjr.core.preferences.UserPreferences;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

/**
 * TextFlowAppender for Log4j 2
 *
 * @author Frederic Thevenet
 */
@Plugin(
        name = "TextFlowAppender",
        category = "Core",
        elementType = "appender",
        printObject = true)
public  class TextFlowAppender extends AbstractAppender {
    private final Lock renderTextLock = new ReentrantLock();
    private final Map logColors = new HashMap<>();
    private final String defaultColor = "log-info";
    private final LogBuffer logBuffer = new LogBuffer<>();
    private Consumer> renderTextDelegate;

    protected TextFlowAppender(String name, Filter filter,
                               Layout layout,
                               final boolean ignoreExceptions) {
        super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
        logColors.put(Level.TRACE, "trace");
        logColors.put(Level.DEBUG, "debug");
        logColors.put(Logger.PERF, "perf");
        logColors.put(Level.INFO, "info");
        logColors.put(Level.WARN, "warn");
        logColors.put(Level.ERROR, "error");
        logColors.put(Level.FATAL, "fatal");
        Timer timer = new Timer(true);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                if (AppEnvironment.getInstance().isDebugMode()) {
                    refreshTextFlow();
                }
            }
        }, 500, 500);
    }

    /**
     * Factory method. Log4j will parse the configuration and call this factory
     * method to construct the appender with
     * the configured attributes.
     *
     * @param name   Name of appender
     * @param layout Log layout of appender
     * @param filter Filter for appender
     * @return The TextFlowAppender
     */
    @PluginFactory
    public static TextFlowAppender createAppender(
            @PluginAttribute("name") String name,
            @PluginElement("Layout") Layout layout,
            @PluginElement("Filter") final Filter filter) {
        if (name == null) {
            LOGGER.error("No name provided for TextFlowAppender");
            return null;
        }
        if (layout == null) {
            layout = PatternLayout.createDefaultLayout();
        }
        return new TextFlowAppender(name, filter, layout, true);
    }

    public void setRenderTextDelegate(Consumer> delegate) {
        this.renderTextDelegate = delegate;
    }

    /**
     * Clear the circular buffer used by the appender
     */
    public void clearBuffer() {
        renderTextLock.lock();
        try {
            logBuffer.clear();
        } finally {
            renderTextLock.unlock();
        }
    }

    /**
     * This method is where the appender does the work.
     *
     * @param event Log event with log data
     */
    @Override
    public void append(LogEvent event) {
        renderTextLock.lock();
        try {
            new String(getLayout().toByteArray(event)).lines().forEach(
                    message -> {
                        Log log = new Log(message, logColors.getOrDefault(event.getLevel(), defaultColor));
                        logBuffer.put(log, null);
                    });
        } finally {
            renderTextLock.unlock();
        }
    }

    private void refreshTextFlow() {
        Dialogs.runOnFXThread(() -> {
            if (renderTextLock.tryLock()) {
                try {
                    if (renderTextDelegate != null && logBuffer.isDirty()) {
                        logBuffer.clean();
                        renderTextDelegate.accept(logBuffer.keySet());
                    }
                } finally {
                    renderTextLock.unlock();
                }
            }
        });
    }

    public record Log(String message, String styleClass) {

        public String getMessage() {
            return message;
        }

        public String getStyleClass() {
            return styleClass;
        }
    }

    private static class LogBuffer extends LinkedHashMap {
        private volatile boolean dirty;

        public void clean() {
            this.dirty = false;
        }

        @Override
        public V put(K key, V value) {
            dirty = true;
            return super.put(key, value);
        }

        @Override
        public void clear() {
            dirty = true;
            super.clear();
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry eldest) {
            return size() > UserPreferences.getInstance().consoleMaxLineCapacity.get().intValue();
        }

        public boolean isDirty() {
            return dirty;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy