应用日志规范 (Java) 1. 技术要求 1.1 记录原则 [强制] 隔离性: 日志输出不应影响系统正常运行; [强制] 安全性: 日志打印本身不应存在逻辑异常或漏洞。导致产生安全问题; [强制] 数据安全: 不应输出机密 敏感信息。如用户联系方式。身份证号码。 token等。应保证日志的完整性 可审计性; [强制] 可监控分析: 日志应提供给监控迸行监控; 分析系统迸行分析; [强制] 可定位排查: 日志信息输出应有意义。应具有可读性。可供开发人员排查线上问题。 1.2 日志级别 在我们日常开发中有四种比较常见的日志打印等级。不同的等级适合在不同的时机下打印日志。 主要使用的有以下四个等级: 1. DEBUG DEUBG级别应主要输出调试性质的内容 该级别日志应用于开发。测试阶段输出。该级别的日志应尽可能地详尽, 开发人员可以将各类详细信息记录到DEBU6里。起到调试的作用。包括参数信息。调试细节信息。返回值信息等 等。便于在开发 测试阶段出现问题或者异常时, 对其迸行分析。 2. INFO INFO级别的主要记录系统关键信息。旨在保留系统正常工作期间关键运行指标。开发人员可以将初始化系统配置 _ 业务状态变化信息。 或者用户业务流程中的核心处理记录到INF0日志中 方便日常运维工作以及错误回溯时上下文 场景复现。应在项目完成后 在测试环境将日志级别调成INFO, 然后通过INF0级别的信息看看是否能了解这个应用 的运用情况。如果出现问题后是否这些日志能否提供有用的排查问题的信息。 WARN WARN 级别的主要输出警告性质的内容。这些内容是可预知且是有规划的。比如。某个方法入参为空或者该参数的值 不满足运行该方法的条件时。在WARN级别的时应输出较为详尽的信息 以便于事后对日志进行分析。 注: 常见的WARN级别异常 用户输入参数错误 非核心组件初始化失败 后端任务处理最终失败 {如果有重试且重试成功。就不需要WARN) 数据插入幂等 ERROR ERROR级别主要针对于 些不可预知的信息。诸如: 错误 异常等。比如。茌catch块中抓获的网络通信_ 数据库连 接等异常 若异常对系统的整个流程影响不大。可使用WARN级别日志输出。在输出ERROR级别的日志时。应尽量多 地输出方法入参数。方法执行过程中产生的对象等数据 在带有错误 异常对象的数据时, 应将该对象一井输出。 注: 常见的ERROR级别异常 程序启动失败 核心组件初始化失败 连不上数据库 核心业务访问依赖的外部系统持续失败 OOM 不应滥用ERROR级别日志。 般来说在配置了告警的系统中 WARN级别一般不会告警 ERROR级别则会设置监控 告警甚至电话报警 ERROR级别日志的出现意味着系统中发生了非常严重的问题。应有人立即处理。 错误的使用ERROR级别日志; 不区分问题的重要程度 只要是问题就采用ERROR级别日志。这是极其不负责任的表 现 因为大部分系统中的告警配置都是根据单位时间内ERROR级别日志出现的数量来定的。随意打ERRORO志会造 成极大的告警噪音。造成重要问题遗漏。 1.3 日志格式 1.3.1 后端日志格式 后端日志可以保存或输出到文件 远程服务器。控制台及数据库等, 日志输出包含以下要素: 时间戳: 2024-01-29T15:30:45 789 日志链路id(traceld, rpcld) (可选) 0b26053315407142451016402XXXXX 日志级别: DEBUG INFO, WARN ERROR。 FATAL 线程名: SofaBizprocessor-4-thread-333 main 类名或Lo99er名称: COm.example MyClass 方法名与行号 (可选) : myMethod (L45) 调用耗时 (可选) Ims 调用是否成功(YIN) (可选) 状态码 (可选) SUCCESS 系统上下文信息(调用系统名。调用系统ip。调用时间戳。是否压测(YIN)) (可选) appName , 1p地址 ,时间戳 请求入参 (可选) 参数1 请求出参 (可选) 参数1 日志消息内容: IThis 15 log message TNo URLs 4i1 be polled as dynamic configuration SOUrCeS 日志自定义输出格式如下: 2024-01-29 15;30:45 789 [0b26053315407142451016402XXXXX 0.3 1i - ] [INFO] [SofaBizprocessor-4-thread-333] {com example.MyCLass } 122 [(Ims 丫,SUCCESS ) (appName 地址 ,时间戳,丫) (参数1 ,参数2) ] - 川o URLs V111 polled Jynamic Confi guration SOUTCes 0b25053315407142451016402XXXX 0.3 是 traceld; [INFO] 是日志级别; [SofaBizProcessor-4-thread-3331 是线程名; {com.example .MyCLass } 是类名; 122是行号; No URLS Wi11 be polled 35 dynamic configuration SOUTCeS 是实际的日志消息。具体的业务相关日志打 印形式可以根据业务实际情况自定义 日志标准输出格式如下: 2024-01-29 15:30:45,789 [INFO] [main] {COm example .MyClass} No URLS wi2l polled dynamic configuration SOUTCes 移动端日志遵循后端日志格式。 1.3.2 前端日志格式 前端日志格式并没有固定的标准。但通常会遵循一些常见的约定和最佳实践。以下是 些常见的前端日志格式和考虑 因素: 1。时间戳: 在日志条目的开头添加一个时间戳, 以便跟踪事件发生的时间。 日志级别: 指定日志条目的级别 (例如。错误。警告。信息。调试等) 以便根据需要迸行筛选和过滤。 3。源代码位置: 在日志条目中包含源代码的位置信息 (例如。文件名 行号等) ,以便快速定位问题。 4。消息内容: 提供有关事件或错误的详细信息。包括任何相关的上下文或错误消息。 5。自定义字段: 根据需要添加其他自定义字段。例如用户I0。会话I。浏览器信息等 以下是一个简单的前端日志格式示例: [2023-03-15 14:32:01] INFO: main.j5:123 User LOBged SUCCessfulIy [2023-03-1514:32:15] ERROR: User.js :45 Failed fetch USer Jata Error: Network Error. 码 在这个示例中 每条日志条目都包含时间戳。日志级别。源代码位置以及消息内容。此外,还可以根据需要添加其他 自定义字段。例如用户I。会话I0等 需要注意的是 前端日志格式应该根据项目的具体需求和规范迸行定制, 以便更好地满足项目的需求 同时。还需要 考虑日志的存储。传输和展示等方面的问题。以确保日志的可用性和可维护性。 1.4日志存储 应及时清理和管理日志, 以避免过大的日志文件或存储空间的浪费。及时地分析日志, 以便发现潜在的问题或改迸系 统性能。 [推荐1 对于信息系统 日志的保存时间至少为6个月。每个月必须备份一次日志文件。在实际应用中, 可以根据日 志文件的重要程度。文件大小以及磁盘空间自行调整 6个月内至少清理 ~次过期的或无用的日志数据 此外。还可 以对日志迸行监控_ 收到磁盘报警时, 对1个月之前的数据可以迸行删除或者转储 1.5 日志安全 1.5.1 保护范围 日志记录中包含的某些内容出于隐私保护 数据安全和法规遵从的要求。不应直接以原始形式显示。以下是一些需要 迸行处理的肉容类型: 法律法规不允许的信息 《今人信息保护法》涉及的敏感信息:《个保法》第二十八条 敏感个人信息是 旦泄露或者非法使用。容易导致 自然人的人格尊严受到侵害或者人身 财产安全受到危害的个人信息。包括生物识别。宗教信仰。特定身份。医 疗健康 金融账户 行踪轨迹等信息, 以及不满十四周岁未成年人的个人信息。根据法规。个^可识别信息如身 份证号码。电话号码。住址 电子邮件地址等必须严格保护, 未经同意不得非法收集 使用或披露。同时。个人 敏感数据如健康状况。医疗记录 生物识别信息等高度私密。处理时需特别谨慎。确保合法合规。 ,澳集团保密要求 包括但不限于涉密技术资料。勘探数据 _ 生产数据 商业合作细节等信息。信息系统日志中应避免直接记录这些 内容 井对所有操作迸行严格的审计和安全控制。确保日志的安全存储。访问权限控制以及必要时的数据脱敏 涉及的商业秘密信息 在日志中 商业秘密信息可能以各种形式体现。例如研发数据。客户名单。内部策略 未公开的产品设计和技术 方案等。 业务定义敏感信息 金融交易信息: 银行卡号 支付卡数据 {CVV码。有效期等〉 是金融行业严控的信息。必须采取加密或其他安全 措施妥善处理和存储。 业务敏感信息: 还包括未公开的战略规划。客户名单 定价策略。市场研究结果_ 内部财务数据等。任何可能导 致企业竞争优势受损或违反合同约定的信息 可能有助于攻击者利用的敏感数据 用户的会话I0(如果需要跟踪会话相关的事件 可以考虑用Hash值代替) 会话10与Hash值: 为了防止会话劫n。原始会话1不应在日志中明爻记录。可以考虑采用哈希值代替 但要确保 该哈希不能被反向解析为原始会话标识= 软件版本及框架版本: 虽然不直接构成安全风险 但在某些情况下暴露版本信息可能让攻击者了解潜在漏洞, 此建议不在日志中详细记录或仅记录到主版本级别。 散列值: 如果散列为敏感数据的散列。例如密码散列。在记录日志时应当谨慎。避免将过于详细的散列值存入日 志 特别是对于易破解的弱散列算法。 访问TokenlAPIToken: 应采取措施避免在日志中完整记录访问令牌或AP!令牌; 以防止泄露后被恶意使用= 认证密码: 绝对不应该在任何日志中明爻记录用户密码。即使是对服务器端的操作日志也应该加密或通过其他方 式去标识化。 数据库连接字符串与密钥: 包含数据库凭据的连接字符串_ 加密密钥和其他主密钥绝对不能出现在日志中 这些 信息一旦泄露可能导致数据大规模泄露或系统完全失控。 1.5.2 脱敏方法 不应在日志中明爻记录敏感信息数据, 必要时对这些数据迸行脱敏处理。对敏感的日志信息迸行加密。以保护数据安 全。确保在存储和传输过程中对敏感信息迸行加密。以防止未授权的访问和泄露 需要脱敏的信息见"保护范围"部分。 注: 在迸行日志脱敏时,会根据不同的敏感级别采用不同的脱敏策略。包括替换 遮盖_ 截断。哈希加密等方法。以 保护这些信息不被泄露。同时确保日志能够满足必要的业务分析和故障排查要求 具体方式包括但不限于: 移除 {Remove) 直接从日志条目中删除敏感字段。 分类与分级处理 根据数据的敏感程度迸行分类。并针对不同类别实施不同的脱敏策略。例如部分脱敏或完全替 换 脱敏 {Masking) 替换敏感字段为星号_ 占位符或其他非敏感字符。如将电话号部分数字脱敏为 UUr」| {量k 1234 净化 (Sanitizing) 对敏感数据进行格式化或规范化。确保不泄露实际意义。例如使用哈希函数处理用户身份信 Hash化: 对于需要保持唯 -性的数据。比如用户名或电子邮件地址 可以采用不可逆的哈希算法迸行处理。用于 审计目的但不能反向还原出原始数据。 实施上述措施有助于企业在满足日志分析和故障排查需求的同时,最大限度地降低数据泄露风险;井符合《网络安全 法》和 《网络安全等级保护基本要求》等法律法规中有关个人信息保护的要求。 注: 在使用日志框架如logback。 1o94j等时, 可以配置相应的插件或者自定义拦截器来实现自动化脱敏功能 1.5.3 安全措施 日志数据安全是确保系统在生成。传输 存储日志时。保护敏感信息不被未经授权的访问。泄露 篡改或丢失的重要 环节。以下是维护日志数据安全的- 些关键措施: 日志数据传输加密 在传输过程中 可使用SSL/TLS等安全协议加密5YSL06或其他形式的日志数据流 2。访问控制: 实施严格的访问控制策略。只有授权人员才能查看和操作日志数据 敏感信息处理: 对于包含敏感信息的日志条目。执行脱敏处理。如替换或模糊化密码。个人识别信息 (P)。信用卡号等敏感内 容 标准化与合规: 确保日志格式和实践符合行业标准和法规要求。比如《网络安全法》和 《网络安全等级保护基本要求》对于数据 处理的规定。 备份与恢复: 定期备份日志。井保证备份数据的安全性。同时设计有效的农难恢复方案。 通过上述措施。可从多个维度保障日志数据的安全性。同时也为数据分柝。故障排查 安全寅计及合规性检查提供了 有力支持 1.6 Java最佳实践 [推荐」 日志语言应使用英文 注: 应在打印日志时输出英文。防止中文缩码与终端不 致导致打印出现乱码的情况。对故障定位和排查存在一定的 干扰。如果日志中的错误信息用英文描述不清楚可使用中文描述。否则容易产生歧义。 国际化团队或海外部署的服务 器由于字符集问题; uing用英文来注释和描述日志错误信息。 [推荐) 日志打印时不宜直接用 JSON工 具将对象转换成String 反例 public void dosth(){ info(wdo Sth ahd print data={}" JSON .LoJSONString (data) ) 业务逻辑 注二 fastjson等序列化组件是通过调用对象的get方法将对象迸行序列化。如果对象里某些get方法被覆写。存在抛出异 常的情况。则可能会因为打印日志而影响正常业务流程的执行。 打日志过程中对一 些对象的序列化过程也是比较耗性能的。首先序列化过程本身时 个计算密集型过程。浪费 Cpu。 其次这个过程会产生很多中间对象。对内存也不友好。 正例 可以使用对象的toStringl) 方法打印对象信息。如果代码中没有对toString() 有定制化逻辑的话。可以使用apache的 ToStringBulider工 具 Dublic Void dosth(){ info(wdo Sth ahd print LOE; data{}" data CoStrine()); Log info(wdo Sth ahd print data{}" TostringBuilder.reflectionTostring (data, (强制] 不应打印无意义(无业务上下文 无关联日志链路id)的日志 反例 不带任何业务信息的日志。对排查故障毫无意义。 public V01d dosth() { info("do Sth ahd print 1oB ) 业务逻辑 1Og 1o, 1Og 1o, 10B 对于无异常分支的代码打印日志, 般流程下 异常分支都会打日志,如果没有出现异常。那就正常执行了 _ public void dosth() { doItI(); info(wdo sth _11" ) doIt2() info Oo sth 222") 正例 日志应带相关的业务信息。有利于排查问题快速定位到原因。 public Void dosth() { info(wdo sth ahd print 14{]" id ) 业务逻辑 [强制] 不应在循环中打印非DEBUG级别的日志 反例 public void dosth() { for (Strine strlist ) info("do Sth anc print log: {}I 5) ; 业务逻辑 [推荐] 在核心业务逻辑中遇到i..else等条件。每个分支首行都应打印日志 在缩写核心业务逻辑代码时。如遇到i..else. . 或者switch这样的条件_ 可以在分支的首行就打印日志。这样排查问题 时 就可以通过日志。确定迸入了哪个分支。代码逻辑更清晰。也更方便排查问题 正例: public void dosth() { af(user.isVip()) { log.info ("该用户足会员 , Id : {} ,开始处理会员逻辑" _ User, getUserId ()); 1/会员逻辑 }else{ info 该用户是非会员 , Id : {} ,开始处理非会员逻辑" _ User getUserId () ) /1非会员逻辑 108 108 108 10B, loB 10e [推荐] 宜打印必要的参数。不宜整个对象打印 反例 public Void dosth() { info("print data={}I data.tostring()); 业务逻辑 注 首先分析下自己是否必须把所有对象里的字段打印出来? 如果对象中有50个字段。但只需其中两个参数就可以定 位具体的原因 那么全量打印字段将浪费内容空间且因为字段过多。影响根因排查。 正例 public Void dosth() { info("print -d={}, type={}" data geCId (), data. getType()); 业务逻辑 注: 使用这个种方法需及时防Lnpe, 井考虑是否核心场景;核心场景建议还是打全; 避免漏打。少打影响线上问题 定位&排查。 [强制] 在接口/方法的入口|口处。打印请求及响应参数日志。 [推荐] 日志内容中不应仅打印特殊字符或数字 [推荐] 日志内容中应包含关键特征类信息, 例如: 用户标识或流水号 10. (强制] 日志单行大小应不超过200K 11 [强制] 打印日志的代码不应失败; 阻断流程! 一定要确保不会因为日志打印语句抛出异常造成业务流程中断。如下所示, shop为null的会导致抛出NPE。 public Void dosth() { info Wdo sth ahd print {}" shop. getId ()); 业务逻辑 108 108 108 108 108 108: 12. [强制] 不应使用System.out.println()输出日志 反例= public V01d dosth() { System OUt.println ( doSth。" ) 业务逻辑 注: 通过分析System.out.println源码可知, System.out println是- 个同步方法。在高井发的情况下。大量执行 println方法会严重影响性能 public V01d println (String synchronized (this) print (x) newLine() 不能实现日志按等级输出。具体来说就是不能和日志框架 -样。有 debug, info, error等级别的控制 System.out。 System.error打印的日志井没有打印在日志文件中。而是直接打印在终端。无法对日志迸行收集 正例= 在日常开发或者调试的过程中 应使用标准日志记录系统1094j2或者logback(但不应直接使用其中的API), 异步的进 行日志统 收集。 public V01d dosth() { lOB.info("dosth...") 业务逻辑 13. (强制] 对于debuglinfo 级别的日志输出,应迸行日志级别的开关判断 反例 public V01d dosth() { String Hame XXXI logger (Iprint l0g" hame ) logger info ("print info LOE" name) 业务逻辑 注 如果配置的日志级别是warn的话, 上述日志不会打印。但是会执行字符串拼接操作。如果name是对象; 还会执行 toString()方法。浪费了系统资源 执行了上述操作。最终日志却没有打印; 因此建议加日志开关判断 正例: 在debug info 级别日志打印前加上对应级别的日志开关判断。通常可以将开关判断逻辑包装在日志工具类中,统 实现 public Void dosth() { 1 (logger. sDebugEnabled () ) Zogger. debug Iprin name / 1 (logger.isInfoEnabled()) logger info ("print Tnfo {}" name 业务逻辑 14. [强制] 打印异常日志应要输出全部错误信息 反例: 没有打印异常e。无法定位出现什么类型的异常 debug gebug Jebug 10B ZOB public Void dosth() { Cry { 业务逻辑 catch (Exception e){ error execute failed") 没有记录详细的堆栈异常信息。只记录错误基本描述信息。不利于排查问题 public Void dosth() { Cry { 业务逻辑 catch (Exception e){ eFror execuTe +a1eqI e. BetMessage()); 正例 般日志框架中的warn, error级别均有存在传递Throwable异常类型的API, 可以直接将抛出的异常传入日志API中 Void error(String VarI, Throwable Varz) public V01d dosth() { Cry { 业务逻辑 catch (Exception e){ error execUe failedw loB lOB loB