We are using Log4j as our logging framework. One of our loggers has custom Log level. When calling actuator /loggers endpoint to get list of all loggers exception raises: EffectiveLevel must not be null

This error is from LoggerConfiguration class constructor which doesnt let null values on effectiveLevel variable:

public LoggerConfiguration(String name, LogLevel configuredLevel, LogLevel effectiveLevel) {
        Assert.notNull(name, "Name must not be null");
        Assert.notNull(effectiveLevel, "EffectiveLevel must not be null");
        this.name = name;
        this.configuredLevel = configuredLevel;
        this.effectiveLevel = effectiveLevel;
    }

but the real problem comes when calling this class constructor. in Log4J2LoggingSystem.convertLoggerConfig:

private LoggerConfiguration convertLoggerConfig(String name, LoggerConfig loggerConfig) {
        if (loggerConfig == null) {
            return null;
        }
        LogLevel level = LEVELS.convertNativeToSystem(loggerConfig.getLevel());
        if (!StringUtils.hasLength(name) || LogManager.ROOT_LOGGER_NAME.equals(name)) {
            name = ROOT_LOGGER_NAME;
        }
        boolean isLoggerConfigured = loggerConfig.getName().equals(name);
        LogLevel configuredLevel = (isLoggerConfigured) ? level : null;
        return new LoggerConfiguration(name, configuredLevel, level);
    }

The LEVELS variable:

private static final LogLevels<Level> LEVELS = new LogLevels<>();

    static {
        LEVELS.map(LogLevel.TRACE, Level.TRACE);
        LEVELS.map(LogLevel.DEBUG, Level.DEBUG);
        LEVELS.map(LogLevel.INFO, Level.INFO);
        LEVELS.map(LogLevel.WARN, Level.WARN);
        LEVELS.map(LogLevel.ERROR, Level.ERROR);
        LEVELS.map(LogLevel.FATAL, Level.FATAL);
        LEVELS.map(LogLevel.OFF, Level.OFF);
    }

is like above. Our custom log level doesn't exist in this Map. so this line returns null

LogLevel level = LEVELS.convertNativeToSystem(loggerConfig.getLevel());

and we get the exception.

Comment From: wilkinsona

Thanks for the report. I wasn't aware that Log4j2 supported custom log levels. As you have noticed, the problem isn't specific to the actuator endpoint. Setting logging.level properties also won't work with a custom level as the values must be one of the levels defined by org.springframework.boot.logging.LogLevel.

We can consider adding support for custom log levels but it looks like it would be quite invasive and require changes to public API. As such, I'm afraid it's unlikely to happen soon.

We could also consider just tolerating levels that we don't recognise, either by dropping them or by performing an approximate mapping. I'm not sure that either would be useful in practice though.

Comment From: miladamery

I think its acceptable to lose some framework features by using custom log levels, but completely losing list of loggers feature is sour. my lack of knowledge forbids me of going forward. If there is anything I can help with please tell.

Comment From: wilkinsona

This also affects Java Util Logging as it too supports custom levels.

Comment From: philwebb

@miladamery Out of interest, how do you define your custom log levels? I assume you use org.apache.logging.log4j.Level.forName(String, int), but when do you make this call?

Comment From: miladamery

@philwebb yeah, you can create your custom log level with that method, we can pass that variable to log method of logger.

import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager

val logger = LogManager.getLogger()
val CUSTOM_LEVEL= Level.forName("CUSTOM_LEVEL", 1000)
logger.log(CUSTOM_LEVEL, log)

Comment From: wilkinsona

We'd like to update 2.7.x to tolerate custom log levels in the actuator endpoint. Whether or not that's possible will depend on the scope of the fix and the need for public API changes.