为什么要用slf4j实现日志打印,在阿里的开发规范中有这么一段:
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架
SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
SLF4J,即简单日志门面(Simple Logging Facade for Java),不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。
大概意思就是说 SLF4J 是一个日志抽象层,允许你使用任何一个日志系统,并且可以随时切换还不需要动到已经写好的程序。
但是我们在使用slf4j的时候一般会像下面这样:
public class MySlf4j {
private static final Logger logger = LoggerFactory.getLogger("MySlf4j");
public static void info(String msg) {
logger.info(msg);
}
}
在每个类里定义Logger,这就是重复的劳动,必须对logger进行封装,编写封装类需要注意,slf4j我们如果不做处理我们调用方法记录日志,像 类名、方法名、行数都变成Logger这个类的信息,这样就无法准确定位日志来源。这样的问题是无法忍受的。
所有我们对Mylf4j工具类做了如下修改:
1.通过Throwable获取堆栈信息。
2.通过SharedSecrets.getJavaLangAccess(),我们访问堆栈信息通过stackDepth定位到调用者类信息。
具体的代码如下:
public class MySlf4j {
/**空数组*/
private static final Object[] EMPTY_ARRAY = new Object[] {};
/**全类名*/
private static final String FQCN = MySlf4j.class.getName();
/**
* 获取栈中类信息
* @param stackDepth 栈深(下标) 2:调用者类信息
* @return org.slf4j.spi.LocationAwareLogger
* @author wxt
* @date 2019/7/10 8:40
*/
private static LocationAwareLogger getLocationAwareLogger(final int stackDepth) {
/**通过堆栈信息获取调用当前方法的类名和方法名*/
JavaLangAccess access = SharedSecrets.getJavaLangAccess();
Throwable throwable = new Throwable();
StackTraceElement frame = access.getStackTraceElement(throwable, stackDepth);
return (LocationAwareLogger) LoggerFactory.getLogger(frame.getClassName());
}
/**
* 封装Debug级别日志
* @param msg
* @param arguments
* 作者:wxt
* 日期:2019年3月27日下午5:38:01
*/
public static void textDebug(String msg, Object... arguments) {
if (arguments != null && arguments.length > 0) {
MessageFormat temp = new MessageFormat(msg);
msg = temp.format(arguments);
}
getLocationAwareLogger(2).log(null, FQCN, LocationAwareLogger.DEBUG_INT, msg, EMPTY_ARRAY, null);
}
/**
* 封装Info级别日志
* @param msg
* @param arguments
* 作者:wxt
* 日期:2019年3月27日下午5:37:42
*/
public static void textInfo(String msg, Object... arguments) {
if (arguments != null && arguments.length > 0) {
MessageFormat temp = new MessageFormat(msg);
msg = temp.format(arguments);
}
getLocationAwareLogger(2).log(null, FQCN, LocationAwareLogger.INFO_INT, msg, EMPTY_ARRAY, null);
}
/**
* 封装Warn级别日志
* @param msg
* @param arguments
* 作者:wxt
* 日期:2019年3月27日下午5:37:30
*/
public static void textWarn(String msg, Object... arguments) {
if (arguments != null && arguments.length > 0) {
MessageFormat temp = new MessageFormat(msg);
msg = temp.format(arguments);
}
getLocationAwareLogger(2).log(null, FQCN, LocationAwareLogger.WARN_INT, msg, EMPTY_ARRAY, null);
}
/**
* 封装Error级别日志
* @param msg
* @param arguments
* 作者:wxt
* 日期:2019年3月27日下午5:37:14
*/
public static void textError(String msg, Object... arguments) {
if (arguments != null && arguments.length > 0) {
MessageFormat temp = new MessageFormat(msg);
msg = temp.format(arguments);
}
getLocationAwareLogger(2).log(null, FQCN, LocationAwareLogger.ERROR_INT, msg, EMPTY_ARRAY, null);
}
/**
* 异常堆栈转字符串
* @param e
* @return
* 作者:wxt
* 日期:2019年3月27日下午5:37:08
*/
public static String ExceptionToString(Exception e) {
StringWriter sw = null;
PrintWriter pw = null;
try {
if (e == null) {
return "无具体异常信息";
}
sw = new StringWriter();
pw = new PrintWriter(sw);
e.printStackTrace(pw);
return sw.toString();
} catch (Exception ex) {
MySlf4j.textError("异常堆栈转字符串异常", ex);
return "";
} finally {
sw.flush();
pw.flush();
pw.close();
}
}
}
loback.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="APP_NAME" value="wxt-provider" />
<property name="LOG_HOME" value="/usr/local/logs/${APP_NAME}" />
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [${APP_NAME}] [%thread] [%X{traceId}] %logger{50} - [%class : %method : %line] - %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender" additivity="false">
<File>${LOG_HOME}/${APP_NAME}.log</File>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [${APP_NAME}] [%thread] [%X{traceId}] %logger{50} - [%class : %method : %line] - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/${APP_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
这样我们通过调用该工具类就可以打印出调用者的类相关信息。