当前位置: 首页 > 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…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

linux 下常用变更-8

1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行&#xff0c;YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID&#xff1a; YW3…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

MySQL:分区的基本使用

目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区&#xff08;Partitioning&#xff09;是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分&#xff08;分区&#xff09;可以独立存储、管理和优化&#xff0c;…...