LoggingUtil.java

/*
 * Copyright 2024 Morimekta Utils Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 net.morimekta.tiny.logback;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.jul.LevelChangePropagator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;

import java.io.IOException;
import java.util.stream.Stream;

/**
 * Utility class for configuring and managing logback logging.
 */
public class LoggingUtil {
    /**
     * Initialize log forwarding from JUL and Java System logger to SLF4J.
     */
    public static void initLogForwarding() {
        // Send JUL & java System logger to SLF4J.
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
        var levelChangePropagator = new LevelChangePropagator();
        levelChangePropagator.setContext(context);
        levelChangePropagator.setResetJUL(true);
        context.addListener(levelChangePropagator);

        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();
    }

    /**
     * Flush all console logging appenders to ensure output is written before exit.
     */
    public static void flushConsoleLogging() {
        // Force console loggers to flush output. Otherwise, some logging may
        // be missing just before application exits.
        rootLoggingAppenderStream()
                .filter(app -> app instanceof ConsoleAppender)
                .map(app -> (ConsoleAppender<?>) app)
                .forEach(app -> {
                    try {
                        app.getOutputStream().flush();
                    } catch (IOException ignore) {
                    }
                });
    }

    /**
     * Flush all file logging appenders to ensure output is written before exit.
     */
    public static void flushFileLogging() {
        // Force file loggers to flush output. Otherwise, some logging may
        // be missing just before application exits.
        rootLoggingAppenderStream()
                .filter(app -> app instanceof FileAppender)
                .map(app -> (FileAppender<?>) app)
                .forEach(app -> {
                    try {
                        app.getOutputStream().flush();
                    } catch (IOException ignore) {
                    }
                });
    }

    private static Stream<Appender<ILoggingEvent>> rootLoggingAppenderStream() {
        return ((LoggerContext) LoggerFactory.getILoggerFactory())
                .getLoggerList()
                .stream()
                .map(Logger::iteratorForAppenders)
                .flatMap(it -> {
                    Stream.Builder<Appender<ILoggingEvent>> builder = Stream.builder();
                    it.forEachRemaining(builder::add);
                    return builder.build();
                });
    }
}