6

I am new with Logging and Log4j. What I want to do is change logger level per request. This means:

Normally, the priority level is set to ERROR, but a user can call the server with a special parameter to set the priority log level to DEBUG, but only for that user/request.

This means that if a user A sends a request http://myServer.com/test it logs only those message that have a priority of ERROR.

But if a user A sends a request http://myServer.com/test?debug=true, the logger logs all messages, however if user B simultaneously sends requests http://myServer.com/test only ERROR messages are logged.

It would be good if those logs can be saved in new appenders.

Dai Bok
  • 3,451
  • 2
  • 53
  • 70
Marko Zadravec
  • 8,298
  • 10
  • 55
  • 97

2 Answers2

8

I think you should use Log4j Filters.

Have this in your log4j2.xml configuration:

<DynamicThresholdFilter key="X-Log-Level" onMatch="ACCEPT" onMismatch="NEUTRAL" defaultThreshold="ERROR">
    <KeyValuePair key="TRACE" value="TRACE"/>
    <KeyValuePair key="DEBUG" value="DEBUG"/>
</DynamicThresholdFilter>

(...) And set up a filter in your request that will assign "X-Log-Level" to the Thread Context via, for example, MDC.

// Replace the hardcoded logLevel value with something dynamic,
// ideally from the http request header.
String logLevel = "DEBUG";
MDC.put("X-Log-Level", logLevel);
juliaaano
  • 1,307
  • 1
  • 13
  • 11
0

I know this post is ancient but ... it was one of the first that came up in my search so I thought I would update it for anyone looking for something similar in 2023. So it is absolutely possible to do this on a request basis, you probably only want to do this for testing purposes but with that being said let's get started. To start with I also found this reference (https://www.springcloud.io/post/2022-03/dynamically-modifying-logger-log-levels/#gsc.tab=0) which gave a pretty ok idea how to programmatically set the log level but not a request basis. If you want to be able to do it on a request basis you should use a spring interceptor. Below is the interceptor implementation I wrote up. Do note that if you change the log level on the inbound request it will stay at that level until you reset it so in the interceptor I also have a postHandle that will reset the log level to the original value.

Note: I do have feature switches and some constants in this example you will have to sub or setup in your project.

package com.me.SpringBoot2022Example.v1.interceptor;

import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.me.SpringBoot2022Example.v1.util.ExampleConstants.*;
import static com.me.SpringBoot2022Example.v1.util.ExampleFeatureSwitches.ALLOW_POSTMAN_SET_LOG_LEVEL;

@Slf4j
public class LogLevelInterceptor implements HandlerInterceptor {

    /**
     * Executed before actual handler is executed
     **/
    @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) {
        // If the feature switch is set to true then we can allow overriding certain request attributes in support of
        // testing. (Note: This would normally be used by postman. See the postman files in this repository.)
        if (ALLOW_POSTMAN_SET_LOG_LEVEL) {
            this.setLogLevelByRequest(request);
        }
        return true;
    }

    /**
     * Executed before after handler is executed
     **/
    @Override
    public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final ModelAndView modelAndView) throws Exception {
        // If the feature switch is set to true then we can allow overriding certain request attributes in support of
        // testing. (Note: This would normally be used by postman. See the postman files in this repository.)
        if (ALLOW_POSTMAN_SET_LOG_LEVEL) {
            log.info("setLogLevelByRequest: Resetting log level to original value ...");
            final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
            // Ensure that the logger context is returned
            if (loggerContext != null) {
                // See if the caller attempted to specify a specific logger to change the log level on
                String loggerName = request.getHeader(HEADER_REQUEST_LOGGER_NAME);
                // If the logger was not specified the logger context will be defaulted to the ROOT logger
                if (loggerName == null || loggerName.isEmpty()) {
                    log.warn("setLogLevelByRequest: Logger to update log level not provided, defaulting to ROOT logger ...");
                    loggerName = org.slf4j.Logger.ROOT_LOGGER_NAME;
                }
                // Check if the provided logger exists
                final Logger logger = loggerContext.exists(loggerName);
                // If the logger exists ensure that it was returned
                if (logger != null) {
                    final String previousLogLevel = request.getAttribute("previousLogLevel").toString();
                    // Attempt to create the new log level with the provided values
                    final Level newLevel = Level.toLevel(previousLogLevel, null);
                    // Verify the updated log level has been created and apply to the logger
                    if (newLevel != null) {
                        log.info("setLogLevelByRequest: Reset log level to original value [{}] for this request ...", newLevel.levelStr);
                        logger.setLevel(newLevel);
                    } else {
                        log.error("setLogLevelByRequest: Failed to create new log level - custom log level can not be reset ...");
                    }
                } else {
                    log.error("setLogLevelByRequest: Failed to get logger [{}] - custom log level can not be reset ...", loggerName);
                }
            } else {
                log.error("setLogLevelByRequest: Failed to get loggerContext - custom log level can not be reset ...");
            }
        }
    }

    private boolean setLogLevelByRequest(final HttpServletRequest request) {
        log.info("setLogLevelByRequest: Setting custom log level for the inbound request ...");
        // If feature is enabled attempt to get the specified log level
        final String logLevel   = request.getHeader(HEADER_REQUEST_LOG_LEVEL_OVERRIDE);
        // If no log level value was specified then the log level will not be reset
        if (logLevel != null && !logLevel.isEmpty()) {
            final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
            // Ensure that the logger context is returned
            if (loggerContext != null) {
                // See if the caller attempted to specify a specific logger to change the log level on
                String loggerName = request.getHeader(HEADER_REQUEST_LOGGER_NAME);
                // If the logger was not specified the logger context will be defaulted to the ROOT logger
                if (loggerName == null || loggerName.isEmpty()) {
                    log.warn("setLogLevelByRequest: Logger to update log level not provided, defaulting to ROOT logger ...");
                    loggerName = org.slf4j.Logger.ROOT_LOGGER_NAME;
                }
                // Check if the provided logger exists
                final Logger logger = loggerContext.exists(loggerName);
                // If the logger exists ensure that it was returned
                if (logger != null) {
                    // Attempt to create the new log level with the provided values
                    final Level newLevel = Level.toLevel(logLevel, null);
                    // Verify the updated log level has been created and apply to the logger
                    if (newLevel != null) {
                        request.setAttribute("previousLogLevel", this.currentLogLevel(loggerContext, loggerName));
                        log.info("setLogLevelByRequest: Custom log level will be set to [{}] for this request ...", newLevel.levelStr);
                        logger.setLevel(newLevel);
                    } else {
                        log.error("setLogLevelByRequest: Failed to create new log level - custom log level will not be applied to this call ...");
                    }
                } else {
                    log.error("setLogLevelByRequest: Failed to get logger [{}] - custom log level will not be applied to this call ...", loggerName);
                }
            } else {
                log.error("setLogLevelByRequest: Failed to get loggerContext - custom log level will not be applied to this call ...");
            }
        } else {
            log.warn("setLogLevelByRequest: Request for custom log level present but no log level provided - custom log level will not be applied to this call ...");
        }
        return true;
    }

    private String currentLogLevel(final LoggerContext loggerContext, final String loggerName) {
        // Get all loggers of the system
        return loggerContext.getLoggerList().stream()
                // There are too many loggers, and they are filtered here for demonstration purposes.
                // Only the logger for the passed in name is returned.
                .filter(logger -> logger.getName().equals(loggerName))
                // Mapping to map
                .map(logger -> logger.getEffectiveLevel().levelStr)
                .collect(Collectors.toList())
                .get(0);
    }
}```
Rhineb
  • 305
  • 3
  • 12