C++简易日志系统:打造高效、线程安全的日志记录工具

目录

引言:

1.日志的基本概念

1.1.什么是日志?

1.2.我们为什么需要日志?

2.自己实现一个简易日志

2.1.日志的等级

2.2日志的格式

2.3.获取时间的方法

2.4.日志的主体实现

参数:

代码解析:

问题:写日志的时候,为什么也要保证线程安全?

一、避免数据竞争和不一致

二、确保日志的完整性和可读性

三、防止资源冲突和死锁

四、提高系统的稳定性和可靠性

3.日志的使用

3.1.代码解析:

3.2.实际效果

引言:

今天给大家带来的是用C++语言编写的一个简易日志系统。

1.日志的基本概念

1.1.什么是日志?

在当前的数字化时代,日志分析已经成为了云安全的重要组成部分,日志文件记录了系统、应用程序和网络的各种活动,通过分析这些日志,我们可以发现潜在的安全问题,预防和应对各种安全威胁

1.2.我们为什么需要日志?

在Linux系统下,日志的作用非常关键,它们记录了系统运行过程中的各种事件和信息,对于系统管理、故障排查、安全审计、性能分析和合规性记录等方面都具有重要作用。

  1. 记录系统事件
    • 日志文件记录了系统启动、运行和关闭过程中的各种事件,包括系统错误、警告、信息性和调试信息等。
    • 这些信息有助于管理员了解系统的整体运行状况,及时发现并解决问题。
  2. 故障排查
    • 当系统或应用程序出现问题时,日志文件可以帮助管理员快速定位问题的根源。
    • 通过分析日志文件,管理员可以了解问题发生的时间、原因和影响范围,从而采取相应的措施进行修复。
  3. 安全审计
    • 安全相关的日志文件记录了用户登录、权限变更、系统访问等安全事件。
    • 这些信息对于检测和防范未授权访问或安全威胁至关重要,有助于管理员及时发现并应对潜在的安全风险。
  4. 性能监控
    • 应用程序和中间件的日志文件可以提供性能指标和资源使用情况的信息。
    • 通过分析这些信息,管理员可以优化系统和应用程序的性能,提高系统的运行效率。
  5. 合规性记录
    • 在某些行业和法规要求下,日志文件作为合规性记录的一部分,用于证明系统操作的合法性和合规性。
    • 这些记录有助于企业满足相关的法律法规要求,避免潜在的法律风险。
  6. 用户行为分析
    • 通过分析日志文件,管理员还可以了解用户在系统中的行为模式。
    • 这有助于管理员进行相应的管理和维护,确保系统的安全和稳定运行。

2.自己实现一个简易日志

2.1.日志的等级

我们使用一个枚举成员来枚举日志等级,枚举的使用使得在代码中引用日志级别时,可以使用更具描述性的名称(如 Level::ERROR),而不是直接使用数字(如 4),从而提高代码的可读性和可维护性。

下面我们来主要讲解每个枚举成员。

  • DEBUG = 1:调试级别的日志。这通常用于开发过程中,记录详细的调试信息,帮助开发者定位和解决问题。这里明确地将 DEBUG 赋值为 1,意味着枚举值是从 1 开始的。
  • INFO:信息级别的日志。用于记录程序的正常运行信息,比如程序的启动和关闭、接收到的请求等。由于枚举值默认递增,INFO 的值会自动设为 2。
  • WARNING:警告级别的日志。用于记录潜在的有害情况,但不一定立即需要采取行动。其值会自动设为 3。
  • ERROR:错误级别的日志。用于记录程序运行时发生的错误,这些错误需要被关注,但程序可能仍然能够继续运行。其值会自动设为 4。
  • FATAL:致命级别的日志。表示程序遇到了无法恢复的错误,程序将无法正常继续运行。其值会自动设为 5。
// 1、日志是有等级的
// 让枚举成员默认为整型,并且可以在创建时进行初始化
enum Level
{DEBUG = 1,INFO,WARNING,ERROR,FATAL
};

2.2日志的格式

日志等级 时间 代码所在的文件名/行数 日志的内容

并且参数是可变参数

2.3.获取时间的方法

我们可以封装一个GetTimeString的函数,方便我们使用,提高代码的可读性。

这个函数旨在获取当前时间并将其格式化为一个字符串。

