1. 一个最基本的例子
- 引入loggerg类和logger工厂类
- 声明logger
- 记录日志
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserService {
// 2. 声明一个Logger,这个是static的方式,我比较习惯这么写。
private final static Logger logger = LoggerFactory.getLogger(UserService. class);
public boolean verifyLoginInfo(String userName, String password) {
// 3. log it,输出的log信息将会是:"Start to verify User [Justfly]
logger.info("Start to verify User [{}]", userName);
return false;
}
}
- 静态Logger对象相对来说更符合语义,节省CPU,节省内存,不支持注入
- 对象变量Logger支持注入,对于一个JVM中运行的多个引用了同一个类库的应用程序,可以在不同的应用程序中对同个类的Logger进行不同的配置。比如Tomcat上部署了俩个应用,他们都引用了同一个lib。
2. Logger接口的方法
2.1 判断Logger级别是否开启的方法
- public boolean isTraceEnabled();
- public boolean isDebugEnabled();
- public boolean isInfoEnabled();
- public boolean isWarnEnabled();
- public boolean isErrorEnabled();
2 logger.debug("["+resultCount+"]/["+totalCount+"] of users are returned");
3 }
2 logger.debug("[{}]/[{}] of users in group are returned", resultCount,totalCount);
3 }
2.2 log信息的方法
2.2.1 方法说明
- public void info(String msg);
- public void info(String format, Object arg);
- public void info(String format, Object arg1, Object arg2);
- public void info(String format, Object... arguments);
- public void info(String msg, Throwable t);
java.io.FileNotFoundException: File not exists
at cn.justfly.training.logging.service.UserServiceTest.testLogResult(UserServiceTest.java:31) ~ [ test-classes/:na ]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~ [ na:1.6.0_45 ]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~ [ na:1.6.0_45 ]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~ [ na:1.6.0_45 ]
at java.lang.reflect.Method.invoke(Method.java:597) ~ [ na:1.6.0_45 ]
参数化说明
2.2.2 如何Log Exception
2.2.2.1 把Exception作为Log方法的最后一个参数
java.io.FileNotFoundException: File not exists
at cn.justfly.training.logging.service.UserServiceTest.testLogResult(UserServiceTest.java:30) [ test-classes/:na ]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~ [ na:1.6.0_45 ]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~ [ na:1.6.0_45 ]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~ [ na:1.6.0_45 ]
at java.lang.reflect.Method.invoke(Method.java:597) ~ [ na:1.6.0_45 ]
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) [ junit.jar:na ]
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [ junit.jar:na ]
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) [ junit.jar:na ]
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) [ junit.jar:na ]
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) [ junit.jar:na ]
2.2.2.2 Exception不会替换log信息中的参数
java.io.FileNotFoundException: File not exists
at cn.justfly.training.logging.service.UserServiceTest.testLogResult(UserServiceTest.java:30) [ test-classes/:na ]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~ [ na:1.6.0_45 ]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~ [ na:1.6.0_45 ]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~ [ na:1.6.0_45 ]
at java.lang.reflect.Method.invoke(Method.java:597) ~ [ na:1.6.0_45 ]
2.2.2.3 参数化Exception
- 把Exception的toString()方法的返回值作为参数
- 不要让Exception成为最后一个参数
3. Log什么
3.1 如何使用不同级别的Log
3.1.1 用户级别
3.1.1.1 Error
- 影响到程序正常运行、当前请求正常运行的异常情况,例如:
- 打开配置文件失败
- 第三方应用网络连接异常
- SQLException
- 不应该出现的情况,例如:
- 某个Service方法返回的List里面应该有元素的时候缺获得一个空List
- 做字符转换的时候居然报错说没有GBK字符集
3.1.1.2 Warn
- 不应该出现但是不影响程序、当前请求正常运行的异常情况,例如:
- 有容错机制的时候出现的错误情况
- 找不到配置文件,但是系统能自动创建配置文件
- 即将接近临界值的时候,例如:
- 缓存池占用达到警告线
3.1.1.3 Info
- 系统运行信息
- Service方法的出入口
- 主要逻辑中的分步骤
- 外部接口部分
- 客户端请求参数和返回给客户端的结果
- 调用第三方时的调用参数和调用结果
3.1.2 开发级别
3.1.2.1 Debug
- 用于记录程序变量,例如:
- 多次迭代中的变量
- 用于替代代码中的注释
// 2. 获取用户休假情况
// 3. 计算用户应得薪资
logger.debug("获取员工[{}] [{}]年的基本薪资为[{}]",employee,year,basicSalary);
logger.debug("开始获取员工[{}] [{}]年[{}]月休假情况",employee,year,month);
logger.debug("员工[{}][{}]年[{}]月年假/病假/事假为[{}]/[{}]/[{}]",employee,year,month,annualLeaveDays,sickLeaveDays,noPayLeaveDays);
logger.debug("开始计算员工[{}][{}]年[{}]月应得薪资",employee,year,month);
logger.debug("员工[{}] [{}]年[{}]月应得薪资为[{}]",employee,year,month,actualSalary);
3.1.2.2 Trace
3.2 Log中的要点
3.2.1 Log上下文
- "开始导入配置文件"
- "开始导入配置文件[/etc/myService/config.properties]"
3.2.2 考虑Log的读者
- "开始执行getUserInfo 方法,用户名[jimmy]"
- "开始获取用户信息,用户名[jimmy]"
- "无法解析参数[12 03, 2013],birthDay参数需要符合格式[yyyy-MM-dd]"
3.2.3 Log中的变量用[]与普通文本区分开来
- 在你阅读Log的时候容易捕捉到有用的信息
- 在使用工具分析Log的时候可以更方便抓取
- 在一些情况下不容易混淆
- "获取用户lj12月份发邮件记录数"
- "获取用户[lj1][2]月份发邮件记录数"
3.2.4 Error或者Warn级别中碰到Exception的情况尽量log 完整的异常信息
- 你是在做什么事情的时候出错了
- 你是在用什么数据做这个事情的时候出错了
- 出错的信息是什么
- log.error("获取用户[{}]的用户信息时出错",userName,ex);
- log.error("获取用户[{}]的用户信息时报错,错误信息:[{}]",userName,ex.getMessage());
- log.error("获取用户信息时出错");
3.2.5 对于Exception,要每次都Log StackTrace吗?
} catch(Exception ex){
String errorMessage=String.format("Error while reading information of user [%s]",userName);
logger.error(errorMessage,ex);
throw new UserServiceException(errorMessage,ex);
}
- 这个信息很重要,我不确认再往上的异常处理层中是否会正常的把它的StackTrace打印出来。
- 如果这个异常信息在往上传递的过程中被多次包装,到了最外层打印StackTrace的时候最底层的真正有用的出错原因有可能不会被打印出来。
- 如果有人改变了LogbackException打印的配置,使得不能完全打印的时候,这个信息可能就丢了。
- 就算重复了又怎么样?都Error了都Warning了还省那么一点空间吗?
为什么要使用SLF4J而不是Log4J
每一个Java程序员都知道日志对于任何一个Java应用程序,尤其是服务端程序是至关重要的,而很多程序员也已经熟悉各种不同的日志库如java.util.logging、Apache log4j、logback。但如果你还不知道SLF4J(Simple logging facade for Java)的话,那么是时候去在你项目中学习使用SLF4J了。
在这篇文章中,我们将学习为什么使用SLF4J比log4j或者java.util.logging要优秀。自从上次我写Java程序员的10个日志技巧已经有一段时间了,我已经不记得我写的关于日志的一切了。
不管怎样,让我们回到这个话题,SLF4J不同于其他日志类库,与其它有很大的不同。SLF4J(Simple logging Facade for Java)不是一个真正的日志实现,而是一个抽象层( abstraction layer),它允许你在后台使用任意一个日志类库。如果是在编写供内外部都可以使用的API或者通用类库,那么你真不会希望使用你类库的客户端必须使用你选择的日志类库。
如果一个项目已经使用了log4j,而你加载了一个类库,比方说 Apache Active MQ——它依赖于于另外一个日志类库logback,那么你就需要把它也加载进去。但如果Apache Active MQ使用了SLF4J,你可以继续使用你的日志类库而无语忍受加载和维护一个新的日志框架的痛苦。
总的来说,SLF4J使你的代码独立于任意一个特定的日志API,这是一个对于开发API的开发者很好的思想。虽然抽象日志类库的思想已经不是新鲜的事物而且Apache commons logging也已经在使用这种思想了,但现在SLF4J正迅速成为Java世界的日志标准。让我们再看看几个使用SLF4J而不是log4j、logback或者java.util.logging的理由。
SLF4J对比Log4J,logback和java.util.Logging的优势
正如我之前说的,在你的代码中使用SLF4J写日志语句的主要出发点是使得你的程序独立于任意特定的日志类库,依赖于特定类可能需要不同与你已有的配置,并且导致更多维护的麻烦。但除此之外,还要一个SLF4J API的特性使得我坚持使用SLF4J而抛弃我长期间钟爱的Lof4j的理由,是被称为占位符(place holder),在代码中表示为“{}”的特性。占位符是一个非常类似于在String的format()方法中的%s,因为它会在运行时被某个提供的实际字符串所替换。这不仅降低了你代码中字符串连接次数,而且还节省了新建的String对象。即使你可能没需要那些对象,但这个依旧成立,取决于你的生产环境的日志级别,例如在DEBUG或者INFO级别的字符串连接。因为String对象是不可修改的并且它们建立在一个String池中,它们消耗堆内存( heap memory)而且大多数时间他们是不被需要的,例如当你的应用程序在生产环境以ERROR级别运行时候,一个String使用在DEBUG语句就是不被需要的。通过使用SLF4J,你可以在运行时延迟字符串的建立,这意味着只有需要的String对象才被建立。而如果你已经使用log4j,那么你已经对于在if条件中使用debug语句这种变通方案十分熟悉了,但SLF4J的占位符就比这个好用得多。
这是你在Log4j中使用的方案,但肯定这一点都不有趣并且降低了代码可读性因为增加了不必要的繁琐重复代码(boiler-plate code):
1
2
3
|
if
(logger.isDebugEnabled()) {
logger.debug(
"Processing trade with id: "
+ id +
" symbol: "
+ symbol);
}
|
另一方面,如果你使用SLF4J的话,你可以得到在极简洁的格式的结果,就像以下展示的一样:
1
|
logger.debug(
"Processing trade with id: {} and symbol : {} "
,
id
, symbol);
|
在SLF4J,我们不需要字符串连接而且不会导致暂时不需要的字符串消耗。取而代之的,我们在一个以占位符和以参数传递实际值的模板格式下写日志信息。你可能会在想万一我有很个参数怎么办?嗯,那么你可以选择使用变量参数版本的日志方法或者用以Object数组传递。这是一个相当的方便和高效方法的打日志方法。记住,在生产最终日志信息的字符串之前,这个方法会检查一个特定的日志级别是不是打开了,这不仅降低了内存消耗而且预先降低了CPU去处理字符串连接命令的时间。这里是使用SLF4J日志方法的代码,来自于slf4j-log4j12-1.6.1.jar中的Log4j的适配器类Log4jLoggerAdapter。
1
2
3
4
5
6
|
public
void
debug(String format, Object arg1, Object arg2) {
if
(logger.isDebugEnabled()) {
FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
}
}
|
同时,我们也很值得知道打日志是对应用程序的性能有着很大影响的,在生产环节上只进行必要的日志记录是我们所建议的。
怎么用SLF4J做Log4J的日志记录
除了以上好处,我想还有一个告诫,就是为了使用SLF4J,你不仅需要包含SLF4J的API jar包,例如 slf4j-api-1.6.1.jar,还需要相关Jar包,这取决于你在后台使用的日志类库。如果你想要使用和Log4J 一起使用SLF4J ,Simple Logging Facade for Java,,你需要包含以下的Jar包在你的classpath中,取决于哪个SLF4J和你在使用的Log4J的版本。例如:
- slf4j-api-1.6.1.jar – JAR for SLF4J API
- log4j-1.2.16.jar – JAR for Log4J API
- slf4j-log4j12-1.6.1.jar – Log4J Adapter for SLF4J
如果你在使用Maven去管理你的项目依赖,你只需要包含SLF4J JAR包,maven会包含它的依赖的相关包。为了和SLF4J一起中使用Log4J,你可以包含以下的依赖在你项目中的pom.xml。
1
2
3
4
5
6
7
8
9
10
11
|
<
dependency
>
<
groupId
>org.slf4j</
groupId
>
<
artifactId
>slf4j-log4j12</
artifactId
>
<
version
>1.6.1</
version
>
</
dependency
>
<
dependency
>
<
groupId
>org.slf4j</
groupId
>
<
artifactId
>slf4j-log4j12</
artifactId
>
<
version
>1.6.1</
version
>
</
dependency
>
|
还有,如果你对于使用变量参数版本(variable argument version )的日志方法感兴趣的话,那么就导入SLF4J 1.7的版本吧。
总结
总结这次说的,我建议使用SLF4J的而不是直接使用 Log4j, commons logging, logback 或者 java.util.logging 已经足够充分了。
- 在你的开源或内部类库中使用SLF4J会使得它独立于任何一个特定的日志实现,这意味着不需要管理多个日志配置或者多个日志类库,你的客户端会很感激这点。
- SLF4J提供了基于占位符的日志方法,这通过去除检查isDebugEnabled(), isInfoEnabled()等等,提高了代码可读性。
- 通过使用SLF4J的日志方法,你可以延迟构建日志信息(Srting)的开销,直到你真正需要,这对于内存和CPU都是高效的。
- 作为附注,更少的暂时的字符串意味着垃圾回收器(Garbage Collector)需要做更好的工作,这意味着你的应用程序有为更好的吞吐量和性能。
- 这些好处只是冰山一角,你将在开始使用SL4J和阅读其中代码的时候知道更多的好处。我强烈建议,任何一个新的Java程序员,都应该使用SLF4J做日志而不是使用包括Log4J在内的其他日志API。
slf4j是一个日志系统的封装,对外提供统一的API
使用slf4j需要下载
slf4j-api-x.x.x.jar 它提供对外一致的API接口,其本身不提供日志实现。
假设我们选择log4j作为我们的日志实现,需要下载
log4j-x.x.x.jar
如果想把slf4j绑定log4j,则需要下载slf4j对log4j的相应”驱动”。
slf4j-log4j12-x.x.x.jar
这样就可以使用slf4j提供的API,用log4j实现打日志了。
所谓驱动,就是实现了slf4j的一些接口,用你喜欢的日志系统打日志。
slf4j还支持好多日志系统,并提供了相应的“驱动”包