如果使用工具类进行封装,减少 类似
private static Logger logger = LogFactory.getLogger(XXX.class);
的定义,也不使用注解@Slf4j
可以在工具类中添加静态代码库解决此问题,示例代码如下
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.springframework.boot.SpringApplication;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
class LOG {
public static void main(String[] args) {
// 一般的 apps
useProps("conf/log4j.properties");
// do something
// spring apps 需要添加运行参数
// java -jar XXX.jar --spring.config.location=file:conf/
SpringApplication.run(LOG.class, args);
useProps("conf/log4j.properties");
}
public static final String TRACE = "trace";
public static final String DEBUG = "debug";
public static final String INFO = "info";
public static final String WARN = "warn";
public static final String ERROR = "error";
public static void error(String msg) {
log(ERROR, msg);
}
public static void error(String format, Object... args) {
log(ERROR, format, args);
}
public static void info(String msg) {
log(INFO, msg);
}
public static void info(String format, Object... args) {
log(INFO, format, args);
}
static void log(String level, String format, Object... args) {
log(level, StrUtils.format(format, args));
}
private static void log(String lvl, String msg) {
Logger logger = getLogger();
switch (lvl) {
case TRACE:
logger.trace(msg);
break;
default:
logger.info(msg);
break;
}
}
public static Logger getLogger() {
final Class<?> module = getCallerClass(10);
return (Logger) LogManager.getLogger(module.getName());
}
private static Class<?> getCallerClass(int limit) {
while (limit-- > 0) {
if (StackLocatorUtil.getCallerClass(limit) == LOG.class) break;
}
return StackLocatorUtil.getCallerClass(limit + 1);
}
private static final String FQCN = LOG.class.getName();
static {
try {
Field field = AbstractLogger.class.getDeclaredField("FQCN");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.setAccessible(true);
field.set(null, FQCN);
modifiersField.setInt(field, field.getModifiers() & Modifier.FINAL);
} catch (Exception e) {
System.out.println(StrUtils.format("modify FQCN to {} failed, err:{}", FQCN, StrUtils.errStr(e)));
}
}
public static void useProps(String configFile) {
try {
if (Utils.exist(configFile)) {
LoggerContext logContext = (LoggerContext) LogManager.getContext(false);
logContext.setConfigLocation(new File(configFile).toURI());
logContext.reconfigure();
info("log4j2 conf file:{}", FileUtils.readAll(configFile).replaceAll("\\s+", " "));
}
} catch (Exception e) {
error("can not configure log4j2, err:{}", StrUtils.errStr(e));
}
}
}
解决了打印代码位置时全部都是工具类位置的问题
原理是,log4j2 中 默认实现为org.apache.logging.log4j.core.Logger;继承自org.apache.logging.log4j.spi.AbstractLogger;打印时loggerName由LogManager.getLogger的传参决定,行号位置信息由AbstractLogger中的代码寻找第一个类名不是AbstractLogger.FQCN的调用堆栈决定,是故通过反射修改此值,即可找到正确的调用堆栈。
支持log4j2.properties 配置文件外置
原理是, (LoggerContext) LogManager.getContext(false); 可获取日志组件上下文;logContext.setConfigLocation(new File(configFile).toURI()); 可绑定日志的配置文件,; logContext.reconfigure(); 可手动触发配置重载。如需实现配置热更新,可以自己写一个文件监听的thread,每X分钟检测文件是否更新时间发生变化,如变化则重载。