Introduction
In this blog post I will describe how you can perform a simple change in SAP Commerce which allows you to change the log level for all nodes at the same time.
As you might know, you can change the log level of a logger during runtime in Hybris Adminstration Console (HAC). Sadly, performing a change does only change the logger of the node where you are right now. All other nodes will keep the log level.
There are two workarounds. One is to login into all HACs of all nodes and perform the change on every node and the second is, you can maybe limit your test to a node/node group and only change the level on this node.
Both workarounds might work for you, but with maybe higher effort and it is also very errorprone if you forget a node or running on a different node than expected.
So, wouldn't it be easier if you had the chance to change the log level for all nodes at the same time? You will see, the necessary changes are quite easy.
Implementation
How is it implemented currently?
In the HAC, there is a Controller method of the class Log4JController which is called:
@ResponseBody
public Map<String, Object> changeLevel(@RequestParam String loggerName, @RequestParam String levelName) {
...
this.log4JFacade.changeLogLevel(loggerName, levelName);
...
}
The Solution: Cluster-aware Events
Invoking the event
In this post, I will not show you how you can extend the HAC. If you need help doing this, this is a good start:
https://help.sap.com/viewer/5c9ea0c629214e42b727bf08800d8dfa/2105/en-US/df4994dfb4464ede8ffe61366c9c...
I have overwritten the controller and replaced the important line which makes the call to the log4JFacade which effectively changes the log level.
With cluster-aware event, you are able to send a signal to all or also some specific nodes.
So, except of changing the log level directly, I have changed the above line to this:
ClusterAwareChangeLogLevelEvent event = new ClusterAwareChangeLogLevelEvent();
event.setLevelName(levelName);
event.setTargetLogger(loggerName);
eventService.publishEvent(event);
The cluster-aware event will send now an event to all nodes to invoke a log-level change.
How is the event sent?
Below, you see the implementation of the ClusterAwareChangeLogLevelEvent:
public class ClusterAwareChangeLogLevelEvent extends AbstractEvent implements ClusterAwareEvent {
private String levelName;
private String targetLogger;
@Override
public boolean publish(int sourceNodeId, int targetNodeId)
{
// decide from and to which clusternode this event should be sent
return true; //broadcast from all to all cluster nodes
}
....
In this situation, I wanted to change the log level for all nodes, so I ever return true. But in your case you can also restrict the notified nodes.
More information about Cluster-Aware-Events:
https://help.sap.com/viewer/3fb5dcdfe37f40edbac7098ed40442c0/2105/en-US/e339428f80b74b94bd1a320a9654...
Of course, I have to add the information of the targetLogger and the desired log-level in the event.
Consuming the event
Ok, having invoked the cluster-aware event would be only the half of the rent. We need a Cluster-Aware EventListener which performs the actual job:
public class ClusterAwareChangeLogLevelEventListener extends AbstractEventListener<ClusterAwareChangeLogLevelEvent> {
private static final Logger LOG = LoggerFactory.getLogger(ClusterAwareChangeLogLevelEventListener.class);
@Override
protected void onEvent(ClusterAwareChangeLogLevelEvent abstractEvent) {
LOG.info("Event {}", abstractEvent);
LoggerConfig config = this.getOrCreateLoggerConfigFor(this.fromPresentationFormat(abstractEvent.getTargetLogger()));
Level level = Level.getLevel(abstractEvent.getLevelName());
LOG.info("Changing level of " + config + " from " + config.getLevel() + " to " + level);
config.setLevel(level);
this.getLoggerContext().updateLoggers();
}
private String fromPresentationFormat(String name) {
return name.equals("root") ? "" : name;
}
private LoggerConfig getOrCreateLoggerConfigFor(String loggerName) {
Configuration configuration = this.getLoggerContext().getConfiguration();
LoggerConfig existingConfig = configuration.getLoggerConfig(loggerName);
if (existingConfig.getName().equals(loggerName)) {
return existingConfig;
} else {
LOG.info("Creating logger " + loggerName);
LoggerConfig rootLoggerConfig = configuration.getRootLogger();
LoggerConfig newLoggerConfig = LoggerConfig.createLogger(true, rootLoggerConfig.getLevel(), loggerName, String.valueOf(rootLoggerConfig.isIncludeLocation()), (AppenderRef[])rootLoggerConfig.getAppenderRefs().toArray(new AppenderRef[0]), (Property[])null, configuration, rootLoggerConfig.getFilter());
rootLoggerConfig.getAppenders().forEach((k, v) -> {
rootLoggerConfig.addAppender(v, (Level)null, (Filter)null);
});
configuration.addLogger(loggerName, newLoggerConfig);
return newLoggerConfig;
}
}
private HybrisLoggerContext getLoggerContext() {
return (HybrisLoggerContext) LogManager.getContext(false);
}
}
If you compare this class with the standard
HacLog4JFacade, you will see no important changes. I have basically copied the same code to the Listener and added some Log-Messages.
The only difference is now that the
onEvent method will now be executed on all nodes in the cluster. When you perform the change you will directly see a log-level change in the logs depending on how much nodes you have in your cluster. This is independent of whether it is the EnPremise or Cloud installation.
The following screenshot will then show you how it would look like in Kibana in SCCv2. In my case, I had running 6 active nodes and changed the log level of com.sap.sce to DEBUG.
Kibana shows that all nodes are changing the log-level.
Summary
By enabling us to change the log level during runtime, we can now easily switch the verbosity of the SAP commerce environment. We can react to specific issue reports and change what and how much the running system writes into the log.
How do you think about it? Are you also often frustrated that you cannot change the log level during runtime on all nodes at once? Would you say that this should be the standard approach or at least configurable? Feel free to leave a note in this blog post.