代码解析:

  1. 获取当前时间:

time_t curr_time = time(nullptr);

这行代码使用 time 函数获取当前的系统时间(自1970年1月1日以来的秒数),并将其存储在 time_t 类型的变量 curr_time 中。time 函数的参数是 nullptr,表示不需要将时间存储在提供的 time_t 对象中(因为我们已经有了 curr_time 来存储它)。

2.将时间转换为本地时间

struct tm *format_time = localtime(&curr_time);

这行代码使用 localtime 函数将 curr_time(UTC时间)转换为本地时间,并将结果存储在 struct tm 类型的指针 format_time 指向的结构体中。struct tm 是一个结构体,包含了年、月、日、小时、分钟、秒等信息。

3.错误检查

if (format_time == nullptr)  

        return "None";

如果 localtime 函数返回 nullptr,这通常意味着转换失败(尽管在实际应用中,这种情况非常罕见)。在这种情况下,函数会返回一个字符串 "None"。

4.格式化时间字符串

        snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",  

             format_time->tm_year + 1900,  

             format_time->tm_mon + 1,  

             format_time->tm_mday,  

             format_time->tm_hour,  

             format_time->tm_min,  

             format_time->tm_sec);

这段代码首先定义了一个字符数组 time_buffer,用于存储格式化后的时间字符串。然后,使用 snprintf 函数将时间格式化为 "YYYY-MM-DD HH:MM:SS" 的形式,并存储在 time_buffer 中。注意,tm_year 是从1900年开始计数的,所以需要加1900来得到当前的年份;tm_mon 是从0开始计数的,所以需要加1来得到当前的月份

代码:

std::string GetTimeString()
{time_t curr_time = time(nullptr);struct tm *format_time = localtime(&curr_time);if (format_time == nullptr)return "None";char time_buffer[1024];snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",format_time->tm_year + 1900,format_time->tm_mon + 1,format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return time_buffer;
}

2.4.日志的主体实现

参数:

这个函数接受多个参数,包括文件名、行号、是否保存日志的标志、日志级别、格式化字符串以及可变数量的参数(用于格式化字符串)

代码解析:

使用了C的可变参数列表(varargs)功能来构建一个格式化字符串。

 va_list 是一个用于访问可变参数列表的类型,

 va_start 宏用于初始化这个列表,

 vsnprintf 函数用于将格式化后的字符串写入到指定的缓冲区中,

 而 va_end 宏则用于清理与可变参数列表相关的资源。

问题:写日志的时候,为什么也要保证线程安全?

写日志时保证线程安全是至关重要的,这主要基于以下几个原因:

一、避免数据竞争和不一致

在多线程环境中,多个线程可能会同时尝试写入日志。如果没有适当的同步机制,就可能出现数据竞争,导致日志记录不完整、混乱或丢失。例如,一个线程可能正在写入日志的一部分,而另一个线程突然插入其日志记录,从而造成日志内容的交错和混乱。

二、确保日志的完整性和可读性

日志是系统调试、监控和故障排查的重要工具。如果日志记录不完整或混乱,将严重影响其可读性和实用性。保证线程安全可以确保每个日志记录都是完整和独立的,从而便于后续的分析和排查。

三、防止资源冲突和死锁

在多线程写入日志时,如果没有正确的同步机制,还可能导致资源冲突和死锁问题。例如,两个线程可能同时尝试获取对日志文件的写入权限,从而造成资源冲突和阻塞。如果这种情况得不到妥善处理,甚至可能导致系统崩溃或死锁。

四、提高系统的稳定性和可靠性

保证日志记录的线程安全可以大大提高系统的稳定性和可靠性。在并发环境下,系统需要能够正确地处理和记录所有事件和状态变化。如果日志记录出现问题,将可能导致系统状态无法准确追踪和恢复,从而影响系统的整体性能和可靠性。

综上所述,写日志时保证线程安全是非常重要的。这不仅可以避免数据竞争和不一致,确保日志的完整性和可读性,还可以防止资源冲突和死锁问题,提高系统的稳定性和可靠性。因此,在多线程环境中进行日志记录时,必须采取适当的同步机制来确保线程安全。

