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

【Linux】模拟实现一个简单的日志系统

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 一、日志的概念
  • 二、储备知识之C式风格的可变参数
  • 三、获取时间
  • 四、实现打印日志函数
  • 五、封装成类并实现将日志信息打印到文件里(完整代码)

一、日志的概念

在编程中,日志是指程序在运行时生成的记录信息和生成对应记录的时间。这些记录信息可以包括程序的状态、错误消息、警告、调试信息等。通过日志,程序员可以更轻松地跟踪程序的执行过程、诊断问题并监视系统的运行情况。

常见的日志等级包括:

  1. info:常规信息

  2. warning:指示可能会引起问题的情况,但程序仍然可以继续执行。

  3. error:指示程序发生了错误,可能需要立即处理,但程序仍然能够继续执行。

  4. fatal:指示程序出现了致命问题,可能导致程序无法继续执行。

  5. debug:提供关于程序详细执行过程的信息,通常用于调试目的。

二、储备知识之C式风格的可变参数

在C语言中,可变参数函数是一种允许函数接受不定数量参数的机制。比方说printf就可以接受不定数量参数。

#include <stdio.h>
int printf(const char *format, ...);

实现这种功能需要使用stdarg.h头文件提供的一些宏。这些宏包括:

  • va_list:可以理解为一个用于存储所以可变参数的容器。
  • va_start:是一个宏函数,它的作用是初始化一个va_list对象,使其指向可变参数列表的第一个参数。以下是它的原型
void va_start(va_list ap, last_arg);

其中

  • 第一个参数是类型为va_list的对象
  • 第二个参数是一个固定参数,即可变参数列表之前的那个参数。因此,可变参数之前必须要有至少一个具体的参数。
  • va_arg:是一个宏函数,访问可变参数列表中的下一个参数,它的具体实现会有指针的自增操作。
type va_arg(va_list ap, type);
  • 第一个参数是类型为va_list的对象。
  • 第二个参数是你希望从可变参数列表中获取的参数类型。
  • va_end:清理va_list对象。

比方说定义一个可变参数函数,计算所有参数的和,用于演示如何编写和使用可变参数函数:

请添加图片描述

【程序结果】

请添加图片描述

三、获取时间

日志中包含时间是非常重要的,因为它可以帮助程序员准确地定位和跟踪问题。获取时间的方法有很多种,如time函数、clock 函数、gettimeofday 函数、strftime 函数等。

这里我以localtime函数为例,以上函数的具体用法大家可以自行搜索。

#include <time.h>
struct tm *localtime(const time_t *timep);

localtime函数可以将time_t类型的时间戳转换为struct tm类型,而struct tm类型有如下成员变量

请添加图片描述

需要注意的是:在C语言的struct tm结构体中,年份(tm_year)的起始值为1900,月份(tm_mon)的起始值为0。这意味着,如果你想要获取实际的年份和月份,需要对tm_yeartm_mon进行一些调整。

  • tm_year表示从1900年开始经过的年数。因此,要获取实际的年份,需要将其加上1900,即tm_year + 1900

  • tm_mon表示月份,范围从011,其中0表示一月,1表示二月,以此类推。因此,要获取实际的月份,需要将其加上1,即tm_mon + 1

以下是代码示例:

请添加图片描述

【程序结果】

请添加图片描述

四、实现打印日志函数

有了以上的知识,我们就可以开始实现打印日志函数了。

首先规定日志的格式:[时间] [等级] [用户自定义内容]

代码如下(含详细注释)

请添加图片描述

【函数解析】

  1. snprintf函数:用于将格式化的数据写入字符数组中。它的声明通常如下:
int snprintf(char *str, size_t size, const char *format, ...);
  • str: 指向存储输出的字符数组。
  • size: 字符数组的大小。
  • format: 传递给格式化字符串的数据格式,与printf类似。
  • ...: 可变数量的参数,这些参数根据格式字符串进行格式化。
  1. vsnprintf函数:与snprintf类似,但它使用va_list类型的参数列表。这对于在函数内部处理可变参数特别有用。其声明通常如下:
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
  • str: 指向存储输出的字符数组。
  • size: 字符数组的大小
  • format: 传递给格式化字符串的数据格式,与printf类似。
  • ap: va_list类型的参数列表,由va_startva_argva_end宏管理。

