4. 日志系统实现

log.h 文件定义了一个单例模式的日志类 Log,用于记录系统日志。

单例设计模式:

主要功能

根据上述分析,这个日志类 Log 主要实现了以下功能:

1. 日志写入

该日志类提供了 write_log() 方法用于将日志内容写入文件。日志内容可以包含不同的 日志级别,如 DEBUGINFOWARNERROR,通过传入日志级别来选择日志前缀。
日志内容支持可变参数,类似于 printf() 的格式化方式,通过 va_list 来处理变长参数,方便使用者记录不同类型的信息。

1.1 异步日志写入

当日志量很大时,直接同步写入可能影响程序的响应时间。该日志类支持 异步模式,可以将日志写入信息推入 阻塞队列 中,通过后台线程异步地将日志内容写入文件。

1.2 同步日志写入

该日志类默认支持 同步模式,直接将日志内容写入文件。如果没有设置异步模式,则所有日志操作都在调用 write_log() 时直接写入文件。这种方式适用于日志量较小且对性能要求不高的场景。
在同步写入时使用了 互斥锁(std::lock_guard)确保多线程环境下对文件写入的安全性,避免多个线程同时写入文件时出现数据混乱的问题。

4. 日志文件的自动分割

为了防止单个日志文件过大,该日志类实现了 日志文件的自动分割功能

  • 当日志文件的行数超过设定的最大行数(m_split_lines)时,自动创建一个新的日志文件继续记录,避免文件过大导致的管理和读取困难。
  • 日志也会按照日期进行分割,当进入新的一天时,创建一个新的日志文件,以便后续管理和查找。

5. 日志缓冲区管理

在初始化时为日志创建了一个缓冲区(m_buf),用于格式化日志内容。写入日志前,将日志内容格式化存储到缓冲区中,然后根据同步或异步模式决定写入方式。
使用缓冲区可以提升日志写入性能,因为可以在内存中组织日志内容,再一次性写入文件,减少磁盘 I/O 操作。

6. 日志内容的格式化

日志内容被格式化为标准化的时间信息加上日志级别标签的字符串,方便后续进行日志的查看和分析。
使用 gettimeofday() 函数获取精确的时间戳(包括秒和微秒),保证日志时间的精度。

7. 后台线程管理

在异步模式下,创建了一个后台线程来从阻塞队列中获取日志并写入文件。
使用了 pthread_create() 来创建线程,并且通过 pthread_detach() 将线程设置为 分离状态,确保后台线程在完成后自动释放资源,不需要显式调用 pthread_join()

8. 日志队列管理

使用了一个 阻塞队列 (block_queue) 来保存待写入的日志内容。当异步模式开启时,日志内容被推入队列,后台线程从队列中取出日志进行写入。
如果队列满了,日志会被丢弃,并输出警告信息,提醒开发者日志队列的状态。这样可以有效防止队列无限增长,导致内存使用超标。

9. 日志刷新功能

提供了 flush() 方法,用于 强制刷新输出缓冲区,将缓存中的日志内容立即写入文件,确保在程序异常或终止时日志不会丢失。
flush() 使用互斥锁确保在多线程环境中对日志文件刷新操作的线程安全。

10. 错误处理与资源管理

在初始化过程中,针对文件打开失败、内存分配失败等情况都提供了合理的错误处理逻辑,避免程序在初始化失败后继续执行。
通过析构函数 (~Log()) 实现了对所有分配资源的清理,包括日志文件指针的关闭、内存的释放等,确保程序在退出时不会发生资源泄漏。

11. 日志丢弃与警告机制

当日志队列满时,当前日志会被丢弃。并且对丢弃的日志进行计数,并每丢弃 100 条日志输出一次警告,提醒开发者系统负载过高。
这能够帮助开发者了解系统的状态,尤其是在高负载情况下可能需要采取措施(例如增大日志队列的容量或优化日志生成逻辑)。

12. 线程安全

为了保证多线程环境下日志写入的安全性,类中的 写入日志和文件操作都被互斥锁保护。使用 std::lock_guard<locker> 来管理互斥锁,确保在多线程环境中不会出现数据竞争的问题。
通过线程安全的 localtime_r() 来替代 localtime(),保证在多线程环境下时间转换的安全性。

代码解析

这里的日志类采用了单例模式的懒汉式设计模式,即需要使用日志时才实例化唯一的一个日志类,并提供一个全局访问点来访问。

阻塞队列

Log 类中,阻塞队列是使用模板类 block_queue<string> 定义的,意味着它存放的是 string类型 的数据。

该阻塞队列 m_log_queue 用于在异步写日志时存放待写入的日志消息。当需要记录日志时,日志内容会以字符串的形放入这个阻塞队列中。后台线程会从队列中取出日志消息,然后写入日志文件。

