禁止使用e.printStackTrace()
e.printStackTrace()打印的是异常堆栈信息,会额外的占用内存空间。正确的姿势是把日志打印到文件中。如下
public static void main(String[] args) {
try {
int i = 1/0;
}catch (Exception e){
//禁止使用 e.printStackTrace()
logger.error("错误",e);
}
}
注意log.error()最后一个参数要填捕获到的异常e,这样才能将异常详细堆栈信息打印到log
使用合适的日志级别
Slf4j有四个级别的log level可供选择,级别从上到下由低到高,优先级高的将被打印出来。
Debug:简单来说,对程序调试有利的信息都可以debug输出。
info:对用户有用的信息,比如最常见的打印接口入参和返参。
warn:可能会导致错误的信息,比如某个对象可能为null的场景判断。
error:顾名思义,发生错误的地方,最常见的catch代码块中的日志。
这里以error日志为例,举一个例子,在合适的场合打印合适的日志,是我们日志界的规范。
public static void main(String[] args) {
try {
int i = 1 / 0;
} catch (Exception e) {
//catch中不适合使用info级别
// logger.info("错误",e);
logger.error("错误", e);
}
}
使用占位符,而不是字符串拼接
Slf4j打印日志使用了占位符,避免了字符串拼接操作。字符串拼接最大的弊端,就是需要new新的字符串对象,增加了内存的开销。
public static void main(String[] args) {
int a = 1;
int b = 0;
try {
int i = a / b;
} catch (Exception e) {
//不要使用+字符串直接拼接
//logger.error("错误 a="+a+" b="+b,e);
logger.error("错误 a={} b={}", a, b, e);
}
}
尽量打印更少的日志,能一行打印的不要分为多行
不要打印无用的日志,不要重复打印日志,尽量不要在for循环中打印日志。
public static void main(String[] args) {
int a = 1;
int b = 0;
try {
int i = a / b;
} catch (Exception e) {
// //能一行打印的不要分为多行
// logger.error("错误 a={}", a);
// logger.error("错误 b={}", b);
// logger.error("错误 ", e);
logger.error("错误 a={} b={}", a, b, e);
}
}
从源码角度看看log打印和e.printStackTrace()的区别
1、log.error底层实现
ch.qos.logback.core.spi.AppenderAttachableImpl#appendLoopOnAppenders
循环迭代appenderArray数组,一般是三个元素分别为:控制台、全日志、错误日志。三个输出目标依次执行!
/**
* Call the <code>doAppend</code> method on all attached appenders.
*/
public int appendLoopOnAppenders(E e) {
int size = 0;
final Appender<E>[] appenderArray = appenderList.asTypedArray();
final int len = appenderArray.length;
for (int i = 0; i < len; i++) {
appenderArray[i].doAppend(e);
size++;
}
return size;
}

分支逻辑 ch.qos.logback.core.UnsynchronizedAppenderBase#doAppend
this.append(eventObject);this有连个两个多态实现:ch.qos.logback.core.ConsoleAppender[STDOUT]和ch.qos.logback.core.rolling.RollingFileAppender[FILE]分别表示控制台输出和日志文件输出
public void doAppend(E eventObject) {
// WARNING: The guard check MUST be the first statement in the
// doAppend() method.
// prevent re-entry.
if (Boolean.TRUE.equals(guard.get())) {
return;
}
try {
guard.set(Boolean.TRUE);
if (!this.started) {
if (statusRepeatCount++ < ALLOWED_REPEATS) {
addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this));
}
return;
}
if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
return;
}
// ok, we now invoke derived class' implementation of append
this.append(eventObject);
} catch (Exception e) {
if (exceptionCount++ < ALLOWED_REPEATS) {
addError("Appender [" + name + "] failed to append.", e);
}
} finally {
guard.set(Boolean.FALSE);
}
}
2、e.printStackTrace()底层实现
java.lang.Throwable#printStackTrace(java.lang.Throwable.PrintStreamOrWriter)
private void printStackTrace(PrintStreamOrWriter s) {
// Guard against malicious overrides of Throwable.equals by
// using a Set with identity equality semantics.
Set<Throwable> dejaVu =
Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
dejaVu.add(this);
synchronized (s.lock()) {
// Print our stack trace
s.println(this);
StackTraceElement[] trace = getOurStackTrace();
for (StackTraceElement traceElement : trace)
s.println("\tat " + traceElement);
// Print suppressed exceptions, if any
for (Throwable se : getSuppressed())
se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);
// Print cause, if any
Throwable ourCause = getCause();
if (ourCause != null)
ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
}
}
java.lang.Throwable.WrappedPrintStream#println
void println(Object o) {
printStream.println(o);
}
java.io.PrintStream#println(java.lang.Object)
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
java.io.PrintStream#write(java.lang.String)
private void write(String s) {
try {
synchronized (this) {
ensureOpen();
textOut.write(s);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush && (s.indexOf('\n') >= 0))
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
e.printStackTrace()使用BufferedWriter.write(s);输出到控制台,而且是逐行输出,存在资源浪费的性能问题。
补充一下alibaba开发手册中日志规范内容
- 【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架 SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Test.class);
-
【强制】所有日志文件至少保存15天,因为有些异常具备以“周”为频次发生的特点。网络运行状态、安全相关信息、系统监测、管理后台操作、用户敏感操作需要留存相关的网络日志不少于6个月。
-
【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。logType:日志类型,如stats/monitor/access等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。
正例:force-web应用中单独监控时区转换异常,如:force_web_timeZoneConvert.log
- 【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式。 说明:因为String字符串的拼接会使用StringBuilder的append()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能。 正例:
logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
- 【强制】对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断。 说明:虽然在debug(参数)的方法体内第一行代码isDisabled(Level.DEBUG_INT)为真时(Slf4j的常见实现Log4j和Logback),就直接return,但是参数可能会进行字符串拼接运算。此外,如果debug(getName())这种参数内有getName()方法调用,无谓浪费方法调用的开销。
正例: // 如果判断为真,那么可以输出trace和debug级别的日志
if (logger.isDebugEnabled()) {
logger.debug("Current ID is: {} and name is: {}", id, getName());
}
- 【强制】避免重复打印日志,浪费磁盘空间,务必在log4j.xml中设置additivity=false。
正例:
<logger name="com.taobao.dubbo.config" additivity="false">
- 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字throws往上抛出。
正例:
logger.error(各类参数或者对象toString() + "_" + e.getMessage(), e);
-
【推荐】谨慎地记录日志。
生产环境禁止输出debug日志
;有选择地输出info日志;如果使用warn来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。 说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处? -
【推荐】可以使用warn日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出error级别,避免频繁报警。 说明:注意日志输出的级别,error级别只记录系统逻辑出错、异常或者重要的错误信息。
-
【推荐】尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用中文描述即可,否则容易产生歧义。【强制】国际化团队或海外部署的服务器由于字符集问题,使用全英文来注释和描述日志错误信息。
网友评论