001/*
002 * Copyright 2023 the original author or authors.
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * https://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.tools.logging;
017
018import static de.cuioss.tools.logging.CuiLoggerFactory.MARKER_CLASS_NAMES;
019import static de.cuioss.tools.string.MoreStrings.lenientFormat;
020import static de.cuioss.tools.string.MoreStrings.nullToEmpty;
021
022import java.util.Comparator;
023import java.util.List;
024import java.util.function.Supplier;
025import java.util.logging.Level;
026import java.util.logging.Logger;
027
028import de.cuioss.tools.collect.CollectionLiterals;
029import de.cuioss.tools.reflect.MoreReflection;
030import lombok.AccessLevel;
031import lombok.Getter;
032import lombok.NonNull;
033import lombok.RequiredArgsConstructor;
034
035/**
036 * Defines the log-levels with implicit mapping
037 *
038 * @author Oliver Wolff
039 */
040@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
041public enum LogLevel {
042
043    /**
044     * Trace Level, maps to {@link Level#FINER}.
045     * <p>
046     * Attention: This is a derivation to
047     * http://www.slf4j.org/apidocs/org/slf4j/bridge/SLF4JBridgeHandler.html. But in
048     * fact this works...
049     */
050    TRACE(Level.FINER),
051
052    /**
053     * Debug Level, maps to {@link Level#FINE}.
054     */
055    DEBUG(Level.FINE),
056
057    /**
058     * Info Level, maps to {@link Level#INFO}.
059     */
060    INFO(Level.INFO),
061
062    /**
063     * Warn Level, maps to {@link Level#WARNING}.
064     */
065    WARN(Level.WARNING),
066
067    /**
068     * Error Level, maps to {@link Level#SEVERE}.
069     */
070    ERROR(Level.SEVERE),
071
072    /**
073     * Off Level, maps to {@link Level#OFF}
074     */
075    OFF(Level.OFF);
076
077    @Getter(AccessLevel.PACKAGE)
078    private final Level juliLevel;
079
080    /**
081     * @param logger to be checked, must not be null
082     * @return {@code true} if the log-level is enabled on the logger, false
083     *         otherwise
084     */
085    boolean isEnabled(final Logger logger) {
086        return logger.isLoggable(getJuliLevel());
087    }
088
089    /**
090     * Logs the message
091     *
092     * @param logger    to be used, must not be null
093     * @param message   must not be null
094     * @param throwable to be logged, may be null
095     */
096    void handleActualLog(final Logger logger, final String message, final Throwable throwable) {
097        if (!isEnabled(logger)) {
098            return;
099        }
100        doLog(logger, message, throwable);
101    }
102
103    /**
104     * @param juliLevel
105     * @return CUI log level
106     */
107    public static LogLevel from(@NonNull final Level juliLevel) {
108        // highest value first, i.e. OFF, ERROR, WARN, INFO, DEBUG, TRACE
109        List<LogLevel> sortedCuiLevels = CollectionLiterals.mutableList(values());
110        sortedCuiLevels.sort(Comparator.comparing(logLevel -> logLevel.getJuliLevel().intValue()));
111        sortedCuiLevels.sort(Comparator.reverseOrder());
112
113        final var juliIntLevel = juliLevel.intValue();
114        for (LogLevel cuiLevel : sortedCuiLevels) {
115            final var cuiIntLevel = cuiLevel.getJuliLevel().intValue();
116            if (cuiIntLevel <= juliIntLevel) {
117                return cuiLevel;
118            }
119        }
120        return TRACE;
121    }
122
123    private void doLog(final Logger logger, final String message, final Throwable throwable) {
124        // We go up the stack-trace until we found the call from CuiLogger.
125        final var caller = MoreReflection.findCallerElement(throwable, MARKER_CLASS_NAMES);
126        if (caller.isPresent()) {
127            // This is needed because otherwise LogRecord will assume this class and this
128            // method as
129            // the source of the log-statement
130            logger.logp(getJuliLevel(), caller.get().getClassName(), caller.get().getMethodName(), message, throwable);
131        } else {
132            logger.log(getJuliLevel(), message, throwable);
133        }
134    }
135
136    @SuppressWarnings("squid:S2629")
137    // False positive, logger state explicitly checked
138    void log(final Logger logger, final String template, final Object... parameter) {
139        if (isEnabled(logger)) {
140            final var replacedTemplate = de.cuioss.tools.logging.CuiLogger.SLF4J_PATTERN.matcher(nullToEmpty(template))
141                    .replaceAll("%s");
142            doLog(logger, lenientFormat(replacedTemplate, parameter), null);
143        }
144    }
145
146    @SuppressWarnings("squid:S2629")
147    // False positive, logger state explicitly checked
148    void log(final Logger logger, Supplier<String> message, final Throwable throwable) {
149        if (isEnabled(logger)) {
150            doLog(logger, message.get(), throwable);
151        }
152    }
153
154    @SuppressWarnings("squid:S2629")
155    // False positive, logger state explicitly checked
156    void log(final Logger logger, final Throwable throwable, final String template, final Object... parameter) {
157        if (isEnabled(logger)) {
158            final var replacedTemplate = de.cuioss.tools.logging.CuiLogger.SLF4J_PATTERN.matcher(nullToEmpty(template))
159                    .replaceAll("%s");
160            doLog(logger, lenientFormat(replacedTemplate, parameter), throwable);
161        }
162    }
163}