【复制即可用】

#pragma once
#include <iostream>
#include <string>
#include <stdarg.h>// 将日志等级用整数表示
#define Info 0    // 常规
#define Debug 1   // 调试
#define Warning 2 // 警告
#define Error 3   // 错误
#define Fatal 4   // 致命// 因为我们这里的日志等级是用一个整数表示的
// 而最后日志打印时需要有具体是什么日志等级
// 因此我们可以封装一个函数将日志等级转化为字符串
std::string levelToString(int level)
{switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}
}// level - 日志等级
// format - 格式化字符串的数据格式。类似于printf前半部分
// ... - 表示可变参数
void logmessage(int level, const char *format, ...)
{// ====== 默认部分:日志等级 + 时间 =========time_t _timestamp = time(NULL); // time函数会返回时间戳// 再将time_t类型转化为struct tm类型struct tm *_tm = localtime(&_timestamp);char defaultPart[1024]; // 默认部分// 打印的日志格式:[日志等级][时间]snprintf(defaultPart, sizeof(defaultPart), "[%s][%d-%d-%d:%d:%d:%d]",levelToString(level).c_str(), _tm->tm_year + 1900, _tm->tm_mon + 1, _tm->tm_mday,_tm->tm_hour, _tm->tm_min, _tm->tm_sec);// ====== 自定义部分:format内容 + 可变参数... =========char self[1024];va_list s;va_start(s, format);vsnprintf(self, sizeof(self), format, s);va_end(s);// ===== 将默认部分和自定义部分整合 =====char logtxt[2048];snprintf(logtxt, sizeof(logtxt), "%s %s\n", defaultPart, self);// ==== 信息全部在logtxt中,你可以打印出来,或者写到一个文件里 ======printf("%s", logtxt);
}

五、封装成类并实现将日志信息打印到文件里(完整代码)

#pragma once
#include <iostream>
#include <string>
#include <stdarg.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>// 将日志等级用整数表示
#define Info 0    // 常规
#define Debug 1   // 调试
#define Warning 2 // 警告
#define Error 3   // 错误
#define Fatal 4   // 致命#define Screen 1
#define OneFile 2
#define ClassFile 3class log
{
public:// 写一个默认构造函数, 默认打印是向屏幕打印log(){printMethod = Screen;logdir = "./logdir/"; // 你需要保证当前路径下有目录名为logdir}// 让用户选择打印方式void Enable(int method){printMethod = method;}// 因为我们这里的日志等级是用一个整数表示的// 而最后日志打印时需要有具体是什么日志等级// 因此我们可以封装一个函数将日志等级转化为字符串std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}// level - 日志等级// format - 格式化字符串的数据格式。类似于printf前半部分// ... - 表示可变参数void logmessage(int level, const char *format, ...){// ====== 默认部分:日志等级 + 时间 =========time_t _timestamp = time(NULL); // time函数会返回时间戳// 再将time_t类型转化为struct tm类型struct tm *_tm = localtime(&_timestamp);char defaultPart[1024]; // 默认部分// 打印的日志格式:[日志等级][时间]snprintf(defaultPart, sizeof(defaultPart), "[%s][%d-%d-%d:%d:%d:%d]",levelToString(level).c_str(), _tm->tm_year + 1900, _tm->tm_mon + 1, _tm->tm_mday,_tm->tm_hour, _tm->tm_min, _tm->tm_sec);// ====== 自定义部分:format内容 + 可变参数... =========char self[1024];va_list s;va_start(s, format);vsnprintf(self, sizeof(self), format, s);va_end(s);// ===== 将默认部分和自定义部分整合 =====char logtxt[2048];snprintf(logtxt, sizeof(logtxt), "%s %s\n", defaultPart, self);// ==== 信息全部在logtxt中,你可以打印出来,或者写到一个文件里 ======// printf("%s", logtxt); // 直接打印printLog(level, logtxt);}// 封装打印日志文件的方法:1. 向屏幕打印 2. 向文件打印 3. 分类打印void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case OneFile:printOneFile("log.txt", logtxt);break;case ClassFile:printClassFile(level, logtxt);break;default:break;}}// 向一个文件写void printOneFile(const std::string filename, const std::string &logtxt){std::string _filename = logdir + filename; // ./logdir/log.txtint fd = open(_filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd < 0){return;}write(fd, logtxt.c_str(), logtxt.size());close(fd);}// 文件分类写。比如Info信息放在一个文件中,Errno放在一个文件中...void printClassFile(int level, const std::string &logtxt){std::string filename = "log.txt";filename += '.';filename += levelToString(level);printOneFile(filename, logtxt);}private:int printMethod;std::string logdir; // 日志文件存放目录
};

