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

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍
目录
- 一、日志的概念
- 二、储备知识之C式风格的可变参数
- 三、获取时间
- 四、实现打印日志函数
- 五、封装成类并实现将日志信息打印到文件里(完整代码)
一、日志的概念
在编程中,日志是指程序在运行时生成的记录信息和生成对应记录的时间。这些记录信息可以包括程序的状态、错误消息、警告、调试信息等。通过日志,程序员可以更轻松地跟踪程序的执行过程、诊断问题并监视系统的运行情况。
常见的日志等级包括:
-
info:常规信息 -
warning:指示可能会引起问题的情况,但程序仍然可以继续执行。 -
error:指示程序发生了错误,可能需要立即处理,但程序仍然能够继续执行。 -
fatal:指示程序出现了致命问题,可能导致程序无法继续执行。 -
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_year和tm_mon进行一些调整。
-
tm_year表示从1900年开始经过的年数。因此,要获取实际的年份,需要将其加上1900,即tm_year + 1900。 -
tm_mon表示月份,范围从0到11,其中0表示一月,1表示二月,以此类推。因此,要获取实际的月份,需要将其加上1,即tm_mon + 1。
以下是代码示例:

【程序结果】

四、实现打印日志函数
有了以上的知识,我们就可以开始实现打印日志函数了。
首先规定日志的格式:[时间] [等级] [用户自定义内容]
代码如下(含详细注释)