void LogMessage(std::string filename, int line, bool ssave, int level, const char *format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t selfid = getpid();char buffer[1024];va_list arg;va_start(arg, format);vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +"[" + std::to_string(selfid) + "]" +"[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer;LockGuard lockguard(&lock);//保证日志的线程安全if (!issave){std::cout << message;}else{SaveFile(logname, message);}
}

3.日志的使用

3.1.代码解析:

do-while(0)结构:宏体被包裹在一个do { ... } while (0)结构中。

这是一种常见的技巧,用于确保宏在使用时能够正确地处理分号(;)和避免潜在的语法错误。

这种结构确保了无论宏体内部有多少语句,宏的使用都像是一个单独的语句一样。

 ##__VA_ARGS__:这是一个GCC扩展,用于处理可变数量的参数。

 ##操作符在这里的作用是,如果__VA_ARGS__为空(即没有提供额外的参数),则前面的逗号会被移除,避免语法错误。

#define LOG(level, format, ...)                                               \do                                                                        \{                                                                         \LogMessage(__FILE__, __LINE__, issave, level, format, ##__VA_ARGS__); \} while (0)

所以我们最终在使用日志的时候,第一个参数传递的就是日志的等级,接着就是我们想要打印的可变参数。

3.2.实际效果

就像上面这样使用。正常运行的效果如下图:

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

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

相关文章

5、JavaScript(五)

28.jquery&#xff1a;js库 简化版本的js&#xff0c;封装了现成功能的js代码。 jquery就是一些封装好了的现成的方法&#xff0c;供我们直接使用。 jquery能实现的js都能实现。 在使用 记得先引入jquery&#xff1a;在菜鸟教程上直接用jquery的绝对路径引入&#xff0c;jq…

Gin框架操作指南03:HTML渲染

官方文档地址&#xff08;中文&#xff09;&#xff1a;https://gin-gonic.com/zh-cn/docs/ 注&#xff1a;本教程采用工作区机制&#xff0c;所以一个项目下载了Gin框架&#xff0c;其余项目就无需重复下载&#xff0c;想了解的读者可阅读第一节&#xff1a;Gin操作指南&#…

java游戏网站源码

题目&#xff1a;java游戏网站源码 编号B22A390 主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Mysql|大数据|SSM|SpringBoot|Vue|Jsp|MYSQL等)、学习资料、JAVA源码、技术咨询 文末联系获取 感兴趣可以先收藏起来&#xff0c;以防走丢&#xff0c;有任何选题、文档编…

什么是 BloomFilter

什么是 BloomFilter 布隆过滤器&#xff08;英语&#xff1a;Bloom Filter&#xff09;是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。主要用于判断一个元素是否在一个集合中。 通常我们会遇到很多要判断一个元素是否在某个集合中的业务场景&a…

Cocos Creator导出obj文件用于后端寻路

Cocos Creator 3.8.0 用这个扩展插件 【杨宗宝】两年前写的网格工具&#xff0c;今天将它开源了。 - Creator 3.x - Cocos中文社区carlosyzy_extensions_mesh: Cocos Creator 3.x mesh插件&#xff0c;负责网格数据的导出。合并&#xff0c;拆封等一系列操作 (gitee.com) 下…

分类任务中评估模型性能的核心指标

在机器学习尤其是分类任务中&#xff0c;Accuracy&#xff08;准确率&#xff09;、Precision&#xff08;精确率&#xff09;、Recall&#xff08;召回率&#xff09;和F1 Score&#xff08;F1分数&#xff09;是评估模型性能的四个核心指标。每个指标都有其独特的含义和用途&…

排序基础方法

逆序&#xff08;inversion&#xff09; 一个序列中存在元素对&#xff0c;顺序与理想顺序相反 注意事项 算法的空间复杂度&#xff0c;即便graph本身要花费VE&#xff0c;但是DFS是V&#xff0c;只考虑自身要用的。 Selection Sort&#xff08;选择排序) 方法 不断选择最…

牛客编程初学者入门训练——BC53 判断是元音还是辅音

BC53 判断是元音还是辅音 描述 KiKi开始学习英文字母&#xff0c;BoBo老师告诉他&#xff0c;有五个字母A(a), E(e), I(i), O(o),U(u)称为元音&#xff0c;其他所有字母称为辅音&#xff0c;请帮他编写程序判断输入的字母是元音&#xff08;Vowel&#xff09;还是辅音&#x…

如何在算家云搭建Video-Infinity(视频生成)

