【Linux】简易日志工具项目

在这里插入图片描述

有些鸟儿是不应该被关在笼子里的,
因为他们的羽毛太丰润了。
当他们飞走,你会由衷地庆贺他获得自由。
--- 肖申克的救赎》---

从零开始构建简易日志系统

  • 1 日志
    • 1.1 什么是日志
    • 1.2 日志的意义
    • 1.3 为什么要构建自己的日志工具
  • 2 构建自己的日志工具
    • 2.1 框架搭建
    • 2.2 LogMessage函数
    • 2.3 线程安全优化
    • 2.4 宏定义优化
  • 3 总结

1 日志

日志(Log)是记录软件运行过程中发生的事件、状态变化和错误信息的记录文件。在软件开发和系统运维中,日志起着至关重要的作用

1.1 什么是日志

定义:日志是一种按时间顺序排列的记录,用于记录软件在运行过程中产生的各种信息,包括操作行为、系统状态、错误警告等。就像日记一样,程序每进行一个任务操作都要留下信息,方便他人查看。

日志通常包含以下几种信息:

  • 时间戳:记录事件发生的时间。
  • 日志级别:表示日志信息的严重程度,如DEBUG、INFO、WARNING、ERROR、FATAL。
  • 来源:指出产生日志的文件行数(可以快速找到对应模块)。
  • 消息内容:具体描述事件或错误信息。

目前,在实际开发中我们有非常丰富的日志库可以选择:

  1. spdlog是一个非常快速、支持并发的C++日志库,它提供了易于使用的接口和丰富的特性,包括异步日志记录、多线程支持、格式化输出等。官方网站在这里
  2. Glog是由Google开发的C++日志库,它提供了基于C++风格的日志API,支持条件日志记录、日志旋转和严重错误时的信号处理。官方网站在这里

1.2 日志的意义

日志在开发中主要有以下一些作用:

  1. 追踪问题:通过日志,开发者可以了解软件在运行过程中的状态,快速定位问题所在。
  2. 分析原因:日志记录了软件运行过程中的详细信息,有助于分析问题产生的原因。
  3. 优化性能:通过分析日志,可以发现软件的性能瓶颈,从而进行优化。
  4. 安全审计:日志记录了软件的操作行为,有助于审计和监控系统的安全性。
  5. 数据挖掘:在某些场景下,日志数据可以用于数据挖掘,为业务分析和决策提供支持。

同样日志在项目开发中至关重要,从开发调试阶段 - 测试阶段 - 部署阶段 - 运行维护阶段…都具有相当重要的作用!并且一个优雅的日志系统是可以让开发者赏心悦目的进行项目开发,优雅!

总之,日志在项目开发中具有举足轻重的作用。一个完善的日志系统可以提高软件的可靠性、稳定性和可维护性,为软件开发和运维提供有力支持。

1.3 为什么要构建自己的日志工具

从学习的角度出发,开发一个自己的简易日志工具可以带来以下好处:

  1. 深入理解日志原理
    通过自己实现日志工具,可以更深入地理解日志记录的基本原理,包括日志的格式化、写入、级别控制等。对以后使用第三方日志库有很大帮助
  2. 掌握核心编程技能
    在开发过程中,可以锻炼和提升核心编程技能,如文件操作、字符串处理、时间管理、异常处理等。这是一笔很重要的经验!
  3. 模块化和抽象思维
    日志工具的开发需要良好的模块化和抽象思维能力,这有助于在未来的项目中更好地组织代码。
  4. 错误处理和调试
    在开发过程中,不可避免地会遇到错误和调试问题,这提供了实践错误处理和调试技巧的机会。
  5. 理解日志在系统中的作用
    通过实现日志工具,可以更深刻地理解日志在系统监控、问题排查、性能分析等方面的重要性。
  6. 增强项目经验
    开发日志工具可以作为一个独立的项目经验,有助于在简历上展示实际编程能力和解决问题的能力。

