How to change WildFly logging levels programatically from within deployed application

626 views Asked by At

I am currently running WildFly 23, and I want to force a certain log level when my application is deployed in it. Meaning that, if someone were to go into the standalone.xml file and change the log level to something else, the application would detect it and change it back to the level I want.

The detecting part is easy, but I'm having trouble setting it back. I work with slf4j and it does not have a setLevel method. I could do it by importing the log4j dependency and change the level through log4j's setLevel method, but I do not want to import specific logger library dependencies, I'd like to stay only with slf4j.

I could also do it through jboss-cli commands, but that requires running a separate script, which could be manually modified as well, so I wanted to do it programatically from inside the code.

I have searched a lot for a way to do this but have not found anything that could help me out. Does anyone have any idea on how one would go about doing that, if it is even possible?

Thank you very much!

3

There are 3 answers

0
randomlygeneratedname On BEST ANSWER

Sorry for posting an answer so long after the question, I actually managed to solve this a while ago but forgot to post my solution here.

As James said in his answer, you should probably avoid doing this, but in my case I had to do it because of regulatory rules where the system could NEVER execute without the logs running. But we also couldn't not execute the system if logs were off because it's a critical system that should never be offline. This might not be the best solution out there, but if you're in the same boat, here's how I did it:

First of all, you'll need the Wildfly Client dependency in your project:

<!-- https://mvnrepository.com/artifact/org.wildfly.core/wildfly-controller-client -->
<dependency>
    <groupId>org.wildfly.core</groupId>
    <artifactId>wildfly-controller-client</artifactId>
    <version>18.1.1.Final</version>
</dependency>

Then you can use the Wildfly Client to execute jboss-cli commands:

//First we find out the WildFly parameters
String hostname = (String) ManagementFactory.getPlatformMBeanServer().getAttribute(new ObjectName("jboss.as:interface=public"), "inet-address");
Integer managementPort = (Integer) ManagementFactory.getPlatformMBeanServer().getAttribute(new ObjectName("jboss.as:socket-binding-group=standard-sockets,socket-binding=management-http"), "port");
Integer portOffset = (Integer) ManagementFactory.getPlatformMBeanServer().getAttribute(new ObjectName("jboss.as:socket-binding-group=standard-sockets"), "port-offset");

//Then we create the client using the parameters obtained above
try (ModelControllerClient client = ModelControllerClient.Factory.create(hostname, managementPort+portOffset)) {
    //Creates the change log level operation
    ModelNode op = new ModelNode();
    op.get("operation").set("change-root-log-level");
    //Writes the way to the root logger that will be changed
    ModelNode addr = op.get("address");
    addr.add("subsystem", "logging");
    addr.add("logging-profile", "myApp"); //myApp is my logging-profile name, yours will be different!
    addr.add("root-logger", "ROOT");
    //Makes the level change
    op.get("level").set("INFO");
    //Executes the operation
    ModelNode returnVal = client.execute(op);
    
    //If you want, you can log the results
    if(logger.isInfoEnabled()) {
        logger.info("Execution results:");
        logger.info("Outcome: {}", returnVal.get("outcome"));
        logger.info("Result: {}", returnVal.get("result"));
    }
}

I hope this helps someone out there!

0
James R. Perkins On

There is no good way to do this as using a logging facade like slf4j you can't configure the log manager. This is a good thing though as, IMO, it's bad practice to change logging programatically.

That said what you could do is create a logging.properties you keep in your deployment which configures logging for you.

# Additional loggers to configure (the root logger is always configured)
loggers=my.logger.name

logger.level=INFO
logger.handlers=CONSOLE,FILE

logger.my.logger.name.level=DEBUG

handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler
handler.CONSOLE.formatter=COLOR-PATTERN
handler.CONSOLE.properties=autoFlush,target
handler.CONSOLE.autoFlush=true
handler.CONSOLE.target=SYSTEM_OUT

handler.FILE=org.jboss.logmanager.handlers.PeriodicRotatingFileHandler
handler.FILE.formatter=PATTERN
handler.FILE.properties=autoFlush,append,fileName,suffix
handler.FILE.constructorProperties=fileName,append
handler.FILE.autoFlush=true
handler.FILE.append=true
handler.FILE.fileName=${jboss.server.log.dir}/my-app.log
handler.FILE.suffix=.yyyy-MM-dd

formatter.COLOR-PATTERN=org.jboss.logmanager.formatters.PatternFormatter
formatter.COLOR-PATTERN.properties=pattern
formatter.COLOR-PATTERN.pattern=%K{level}%d{HH\:mm\:ss,SSS} %-5p [%c] (%t) %s%e%n

formatter.PATTERN=org.jboss.logmanager.formatters.PatternFormatter
formatter.PATTERN.properties=pattern
formatter.PATTERN.pattern=%d{yyyy-MM-dd HH\:mm\:ss,SSS} %-5p [%c] (%t) %s%e%n

The one catch is you can't write to the server.log, but you can still write to the console. You'd place this file in your deployment and every time it's deployed it would configure a log context for you which cannot be overridden via management operations like CLI.

0
mrts On

Using the WildFly client is a great way to change the log level. It is a bit easier with the in-JVM client connection using scriptsupport.CLI instead of ModelControllerClient (also note that I'm using Lombok's val here):

import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.jboss.as.cli.scriptsupport.CLI;

@Slf4j
public class LogLevelChanger {

    private static final String LOGGER_SUBSYSTEM = "/subsystem=logging/logger=";

    public static void changeLogLevel(String loggerName, String newLevel) {
        val cli = CLI.newInstance();
        try {
            cli.connect();
            changeConsoleHandlerLogLevel(cli, newLevel);
            val command = String.format(LOGGER_SUBSYSTEM +
                    (loggerExists(cli, loggerName) ?
                        "%s:change-log-level" :
                        "%s:add") +
                    "(level=%s)",
                loggerName, newLevel);
            val result = cli.cmd(command);
            if (result.isSuccess()) {
                log.info("{} log level changed successfully to {} - {}", loggerName, newLevel, result.getResponse());
            } else {
                log.warn("Changing {} log level to {} FAILED - {}", loggerName, newLevel, result.getResponse());
            }
        } finally {
            cli.disconnect();
        }
    }

    // You need to change the console handler log level too or the log will not be output.
    private static void changeConsoleHandlerLogLevel(CLI cli, String newLevel) {
        val command = String.format("/subsystem=logging/console-handler=CONSOLE:write-attribute(name=level,value=%s", newLevel);
        val result = cli.cmd(command);
        if (!result.isSuccess()) {
            throw new RuntimeException("Changing console handler log level failed: " + result.getResponse());
        }
    }

    private static boolean loggerExists(CLI cli, String loggerName) {
        val command = String.format(LOGGER_SUBSYSTEM + "%s:read-resource", loggerName);
        return cli.cmd(command).isSuccess();
    }
}

The dependency is

<dependency>
    <groupId>org.wildfly.core</groupId>
    <artifactId>wildfly-cli</artifactId>
    <version>...</version>
</dependency>