Reputation: 365
Given a logging utility class, how to log everything through that class instead of creating a Logger
object per class?
For example, instead of:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Main {
private static final Logger LOG = LogManager.getLogger(Main.class);
public static void main(String[] args) {
LOG.info("Application started!");
}
}
I would like to do something like this:
import my.utils.LogUtils;
public class Main {
public static void main(String[] args) {
LogUtils.info("Application started!");
}
}
My LogUtils
class looks like this:
package my.utils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.HashMap;
import java.util.Map;
public final class LogUtils {
private LogUtils() {
throw new AssertionError("private constructor: " + LogUtils.class.getName());
}
private static final Map<Class<?>, Logger> LOGGERS = new HashMap<>();
static {
Class<?> current = LogUtils.class;
LOGGERS.put(current, LogManager.getLogger(current));
}
public static void info(Object msg) {
Logger logger = getFor(getCallerClass());
// logger.info()... Here's where I am stuck! What I want to log in the stack trace is the *caller* of the "info" method, not the "info" method.
}
private static Logger getFor(Class<?> clazz) { return LOGGERS.computeIfAbsent(clazz, key -> LogManager.getLogger(key)); }
private static Class<?> getCallerClass() {
try {
return Class.forName(getCaller(3).getClassName());
} catch (ClassNotFoundException e) {
return LogUtils.class;
}
}
// This method should return "main" method name, but it's not being used because I don't know what should I do now
private static String getCallerMethod() { return getCaller(3).getMethodName(); }
private static StackTraceElement getCaller(int level) { return Thread.currentThread().getStackTrace()[level]; }
}
I have read several log4j2 documentation pages, but I found nothing regarding my question, and I also checked several stack overflow questions, but it seems like, whatever I try to search, results in a completely different question.
Is this even possible to do? Because I am starting to doubt it. Denote that I am trying to avoid the usage of a Logger per class... I wouldn't ask the question otherwise. It is, at least, possible to create a custom logger which logs a custom stack trace level?
As a side note, my Maven dependencies are the ones given on the log4j2 page:
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
</dependencies>
I also must mention that, on an answer, there's this call:
LOG.log(LoggingHelper.class.getCanonicalName(), Level.INFO, message, null);
I can't find a method in org.apache.logging.log4j.Logger
which (Javadoc like) refers like this:
Logger#log(String, Level, Object, Throwable);
It simply doesn't exist.
Upvotes: 2
Views: 1975
Reputation: 138
I wrote a maven plugin exactly for this purpose when using SLF4J (supports Log4j): slf4j-caller-info-maven-plugin
This plugin injects the caller of the logging methods to the MDC which can be simply used in the log pattern.
To achieve what you want:
pom.xml
:<build>
<plugins>
<plugin>
<groupId>io.github.philkes</groupId>
<artifactId>slf4j-caller-info-maven-plugin</artifactId>
<version>1.1.0</version>
<executions>
<execution>
<goals>
<goal>inject</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- Inject only the class name-->
<injection>%class</injection>
<!-- Method descriptors for all logging methods of your Util class -->
<injectedMethods>
<injectedMethod>my/utils/LogUtils#info</injectedMethod>
</injectedMethods>
<!-- If you want the package name of the class to be included -->
<includePackageName>true</includePackageName>
</configuration>
</plugin>
</plugins>
</build>
callerInformation
parameter in your log4j.xml
pattern, e.g.:<PatternLayout>
<Pattern>%d %p %X{callerInformation} - %m %ex%n</Pattern>
</PatternLayout>
Then your LogUtils.info("Application started!");
should output:
18:50:12.344 INFO my.Main - Application Started!
Upvotes: 0
Reputation: 3063
While it is possible when using Log4j2 Loggers directly (using the %M
parameter), which is what you are trying to avoid, IMO, encapsulating the logging framework is the wrong way to go.
slf4j
.log4j2
directly (though, logging the caller method using %M
is very expensive, as described hereUpvotes: 1