反例:
这个demo里面,典型的空指针异常,如果这样打印日志,输出的结果毫无意义,因为没有异常栈,无法精确定位到哪一行。
日志和异常:
日志是日志,异常是异常
1.并不是一定要抛了异常才要打日志
2.即使抛了异常打日志,如果只打了错误名称,也很难找问题。
线上的截图
订单最好打印订单号
请求参数最好说明是哪个参数的那个字段
案例一:
线上报警日志
根据该日志 定位到VatDeclarationForm.java:247 行
java.lang.Exception: Unparseable date: “Invalid date”
at com.souche.financial.report.json.VatDeclarationForm.getExcel(VatDeclarationForm.java:247)
at sun.reflect.GeneratedMethodAccessor273.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.souche.optimus.core.engine.ParamResolverFactory.invoke(ParamResolverFactory.java:127)
at com.souche.optimus.core.controller.AbstractJsonController.invoke(AbstractJsonController.java:54)
at com.souche.optimus.core.controller.JsonController.doRender(JsonController.java:76)
at sun.reflect.GeneratedMethodAccessor232.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at com.souche.optimus.core.interceptor.CrossDomainFilter.doFilter(CrossDomainFilter.java:21)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:94)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:676)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:502)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1132)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:684)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1539)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1495)
at com.alibaba.ttl.TtlRunnable.run(TtlRunnable.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
定位到VatDeclarationForm.java:247 行
try catch范围太大,无法定位Invalid date 哪里来的。
推荐打印的时候 打log.info("{}",e); 会打印出异常栈
log.info("{}",e.getMessage()); 不会打印异常栈
应该要记录异常栈,这样才可以精确到行找到问题所在。 e.getmessage 推荐使用在返回给接口调用方的时候,或者小范围自己明确知道可能出现错误的地方。
注意! 返回异常栈给调用方,外部调用方又不能看到你的代码,总不能还要去调用方那边看日志。
反面案例:gateway 调server ,server没有处理异常,然后异常被抛到了gateway。
日志级别:
改回info之后
开发和测试联调阶段,可以多打一些debug级别的日志,logback.xml 设置成debug ,上线之后关掉就没有了。
日志的作用:
1.报错了知道错在哪
2.没报错的时候,知道关键节点的数据状态 ,很多时候并没有报错,但是就是数据的内容或者状态不对。
如果没有日志,就只能debug模式自己调 ,线上没法debug。
日志给谁看:
debug日志给开发和测试看。
info和error日志给开发测试运维看。
什么时候看:
开发自测的时候,通过debug日志埋点可以知道自己的数据流在每个阶段的状态
比如说 接收到数据 参数->处理完成 参数->返回结果 参数 有三个阶段
每个阶段埋点打印日志,这样可以快速知道自己的数据在经历每个节点的时候的具体内容
很多时候是没有报错,但是就是数据内容和预期不一致。
特别是对于测试来说,如果测试出现问题,结果不对,能找到原因。 我们的测试是比较负责的,会去找原因,
而不是让开发自己去找原因。
什么情况下打日志:
1.开发环境下,流程上埋点debug日志
2.关键节点 打info日志:
比如说
a. 外部接口调用 入参信息 非常重要
b.数据校验或者操作 校验的参数信息 非常重要
c. 外部交互比如说mq 发送的内容信息
3.为了保证不影响正常流程的erro日志。 比如说,excel解析,
在每一行的时候,不能因为一行的异常没有被捕获而导致整个文件解析失败。
因此最好在最外层的时候就将整行解析方法try -catch ,出现问题打印error日志 ,记录出错的原始数据和信息
inoke解析一行的数据,如果在verify和dohandler里面都没有被开发捕获到的异常
就会进入到try-catch 而不会导致整个文件的解析失败。
4.高危操作打日志:
比如说删数据, 因为如果到时候明细找不到了,没法区分到底是我删了还是真的对方没有推送过来
总结:
日志要给出当前操作做了什么
使用的什么数据.
好的日志应该被看成文档注释的一部分.
最后一点, 切记不要在日志中包含密码和个人隐私信息,比如说不要在日志里面把人家用户信息账户密码都打印出来!
错误日志示范:request可能为空,导致本来没问题的代码,打日志打出来空指针。
log.debug(“Processing request with id: {}”, request.getId());
log.debug(“Processing request with id: {}”,user));
万一user有100万条呢? 日志刷屏是一回事,搞不好打日志打出来内存溢出。
集合 一般打印条数和这个集合的查询关键字。 这样真的有需要的话,可以根据关键字去数据库查。
3.核心方法调用前后加上了日志
因为一旦出现问题,比如说方法里面发生了死锁。这种情况下都只有输入日志, 不会有输出日志。
最后:
不要用system.out.print 和e.printStatcktrace 打日志
这两个输出是到控制台,而不是到日志文件。
我们看日志的时候,看的是日志文件,而不是控制台。
如果是高并发接口里面不要加e.printStatcktrace
里面封装了个system.err的流,而且还加了同步锁。