摘要:在软件开发中,我们通常使用五种日志级别:错误(Error)、警告(Warn)、信息(Info)、调试(Debug)和追踪(Trace)。选择正确的日志级别对于监控系统运行状态有着重要作用。以下是各级别的简要说明:
在软件开发中,我们通常使用五种日志级别:错误(Error)、警告(Warn)、信息(Info)、调试(Debug)和追踪(Trace)。选择正确的日志级别对于监控系统运行状态有着重要作用。以下是各级别的简要说明:
错误(Error):记录严重错误,这些错误会影响业务流程,需要运维团队密切关注和处理。警告(Warn):记录一般性错误,虽然对业务影响不大,但应引起开发团队的注意。信息(Info):记录关键信息,便于问题排查,如方法调用的时间、输入输出参数等。调试(Debug):记录关键逻辑的运行时数据,主要用于开发过程中的问题调试。追踪(Trace):提供最详尽的信息,通常仅在日志文件中记录,用于深入分析。在日志管理中,我们追求的是精准而非数量。关键在于记录那些能够帮助我们快速定位问题的日志。具体来说:
输入参数记录:每当一个方法被调用时,记录其输入参数。这为我们提供了方法执行的初始状态,是问题排查的起点。输出与返回值记录:在方法执行完毕后,记录其输出参数和返回值。这些信息对于理解方法的执行结果至关重要。关键信息标记:特别留意记录那些关键信息,例如用户ID(userId)。这些细节在后续的问题诊断和数据分析中扮演着重要角色。通过这样的日志记录策略,能够确保日志的实用性和有效性,为系统的稳定运行和问题快速解决提供有力支持。
一个良好的日志格式是高效日志管理的基础,应该包含所有必要的基本信息,以便我们能够迅速理解日志条目的上下文。日志应包含以下核心信息:
时间戳:记录事件的精确时间,以毫秒为单位。日志级别:标识信息的紧急程度,如错误、警告或信息。线程名称:指明哪个线程生成了日志,尤其在多线程应用中非常有用。logback 日志可以这样配置:
%d{HH:mm:ss.SSS} %-5level [%thread][%logger{0}] %m%n
在代码中遇到if...else...或switch等条件判断时,建议在每个分支的起始位置添加日志记录。
这样做可以帮助我们在问题排查时快速确定程序的执行路径,使代码逻辑更加透明,便于追踪和诊断问题。
if(user.isVip){ log.info("User isVip, Id:{}", user, getUserId);} else { log.info("User not isVip, Id:{}", user, getUserId)}对于trace/debug等低级别的日志,应先进行日志级别的条件判断。
User user = new User(666L, "demo");if (log.isDebugEnabled) { log.debug("userId is: {}", user.getId);}这样做是为了避免在日志级别较高时(如warn),执行不必要的字符串拼接或对象的toString方法调用,从而节省系统资源。如果日志级别设置较高,这些操作虽然执行了,但日志内容并不会被输出,因此添加日志开关判断是推荐的做法。
在日志系统中,我们不推荐直接使用Log4j或Logback的API,而是应该通过SLF4J(Simple Logging Facade for Java)这个门面模式的日志框架来操作。
SLF4J能够统一不同日志框架的接口,方便维护和处理日志,同时允许我们在不修改代码的情况下更换底层日志实现,提高了灵活性和可维护性。
import org.slf4j.Logger; import org.slf4j.LoggerFactory;private static final Logger logger = LoggerFactory.getLogger(Demo.class);在记录日志时,推荐使用参数占位符{}而不是使用+操作符进行字符串拼接。
不当示例:
logger.info("处理交易,ID:" + id + " 和符号:" + symbol);这种方式在性能上存在损耗,因为每次拼接都会生成新的字符串对象。
正确用法:
logger.info("处理交易,ID:{} 和符号:{}", id, symbol);使用大括号{}作为参数占位符,不仅代码更简洁,而且性能更优。相较于字符串拼接,参数占位符避免了不必要的对象创建,从而提升了日志记录的效率。
日志输出通常涉及文件或其他输出流的IO操作,这对性能有较高要求。采用异步方式输出日志可以显著提升IO性能,减少对主线程的阻塞。
一般建议: 除非有特别需求,否则推荐使用异步日志输出。这样做可以避免日志操作影响应用程序的响应时间和吞吐量。
配置示例(以logback为例): 在logback中配置异步日志输出非常简单,只需添加AsyncAppender即可。以下是配置代码示例:
通过这种方式,可以确保日志系统高效运行,同时减少对主业务流程的干扰。
在异常处理中,不推荐使用e.printStackTrace来打印错误信息。
不当做法:
try { // 尝试执行代码} catch(Exception e) { e.printStackTrace;}这种方式会将堆栈跟踪日志与业务代码日志混合,不利于异常日志的检查和分析。
推荐做法:
try { // 尝试执行代码} catch(Exception e) { log.error("错误", e);}通过使用日志框架记录异常,可以更清晰地管理和检索错误信息。此外,e.printStackTrace生成的堆栈信息如果过长,可能会导致内存溢出,影响用户请求处理。因此,使用日志框架的错误记录方法更为稳妥,能够避免内存问题,同时保持日志的整洁和有序。
在处理异常时,我们应当记录完整的错误信息,而不是仅仅记录错误摘要。
不当做法:
仅记录错误级别,不包含异常详情:try {// 尝试执行代码
} catch (Exception e) {
LOG.error("错误");
}
这种做法没有记录具体的异常信息,导致无法了解具体抛出了哪种异常。仅记录异常消息,不包含堆栈信息:try {
// 尝试执行代码
}
LOG.error("错误", e.getMessage);
}
这种方式只记录了异常的基本描述,缺少详细的堆栈信息,不利于深入分析和排查问题。
正确做法:
try { // 尝试执行代码} catch (Exception e) { LOG.error("错误", e);}通过这种方式,可以记录完整的异常堆栈信息,这对于后续的问题诊断和修复非常关键。
来源:散文随风想一点号