init
@@ -0,0 +1,391 @@
|
||||
应用日志规范(Java)
|
||||
1.技术要求
|
||||
1.1记录原则
|
||||
1.【强制】隔离性:日志输出不应影响系统正常运行;
|
||||
2.【强制】安全性:日志打印本身不应存在逻辑异常或漏洞,导致产生安全问题;
|
||||
3.【强制】数据安全:不应输出机密、敏感信息,如用户联系方式、身份证号码、token等,应保证日志的完整性、
|
||||
可审计性;
|
||||
4.【强制】可监控分析:日志应提供给监控进行监控,分析系统进行分析;
|
||||
5.【强制】可定位排查:日志信息输出应有意义,应具有可读性,可供开发人员排查线上问题。
|
||||
1.2日志级别
|
||||
在我们日常开发中有四种比较常见的日志打印等级,不同的等级适合在不同的时机下打印日志。
|
||||
主要使用的有以下四个等级:
|
||||
1.DEBUG
|
||||
DEUBG级别应主要输出调试性质的内容,该级别日志应用于并发、测试阶段输出。该级别的日志应尽可能地详尽
|
||||
开发人员可以将各类详细信息记录到DEBUG里,起到调试的作用,包括参数信息,调试细节信息,返回值信息等
|
||||
等,便于在开发、测试阶段出现问题或者异常时,对其进行分析。
|
||||
2. INFO
|
||||
INFO级别的主要记录系统关键信息,旨在保留系统正常工作期间关键运行指标,开发人员可以将初始化系统配置,
|
||||
业务状态变化信息,或者用户业务流程中的核心处理记录到INFO日志中,方便日常运维工作以及错误回溯时上下文
|
||||
场景复现。应在项目完成后,在测试环境将日志级别调成INFO,然后通过INFO级别的信息看看是否能了解这个应用
|
||||
的运用情况,如果出现问题后是否这些日志能否提供有用的排查问题的信息。
|
||||
3.WARN
|
||||
WARN级别的主要输出警告性质的内容,这些内容是可预知且是有规划的,比如,某个方法入参为空或者该参数的值
|
||||
不满足运行该方法的条件时。在WARN级别的时应输出较为详尽的信息,以便于事后对日志进行分析。
|
||||
注:常见的WARN级别异常
|
||||
·用户输入参数错误
|
||||
·非核心组件初始化失败
|
||||
·后端任务处理最终失败(如果有重试且重试成功,就不需要WARN)
|
||||
·数据插入幂等
|
||||
4.ERROR
|
||||
ERROR级别主要针对于一些不可预知的信息,诸如:错误、异常等,比如,在catch块中抓获的网络通信、数据库连
|
||||
接等异常,若异常对系统的整个流程影响不大,可使用WARN级别日志输出。在输出ERROR级别的日志时,应尽量多
|
||||
地输出方法入参数、方法执行过程中产生的对象等数据,在带有错误、异常对象的数据时,应将该对象一并输出。
|
||||
注:常见的ERROR级别异常
|
||||
·程序启动失败
|
||||
·核心组件初始化失败
|
||||
·连不上数据库
|
||||
·核心业务访问依赖的外部系统持续失败
|
||||
Woo·
|
||||
不应滥用ERROR级别日志。一般来说在配置了告警的系统中,WARN级别一般不会告警,ERROR级别则会设置监控
|
||||
告警甚至电话报警,ERROR级别日志的出现意味着系统中发生了非常严重的问题,应有人立即处理。
|
||||
错误的使用ERROR级别日志,不区分问题的重要程度,只要是问题就采用ERROR级别日志,这是极其不负责任的表
|
||||
现,因为大部分系统中的告警配置都是根据单位时间内ERROR级别日志出现的数量来定的,随意打ERROR日志会造
|
||||
成极大的告警噪音,造成重要问题遗漏。
|
||||
1.3日志格式
|
||||
1.3.1后端日志格式
|
||||
后端日志可以保存或输出到文件,远程服务器,控制台及数据库等,日志输出包含以下要素:
|
||||
·时间戳:2024-01-29T15:30:45,789
|
||||
·日志链路id(traceld、rpcld)(可选)0b26053315407142451016402xxxxx0.3-///-
|
||||
·日志级别:DEBUG,INFO,WARN,ERROR,FATAL
|
||||
·线程名:SofaBizProcessor-4-thread-333,main
|
||||
·类名或Logger名称:com.example.MyClass
|
||||
·方法名与行号(可选):·myMethod(L45)
|
||||
·调用耗时(可选)1ms
|
||||
·调用是否成功(Y/N)(可选)
|
||||
·状态码(可选)SUCCESS
|
||||
·系统上下文信息(调用系统名、调用系统ip、调用时间戳、是否压测(Y/N))(可选)appName,ip地址,时间戳,Y
|
||||
·请求入参(可选)参数1
|
||||
·请求出参(可选)参数1
|
||||
·日志消息内容:"Thisis alog message","No URLs will be polled as dynamic
|
||||
configurationsources"
|
||||
日志自定义输出格式如下:
|
||||
[SofaBizProcessor-4-thread-333] {com.example.MyClass}-L22-[(1ms,Y,SUCCESS)(appName,ip
|
||||
地址,时间戳,Y)(参数1,参数2)]-NoURLswillbe polled asdynamicconfiguration sources
|
||||
[ob26053315407142451016402xXXxx0.3-///-]是traceld;[INF0]是日志级别;
|
||||
[SofaBizProcessor-4-thread-333]是线程名;{com.example.MyClass}是类名;L22是行号;No
|
||||
URLswillbepolledasdynamicconfigurationsources是实际的日志消息,具体的业务相关日志打
|
||||
印形式可以根据业务实际情况自定义。
|
||||
日志标准输出格式如下:
|
||||
1 2024-01-29 15:30:45,789 [INF0] [main]{com.example.MyClass} ]-No URLs will be polled
|
||||
as dynamicconfigurationsources
|
||||
注:移动端日志遵循后端日志格式。
|
||||
1.3.2前端日志格式
|
||||
因素:
|
||||
1.时间戳:在日志条目的开头添加一个时间戳,以便跟踪事件发生的时间。
|
||||
2.日志级别:指定日志条目的级别(例如,错误、警告、信息、调试等),以便根据需要进行筛选和过滤。
|
||||
3.源代码位置:在日志条目中包含源代码的位置信息(例如,文件名、行号等),以便快速定位问题。
|
||||
4.消息内容:提供有关事件或错误的详细信息,包括任何相关的上下文或错误消息。
|
||||
5.自定义字段:根据需要添加具他自定义字段,例如用户ID、会话ID、浏览器信息等。
|
||||
以下是一个简单的前端日志格式示例:
|
||||
1[2023-03-15 14:32:01] INF0: main.js:123 -User logged in successfully.
|
||||
[2023-03-15 14:32:15] ERR0R: user.js:45-Failed to fetch user data. Error: Network
|
||||
Error.码
|
||||
在这个示例中,每条日志条目都包含时间戳、日志级别、源代码位置以及消息内容。此外,还可以根据需要添加其他
|
||||
自定义字段,例如用户ID、会话ID等。
|
||||
需要注意的是,前端日志格式应该根据项目的具体需求和规范进行定制,以便更好地满足项目的需求。同时,还需要
|
||||
考虑日志的存储、传输和展示等方面的问题,以确保日志的可用性和可维护性。
|
||||
1.4日志存储
|
||||
应及时清理和管理日志,以避免过大的日志文件或存储空间的浪费。及时地分析日志,以便发现潜在的问题或改进系
|
||||
统性能。
|
||||
志文件的重要程度、文件大小以及磁盘空间自行调整,6个月内至少清理一次过期的或无用的日志数据,此外,还可
|
||||
以对日志进行监控,收到磁盘报警时,对1个月之前的数据可以进行删除或者转储。
|
||||
1.5日志安全
|
||||
1.5.1保护范围
|
||||
日志记录中包含的某些内容出于隐私保护、数据安全和法规遵从的要求,不应直接以原始形式显示。以下是一些需要
|
||||
进行处理的内容类型:
|
||||
1.法律法规不充许的信息
|
||||
·《个人信息保护法》涉及的敏感信息:《个保法》第二十八条敏感个人信息是一旦泄露或者非法使用,容易导致
|
||||
自然人的人格尊严受到侵害或者人身、财产安全受到危害的个人信息,包括生物识别、宗教信仰、特定身份、医
|
||||
疗健康、金融账户、行踪轨迹等信息,以及不满十四周岁未成年人的个人信息。根据法规,个人可识别信息如身
|
||||
份证号码、电话号码、住址、电子邮件地址等必须严格保护,未经同意不得非法收集、使用或披露。同时,个人
|
||||
敏感数据如健康状况、医疗记录、生物识别信息等高度私密,处理时需特别谨慎,确保合法合规。
|
||||
2.石油集团保密要求
|
||||
·包括但不限于涉密技术资料、勘探数据、生产数据、商业合作细节等信息,信息系统日志中应避免直接记录这些
|
||||
内容,并对所有操作进行严格的审计和安全控制,确保日志的安全存储、访问权限控制以及必要时的数据脱敏。
|
||||
3.涉及的商业秘密信息
|
||||
·在日志中,商业秘密信息可能以各种形式体现,例如研发数据、客户名单、内部策略、未公开的产品设计和技术
|
||||
方案等。
|
||||
4.业务定义敏感信息
|
||||
·金融交易信息:银行卡号、支付卡数据(CVV码、有效期等)是金融行业严控的信息,必须采取加密或具他安全
|
||||
措施妥善处理和存储。
|
||||
致企业竞争优势受损或违反合同约定的信息。
|
||||
5.可能有助于攻击者利用的敏感数据
|
||||
·用户的会话lD(如果需要跟踪会话相关的事件,可以考虑用Hash值代替)
|
||||
·会话ID与Hash值:为了防止会话劫持,原始会话ID不应在日志中明文记录,可以考虑采用哈希值代替,但要确保
|
||||
该哈希不能被反向解析为原始会话标识。
|
||||
此建议不在日志中详细记录或仅记录到主版本级别。
|
||||
·散列值:如果散列为敏感数据的散列,例如密码散列,在记录日志时应当谨慎,避免将过于详细的散列值存入日
|
||||
志,特别是对于易破解的弱散列算法。
|
||||
式去标识化。
|
||||
·数据库连接字符串与密钥:包含数据库凭据的连接字符串、加密密钥和其他主密钥绝对不能出现在日志中,这些
|
||||
信息一旦泄露可能导致数据大规模泄露或系统完全失控。
|
||||
1.5.2脱敏方法
|
||||
不应在日志中明文记录敏感信息数据,必要时对这些数据进行脱敏处理。对敏感的日志信息进行加密,以保护数据安
|
||||
全。确保在存储和传输过程中对敏感信息进行加密,以防止未授权的访问和泄露。
|
||||
需要脱敏的信息见”保护范围“部分。
|
||||
注:在进行日志脱敏时,会根据不同的敏感级别采用不同的脱敏策略,包括替换、遮盖、截断、哈希加密等方法,以
|
||||
保护这些信息不被泄露,同时确保日志能够满足必要的业务分析和故障排查要求
|
||||
具体方式包括但不限于:
|
||||
·移除(Remove):直接从日志条目中删除敏感字段。
|
||||
·分类与分级处理:根据数据的敏感程度进行分类,并针对不同类别实施不同的脱敏策略,例如部分脱敏或完全替
|
||||
换。
|
||||
·脱敏(Masking):替换敏感字段为星号、占位符或其他非敏感字符,如将电话号部分数字脱敏为“******
|
||||
1234”。
|
||||
·净化(Sanitizing):对敏感数据进行格式化或规范化,确保不泄露实际意义,例如使用哈希函数处理用户身份信
|
||||
息。
|
||||
·Hash化:对于需要保持唯一性的数据,比如用户名或电子邮件地址,可以采用不可逆的哈希算法进行处理,用于
|
||||
审计目的但不能反向还原出原始数据。
|
||||
实施上述措施有助于企业在满足日志分析和故障排查需求的同时,最大限度地降低数据泄露风险,并符合《网络安全
|
||||
注:在使用日志框架如logback、Log4j等时,可以配置相应的插件或者自定义拦截器来实现自动化脱敏功能。
|
||||
1.5.3安全措施
|
||||
日志数据安全是确保系统在生成、传输、存储日志时,保护敏感信息不被未经授权的访问、泄露、篡改或丢失的重要
|
||||
环节。以下是维护日志数据安全的一些关键措施:
|
||||
1.日志数据传输加密:
|
||||
·在传输过程中,可使用SSL/TLS等安全协议加密SYSLOG或具他形式的日志数据流。
|
||||
2.访问控制:
|
||||
·实施严格的访问控制策略,只有授权人员才能查看和操作日志数据,
|
||||
3.敏感信息处理:
|
||||
·对于包含敏感信息的日志条目,执行脱敏处理,如替换或模糊化密码、个人识别信息(PII)、信用卡号等敏感内
|
||||
容。
|
||||
4.标准化与合规:
|
||||
·确保日志格式和实践符合行业标准和法规要求,比如《网络安全法》和《网络安全等级保护基本要求》对于数据
|
||||
处理的规定。
|
||||
5.备份与恢复:
|
||||
·定期备份日志,并保证备份数据的安全性,同时设计有效的灾难恢复方案。
|
||||
通过上述措施,可从多个维度保障日志数据的安全性,同时也为数据分析、故障排查、安全审计及合规性检查提供了
|
||||
有力支持。
|
||||
1.6Java最佳实践
|
||||
1.【推荐】日志语言应使用英文
|
||||
注:应在打印日志时输出英文,防止中文编码与终端不一致导致打印出现乱码的情况,对故障定位和排查存在一定的
|
||||
干扰。如果日志中的错误信息用英文描述不清楚可使用中文描述,否则容易产生歧义。国际化团队或海外部署的服务
|
||||
器由于字符集问题,uing用英文来注释和描述日志错误信息。
|
||||
2.【推荐】日志打印时不宜直接用JSON工具将对象转换成String
|
||||
反例:
|
||||
1 public void doSth(){
|
||||
2
|
||||
log.info("do sth and print log,data={}",JsoN.toJsoNstring(data));
|
||||
3
|
||||
//业务逻辑
|
||||
4
|
||||
51
|
||||
注:
|
||||
·fastison等序列化组件是通过调用对象的get方法将对象进行序列化,如果对象里某些get方法被覆写,存在抛出异
|
||||
常的情况,则可能会因为打印日志而影响正常业务流程的执行。
|
||||
打日志过程中对一些对象的序列化过程也是比较耗性能的。首先序列化过程本身时一个计算密集型过程,浪费
|
||||
cpu。其次这个过程会产生很多中间对象,对内存也不友好。
|
||||
正例:
|
||||
可以使用对象的toString(()方法打印对象信息,如果代码中没有对toString(有定制化逻辑的话,可以使用apache的
|
||||
ToStringBulider工具。
|
||||
1public void doSth(){
|
||||
2
|
||||
log.info("do sth and print log, data={}", data.toString());
|
||||
3
|
||||
log.info("do sth and print log, data=[}", ToStringBuilder.reflectionToString(data,T
|
||||
4
|
||||
3.
|
||||
【强制】不应打印无意义(无业务上下文、无关联日志链路id)的日志
|
||||
反例:
|
||||
不带任何业务信息的日志,对排查故障毫无意义。
|
||||
1public void doSth(){
|
||||
2
|
||||
log.info("do sth and print log");
|
||||
3
|
||||
//业务逻辑
|
||||
4
|
||||
5}
|
||||
对于无异常分支的代码打印日志,一般流程下,异常分支都会打日志,如果没有出现异常,那就正常执行了。
|
||||
1public void doSth(){
|
||||
2
|
||||
doIt1();
|
||||
3
|
||||
log.info("do sth 1l1");
|
||||
4
|
||||
doIt2();
|
||||
5
|
||||
log.info("do sth 222");
|
||||
6}
|
||||
正例:
|
||||
·日志应带相关的业务信息,有利于排查问题快速定位到原因。
|
||||
1public void doSth(){
|
||||
2
|
||||
log.info("do sth and print log, id=[}",id);
|
||||
3
|
||||
//业务逻辑
|
||||
4
|
||||
+
|
||||
【强制】不应在循环中打印非DEBUG级别的日志
|
||||
反例:
|
||||
1 public void doSth(){
|
||||
2
|
||||
for(String s:strList){
|
||||
3
|
||||
log.info("do sth and print log: {}",s);
|
||||
4
|
||||
//业务逻辑
|
||||
5
|
||||
6
|
||||
7
|
||||
5.【推荐】在核心业务逻辑中遇到if..else等条件,每个分支首行都应打印日志
|
||||
在编写核心业务逻辑代码时,如遇到if..else...或者switch这样的条件,可以在分支的首行就打印日志,这样排查问题
|
||||
时,就可以通过日志,确定进入了哪个分支,代码逻辑更清晰,也更方便排查问题。
|
||||
正例:
|
||||
1 public void doSth(){
|
||||
2
|
||||
if(user.isVip()){
|
||||
3
|
||||
log.info("该用户是会员,Id:[},开始处理会员逻辑",user,getUserId());
|
||||
4
|
||||
//会员逻辑
|
||||
5
|
||||
}else{
|
||||
6
|
||||
log.info("该用户是非会员,Id:[},开始处理非会员逻辑",user,getUserId())
|
||||
//非会员逻辑
|
||||
9}
|
||||
6.【推荐】宜打印必要的参数,不宜整个对象打印
|
||||
反例:
|
||||
1 public void doSth(){
|
||||
2
|
||||
log.info("print log,data={}",data.toString());
|
||||
3
|
||||
//业务逻辑
|
||||
4
|
||||
5}
|
||||
注:首先分析下自己是否必须把所有对象里的字段打印出来?如果对象中有50个字段,但只需其中两个参数就可以定
|
||||
位具体的原因,那么全量打印字段将浪费内容空间且因为字段过多,影响根因排查。
|
||||
正例:
|
||||
1 public void doSth(){
|
||||
2
|
||||
log.info("print log,id={},type={}",data.getId(),data.getType());
|
||||
3
|
||||
//业务逻辑
|
||||
4
|
||||
5}
|
||||
注:使用这个种方法需及时防止npe,并考虑是否核心场景,核心场景建议还是打全,避免漏打、少打影响线上问题
|
||||
定位&排查。
|
||||
7.【强制】在接口/方法的入口/出口处,打印请求及响应参数日志。
|
||||
8
|
||||
【推荐】日志内容中不应仅打印特殊字符或数字。
|
||||
9.
|
||||
【推荐】日志内容中应包含关键特征类信息,例如:用户标识或流水号。
|
||||
10.
|
||||
【强制】日志单行大小应不超过200K
|
||||
11.【强制】打印日志的代码不应失败,阻断流程!
|
||||
一定要确保不会因为日志打印语句抛出异常造成业务流程中断,如下所示,shop为null的会导致抛出NPE。
|
||||
1public void doSth(){
|
||||
2
|
||||
log.info("do sth and print log: [}",shop.getId());
|
||||
3
|
||||
//业务逻辑
|
||||
4
|
||||
5}
|
||||
12.【强制】不应使用System.out.println(输出日志
|
||||
反例:
|
||||
1public void doSth(){
|
||||
2
|
||||
System.out.println("doSth...");
|
||||
3
|
||||
//业务逻辑
|
||||
4
|
||||
5}
|
||||
注:通过分析System.out.println源码可知,System.out.println是一个同步方法,在高并发的情况下,大量执行
|
||||
println方法会严重影响性能。
|
||||
1 public void println(String x){
|
||||
2
|
||||
synchronized (this)[
|
||||
3
|
||||
print(x) ;
|
||||
4
|
||||
newLine();
|
||||
5
|
||||
6
|
||||
不能实现日志按等级输出。具体来说就是不能和日志框架一样,有debug,info,error等级别的控制。
|
||||
System.out、System.error打印的日志并没有打印在日志文件中,而是直接打印在终端,无法对日志进行收集。
|
||||
正例:
|
||||
在日常开发或者调试的过程中,应使用标准日志记录系统log4j2或者logback(但不应直接使用其中的APl),异步的进
|
||||
行日志统一收集。
|
||||
1 public void doSth(){
|
||||
2
|
||||
log.info("doSth...") ;
|
||||
3
|
||||
//业务逻辑
|
||||
13.【强制】对于debug/info级别的日志输出,应进行日志级别的开关判断
|
||||
反例:
|
||||
1 public void doSth(){
|
||||
2
|
||||
String name = "xxx";
|
||||
3
|
||||
logger.debug("print debug log"+ name);
|
||||
4
|
||||
logger.info("print infolog"+ name);
|
||||
5
|
||||
//业务逻辑
|
||||
6
|
||||
7}
|
||||
注:
|
||||
如果配置的日志级别是warn的话,上述日志不会打印,但是会执行字符串拼接操作,如果name是对象,还会执行
|
||||
toString(方法,浪费了系统资源,执行了上述操作,最终日志却没有打印,因此建议加日志开关判断。
|
||||
正例:
|
||||
在debug、info级别日志打印前加上对应级别的日志开关判断,通常可以将开关判断逻辑包装在日志工具类中,统一
|
||||
实现。
|
||||
1 public void doSth(){
|
||||
2
|
||||
if(logger.isDebugEnabled()){
|
||||
3
|
||||
logger.debug("print debug log {}",name);
|
||||
4
|
||||
5
|
||||
if(logger.isInfoEnabled()){
|
||||
6
|
||||
logger.info("print info log [}",name;
|
||||
7
|
||||
}
|
||||
8
|
||||
//业务逻辑
|
||||
9
|
||||
10}
|
||||
14.
|
||||
【强制】打印异常日志应要输出全部错误信息
|
||||
反例:
|
||||
没有打印异常e,无法定位出现什么类型的异常
|
||||
1 public void doSth(){
|
||||
2
|
||||
tryf
|
||||
3
|
||||
//业务逻辑
|
||||
4
|
||||
5
|
||||
}catch(Exception e){
|
||||
6
|
||||
log.error("execute failed");
|
||||
7
|
||||
8}
|
||||
没有记录详细的堆栈异常信息,只记录错误基本描述信息,不利于排查问题。
|
||||
1 public void doSth(){
|
||||
2
|
||||
tryf
|
||||
3
|
||||
//业务逻辑
|
||||
4
|
||||
5
|
||||
}catch (Exception e){
|
||||
6
|
||||
log.error("execute failed",e.getMessage());
|
||||
7
|
||||
8
|
||||
正例:
|
||||
一般日志框架中的warn、error级别均有存在传递Throwable异常类型的APl,可以直接将抛出的异常传入日志APl中。
|
||||
1void error(String varl, Throwable var2);
|
||||
2 public void doSth(){
|
||||
3
|
||||
tryf
|
||||
4
|
||||
//业务逻辑
|
||||
5
|
||||
6
|
||||
}catch (Exception e){
|
||||
7
|
||||
log.error("executefailed",e);
|
||||
8
|
||||
9}
|
||||
@@ -0,0 +1,633 @@
|
||||
应用日志规范
|
||||
(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
|
||||
|
After Width: | Height: | Size: 367 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 291 KiB |
|
After Width: | Height: | Size: 409 KiB |
|
After Width: | Height: | Size: 391 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 209 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 130 KiB |