总之,开发一个自己的简易日志工具是一个综合性的学习过程,下面我们来开发一个自己的日志工具!

2 构建自己的日志工具

2.1 框架搭建

设计一个日志系统首先要明确我们希望打印出什么格式的日志信息:
在这里插入图片描述
我们想要呈现出上面这样的日志信息,就需要设置一个信息类logmessage来储存信息,类内需要这些信息:

  • int _level : 日志等级,通过枚举变量来快速通过数字对应等级
  • pid_t _id : 进程ID
  • std::string _filename : 文件名
  • int _filenumber : 行号
  • std::string _curr_time : 当前时间
  • std::string _message_info : 日志信息

然后我们在设计一个初步的日志类Log,我们希望的是通过:

Log lg;
lg.LogMessage(__FILE__ , __LINE__ , DEBUG , "%d %s %f" , 1 , "你好" , 3.14);

这样的上层调用来实现日志信息的打印,所以Log内部不需要设置信息类logmessage。只需要在LogMessage函数中设置一个临时变量,保证每次调用都会通过这个临时来储存信息。为了可以区分是向显示器打印还是向文件打印,我们添加一个成员变量_type来方便后期确认打印方式!

#pragma once#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <aio.h>
#include <stdarg.h>
#include <fstream>
#include <cstring>//打印方式
#define SCREEN_TYPE 1
#define FILE_TYPE 2const std::string file = "log.txt";// 等级划分
enum
{DEBUG = 1,INFO,WARNING,ERROR,FATAL,
};// 信息类
class logmessage
{public:std::string _level;        // 日志信息等级int _id;                   // 进程IDstd::string _curr_time;    // 当前时间std::string _filename;     // 文件名int _filenumber;           // 行号std::string _message_info; // 日志信息
};// 日志类
class Log
{
private:std::string LevelToString(int level){switch (level){case 1:return "DEBUG";case 2:return "INFO";case 3:return "WARNING";case 4:return "ERROR";case 5:return "FATAL";default:return "UNKNOW";}}public:// 空的构造函数Log() : _type(SCREEN_TYPE){}// 处理数据void LogMessage(std::string filename, int filenumber, int level, const char *format, ...){      }~Log(){}private:int _type;
};

接下来我们来处理最重要的LogMessage函数。

2.2 LogMessage函数

LogMessage函数中我们需要依次处理传入的信息,并储存在logmessage类中。函数一定要支持可变参数,才能更好的支持外部调用的功能性!

 logMessage(std::string filename , int level , int filenumber , const char* format , ...) 

接下来我们进行信息类的处理,依次处理 日志等级、进程ID、文件名、行号、当前时间、日志信息:

  1. _level :通过公共方法LevelToString()将 等级 转换为 字符串:简单的通过switch语句实现
     std::string LevelToString(int level)
    {switch (level){case 1:return "DEBUG";case 2:return "INFO";case 3:return "WARNING";case 4:return "ERROR";case 5:return "FATAL";default:return "UNKNOW";}
    }
    
  2. 获取 pid + 文件名 + 行号:这个很简单!
    // 处理文件名 行号lm._filename = filename;lm._filenumber = filenumber;// 获取进程IDlm._id = getpid();
    
  3. _curr_time: 获取时间 time() ,再通过localtime()得到当前时间的结构体,然后通过方法 TimeToString() 转换为字符串就可以了,需要注意的是,获取的时间结构体内的时间和原本时间有出入,需要进行一些处理:
    std::string TimeToString()
    {time_t now = time(nullptr);struct tm *t = localtime(&now);int year = t->tm_year;int mon = t->tm_mon;int day = t->tm_mday;int hour = t->tm_hour;int min = t->tm_min;int sec = t->tm_sec;char buffer[1024];snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",year + 1900,mon + 1,day,hour,min,sec);return buffer;
    }
    
  4. _message_info:日志信息是一段带有可变参数的字符串,使用vsnprintf可以简单解决。首先进行va_list 的初始化,然后 vsnprintf() 可以直接将可变参数中进行提取 ,(va_start标定开始位置 , va_end结束)
    // 日志信息char buffer[1024];va_list ap;va_start(ap, format);vsnprintf(buffer, sizeof(buffer), format, ap);va_end(ap);lm._message_info = buffer;
    

