日志代码编写

🌎日志代码编写


文章目录:

日志代码编写

    了解日志

    日志编写
      日志等级
      获取时间信息
      获取文件名行号及处理可变参数列表
      以宏的形式传参
      日志加锁
      日志消息输出方式

    完整代码


🚀了解日志

  日志是程序周期性运转或者特定时刻等一些常规或者特殊消息以特殊的形式打印出来,我们称为日志,关于日志,AI是这样回答的:

在这里插入图片描述

  而我们今天要编写的日志,是使用C++编写的日志,日志格式为纯文本日志类型是应用程序日志。


🚀日志编写

✈️日志等级

  日志是有等级的,就类似程序在调试的时候分为警告,错误,和崩溃等 等级一般,日志也有自己的等级,不过这里需要人为的将日志等级进行分类。

enum Level
{DEBUG = 0, // 普通信息INFO,	   // 消息打印WARNING,   // 警告信息ERROR,	   // 错误信息FATAL	   // 重大错误信息
};

✈️获取时间信息

  将来我们需要将日志信息以纯文本的形式正确的打印出来,所以日志信息就作为了打印的格式。

  日志等级表示不同的信息情况,那么我们需要把日志等级转换为字符串:

// 将日志等级转化为字符串
std::string LevelToString(int level)
{switch(level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknown";}
}

  除此之外,日志时间也是尤为重要的,在大型项目中版本经常更新迭代,日志信息的时间就显得尤为重要,在C++中,获取时间可以使用 gettimeofday 获取时间戳

在这里插入图片描述
在这里插入图片描述

  我们还可以直接使用 time 接口直接获取时间戳:

在这里插入图片描述

  而我们日志显示信息通常不是以时间戳形式显示的,所以我们需要将获取的时间戳转化为年月日时分秒的形式,我们可以使用 localtime 接口,返回一个结构体 tm:

在这里插入图片描述

  • 需要注意的是,这里的tm_year是 当前年份减去 1900年的值,tm_mon是当前月份的上一个月

  获取时间及日志等级信息测试:

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];// 写回到time_buffer中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;
}void LogMessage(int level, const char* format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();std::cout << levelstr << ": " << timestr << std::endl;
}
#include <iostream>
#include "Logtest.hpp"int main()
{LogMessage(DEBUG, "hello world");return 0;
}

在这里插入图片描述
  这样我们就可以获取到当前的时间了。


✈️获取文件名行号及处理可变参数列表

  正常的日志绝对少不了文件名和行号的,不然怎么知道是哪里发出的日志信息?而在C语言中,我们曾经学习过下面的语法:

__LINE__ // 获取当前行号
__FILE__ // 获取当前文件名

  这样,我们在获取日志信息的函数前加上这两个参数即可。

  我上面在写日志信息函数的时候,在形参最后是有着可变参数列表的,为了后面可以传多个参数做准备,而我们虽然有可变参数列表,但是我们如何拿到可变参数才是重中之重。我们常常使用stdarg系列宏来处理可变参数列表。

  首先,可变参数列表使用的前提是参数内必须要有一项是确定的,并且这个参数需要再可变参数列表的左侧。首先,我们使用 va_start 宏来使用参数初始化结构体 va_list(实质上是一个类型为 void* 的指针),va_end 来清空va_list:

// num表示可变参数的个数,并且传入函数的都是整数
void test(int num, ...)
{va_list arg;va_start(arg, num);// 使用num的地址来初始化arg指针,这样就可以索引到参数列表了va_end(arg);
}

  这个时候我们已经拿到了可变参数列表了,而第一个参数num表示的是可变参数的个数,而我们要处理每一个可变参数,就可以使用 va_arg 宏来处理可变参数:

void test(int num, ...)
{va_list arg;va_start(arg, num);while(num){int data = va_arg(arg, int);// 因为传入的参数都为int类型,则每次处理都会以四个字节为单位索引参数列表中的每一个元素std::cout << data << " " << std::endl;num--;}va_end(arg);
}

在这里插入图片描述

#include <iostream>
#include "Logtest.hpp"int main()
{test(4, 11, 22, 33, 44);return 0;
}

在这里插入图片描述

  在实际运用当中,没必要这么麻烦,库里已经为我们提供好了接口:

在这里插入图片描述

  vsprintf表示,ap指针将以用户要求的 format格式 ,向字符串str中进行写入参数信息。而vsnprintf为安全模式下的输入参入信息。

  经过上述的了解,我们可以将日志信息改为如下:

void LogMessage(std::string filename, int line, int level, const char* format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t selfpid = getpid();char buffer[4096];va_list arg;va_start(arg, format);vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);std::cout << levelstr << ": " << timestr << " : " << filename << " : " << line << " : " << buffer << std::endl;
}int main()
{LogMessage(__FILE__, __LINE__, DEBUG, "helloworld");LogMessage(__FILE__, __LINE__, DEBUG, "helloworld: %s", "word");LogMessage(__FILE__, __LINE__, DEBUG, "helloworld: %s, %d", "word", 10);LogMessage(__FILE__, __LINE__, DEBUG, "helloworld: %s, %d, %f", "word", 10, 3.14);return 0;
}