主要功能

  1. 线程安全的队列操作:
    通过互斥锁 (locker m_mutex) 确保对队列的所有操作(如添加、移除、查看队首/队尾等)都是线程安全的。每次进行队列操作之前都需要先获取互斥锁,操作结束后再解锁。
  2. 阻塞与条件变量:
    使用条件变量 (cond m_cond) 实现生产者-消费者模式。
    ○ 当调用 push() 往队列中添加元素时,如果有消费者在等待队列不为空,则会通过条件变量通知消费者。
    ○ 当调用 pop() 从队列中取出元素时,如果队列为空,则消费者会等待直到队列中有可用元素。
  3. 循环数组实现:
    ○ 使用循环数组实现队列结构,防止频繁的内存分配和释放,最大化地提高了队列的性能。
    ○ m_back = (m_back + 1) % m_max_size; 实现了循环数组的功能,确保队列头尾位置可以循环利用。
  4. 队列状态检查:
    ○ 提供了 full()、empty()、size() 等方法,用于检查队列的当前状态,判断是否已满或为空,以及获取当前的队列大小。
  5. 超时等待功能:
    ○ 在 pop() 方法中增加了超时版本,调用者可以设定超时时间(毫秒),在指定时间内等待队列中的元素变为可用。超时后将返回失败状态,避免长时间阻塞。

为什么使用阻塞队列

  1. 异步日志

通过阻塞队列实现 异步日志写入,可以显著提高日志记录的性能。

主线程将日志内容放入队列后继续执行,后台线程在空闲时从队列中取出日志进行写入。

  1. 提高响应速度

异步日志系统可以让主线程将日志写入队列而不需要等待文件写入完成,从而降低日志写入对业务逻辑的阻塞影响,尤其是在高并发场景下。

  1. 线程安全

阻塞队列通常是线程安全的,能够确保多个线程之间安全地访问队列中的数据,避免竞争条件。

改进建议

  1. 目前使用 m_mutex.lock()m_mutex.unlock() 来进行资源的上锁和解锁
    改进: 可以考虑使用 std::lock_guard 或者 std::unique_lock,以减少手动管理锁的复杂性。

std::lock_guard是 C++ 标准库中的一种同步原语工具,主要用于管理互斥锁的生命周期以确保线程安全。

在多线程编程中,它可以自动对给定的互斥锁进行加锁操作,在std::lock_guard对象的生命周期内,保持互斥锁处于锁定状态。当std::lock_guard对象被销毁时,它会自动对互斥锁进行解锁操作,从而避免了因忘记解锁互斥锁而导致的死锁等问题

使用 std::lock_guard 时,需要引用头文件 <mutex>

std::lock_guardRAII(资源获取即初始化)模式的实现,通过在作用域结束时自动释放锁,来保证锁的安全管理。

/* 从阻塞队列中取日志写入文件 */
void *async_write_log()
{string single_log;/* 从阻塞队列中取出一个日志string,写入文件 */while (m_log_queue->pop(single_log)){std::lock_guard<locker> lock(m_mutex); /* 使用 lock_guard 自动管理互斥锁的生命周期 */fputs(single_log.c_str(), m_fp);}
}
  1. 加入异常处理,以防止在日志写入过程中出现不可预期的错误导致线程崩溃。
/* 线程入口函数,用于异步写入日志 */
static void *flush_log_thread(void *args)
{try{// 获取日志单例并执行异步写入Log::get_instance()->async_write_log();}catch (const std::exception &e){std::cerr << "Exception in flush_log_thread: " << e.what() << std::endl;}return nullptr;
}
  1. localtime() 不是线程安全的,因为它返回的是静态的 tm 结构体。如果多个线程同时调用 localtime(),返回的内容可能会发生冲突。
    改进: 对于线程安全的版本,可以使用 localtime_r()(POSIX); 检查localtime_r() 的返回值,可以防止时间转换失败时程序出现崩溃问题。
time_t t = time(NULL);                  /* 获取当前的系统时间,返回的是从 1970 年 1 月 1 日(即 Unix 纪元)到现在的秒数 */
struct tm my_tm;                        /* 包含了当前的本地时间信息 */
if (localtime_r(&t, &my_tm) == nullptr) {std::cerr << "Failed to get local time." << std::endl;return false;
}
  1. 异步写入日志时,如果阻塞队列已满,会直接进入同步写入逻辑,在高负载场景下,可能会导致主线程因为写日志而阻塞,产生性能瓶颈。
    改进: 在异步写入且阻塞队列已满的情况下,丢弃当前日志,并通过标准错误流输出警告,避免产生性能瓶颈。
/* 异步写入 && 阻塞队列未满 */
if (m_is_async && !m_log_queue->full())
{m_log_queue->push(log_str); /* 将日志字符串 log_str 推入日志队列 */
}
else
{/* 如果队列已满,选择丢弃当前日志,并增加 dropped_logs 计数器。 */static int dropped_logs = 0;if (m_is_async && m_log_queue->full()){dropped_logs++;if (dropped_logs % 100 == 0){/* 每丢弃 100 条日志时,通过标准错误流(std::cerr)输出警告,提醒队列已满 */std::cerr << "Log queue full. Dropped logs count: " << dropped_logs << std::endl;}}else{/* 同步写入日志文件 */std::lock_guard<locker> lock(m_mutex);fputs(log_str.c_str(), m_fp);}
}

这里还有进一步的改进空间,比如:

  • 扩容:当检测到日志队列 频繁满 的情况时,可以 动态增加队列大小
  • 优先级队列:如果日志级别非常重要(如错误日志),可以考虑不丢弃这类日志,而是选择丢弃低优先级的日志(如调试日志)。
  • 使用一个 优先级队列 或通过逻辑控制,保证高优先级日志不会被丢弃。
  1. 内存泄漏的潜在风险:在初始化失败的情况下,某些已经分配的内存(如 m_bufm_log_queue)没有被释放,可能会造成内存泄漏。
    改进: 在初始化失败时和析构函数中,手动删除动态分配的内存(或者直接使用智能指针)
if (m_fp == NULL)
{std::cerr << "Failed to open log file: " << log_full_name << std::endl;delete[] m_buf;if (m_is_async) delete m_log_queue;return false;               /* 初始化失败 */
}
Log::~Log()
{if (m_fp != nullptr){fflush(m_fp);fclose(m_fp);}if (m_is_async){delete m_log_queue;}delete[] m_buf;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/461151.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SQL】SQL函数

&#x1f4e2; 前言 函数 是指一段可以直接被另一段程序调用的程序或代码。主要包括了以下4中类型的函数。 字符串函数数值函数日期函数流程函数 &#x1f384; 字符串函数 ⭐ 常用函数 函数 功能 CONCAT(S1,S2,...Sn) 字符串拼接&#xff0c;将S1&#xff0c;S2&#xff0…

论文翻译 | PROMPTAGATOR : FEW-SHOT DENSE RETRIEVAL FROM 8 EXAMPLES

摘要 最近的信息检索研究主要集中在如何从一个任务&#xff08;通常有丰富的监督数据&#xff09;转移到其他各种监督有限的任务上&#xff0c;其隐含的假设是从一个任务可以泛化到所有其他任务。然而&#xff0c;这忽略了这样一个事实&#xff0c;即存在许多多样化和独特的检索…

【MySQL】深入理解隔离性

目录 一、数据库并发的场景 1. 读-读并发 2. 读-写并发 3. 写-写并发 二、多版本并发控制&#xff08; MVCC &#xff09; 2.1.MVCC的核心思想 2.2.MVCC的优势 2.3.MVCC的工作原理 2.4.MVCC的应用场景 三、理解MVCC 3.1. 3个记录隐藏字段 3.2.undo日志 4.快照的概…

目录遍历漏洞

目录遍历 目录 概念漏洞分析 加密型传递参数编码绕过目录限定绕过绕过文件后缀过滤(截断上传原理) 漏洞挖掘 访问图片文件测试时去掉文件名只访问目录路径搜索引擎谷歌关键字 pikachu目录遍历 目录遍历与任意文件下载其实差不多,但是如果目录遍历比如etc/passwd只能看不能下…

GitLab在Linux上的详细部署教程并实现远程代码管理与协作

文章目录 前言1. 下载Gitlab2. 安装Gitlab3. 启动Gitlab4. 安装cpolar5. 创建隧道配置访问地址6. 固定GitLab访问地址6.1 保留二级子域名6.2 配置二级子域名 7. 测试访问二级子域名 前言 本文主要介绍如何在Linux CentOS8 中搭建GitLab私有仓库并且结合内网穿透工具实现在公网…

LC:贪心题解

文章目录 376. 摆动序列 376. 摆动序列 题目链接&#xff1a;https://leetcode.cn/problems/wiggle-subsequence/description/ 这个题目自己首先想到的是动态规划解题&#xff0c;贪心解法真的非常妙&#xff0c;参考下面题解&#xff1a;https://leetcode.cn/problems/wiggle…

Javaee:阻塞队列和生产者消费者模型

文章目录 什么是阻塞队列java中的主要阻塞队列生产者消费者模型阻塞队列发挥的作用解耦合削峰填谷 模拟实现阻塞队列put方法take方法生产者消费者模型 什么是阻塞队列 阻塞队列是一种支持阻塞操作的队列&#xff0c;在多线程中实现通线程之间的通信协调的特殊队列 java中的主…

Redis特性和应用场景以及安装

目录 Redis特性 1.数据在内存中存储 2.可编程性 3.可拓展性 4.集群 5.高可用 6.持久化 7.主从复制 8.速度快 Redis的应用场景 1.用作数据库 2.用作缓存或保存会话 3.用作消息队列 Redis 不可以做什么 Redis的安装 Redis特性 Redis 之所以受到如此多公司的⻘睐…

如何在VMware中安全地恢复已删除的快照?

在VMware中是否可以恢复已删除的快照&#xff1f; 答案是肯定的&#xff0c;您有几种方法可以尝试恢复被删除的快照文件&#xff1a; 仅删除了快照描述符文件&#xff08;如VMname-000000#.vmdk&#xff09;&#xff1a;这种情况下&#xff0c;可以手动重新创建描述符文件&…

强化学习DQN实践(gymnasium+pytorch)

Pytorch官方教程中有强化学习教程&#xff0c;但是很多中文翻译都太老了&#xff0c;里面的代码也不能跑了 这篇blog按照官方最新教程实现&#xff0c;并加入了一些个人理解 工具 gymnasium&#xff1a;由gym升级而来&#xff0c;官方定义&#xff1a;An API standard for rei…

ubuntu22.04安装向日葵

1、下载deb安装包 进入官网下载图形版本&#xff1a;https://sunlogin.oray.com/download/linux?typepersonal 2、命令行安装 sudo chmod x 文件名.deb sudo dpkg -i 文件名.deb 3、开始报错的看这里&#xff01; 首先展示一下安装成功的效果图&#xff1a; 接下来是我安…

Vuestic 数据表格 使用demo

<template><br><div class"grid sm:grid-cols-3 gap-6 mb-6"><VaButton click"()>{for(const it in this.selectedItems){console.log(this.selectedItems);}}">参数设置</VaButton><VaButton>参数刷新</VaButt…

深入了解 美国高防 CN2 :如何提升全球化业务的网络安全与性能

美国高防 CN2 的重要性 在跨国企业和全球化业务的不断扩展下&#xff0c;对高性能和安全的网络连接需求不断增加。美国高防 CN2&#xff08;Global Internet Access&#xff09;以其卓越的跨境传输效率和强大的防护能力&#xff0c;成为许多企业关注的焦点。尤其是对电商、游戏…

NVR批量管理软件/平台EasyNVR多个NVR同时管理支持视频投放在电视墙上

在当今智能化、数字化的时代&#xff0c;视频监控已经成为各行各业不可或缺的一部分&#xff0c;无论是公共安全、交通管理、企业监控还是智慧城市建设&#xff0c;都离不开高效、稳定的视频监控系统的支持。而在这些应用场景中&#xff0c;将监控视频实时投放到大屏幕电视墙上…

新材料产业数据管理:KPaaS平台的创新驱动

近日&#xff0c;工业和信息化部、财政部、国家数据局联合印发《新材料大数据中心总体建设方案》&#xff08;以下简称《建设方案》&#xff09;&#xff0c;为新材料产业的发展注入了强大动力。该方案规划清晰&#xff0c;目标明确&#xff0c;旨在充分发挥大数据、人工智能对…

AI代币是什么?AI与Web3结合的未来方向在哪里?

近两年随着人工智能的崛起&#xff0c;AI已经渗透到制造业、电商、广告、医药等各个行业&#xff0c;加密货币领域也不例外&#xff0c;人工智能与区块链的融合&#xff0c;让我们看到了独特的数字资产 — AI加密代币。 它的流行始于2022年底&#xff0c;随着OpenAI智能聊天机…

关于springboot跨域与拦截器的问题

今天写代码的时候遇到的一个问题&#xff0c;在添加自己设置的token拦截器之后&#xff0c;报错&#xff1a; “ERROR Network Error AxiosError: Network Error at XMLHttpRequest.handleError (webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:112:14) at Axi…

基于微信小程序实现信阳毛尖茶叶商城系统设计与实现

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…

FPGA开发verilog语法基础1

文章目录 主体内容1.1 逻辑值1.2 数字进制格式1.3 数据类型1.3.1 寄存器类型1.3.2 线网类型1.3.3 参数类型1.3.4 存储器类型 参考资料 主体内容 1.1 逻辑值 1&#xff0c;逻辑0&#xff0c;表示低电平 2&#xff0c;逻辑1&#xff0c;表示高电平 3&#xff0c;逻辑X&#xff0…

Java阶段三02

第3章-第2节 一、知识点 面向接口编程、什么是spring、什么是IOC、IOC的使用、依赖注入 二、目标 了解什么是spring 理解IOC的思想和使用 了解IOC的bean的生命周期 理解什么是依赖注入 三、内容分析 重点 了解什么是spring 理解IOC的思想 掌握IOC的使用 难点 理解IO…