这样最重要的数据转换我们就完成了,接下来就是打印的问题了,我们设计一个FlushLog刷新日志信息的函数,在里面进行打印的处理,根据打印格式打印对应信息:

// 刷新数据void FlushLog(const logmessage &lg){switch (_type){case 1:FlushToScreen(lg);break;case 2:FlushToFile(lg);break;}}

打印方式有两种:

  1. 向显示器打印:这个很好写,直接使用printf打印特定格式就好
  2. 向文件打印:使用文件流操作fstream快速进行写入处理(非常好用!)
 void FlushToScreen(const logmessage &lg){printf("[%s][%d][%s][%d][%s] %s \n",lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._curr_time.c_str(),lg._message_info.c_str());}void FlushToFile(const logmessage &lg){std::fstream out(_logfile.c_str(), std::ios_base::out | std::ios_base::app );char buffer[1024];snprintf(buffer, sizeof(buffer), "[%s][%d][%s][%d][%s] %s \n",lg._level.c_str(),lg._id,lg._filename.c_str(),lg._filenumber,lg._curr_time.c_str(),lg._message_info.c_str());out.write(buffer, strlen(buffer));out.close();}

现在我们 运行测试一下:
在这里插入图片描述
可以看到我们的日志工具已经可以规范的打印消息了!非常优雅!

2.3 线程安全优化

单线程的情况,我们的日志工具肯定是没有问题的!如果是多线程呢?我们来看看我们有哪些是全局的变量需要互斥锁保护:只有显示器打印和文件打印是对全局的资源进行操作,所以我们只需要对FlushLog中进行线程保护即可!

//全局锁
pthread_mutex_t _mtx = PTHREAD_MUTEX_INITIALIZER;

为了更加优雅的进行操作,我们使用之前编写的RAII规则的锁守卫LockGuard进行保护:

// 刷新数据void FlushLog(const logmessage &lg){LockGuard lock(&_mtx);switch (_type){case 1:FlushToScreen(lg);break;case 2:FlushToFile(lg);break;}}

这样我们的日志类就可以保证多线程下的安全运行了!

2.4 宏定义优化

上面的代码已经可以满足日志的书写的工作了,但是如果还想要更加的优雅的操作,我们可以使用宏定义来免去书写文件名和行号的操作,并且不在需要手动创建类,可以直接调用宏定义来进行日志的书写!

Log lg;#define Log(Level, Format, ...)                                \do                                                         \{                                                          \lg.LogMessage(__FILE__, __LINE__, Level , Format, ##__VA_ARGS__); \} while (0)#define EnableScreen()          \do                          \{                           \lg.Enable(SCREEN_TYPE); \} while (0)#define EnableFile()          \do                        \{                         \lg.Enable(FILE_TYPE); \} while (0)

宏定义会在调用位置直接进行打开,所以__FILE__, __LINE__,就直接可以传入文件和行数了,不在需要我们书写:

int main()
{int cnt = 5;while (cnt--){Log(DEBUG, "%d %s %f", cnt, "你好", 3.1415);sleep(1);}EnableFile();cnt = 5;while (cnt--){Log(DEBUG, "%d %s %f", cnt, "你好", 3.1415);sleep(1);}return 0;
}

这样是在是优雅:
在这里插入图片描述
这样我们就完成了日志工具项目的构建!!!

3 总结

项目技术栈:

编程语言:C++
操作系统相关:POSIX线程(pthread)、文件操作、时间处理
编程技巧:面向对象编程、设计模式(单例模式、工厂模式)、RAII(资源获取即初始化)