在这里插入图片描述
  为了美观,可以改变一下输出形式:

void LogMessage(std::string filename, int line, int level, const char* format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t selfpid = getpid();char buffer[4096];va_list arg;va_start(arg, format);vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);std::cout << "[" << timestr << "]"<< "[" << levelstr << "]"<< "[ pid: " << selfpid << "]"<< "[" << filename << "]"<< "[" << line << "]"<< buffer << std::endl;
}

在这里插入图片描述


✈️以宏的形式传参

  我们直接使用函数调用的形式,需要每次都传参__LINE__, __FILE__ 的字样,这样写起来很不舒服,所以,我们可以采用宏定义的方式规避每次都传入这两个参数。

  首先我们应该考虑到,可变参数列表如何进行宏替换,实际上,宏是支持可变参数列表的,但是函数如果要接收所有的参数,则可变参数部分需要使用宏 __VA_ARGS__ 来接收所有可变参数。

#define LOG(level, format, ...) LogMessage(__FILE__, __LINE__, level, format, __VA_ARGS__)int main()
{LOG(DEBUG, "helloworld, %d", 10);return 0;
}

在这里插入图片描述

  尽管如此,如果我们不传入可变参数部分,__VA_ARGS__ 就会出现特殊字符,导致错误发生,所以我们可以在此参数前加上 ## 如果没有可变参数部分就会在内部将其清空。同时为了防止出现嵌套错误,我们可以在宏外侧使用do{}while(0):

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

✈️日志加锁

  我们的日志可以适用于很多场景,多线程场景也不例外,所以,我们有必要对一些代码进行加锁:

LockGuard:

#pragma once#include <pthread.h>class LockGuard
{
public:// 构造加锁LockGuard(pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}~LockGuard(){pthread_mutex_unlock(_mutex);}private:pthread_mutex_t *_mutex;
};

LOG.hpp:

#pragma once#include <iostream>
#include <cstdio>
#include <sys/types.h>
#include <cstdarg>
#include <unistd.h>
#include "LockGuard.hpp"
#include <string>
#include <ctime>// 日志等级划分
enum Level
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};// 将日志等级转化为字符串
std::string LevelToString(int level)
{switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknown";}
}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];// 写回到time_buffer中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;
}#define LOG(level, format, ...)                                       \do                                                                \{                                                                 \LogMessage(__FILE__, __LINE__, level, format, ##__VA_ARGS__); \} while (0)pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;// 初始化全局互斥锁void LogMessage(std::string filename, int line, int level, const char *format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t selfpid = getpid();char buffer[4096];va_list arg;va_start(arg, format);vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);LockGuard lockguard(&lock);// 加锁std::cout << "[" << timestr << "]"<< "[" << levelstr << "]"<< "[ pid: " << selfpid << "]"<< "[" << filename << "]"<< "[" << line << "]"<< buffer << std::endl;
}

✈️日志消息输出方式

  日志消息不仅仅可以打印在屏幕上,也可以选择打印在文件当中,在全局范围内设置一个表示,默认是不往文件当中打印的,在日志信息处理那一块,我们对该参数进行判断处理:

const std::string logname = "log.txt";void SaveFile(const std::string &filename, const std::string &message)
{std::ofstream out(filename, std::ios::app);if(!out.is_open()){return;}out << message;out.close();
}void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t selfpid = getpid();char buffer[4096];va_list arg;va_start(arg, format);vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);LockGuard lockguard(&lock);// 加锁std::string message;message = "[" + timestr + "]" + "[" + levelstr + "]" + "[ pid: " + std::to_string(selfpid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "]" + buffer + "\n";LockGuard lockguard(&lock); // 加锁if (!issave){std::cout << "[" << timestr << "]"<< "[" << levelstr << "]"<< "[ pid: " << selfpid << "]"<< "[" << filename << "]"<< "[" << line << "]"<< buffer << std::endl;}else{SaveFile(logname, message);}
}

  这样就可以选择性的将日志信息保存在文件或者打印到显示器当中了。


🚀完整代码

Log.hpp:

