当前位置: 首页 > news >正文

Java中的日志记录

在开发Java应用程序时,日志系统是开发者进行调试、追踪程序运行状态以及异常处理的重要工具,但是有很多时候错误的日志记录方式非常不利于排查线上问题。好了,上代码!

示例1:错误的日志信息记录方式

import java.util.logging.Logger;public class BadLoggingExample {private static final Logger logger = Logger.getLogger(BadLoggingExample.class.getName());public void process(String input) {try {// 模拟可能出现异常的操作if (input == null) {throw new NullPointerException("输入参数不能为空");}// ...} catch (NullPointerException e) {// 错误的做法:只记录异常的消息内容,而没有包含堆栈跟踪logger.severe("发生空指针异常:" + e.getMessage());}}
}

问题分析:在这个例子中,当捕获到NullPointerException时,仅记录了异常的消息内容。然而,在实际场景中,e.getMessage()可能会为空,此时无法得知异常的具体来源和上下文信息。此外,未记录堆栈跟踪信息会大大增加定位问题源头的难度。

示例2:正确的日志信息记录方式

import java.util.logging.Logger;public class ProperLoggingExample {private static final Logger logger = Logger.getLogger(ProperLoggingExample.class.getName());public void process(String input) {try {// 模拟可能出现异常的操作if (input == null) {throw new NullPointerException("输入参数不能为空");}// ...} catch (NullPointerException e) {// 正确的做法:记录异常的完整信息,包括消息内容和堆栈跟踪logger.log(java.util.logging.Level.SEVERE, "发生空指针异常", e);}}
}

优点:现在,当出现异常时,不仅记录了异常的消息内容,还包含了完整的堆栈跟踪信息。这样在排查问题时,能够迅速找到引发异常的确切代码位置及调用链路。

示例3:日志对象未初始化


public class UninitializedLoggingExample {// 错误做法:没有正确初始化Logger实例private static Logger logger;public void process(String input) {try {if (input == null) {throw new NullPointerException("输入参数不能为空");}} catch (NullPointerException e) {// 在这里调用未初始化的logger会导致空指针异常logger.error("发生空指针异常:" + e.getMessage(), e);}}
}

问题分析:未初始化的日志器会导致运行时抛出NullPointerException。在实际使用前,必须确保通过日志框架提供的方法(如Logger.getLogger())正确初始化日志器。

示例4:不适当的日志级别和信息丢失

在讲解示例4之前,应该先了解日志级别的优先级和含义。以下是Java Util Logging 的日志级别及其优先级:

  1. SEVERE(严重):表示应用程序无法继续运行或者系统崩溃的错误信息。

  2. WARNING(警告):指出潜在的问题或非预期的情况,但程序仍可继续运行。

  3. INFO(信息):用于记录应用程序的基本运行状态、重要事件等常规信息。

  4. CONFIG(配置):一般用于记录与应用配置相关的消息。

  5. FINE(详细):更详细的调试信息,通常在开发阶段启用以获取更多上下文信息。

  6. FINER(更详细):比FINE级别更为详细的调试信息。

  7. FINEST(最详细):最详细的调试信息,仅在需要深入分析问题时启用。

下面是错误的示例:

import java.util.logging.Logger;
import java.util.logging.Level;public class InappropriateLogLevelExample {private static final Logger logger = Logger.getLogger(InappropriateLogLevelExample.class.getName());public void logSomeInfo() {// 错误做法:将全局日志级别设置为SEVERE,低于该级别的日志都不会被输出logger.setLevel(Level.SEVERE);// 这些日志由于级别低于SEVERE,所以不会被打印logger.config("这是CONFIG级别的日志");logger.info("这是INFO级别的日志");logger.fine("这是FINE级别的调试信息");logger.warning("警告信息");  // 只有SEVERE及以上级别的消息会被记录}
}

问题分析:当全局日志级别设置得过高,如本例中的Level.SEVERE,那么低于此级别的CONFIGINFOFINE等日志信息都将被忽略。这可能导致在排查问题时缺乏关键的运行状态和调试信息。因此,在实际应用中,应根据不同的环境和需求灵活调整日志级别。

示例5:忽略多线程环境下的日志安全问题

import java.util.logging.Logger;public class UnsafeLogging {private static final Logger logger = Logger.getLogger(UnsafeLogging.class.getName());public void processTask(String taskName) {logger.info("开始处理任务:" + taskName);// 模拟耗时操作simulateWork();logger.info("完成处理任务:" + taskName);}private void simulateWork() {try {Thread.sleep(1000);  // 模拟工作耗时} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {UnsafeLogging unsafeLogger = new UnsafeLogging();for (int i = 1; i <= 5; i++) {new Thread(() -> unsafeLogger.processTask("任务" + i)).start();}}
}

问题分析:当多个线程同时调用logger.info()方法记录日志时,由于java.util.logging.Logger是线程安全的,不会导致内存一致性错误。但是,这并不意味着日志信息一定会按照线程执行的顺序进行输出。例如,可能的情况是线程A和线程B几乎同时打印一条日志消息,但由于线程切换或系统调度的原因,在控制台或其他日志目的地可能会先看到线程B的日志,然后才是线程A的日志。

为了更直观地理解这种“混乱”,想象一下可能的输出结果:

开始处理任务:任务3

开始处理任务:任务1

开始处理任务:任务2

开始处理任务:任务4

开始处理任务:任务5

可以看到,在多线程并发情况下,日志的输出顺序仍然可能出现无序的情况。如果实际应用中有严格要求日志按时间顺序排列的需求,还应考虑使用带有排序功能的队列或其他方式确保日志输出的有序性。

下面是修改之后的代码:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;public class SafeLogging {private static final Logger logger = Logger.getLogger(SafeLogging.class.getName());private static final Lock logLock = new ReentrantLock();public void processTask(String taskName) {logLock.lock();try {logger.info("开始处理任务:" + taskName);simulateWork();logger.info("完成处理任务:" + taskName);} finally {logLock.unlock();}}// ... 其他代码不变public static void main(String[] args) {SafeLogging safeLogger = new SafeLogging();// 启动线程的方式与之前相同...}
}
开始处理任务:任务1
开始处理任务:任务2
开始处理任务:任务3
开始处理任务:任务4
开始处理任务:任务5

 

问题解决与分析:

在上述示例中,使用互斥锁ReentrantLock是为了确保同一时间只有一个线程能够访问和修改共享资源——日志记录。当一个线程获取到锁后,其他尝试获取锁的线程将被阻塞,直到该线程完成日志写入并释放锁。

因此,在加了互斥锁的情况下,尽管多个线程依然并发执行,但它们对日志文件或输出流的写入操作是有序进行的。这样可以保证日志记录严格按照任务开始到结束的时间顺序排列,避免了无序情况的发生。

不过,虽然使用互斥锁能保证日志的有序性,但在多线程环境下频繁获取和释放锁可能会引入额外的性能开销。在实际应用中,需要根据具体场景权衡日志的有序性和程序性能之间的关系。

http://www.xdnf.cn/news/799165.html

相关文章:

  • qvod(快播)电影批量下载器(轻松下载连续剧)
  • 【2025RAG最新进展】
  • 卡巴斯基KAV/KIS 6.0/7.0 授权许可文件永久免费更新方法
  • 计算机组中电脑无法访问,【工作组计算机无法访问】解决方法
  • Windows安装pyav报错:ERROR: Failed building wheel for av.Failed to build av. ERROR: Could not build wheel
  • 权威汇总 | 2023年交通运输工程类国际会议
  • HgzVip1.2.rar
  • 动态规划- 【气球游戏】
  • 注册表 Run、RunOnce 键值解析
  • PB常用函数
  • 国外10个最佳和最流行的酷站推荐网站
  • 电脑显示没有被指定在上运行_.dll没有被指定在windows上运行怎么办
  • backup exec 概念
  • Firebug使用详解
  • IOS 配置XCode捕获程序异常
  • HTCDesire HD(g10)完整刷机教程
  • 《暮色4:破晓(上)》暮光之城 高清蓝光BD 1080P 720P下载,附中英双字字幕!...
  • Windows XP八招简单实用技巧
  • OpenCV学习笔记(八) 边缘、线与圆的检测
  • 关于uClinux
  • PyCharm社区版支持深度学习_深度学习,大家都看哪些社区论坛?
  • asp毕业设计——基于asp+access的学生论坛设计与实现(毕业论文+程序源码)——学生论坛
  • 分享8个强大的黑客技术学习网站,让你从萌新成为大佬_黑客技术自学网站
  • 高清网站思路又出变故 原域名被篡权
  • 使用RenderControl方法实现动态加载用户控件
  • 超过响应缓冲区限制
  • Libevent源码学习笔记一:event2/event.h
  • Metro UI 界面完全解析 (转载)
  • 网页版QQ、MSN等等聊天工具web版大全
  • 优秀站长工具推荐之百度统计热力图用户体验要素