相关文章:

【Linux】模拟实现一个简单的日志系统

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…...

MongoDB 多层级查询

多层级查询 注意&#xff1a;要注意代码顺序 查询层级数据代码放前面&#xff0c;查询条件放后面 if (StringUtils.isBlank(params.getDocType())) {params.setDocType(DOC_TDCTYPE);}String docName mapper.findByDocInfo(params.getDocType());List<ExpertApprovalOpin…...

grpc代理服务的实现(一)

最近公司需要无感知基于服务代号来实现通信, 并监控和管理通信连接&#xff0c;目前公司使用的是如下的逻辑(当然逻辑简化了&#xff0c;但是思想不变) 目录 简单的原理图代理服务的实现创建 tls tcp 服务, 用于grpc client 和 grpc service 通信保存 与 代理服务建立的 grpc …...

FastAPI系列 4 -路由管理APIRouter

FastAPI系列 -路由管理APIRouter 文章目录 FastAPI系列 -路由管理APIRouter一、前言二、APIRouter使用示例1、功能拆分2、users、books模块开发3、FastAPI主体 三、运行结果 一、前言 未来的py开发者请上座&#xff0c;在使用python做为后端开发一个应用程序或 Web API&#x…...

数据驱动制造:EMQX ECP 指标监测功能增强生产透明度

迈向未来的工业生产&#xff0c;需要的不仅是自动化&#xff0c;更是智能化。如果工业企业的管理者能够实时监测每一生产环节的设备运行状态&#xff0c;每一数据点位情况&#xff0c;洞察和优化每一步生产流程&#xff0c;他们将能够做出更精准的决策&#xff0c;提高生产效率…...

一行代码实现鼠标横向滚动

&#x1f9d1;‍&#x1f4bb; 写在开头 点赞 收藏 学会&#x1f923;&#x1f923;&#x1f923; 在项目中我们可能会遇到当鼠标在某个区域内&#xff0c;我们希望滚动鼠标里面的内容可以横向滚动&#xff1b; 比如我们一些常见的后台状态栏&#xff1a; 那这种该怎么写&…...

Flink集群架构

在上一章节我们对flink有了一个基本的了解。从它的应用的场景以及它的一些基本的一些核心的一些概念。从本章节开始&#xff0c;我们对flink从它的一个集群的一个架构以及它的一个部署模式着手&#xff0c;去了解flink如何去部署在不同的这样的一个集群的一些资源管理器上面&am…...

计算机网络(6) UDP协议

一.UDP数据报格式 UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是一种简单的传输层协议&#xff0c;与TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;相比&#xff0c;UDP提供一种无连接、不可靠的数据传…...

单片机(STM32)与上位机传输浮点数

目录 单片机(STM32)与上位机传输数据的方法1. 传输整形数据2. 传输浮点数据3. 如何打包与解包 单片机(STM32)与上位机传输数据的方法 在进行单片机程序的开发时&#xff0c;常常需要与其他设备进行通信。一种情况是与其他电路板通信&#xff0c;比如STM32主机与STM32从机通信&…...

50etf期权交易规则杠杆怎么计算?

今天带你了解50etf期权交易规则杠杆怎么计算&#xff1f;近年来&#xff0c;期权交易在股票市场中变得愈发流行&#xff0c;其中50ETF期权备受关注。作为一种金融衍生品&#xff0c;50ETF期权为投资者提供了更灵活的投资方式和更多的策略选择。 50etf期权交易规则杠杆怎么计算&…...

鸿蒙: 基础认证

先贴鸿蒙认证 官网10个类别总结如下 https://developer.huawei.com/consumer/cn/training/dev-cert-detail/101666948302721398 10节课学习完考试 考试 90分合格 3次机会 1个小时 不能切屏 运行hello world hvigorfile.ts是工程级编译构建任务脚本 build-profile.json5是工程…...