#pragma once#include <cstdio>
#include <iostream>
#include <time.h>
#include <cstdarg>
#include <fstream>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "LockGuard.hpp"
#include <string>
#include <pthread.h>bool gissave = false;// 是否保存到文件
const std::string logname = "log.txt";// 日志等级划分
enum Level
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};// 将日志等级转化为字符串
std::string LevelToString(int level)
{switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknown";}
}void SaveFile(const std::string &filename, const std::string &message)
{std::ofstream out(filename, std::ios::app);if(!out.is_open()){return;}out << message;out.close();
}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];// 写回到time_buffer中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;
}#define LOG(level, format, ...)                                       \do                                                                \{                                                                 \LogMessage(__FILE__, __LINE__, gissave, format, ##__VA_ARGS__); \} while (0)pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;// 初始化全局互斥锁void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t selfpid = getpid();char buffer[4096];va_list arg;va_start(arg, format);vsnprintf(buffer, sizeof(buffer), format, arg);va_end(arg);LockGuard lockguard(&lock);// 加锁std::string message;message = "[" + timestr + "]" + "[" + levelstr + "]" + "[ pid: " + std::to_string(selfpid) + "]" + "[" + filename + "]" + "[" + std::to_string(line) + "]" + buffer + "\n";LockGuard lockguard(&lock); // 加锁if (!issave){std::cout << "[" << timestr << "]"<< "[" << levelstr << "]"<< "[ pid: " << selfpid << "]"<< "[" << filename << "]"<< "[" << line << "]"<< buffer << std::endl;}else{SaveFile(logname, message);}
}

LockGuard.hpp:

#pragma once#include <pthread.h>class LockGuard
{
public:// 构造加锁LockGuard(pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}~LockGuard(){pthread_mutex_unlock(_mutex);}private:pthread_mutex_t *_mutex;
};

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

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

相关文章

告别繁琐统计,一键掌握微信数据

微信数据管理的挑战在数字时代&#xff0c;微信已成为我们日常沟通和商业活动的重要工具。然而&#xff0c;随着微信号数量的增加&#xff0c;手动统计每个账号的数据变得越来越繁琐。从好友数量到会话记录&#xff0c;再到转账和红包&#xff0c;每一项都需要耗费大量的时间和…

【第几小】

题目 代码 //分块可以AC 20个点的块长&#xff0c; sqrt(n)*5#include<bits/stdc.h> using namespace std;int main(){ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);int n; cin>>n;vector<int> a(n1,0);//分块int len sqrt(n)*5; //块长int k (n%len…

详细分析Pytorch中的transpose基本知识(附Demo)| 对比 permute

目录 前言1. 基本知识2. Demo 前言 原先的permute推荐阅读&#xff1a;详细分析Pytorch中的permute基本知识&#xff08;附Demo&#xff09; 1. 基本知识 transpose 是 PyTorch 中用于交换张量维度的函数&#xff0c;特别是用于二维张量&#xff08;矩阵&#xff09;的转置操…

使用Docker构建和部署微服务

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 [TOC] Docker 是一个开源的容器化平台&#xff0c;可以帮助开发者轻松构建、打包和部署应用程序。本文将详细介绍如何使用 Dock…

Python+Appium+Pytest+Allure自动化测试框架-代码篇

文章目录 自动化测试框架工程目录示例测试代码示例结果查看allurepytest编写pytest测试样例的规则pytest conftest.py向测试函数传参 appium启动appium服务代码端通过端口与appium服务通信对设备进行操作在pytest测试用例中调用appium 更多功能 PythonAppiumPytestAllure自动化…

Elasticsearch Interval 查询:为什么它们是真正的位置查询,以及如何从 Span 转换

作者&#xff1a;来自 Elastic Mayya Sharipova 解释 span 查询如何成为真正的位置查询以及如何从 span 查询过渡到它们。 长期以来&#xff0c;Span 查询一直是有序和邻近搜索的工具。这些查询对于特定领域&#xff08;例如法律或专利搜索&#xff09;尤其有用。但相对较新的 …

IoTDB时序数据库使用

简介 Apache IoTDB 是一款低成本、高性能的物联网原生时序数据库。它可以解决企业组建物联网大数据平台管理时序数据时所遇到的应用场景复杂、数据体量大、采样频率高、数据乱序多、数据处理耗时长、分析需求多样、存储与运维成本高等多种问题。 IoTDB官网 1. 连接数据库 官方…

河北冠益荣信科技公司洞庭变电站工程低压备自投装置的应用

摘 要&#xff1a;随着电力需求增长&#xff0c;供电可靠性变得至关重要&#xff0c;许多系统已有多回路供电。备用电源自动投入装置能提升供电可靠性&#xff0c;它能在主电源故障时迅速切换到备用电源。本文介绍的AM5-DB低压备自投装置&#xff0c;为洞庭变电站提供多种供电方…

