网站首页 > 技术文章 正文
最近涌现出各种各样能帮助你理解日志的新工具,有类似 Scribe、Logstash 这样的开源项目,也有类似 Splunk 的预付费工具,还有托管服务如 SumoLogic 和 PaperTrail。这些工具的共同点是对日志数据进行清洗,在大量日志中提取一些更有价值的文件。
但有一件事这些工具却爱莫能助,因为它们完全依赖你实际投入的日志数据,而如何保证数据的质量和数量则需要用户自行完成。因此,在关键时刻,如果你需要基于部分或者遗漏日志做代码调试时,事情可能会变得非常棘手。
为了减少这种情况发生,在这里分享五个建议,在你记录日志时最好能铭记于心:
1. 你好,我的(线程)名字是
正如 Ringo,线程名称这个属性是 Java 中最被低估的方法之一。其原因是线程名称大部分是描述性的。然而问题同样出现在这里,类似人们自己,起名时通常会被赋予一定的意义。而在多线程日志中,线程名同样挥着关键作用。通常情况下,大多数日志框架会记录当前所调用的线程名称。可悲的是,我们通常会看到http-nio-8080-exec-3这种名字,简单地由线程池或容器进行分配。
出于某种原因,我们曾不止一次地听过这种误解——线程名称是不可变的。与之相反,在日志中,线程名称占据基本主要地位,你应该确保能正确使用。比如将它与具体情境结合起来,例如 Servlet 的名字、任务相关,或者一些动态语境如用户或消息 ID。
这样的话,代码接口应该是这样:
Thread.currentThread().setName(ProcessTask.class.getName()+“:“+message.getID);
更先进的版本将被加载到当前线程的线程局部变量,配置 log appender,并自动将其添加到日志条目。
当多个线程写入服务器日志,但你需要集中在单一线程上时,这将会非常有用。如果你在一个分布式 /SOA 环境下运行,更能看到它得天独厚的优势。
2. 分布式的标识符
在 SOA 或消息驱动的架构,任务执行很可能跨多台机器。当处理这种环境下的故障时,连接相关机器和它们的状态将是了解具体情况的关键。大多数日志分析器会将这些日志信息分组,假设你为它们提供了唯一标识,它们便可以作为实际日志消息的一部分。
从设计的角度出发,这意味着,从进入系统到操作完成,每一个入站操作应该有其唯一的 ID 对应。请注意,一个持久的标识符,如用户 ID 可能不是一个好容器。在记录日志文件的过程中,用户可能有多个操作,这将使得隔离特定流更加困难。UUIDs 可能是个不错的选择。它的值可以被加载到实际线程名称或者作为 TLS-thread 的局部储存器。
3. 不要使用文本+驱动器,不要日志+循环
很多时候,你会看到一段代码在紧密的循环中运行,并执行相应的日志操作。基本假设是,该代码运行的次数是有限的。
很可能运行情况非常良好。但是当代码得到意外输入时,循环可能并不会中断。在这种情况下,你不只是处理一个无限循环「虽然这样已经很糟糕了」,你正在处理的代码正将无限量的数据写到磁盘或网络。
在单机场景中它可能会造成一台服务器崩溃,而在分布式场景中,被影响的则是整个集群。因此如果可能,不要在紧密循环中记录日志。捕获错误时,这一点尤其如此。
下面这个例子,记录了一个 while 循环中的异常:
voidread(){
while(hasNext()){
try{
readData();
}catch{Exceptione){
// this isn’t recommend
logger.error(“error reading data“,e);
}
}
}
如果 readData 抛出异常,而 hasNext 返回值为 true,这里将会写入无限量的日志数据。要解决这个问题的方法是确保不会记录这一切:
voidread(){
intexceptionsThrown=0;
while(hasNext()){
try{
readData();
}catch{Exceptione){
if(exceptionsThrown<THRESHOLD){
logger.error(“error reading data", e);
exceptionsThrown++;
} else {
// Now the error won’t choke the system.
}
}
}
}
另一种方法是从循环中移除日志记录,并保存第一/最后一个异常对象并在其它地方记录。
4. 未捕获的处理程序
Westeros 有最后一道防御墙,而你有Thread.uncaughtExceptionHandler。因此,尽量使用它们。如果没有安装这些处理程序,在异常抛出时,你只能获得很少有价值的上下文,同时你也无法控制在结束之前你已经将其记录,并确定记录的位置。
请注意,即使在未捕获的异常处理程序,看起来你没有任何办法访问线程中(已终止)的任何变量,你仍然可以获得实际线程对象的引用。如果你坚持# 1步,你仍然会得到一个有意义的thread.getName()值可记录。
5. 捕获外部调用
每当调用一个外部的 API, JVM 异常的几率将大大增加。这包括 Web 服务、 HTTP、 DB、 文件系统、操作系统和任何其他 JNI 调用。认真对待每个调用,因为它随时会爆炸 「它很有可能发生在同样的点」。
大多数情况下,外部 API 故障的原因是意外输入,日志中对其记录是修复代码的关键。
在这一点上,你可以选择不记录错误,只是抛出异常也可以。在这种情况下,只要收集到调用的相关参数,并将其解析为异常错误信息。
只要确保异常被捕获并记录在更高级别的堆栈调用即可。
try{
returns3client.generatePresignedUrl(request);
}catch(Exceptione){
Stringerr=String.format(“Errorgenerating request:%s bucket:%s key:%s.method:%s", request, bucket, path, method);
log.error(err, e); //you can also throw a nested exception here with err instead.
}
猜你喜欢
- 2024-12-18 Java开发中MongoDB数据库相关操作
- 2024-12-18 HashMap有几种遍历方法?推荐使用哪种?
- 2024-12-18 在RedisTemplate中使用scan代替keys指令
- 2024-12-18 MQ的发布订阅模式(fanout) mq几种消息模式
- 2024-12-18 Mybatis参数-ParameterMapping处理参数
- 2024-12-18 既然有MySQL了,为什么还要有MongoDB?
- 2024-12-18 Java遍历一个 List 有哪些方式?每种的实现原理以及哪种最高效?
- 2024-12-18 为什么很多人不愿意用hibernate了?
- 2024-12-18 Qt中 QMap 类、QHash 类、QVector 类详解
- 2024-12-18 半小时搞懂 IO 模型 io模型详解
- 1507℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 512℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 488℃MySQL service启动脚本浅析(r12笔记第59天)
- 467℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 465℃启用MySQL查询缓存(mysql8.0查询缓存)
- 445℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 424℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 422℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)