编程技巧与学习点:

  1. 日志原理与设计 :文章深入探讨了日志的定义、组成和重要性,以及如何设计一个日志系统。
  2. 核心编程技能:通过实现日志工具,锻炼了文件流操作、字符串处理、时间管理等技能。
  3. 错误处理与调试:在开发过程中,实践了错误处理和调试技巧,特别是在多线程环境下的线程安全问题。
  4. 线程安全 :通过引入互斥锁(mutex)和锁守卫(LockGuard),确保了日志工具在多线程环境下的安全使用。
  5. 宏定义优化 :使用宏定义简化了日志记录的代码,提高了代码的简洁性和易用性。

我们通过构建一个简易的日志工具,展示了从需求分析、系统设计到具体实现的完整过程。介绍了如何使用C++构建一个具有基本功能的日志系统,包括日志消息的格式化、文件和屏幕输出、日志级别的控制等。实践了日志工具的线程安全优化,确保了其在多线程环境下的稳定性。

通过这个项目,可以学习到如何从零开始构建一个日志系统,掌握相关的编程技能和设计理念,同时也能够加深对日志在软件开发中作用的理解。

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

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

相关文章

gin快速入门

gin 项目地址晓智科技晓智科技晓智文档晓智文档文档源码文档源码 快速体验 func HandlerPong(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "pong",}) }func main() {r : gin.Default()r.GET("/ping", HandlerPong)_ r.Run(&qu…

Windows电脑自建我的世界MC服务器并与好友远程联机游戏教程

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

美股收涨,半导体板块领涨;苹果iPhone出货预测上调

市场概况 在昨夜的交易中&#xff0c;美股三大股指全线收涨。道琼斯工业平均指数上涨1.39%&#xff0c;纳斯达克综合指数上涨2.34%&#xff0c;标准普尔500指数上涨1.61%。值得注意的是&#xff0c;英伟达股票涨幅近4%&#xff0c;推动了科技股的整体表现。美国十年期国债收益…

vue3使用i18n实现国际化

安装vue-i18n npm install vue-i18n创建一个ts文件用于存储各种翻译 globalLang.ts的内容如下&#xff1a; export default {"cn": {},"en": {},"de": {},"es": {},"fr": {},"id": {},"it": {},&quo…

【Linux操作系统】进程间通信(1)

目录 一、认识进程间通信二、匿名管道三、命名管道 一、认识进程间通信 进程间不能直接传递数据&#xff0c;因为进程具有独立性&#xff0c;直接传递会破坏进程的独立性。 进程间通信是什么&#xff1f; 一个进程把自己的数据交给另一个进程。 为什么要有进程间通信&#xf…

DAG计算框架:实现业务编排

文章目录 DAG如何实现DAG计算框架Node的实现Engine的实现Graph的实现具体某个节点如何使用 在工作几年之后&#xff0c;大部分人如果还在继续做着 CRUD的简单重复工作&#xff0c;即使领导不提出对你更高的期望&#xff0c;自身也会感到焦虑吧。学如逆水行舟不进则退&#xff…

怎么整合spring security和JWT

什么是spring security spring security是一个安全框架,它里面有过滤器链,可以多次过滤,其实他可以给前端的cookie传入一个jsessionid,都可以不使用jwt也能完成校验 第一步:导入依赖 <!-- springboot security --> <dependency><groupId>org.springframew…

git错误fatal: Unpack error, check server log

git错误fatal: Unpack error, check server log fatal: Unpack error, check server log error: remote unpack failed: error Missing tree xxxxxxxxxxxxxxxxxx 先执行 git fetch 命令&#xff0c;再push。 git拉取远程所有分支/添加远程仓库_git pull所有分支代码-CSDN博客…

OpenGL-ES 学习(8) ---- FBO

目录 FBO OverViewFBO 优点使用FBO的步骤 FBO OverView FBO(FrameBuffer Object) 指的是帧缓冲对象&#xff0c;实际上是一个可以添加缓冲区容器&#xff0c;可以为其添加纹理或者渲染缓冲区对象(RBO) FBO(FrameBuffer Object) 本身不能用于渲染&#xff0c;只有添加了纹理或者…

【C++ Primer Plus习题】3.6

问题: 解答: #include <iostream> using namespace std;int main() {float miles 0;float gallons 0;float gallon 0;cout << "请输入驱车里程(单位为英里):";cin >> miles;cout << "请输入使用的汽油量(单位为加仑):";cin &g…

【Three.js基础学习】19.Custom models with Blender

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 blender模型资源:【blender】一个汉堡包-CSDN博客 一、代码 import ./style.css import * as THREE from three import { OrbitControls } from three/examples/jsm/co…

CentOS服务器三级等保加固

1.密码周期: vim /etc/login.defs max_days:90 mindays:2 minlen:8 warnage:72.密码复杂度: vim /etc/pam.d/system-auth &#xff1a; password requisite pam_cracklib.so retry3 difok3 minlen8 lcredit-1 dcredit-1 ucredit-1 ocredit-1 【Ubuntu系统->vim /etc/pam.d/c…

【STM32】C语言基础补充

学习过程中发现自己好些需要用到的C语言语法、特征都不太熟练了&#xff0c;特意记录一下&#xff0c;免得忘记了&#xff0c;以后遇到了新的也会继续更新 目录 1 全局变量 2 结构体 3 静态变量 4 memset()函数 5 使用8位的存储器存16位的数 1 全局变量…

C++学习笔记----4、用C++进行程序设计(四)---- 复合关系与继承关系之间的细线

在现实世界只是很容易区分对象之间是复合关系还是继承关系。没有人会说桔子有一个水果--而只能是桔子是一种水果。但是&#xff0c;在代码中&#xff0c;有时候就不是那么清晰了。 设想有一个代表关联数组的假想类&#xff0c;将一个键影射到一个值的数据结构。例如&#xff0c…

【Canvas与艺术】环形橄榄枝纹饰

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>环形橄榄枝(draft2)</title><style type"text/css&quo…

MySql 高阶二(SQL 性能分析)

SQL 性能分析&#xff1a; 查看当前数据库的 增删改查的使用情况 show global status like Com_______;慢查询日志&#xff1a; -- 查看状态 show variables like slow_query_log目前是开启状态。如何开启&#xff0c;编辑my.cnf 文件 添加下面的语句&#xff0c;编辑完成后…

Ansible远程自动化运维

目录 概念 安装ansible modules模块和语法 命令行语法 模块 1. command 基础模块 常用的参数 2. shell模块 3. cron定时任务模块 4. user用户管理模块 参数 5. copy复制模块 参数 6. file模块 设置文件属性 参数 实验&#xff1a;批量创建目录 7…

设备实时数据采集:开启制造业智能化、自动化的新篇章

传统制造业在进行生产过程中&#xff0c;会涉及到设备实时数据采集需求&#xff0c;这些数据对于监控生产流程、优化生产效率、保证产品质量以及降低成本等方面至关重要。以下是一些常见的数据采集需求&#xff1a; 1.生产数据&#xff1a;包括生产数量、生产批次、生产速度等&…

如何禁止编辑PDF文件?推荐两种方法!

在日常工作中&#xff0c;我们经常会遇到需要分享重要的PDF文件的情况&#xff0c;但又希望文件内容不被随意更改。为此&#xff0c;设置PDF文件的修改限制是一个非常有效的措施。今天分享两种常见的禁止修改PDF的方法&#xff0c;一起来看看如何设置。 方法一&#xff1a;使用…

FPGA开发——DS18B20读取温度并且在数码管上显示

一、简介 在上一篇文章中我们对于DS18B20的相关理论进行了详细的解释&#xff0c;同时也对怎样使用DS18B20进行了一个简单的叙述。在这篇文章我们通过工程来实现DS18B20的温度读取并且实现在数码管伤显示。 1、基本实现思路 根据不同时刻的操作&#xff0c;我们可以使用一个状…