【函数解析】
snprintf函数:用于将格式化的数据写入字符数组中。它的声明通常如下:int snprintf(char *str, size_t size, const char *format, ...);
str: 指向存储输出的字符数组。size: 字符数组的大小。format: 传递给格式化字符串的数据格式,与printf类似。...: 可变数量的参数,这些参数根据格式字符串进行格式化。
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_start、va_arg和va_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】模拟实现一个简单的日志系统
👦个人主页:Weraphael ✍🏻作者简介:目前正在学习c和算法 ✈️专栏:Linux 🐋 希望大家多多支持,咱一起进步!😁 如果文章有啥瑕疵,希望大佬指点一二 如果文章对…...
MongoDB 多层级查询
多层级查询 注意:要注意代码顺序 查询层级数据代码放前面,查询条件放后面 if (StringUtils.isBlank(params.getDocType())) {params.setDocType(DOC_TDCTYPE);}String docName mapper.findByDocInfo(params.getDocType());List<ExpertApprovalOpin…...
grpc代理服务的实现(一)
最近公司需要无感知基于服务代号来实现通信, 并监控和管理通信连接,目前公司使用的是如下的逻辑(当然逻辑简化了,但是思想不变) 目录 简单的原理图代理服务的实现创建 tls tcp 服务, 用于grpc client 和 grpc service 通信保存 与 代理服务建立的 grpc …...
FastAPI系列 4 -路由管理APIRouter
FastAPI系列 -路由管理APIRouter 文章目录 FastAPI系列 -路由管理APIRouter一、前言二、APIRouter使用示例1、功能拆分2、users、books模块开发3、FastAPI主体 三、运行结果 一、前言 未来的py开发者请上座,在使用python做为后端开发一个应用程序或 Web API&#x…...
数据驱动制造:EMQX ECP 指标监测功能增强生产透明度
迈向未来的工业生产,需要的不仅是自动化,更是智能化。如果工业企业的管理者能够实时监测每一生产环节的设备运行状态,每一数据点位情况,洞察和优化每一步生产流程,他们将能够做出更精准的决策,提高生产效率…...
一行代码实现鼠标横向滚动
🧑💻 写在开头 点赞 收藏 学会🤣🤣🤣 在项目中我们可能会遇到当鼠标在某个区域内,我们希望滚动鼠标里面的内容可以横向滚动; 比如我们一些常见的后台状态栏: 那这种该怎么写&…...
Flink集群架构
在上一章节我们对flink有了一个基本的了解。从它的应用的场景以及它的一些基本的一些核心的一些概念。从本章节开始,我们对flink从它的一个集群的一个架构以及它的一个部署模式着手,去了解flink如何去部署在不同的这样的一个集群的一些资源管理器上面&am…...
计算机网络(6) UDP协议
一.UDP数据报格式 UDP(User Datagram Protocol,用户数据报协议)是一种简单的传输层协议,与TCP(Transmission Control Protocol,传输控制协议)相比,UDP提供一种无连接、不可靠的数据传…...
单片机(STM32)与上位机传输浮点数
目录 单片机(STM32)与上位机传输数据的方法1. 传输整形数据2. 传输浮点数据3. 如何打包与解包 单片机(STM32)与上位机传输数据的方法 在进行单片机程序的开发时,常常需要与其他设备进行通信。一种情况是与其他电路板通信,比如STM32主机与STM32从机通信&…...
50etf期权交易规则杠杆怎么计算?
今天带你了解50etf期权交易规则杠杆怎么计算?近年来,期权交易在股票市场中变得愈发流行,其中50ETF期权备受关注。作为一种金融衍生品,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年最佳插电式混合动力电动汽车
对电动汽车充满好奇和环保意识的司机们还没有准备好跨入纯电动汽车,他们可以找到一个折衷方案,即插电式混合动力车。 在过去的16年里,我一直在把握汽车行业的脉搏。试驾数百辆汽车、电动汽车、插电式混合动力车,跟踪汽车行业的新闻…...
上海交通大学、中科大 开源镜像站停止 Docker Hub 仓库镜像支持后的可用替代源
上海交通大学 Linux 用户组发布公告: 即时起中止对 Docker Hub 仓库的镜像。Docker 相关工具默认会自动处理失效镜像的回退,如果对官方源有访问困难问题,建议尝试使用其他仍在服务的镜像源。 源加速地址 有网友表示百度的 Docker Hub 加速器…...
【Linux】shell——条件判断test,各种运算符,expr
条件判断——test 真——0 假——1 test expression or [ expression ] 整数运算符 字符串运算符 -z 长度是否为0 -n 长度是否不为0 str1 str2 str1 ! str2 补 &&-->逻辑与,前面为真后面才会执行 || -->逻辑或,前面为假后面才…...
中介子方程二十二
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网线?
在现代工业自动化领域,ProfiNET和EtherCAT是两种非常流行的通信协议。选择合适的网线对于确保通信的稳定性和效率至关重要。 ProfiNET是什么? ProfiNET是一种基于以太网的通信协议,由德国西门子公司开发。它支持实时通信,广泛应用…...
醉美酒话:承载着深厚文化底蕴的敬酒词
这些敬酒词凝聚了中华酒文化的精髓,每一句都体现了对美好愿景的深深祝愿,同时也展示了中文语言的丰富与魅力。 一、“步步高升”酒: 第一杯,酒至三分,象征着龙洒点滴、财运将至。我衷心祝愿您财富如江水般滚滚而来&a…...
vue3-sfc-loader动态加载一个异步vue组件生成cesium画面
在 Vue.js 3 中,使用 vue3-sfc-loader 可以动态加载异步的 Vue 单文件组件(.vue 文件)。这个工具允许你在运行时根据需要加载和解析 .vue 文件,使得组件的加载变得更加灵活和动态。 下面是一个简单的示例,演示如何使用…...
flink学习-状态管理
状态管理 在flink中,算子可以分为无状态和有状态两种情况。 无状态的算子只需要观察每个独立事件,根据当前输入的数据直接输出结果。像:filter、flatMap、map都属于无状态的算子。 有状态的算子则是除当前数据之外,还需要一些其他…...
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…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...
如何配置一个sql server使得其它用户可以通过excel odbc获取数据
要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据,你需要完成以下配置步骤: ✅ 一、在 SQL Server 端配置(服务器设置) 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到:SQL Server 网络配…...