2024年最佳插电式混合动力电动汽车

对电动汽车充满好奇和环保意识的司机们还没有准备好跨入纯电动汽车&#xff0c;他们可以找到一个折衷方案&#xff0c;即插电式混合动力车。 在过去的16年里&#xff0c;我一直在把握汽车行业的脉搏。试驾数百辆汽车、电动汽车、插电式混合动力车&#xff0c;跟踪汽车行业的新闻…...

上海交通大学、中科大 开源镜像站停止 Docker Hub 仓库镜像支持后的可用替代源

上海交通大学 Linux 用户组发布公告&#xff1a; 即时起中止对 Docker Hub 仓库的镜像。Docker 相关工具默认会自动处理失效镜像的回退&#xff0c;如果对官方源有访问困难问题&#xff0c;建议尝试使用其他仍在服务的镜像源。 源加速地址 有网友表示百度的 Docker Hub 加速器…...

【Linux】shell——条件判断test,各种运算符,expr

条件判断——test 真——0 假——1 test expression or [ expression ] 整数运算符 字符串运算符 -z 长度是否为0 -n 长度是否不为0 str1 str2 str1 ! str2 补 &&-->逻辑与&#xff0c;前面为真后面才会执行 || -->逻辑或&#xff0c;前面为假后面才…...

中介子方程二十二

X$XFX$XdXuXWXπX$XWXeXyXeXyXeXWX$XπXWXuXdX$XFX$XEXyXαXiX$XαXiXrXkXtXyX$XpXVX$XdXuXWXπX$XWXeXyXeXyXeXWX$XπXWXuXdX$XVXpX$XyXtXkXrXiXαX$XiXαXyXEX$XFX$XEXyXαXiX$XαXiXrXkXtXyX$XpXVX$XdXuXWXπX$XWXeXyXeXyXeXWX$XπXWXuXdX$XVXpX$XyXtXkXrXiXαX$XiXαXyXEX$…...

你还不会选ProfiNET和EtherCAT网线?

在现代工业自动化领域&#xff0c;ProfiNET和EtherCAT是两种非常流行的通信协议。选择合适的网线对于确保通信的稳定性和效率至关重要。 ProfiNET是什么&#xff1f; ProfiNET是一种基于以太网的通信协议&#xff0c;由德国西门子公司开发。它支持实时通信&#xff0c;广泛应用…...

醉美酒话:承载着深厚文化底蕴的敬酒词

这些敬酒词凝聚了中华酒文化的精髓&#xff0c;每一句都体现了对美好愿景的深深祝愿&#xff0c;同时也展示了中文语言的丰富与魅力。 一、“步步高升”酒&#xff1a; 第一杯&#xff0c;酒至三分&#xff0c;象征着龙洒点滴、财运将至。我衷心祝愿您财富如江水般滚滚而来&a…...

vue3-sfc-loader动态加载一个异步vue组件生成cesium画面

在 Vue.js 3 中&#xff0c;使用 vue3-sfc-loader 可以动态加载异步的 Vue 单文件组件&#xff08;.vue 文件&#xff09;。这个工具允许你在运行时根据需要加载和解析 .vue 文件&#xff0c;使得组件的加载变得更加灵活和动态。 下面是一个简单的示例&#xff0c;演示如何使用…...

flink学习-状态管理

状态管理 在flink中&#xff0c;算子可以分为无状态和有状态两种情况。 无状态的算子只需要观察每个独立事件&#xff0c;根据当前输入的数据直接输出结果。像&#xff1a;filter、flatMap、map都属于无状态的算子。 有状态的算子则是除当前数据之外&#xff0c;还需要一些其他…...

OpenCV图像算术位运算

一 图像相加 import cv2 import numpy as npgirlcv2.imread(./2037548.jpg)#图像的加法运算就是矩阵的加法运算 #因此加法运算的两张图必须是相等的print(girl.shape)imgnp.ones((1920,1080,3),np.uint8)*50 cv2.imshow(girl,girl) resultcv2.add(girl,img) cv2.imshow(result…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...

Go 并发编程基础:通道(Channel)的使用

在 Go 中&#xff0c;Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式&#xff0c;用于在多个 Goroutine 之间传递数据&#xff0c;从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

scikit-learn机器学习

# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...