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

com.dua3.utility.logging.slf4j.LoggerFactorySlf4j Maven / Gradle / Ivy

There is a newer version: 15.0.2
Show newest version
package com.dua3.utility.logging.slf4j;

import com.dua3.utility.data.Pair;
import com.dua3.utility.lang.LangUtil;
import com.dua3.utility.logging.LogEntryFilter;
import com.dua3.utility.logging.LogEntryHandler;
import com.dua3.utility.logging.ConsoleHandler;
import com.dua3.utility.logging.LogEntryDispatcher;
import org.jspecify.annotations.Nullable;
import org.slf4j.ILoggerFactory;
import org.slf4j.event.Level;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;

/**
 * The LoggerFactorySlf4j class is an implementation of the ILoggerFactory and LogEntryDispatcher interfaces.
 */
public class LoggerFactorySlf4j implements ILoggerFactory, LogEntryDispatcher {
    /**
     * Specifies the logging level for the {@code LoggerFactorySlf4j}.
     *
     * 

This variable defines the configuration key used to set the logging level * across the application. It is typically read from a properties file during * the initialization of the logger factory. * *

Possible values include logging levels such as "DEBUG", "INFO", "WARN", "ERROR", etc. */ public static final String LEVEL = "logger.level"; /** * Configuration key used to specify the stream to which log entries are written for console logging. * *

This key can be used to configure the console output stream in the logging properties file, * enabling redirection of log entries to different streams such as System.out or System.err. */ public static final String LOGGER_CONSOLE_STREAM = "logger.console.stream"; /** * Property key to enable or disable colored output for the console logger. */ public static final String LOGGER_CONSOLE_COLORED = "logger.console.colored"; private final List> prefixes = new ArrayList<>(); private final List> handlers = new ArrayList<>(); private final @Nullable LogEntryHandler defaultHandler; private volatile LogEntryFilter filter = LogEntryFilter.ALL_PASS_FILTER; /** * Constructs a new instance of LoggerFactorySlf4j. * *

The constructor initializes logging properties from a properties file, * sets the default logging level, configures logging prefixes and console handlers. * *

The initialization process includes: *

    *
  1. Loading properties from the logging properties file. *
  2. Parsing the log level declaration and setting the global/default log level. *
  3. Configuring specific log levels for various log message prefixes. *
  4. Setting up console handlers based on properties for console stream and colored output. *
* * @throws IllegalArgumentException if an invalid logging configuration is detected. */ public LoggerFactorySlf4j() { Properties properties = getProperties(); // parse log level entry String levelDeclaration = properties.getProperty(LEVEL, Level.INFO.name()); String[] decls = levelDeclaration.split(","); if (decls.length > 0) { LoggerSlf4j.setDefaultLevel(Level.valueOf(decls[0].strip())); } Arrays.stream(decls) .skip(1) // global level has already been set .forEachOrdered(s -> { String[] parts = s.split(":"); LangUtil.check(parts.length == 2, "invalid log level declaration: %s", s); String prefix = parts[0].strip(); Level level = Level.valueOf(parts[1].strip()); var entry = getPrefixEntry(prefix); LangUtil.check(entry.isEmpty(), () -> new IllegalStateException("prefix '%s' is shadowed by '%s'".formatted(prefix, entry.orElseThrow().first()))); prefixes.add(Pair.of(prefix, level)); }); // configure console handler String propertyConsoleStream = properties.getProperty(LOGGER_CONSOLE_STREAM, "").trim().toLowerCase(Locale.ROOT); final PrintStream stream = switch (propertyConsoleStream) { case "" -> null; case "system.err" -> //noinspection UseOfSystemOutOrSystemErr System.err; case "system.out" -> //noinspection UseOfSystemOutOrSystemErr System.out; default -> throw new IllegalArgumentException("invalid value for property " + LOGGER_CONSOLE_STREAM + ": '" + propertyConsoleStream + "'"); }; String propertyConsoleColored = properties.getProperty(LOGGER_CONSOLE_COLORED, "auto").trim().toLowerCase(Locale.ROOT); final boolean colored = switch (propertyConsoleColored) { case "true" -> true; case "false" -> false; // "auto" enables colored output when a terminal is attached and the TERM environment variable is set case "auto" -> System.console() != null && System.getenv().get("TERM") != null; default -> throw new IllegalArgumentException("invalid value for property " + LOGGER_CONSOLE_COLORED + ": '" + propertyConsoleColored + "'"); }; this.defaultHandler = stream != null ? new ConsoleHandler(stream, colored) : null; if (defaultHandler != null) { handlers.add(new WeakReference<>(defaultHandler)); } } @SuppressWarnings("UseOfSystemOutOrSystemErr") private static Properties getProperties() { Properties properties = new Properties(); try (InputStream in = ClassLoader.getSystemResourceAsStream("logging.properties")) { if (in == null) { properties.setProperty(LOGGER_CONSOLE_STREAM, "system.out"); } else { properties.load(in); } } catch (IOException e) { properties.setProperty(LOGGER_CONSOLE_STREAM, "system.out"); e.printStackTrace(System.err); } return properties; } private Optional> getPrefixEntry(String name) { return prefixes.stream().filter(p -> name.startsWith(p.first())).findFirst(); } private Level getLevel(String name) { return getPrefixEntry(name).map(Pair::second).orElseGet(LoggerSlf4j::getDefaultLevel); } @Override public org.slf4j.Logger getLogger(String name) { LoggerSlf4j logger = new LoggerSlf4j(name, handlers); logger.setLevel(getLevel(name)); return logger; } @Override public void addLogEntryHandler(LogEntryHandler handler) { handlers.add(new WeakReference<>(handler)); } @Override public void removeLogEntryHandler(LogEntryHandler handler) { handlers.removeIf(h -> h.get() == handler); } @Override public void setFilter(LogEntryFilter filter) { this.filter = filter; } @Override public LogEntryFilter getFilter() { return filter; } @Override public Collection getLogEntryHandlers() { return handlers.stream().map(WeakReference::get).filter(Objects::nonNull).toList(); } /** * Returns the default {@code LogEntryHandler}. * * @return the default log entry handler */ public Optional getDefaultHandler() { return Optional.ofNullable(defaultHandler); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy