手机营销型网站建设,小程序开发者文档,做app 的模板下载网站有哪些,网站开发网络结构图EaseLog(1)基础C日志功能实现 Author: Once Day Date: 2025年2月22日 一位热衷于Linux学习和开发的菜鸟#xff0c;试图谱写一场冒险之旅#xff0c;也许终点只是一场白日梦… 漫漫长路#xff0c;有人对你微笑过嘛… 注#xff1a;本简易日志组件代码实现参考了Google … EaseLog(1)基础C日志功能实现 Author: Once Day Date: 2025年2月22日 一位热衷于Linux学习和开发的菜鸟试图谱写一场冒险之旅也许终点只是一场白日梦… 漫漫长路有人对你微笑过嘛… 注本简易日志组件代码实现参考了Google Chrome Log和moduo Log(作者陈硕)源码。 全系列文章可参考专栏: Linux实践记录_Once-Day的博客-CSDN博客 参考文章: Google Code Archive - Long-term storage for Google Code Project Hosting.emilk/loguru: A lightweight C logging libraryOnceDay11/easelog 文章目录 EaseLog(1)基础C日志功能实现1. 功能定义2. 实现分析3. 实际测试4. 源码文件 1. 功能定义
(1) 支持日志级别分类DEBUG、INFO、WARNING、ERROR可选支持FATAL。
(2) 输出信息类别时间戳、进程ID、线程ID、函数名、代码行号和日志信息(支持不定参数)。
(3) 支持多线程要求时间戳不能乱序性能方面没有特别要求。
2. 实现分析
一般常见的日志是Debug日志或者说程序错误日志典型代表是Linux环境下的syslog接口其满足上述的要求。多线程依靠锁来避免时序问题性能一般但拓展性和可读性很好以文本的形式保存和呈现。
还有一类是写到数据库的日志具有严格定义的字段和值说明对性能要求极高在多线程场景下需要通过Per-Core数据结构和无锁操作(原子指令)来优化性能但相应的代码会复杂很多。
这里实现的日志库为普通的程序Debug日志通过互斥锁来避免并发时序问题。虽然看起来互斥锁在多线程环境下性能一般但是程序Debug日志是文本类日志其文本格式化本身需要消耗较大性能日志量也不可能太大。对于性能敏感型的多线程应用一般会使用数据库日志来记录相关信息。
在实现上一般采用分层设计如下所示 这里面最核心的部分就是日志格式化处理一般提供的API函数只有一个然后通过宏包装扩展到各式各样的日志接口。
程序debug日志底层接口基本都支持自定义的回调函数然后回调函数里再写入到syslog中同时也可以直接输出到标准输出或者标准错误(STDOUT/STDERR)。程序很少会自己写日志文件像rsyslog这类标准库更适合拿来就有毕竟整理和打包大量应用的日志文件是一件复杂的事情。
Chrome和muduo里面的日志组件代码写文件的时候会上锁其他输出方式则都是无锁。这点很有意思它们在格式化时间戳时都存在时序问题也就是时间戳乱序但开发者似乎并不在意。
syslog接口输出的日志时间戳不会乱序(至少rsyslog如此)syslog日志文件里面的时间戳并不在程序Log函数中格式化而是rsyslogd进程收集到所有日志消息后统一格式化。
本日志库实现要求中需要在程序Log函数里格式化时间戳这意味着在格式化日志时就需要上锁性能会存在一些影响。这种实现方式比较简单如下所示: 这个日志组件实现的问题在于锁的粒度太大了并发线程较多的情况下debug日志会互相堵塞拖慢程序执行。一种可行的优化方式是通过多生产者单消费者的无锁环形队列配合互斥锁实现更小的锁粒度如下: 通过无锁队列可以将普通参数信息的格式化剥离出来但是所有线程仍然会去抢锁写入日志正常情况是不同线程轮流负责日志写入串行化写入可以保证时间戳获取点和写入点的顺序一致从而避免乱序。
想再提高性能最好的方式是异步日志(上面有一定异步化但不够彻底)直接使用单独的日志线程这个实现起来更加简单而且无需互斥锁直接通过无锁队列实现。
本日志组件最终实现两种模式
低并发度下采取上述的互斥锁方法这样节省线程资源性能相对也会更好(减少线程切换)。高并发度下采取单独日志线程的方法优先保证业务的并发处理能力避免日志堵塞全局效果更优。
3. 实际测试
目前只实现了基础功能低并发度下采取互斥锁更上层的复杂日志宏API暂未实现。
测试方面通过创建三个线程来模拟并发日志写入为了更容易触发乱序引入随机Sleep操作在日志格式化和实际写入操作之间如下所示:
// 用于构造并发时序, 随机等待 10-50ms
void RandomSleep()
{if (g_log_enable_random_sleep) {std::this_thread::sleep_for(std::chrono::milliseconds(10 rand() % 40));}
}// 创建的线程重复20次输出日志
std::thread t1([]() {pthread_setname_np(pthread_self(), thread1);for (int i 0; i REPEAT_TIMES; i) {LOG(INFO) log message test: i;}
});// 没有锁保护下的直接日志写入
if (ShouldLogToStderr(severity_)) {// 生成时间戳std::string timestamp;LogSyslogPrefixTimestamp(log_settings, timestamp);// 引入随机延迟RandomSleep();// 写入日志信息WriteToFd(STDERR_FILENO, timestamp.data(), timestamp.size());WriteToFd(STDERR_FILENO, str_newline.data(), str_newline.size());
}运行后可以在输出日志里发现明显的乱序情况如下: 引入互斥锁后可以避免乱序:
if (ShouldLogToStderr(severity_)) {std::lock_guard std::mutex lock(g_log_mutex);// 生成时间戳std::string timestamp;LogSyslogPrefixTimestamp(log_settings, timestamp);RandomSleep();// 写入日志信息WriteToFd(STDERR_FILENO, timestamp.data(), timestamp.size());WriteToFd(STDERR_FILENO, str_newline.data(), str_newline.size());
}运行截图如下: 但对性能影响较大整体运行时间较没有上锁增加了2倍因为线程需要互相等待对方sleep结束才能拿到锁。
不过实际运行的程序很少会出现这么久的锁内延迟时间这毕竟只是一个测试模拟情况。
下一步准备实现第二种模式高并发度下采取单独日志线程。
4. 源码文件
代码已经开源请在Github上查看OnceDay11/easelog 下一步准备实现第二种模式高并发度下采取单独日志线程。 Once Day 也信美人终作土不堪幽梦太匆匆...... 如果这篇文章为您带来了帮助或启发不妨点个赞和关注再加上一个小小的收藏⭐ (◕‿◕)感谢您的阅读与支持~~~