STM32实现IAP串口升级含源码(HAL库)

文章目录 一. 关于IAP升级二. IAP升级的分类二. IAP升级原理2.1 正常启动流程2.2 IAP启动流程 三. Ymodem协议3.1 传输过程3.2 帧命令3.3 起始帧3.4 数据帧3.5 结束帧 四. IAP代码实现4.1 Boot 程序4.2 App 程序4.3 展示效果 五. Demo源码六. Qt 上位机 一. 关于IAP升级 IAP&am…

【Hello World 】

【Hello World 】! C语言实现C实现Java实现Python实现 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 几乎每一个程序员都是从Hello World!开始自己的程序人生&#xff0c;作为一个初学编程的小朋友&#xff0c;也需要先编程来输出Hello Wo…

LabVIEW程序员赚钱不仅限于上班

LabVIEW程序员拥有多种途径来实现财富增值&#xff0c;而不仅仅局限于传统的全职工作。以下是一些他们可以利用自身技能和专业知识实现更高财务收益的方法&#xff1a; 1. 专注领域的自由职业与合同工作 制造、科研、医疗技术等行业都需要LabVIEW的专业知识。通过自由职业&…

vue3项目中el-tooltip实现内容溢出时再显示,并设置tip的最大宽度

html代码 <el-tooltip :disabled"!textIsOverflow" placement"top"><template #content><div class"tooltip-div">tootip的内容</div></template><div class"textOverflow" ref"textRef"…

文案语音图片视频管理分析系统-视频矩阵

文案语音图片视频管理分析系统-视频矩阵 1.产品介绍 产品介绍方案 产品名称&#xff1a; 智驭视频矩阵深度分析系统&#xff08;SmartVMatrix&#xff09; 主要功能&#xff1a; 深度学习驱动的视频内容分析多源视频整合与智能分类高效视频检索与编辑实时视频监控与异常预警…

HTML 基础标签——分组标签 <div>、<span> 和基础语义容器

文章目录 1. `<div>` 标签特点用途示例2. `<span>` 标签特点用途示例3. `<fieldset>` 标签特点用途示例4. `<section>` 标签特点用途示例5. `<article>` 标签特点用途示例总结HTML中的分组(容器)标签用于结构化内容,将页面元素组织成逻辑区域…

安防被动红外和主动红外

被动红外探测器是依靠被动的吸收热能动物活动时身体散发出的红外热能进行报警的&#xff0c;也称热释红外探头&#xff0c;其探测器本身是不会发射红外线的。 被动红外探测器中有2个关键性元件&#xff0c;一个是菲涅尔透镜&#xff0c;另一个是热释电传感器。**自然界中任何高…

Windows下将网盘挂载到本地使用(Docker+AList+RaiDrive)

文章目录 安装安装Docker安装Alist安装RaiDrive 安装 安装Docker Windows下安装Docker网上有很多教程&#xff0c;也可以参考我写的博客链接 3.1章节 安装Alist 官网 “切换中文”并找到“使用指南” ”安装“–>"使用Docker” 打开cmd执行如下命令启动容器 do…

C语言 | Leetcode C语言题解之第519题随机翻转矩阵

题目&#xff1a; 题解&#xff1a; typedef struct {unsigned long long val;UT_hash_handle hh; } Hash;typedef struct {Hash *hash;int n_rows;int n_cols; } Solution, SL;Solution* solutionCreate(int n_rows, int n_cols) {SL *obj malloc(sizeof(SL));obj->hash …

C++之多态(上)

C之多态 多态的概念 多态(polymorphism)的概念&#xff1a;通俗来说&#xff0c;就是多种形态。多态分为编译时多态(静态多态)和运⾏时多 态(动态多态)&#xff0c;这⾥我们重点讲运⾏时多态&#xff0c;编译时多态(静态多态)和运⾏时多态(动态多态)。编译时 多态(静态多态)主…

EtherCAT转ModbusTCP相关技术

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 MS-GW15 概述 MS-GW15 是 EtherCAT 和 Modbus TCP 协议转换网关&#xff0c;为用户提供一种 PLC 扩展的集成解决方案&#xff0c;可以轻松容易将 Modbu…

kafka相关面试题

文章目录 什么是消息中间件&#xff1f;kafka 是什么&#xff1f;有什么作用&#xff1f;kafka 的架构是怎么样的&#xff1f;Kafka Replicas是怎么管理的&#xff1f;如何确定当前能读到哪一条消息&#xff1f;生产者发送消息有哪些模式&#xff1f;发送消息的分区策略有哪些&…