一、模型介绍 Video-Infinity是一个先进的视频生成模型&#xff0c;使用多个 GPU 快速生成长视频&#xff0c;无需额外训练。它能够基于用户提供的文本或图片提示&#xff0c;创造出高质量、多样化的视频内容。 二、模型搭建流程 1.大模型 Video-Infinity 一键使用 基础环境…

Axure使用echarts详细教程

本次使用的axure版本为rp9,下面是效果图。 接下来是详细步骤 【步骤1】在axure上拖一个矩形进来&#xff0c;命名为myChart(这个根据实际情况来,和后面的代码对应就好) 【步骤2】 点击交互->选择加载时->选择打开链接->链接外部地址 点击fx这个符号 【步骤3】在弹…

【GIT】.cr、.gitattributes 、 .gitignore和.git各文件夹讲解介绍

在 Git 项目中&#xff0c;.cr、.gitattributes 和 .gitignore 文件分别用于不同的配置和管理功能。下面分别解释这些文件的作用和用途&#xff1a; 1. .gitignore 文件 作用&#xff1a; .gitignore 文件用于指定哪些文件或目录应该被 Git 忽略&#xff0c;不会被追踪或提交…

LabVIEW提高开发效率技巧----减少UI更新频率

在LabVIEW开发中&#xff0c;图形化用户界面&#xff08;UI&#xff09;的更新频率对程序的响应速度有着显著影响。频繁的UI更新会占用大量资源&#xff0c;导致系统性能下降。本文将详细介绍如何通过减少UI更新频率来提升LabVIEW程序的运行效率&#xff0c;从多个角度进行分析…

Leetcode 判断子序列

通过双指针来判断字符串s是否是字符串t的子序列。 算法思想&#xff1a; 双指针法&#xff1a; 我们使用两个指针i和j分别遍历字符串s和t。初始时&#xff0c;i指向s的第一个字符&#xff0c;j指向t的第一个字符。 匹配字符&#xff1a; 每次比较s[i]和t[j]&#xff1a; 如果…

大模型撬动数据新质生产力,我们重新解构了智能BI

大模型撬动数据新质生产力&#xff0c; 我们重新解构了智能BI 作者 | 曾响铃 文 | 响铃说&#xff08;xiangling0815&#xff09; “超级人工智能将在‘几千天内’降临。” 最近&#xff0c;OpenAI 公司 CEO 山姆奥特曼在社交媒体罕见发表长文&#xff0c;预言了这一点。之前…

web前端-----html5----用户注册

以改图为例 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>用户注册</title> </hea…

IC验证面试中常问知识点总结(五)附带详细回答!!!

13、phase相关 13.1 phase列表及分类 task phase: 耗费仿真时间,如run phase;给DUT施加激励、监测DUT的输出都是在这些phase中完成的。 function phase:如build_phase、connect_phase等,这些phase都不耗费仿真时间。 13.2 为什么引入动态运行phase(12个小phase)? 为了…

JNA调用c++动态库返回数据

jna学习网站 JNA Examples 1、返回String, pch.h头文件 // pch.h: 这是预编译标头文件。 // 下方列出的文件仅编译一次&#xff0c;提高了将来生成的生成性能。 // 这还将影响 IntelliSense 性能&#xff0c;包括代码完成和许多代码浏览功能。 // 但是&#xff0c;如果此处…

docker harbor

文章目录 一&#xff0c;搭建私有仓库1.1下载registry1.2在 daemon.json 中添加私有镜像仓库地址1.3重新加载重启docker1.4运行容器1.5拉取一个centos7镜像1.6给镜像加标签1.7上传镜像1.8显示私有仓库的所有镜像1.8查看私有仓库的 centos 镜像有哪些tag 二&#xff0c;什么是ho…

SVN——常见问题

基本操作 检出 提交 更新 显示日志 撤销本地修改 撤销已提交内容 恢复到指定版本 添加忽略 修改同一行 修改二进制文件

个人博客搭建 | Hexo框架

文章目录 1.Hexo安装2.创建博客3.将博客通过GitHub来部署4.更换主题 1.Hexo安装 Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown&#xff08;或其他标记语言&#xff09;解析文章&#xff0c;在几秒内&#xff0c;即可利用靓丽的主题生成静态网页。搭建Hexo首先要…