日志代码编写

🌎日志代码编写


文章目录:

日志代码编写

    了解日志

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

    完整代码


🚀了解日志

  日志是程序周期性运转或者特定时刻等一些常规或者特殊消息以特殊的形式打印出来,我们称为日志,关于日志,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.mzph.cn/diannao/58778.shtml

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

相关文章

HCIA笔记整合

第一部分&#xff1a; OSI七层模型 应用层&#xff1a;人机交互 抽象语言--------编码 表示层&#xff1a;编码------二进制 会话层&#xff1a;提供会话号 传输层&#xff1a;TCP/UDP 分段&#xff08;收到MTU值的限制&#xff09; MTU&#xff1a;最大传输单元&#xff…

Kafka集群数据迁移方案

概述 MirrorMaker2&#xff08;后文简称 MM2&#xff09;在 2019 年 12 月随 Kafka 2.4.0 一起推出。顾名思义&#xff0c;是为了解决 Kafka 集群之间数据复制和数据同步的问题而诞生的 Kafka 官方的数据复制工具。在实际生产中&#xff0c;经常被用来实现 Kafka 数据的备份&a…

Prometheus监控平台部署与应用

Prometheus特点 多维数据模型 PromSQL&#xff1a;一种灵活的查询语言&#xff0c;可以利用多维数据完成复杂的查询 不依赖分布式存储&#xff0c;单个服务器节点可直接工作 基于HTTP的pull方式采集时间序列数据 推送时间序列数据通过PushGateway组件支持 通过服务发现或静态配…

vue3 栅栏式拖拽布局组件

先看效果&#xff1a; 使用方法&#xff1a; 1、npm install fencelayout 2、引入使用 <template><Fencelayout><!-- 需要写的模块直接嵌套在这个下面就可以 --><div class"aaaa"><a-button>模块1</a-button></div><…

探索设计模式:命令模式

探索设计模式&#xff1a;命令模式 &#x1f9d0;1. 概念&#x1f3af;2. 作用&#x1f4e6;3. 实现3.1 定义命令接口3.2 实现具体命令3.3 实现接收者3.4 实现调用者3.5 使用 &#x1f4bb;4. 应用场景 命令模式&#xff08;Command Pattern&#xff09;就是一种行为型设计模式…

茅台最新任务脚本

茅台最新任务脚本 –小白教程— 这个脚本的作用是实现i茅台应用的自动预约功能&#xff0c;主要功能包括生成请求头、预约商品、计算距离和库存情况、发送微信推送消息等。 代码如下#!/usr/bin/python3cron: 0 0 9/21 * * * new Env(i茅台) import logging import sysimpor…

​CSS之三

CSS三大特性 CSS 有三个非常重要的三个特性:层圣性、继承性、优先级 层叠性 相同选择器给设置相同的样式&#xff0c;此时一个样式就会覆盖(层曼)另一个冲突的样式。层曼性主要解决样式冲突的问题 层叠性原则: - 样式冲突&#xff0c;遵循的原则是就近原则&#xff0c;哪个…

C++设计模式创建型模式———简单工厂模式、工厂方法模式、抽象工厂模式

文章目录 一、引言二、简单工厂模式三、工厂方法模式三、抽象工厂模式四、总结 一、引言 创建一个类对象的传统方式是使用关键字new &#xff0c; 因为用 new 创建的类对象是一个堆对象&#xff0c;可以实现多态。工厂模式通过把创建对象的代码包装起来&#xff0c;实现创建对…

python爬虫抓取豆瓣数据教程

环境准备 在开始之前&#xff0c;你需要确保你的Python环境已经安装了以下库&#xff1a; requests&#xff1a;用于发送HTTP请求。BeautifulSoup&#xff1a;用于解析HTML文档。 如果你还没有安装这些库&#xff0c;可以通过以下命令安装&#xff1a; pip install requests…

代码-画图函数示例

热力图 import matplotlib.pyplot as plt import seaborn as sns import numpy as npdef create_heatmap(people, categories, dataNone, title热力图, xlabel类别, ylabel人员,value_range(0.6, 0.95), figsize(10, 6),cmapYlOrRd, decimal_places3):"""创建热…

2024最新Twitter养号全面指南,品牌起号必看!

X (Twitter)作为活跃用户数以亿计的社交媒体平台&#xff0c;用户数依然在不断增长&#xff0c;其中巨大的流量吸引着个人用户与品牌和卖家。 Twitter养号是有必要的&#xff0c;有大量案例表明养好号&#xff0c;可以大幅度降低账号被冻结的几率&#xff0c;并提升账号的稳定…

百度如何打造AI原生研发新范式?

&#x1f449;点击即可下载《百度AI原生研发新范式实践》资料 2024年10月23-25日&#xff0c;2024 NJSD技术盛典暨第十届NJSD软件开发者大会、第八届IAS互联网架构大会在南京召开。本届大会邀请了工业界和学术界的专家&#xff0c;优秀的工程师和产品经理&#xff0c;以及其它行…

基于大语言模型(LLM)自主Agent 智能体综述

近年来,LLM(Large Language Model)取得了显著成功,并显示出了达到人类智能的巨大潜力。基于这种能力,使用LLM作为中央控制器来构建自助Agent,以获得类人决策能力。 Autonomous agents 又被称为智能体、Agent。指能够通过感知周围环境、进行规划以及执行动作来完成既定任务。…

电脑怎么设置开机密码:保障个人信息安全的第一步

在数字化时代&#xff0c;个人信息的安全至关重要。电脑作为我们日常工作和生活中不可或缺的设备&#xff0c;存储了大量的私人数据和敏感信息。为了防止未经授权的访问&#xff0c;设置开机密码是保护个人隐私和信息安全的基本措施之一。本文将详细介绍如何在不同操作系统下为…

分析 std::optional 的使用与常见错误

文章目录 引言常见错误及解决方案1. 错误使用 std::optional 变量进行算术运算2. 错误检查 std::optional 是否有值3. 忽视 std::optional 的默认值 结论 引言 std::optional 是 C17 引入的一个模板类&#xff0c;用于表示可能有也可能没有值的情况。它特别适用于函数返回值&a…

DB-GPT系列(二):DB-GPT部署(镜像一键部署、源码部署)

一、简介 DB-GPT 是一个开源项目&#xff0c;其将大语言模型 LLM 与数据库紧密结合。该项目主要致力于探索如何让预训练的大规模语言模型&#xff08;例如 GPT&#xff09;能够直接与数据库进行交互&#xff0c;从而生成更为准确且信息丰富的回答。 DB-GPT部署后能否直接使用…

Web组件之 Listener (监听器)

文章目录 1.1 Listener概述1.2 Listener快速入门① xml版本② 注解版本 1.3 案例&#xff1a;模拟spring框架 1.1 Listener概述 ​ JavaWeb 中的监听器是监听 ServletContext HttpSession HttpServletRequest 三个数据域对象创建和销毁以及监听数据域对象中数据的变化&#xf…

【论文翻译】IJCAI 2019 | Graph WaveNet:用于深度时空图建模的Graph WaveNet

论文题目Graph WaveNet for Deep Spatial-Temporal Graph Modeling作者团队Zonghan Wu, Shirui Pan, Guodong Long, Jing Jiang, Chengqi Zhang机构澳大利亚悉尼科技大学人工智能中心 (UTS) 和 澳大利亚莫纳什大学发表会议IJCAI 2019论文链接https://www.ijcai.org/proceedings…

Java数组的定义与使用

今天来学习Java数组的定义与使用 目录 1 数组的基本概念1.1 数组的意义1.2 数组的定义1.3 数组的创建及初始化1.3.1 数组的创建1.3.2 数组的初始化 1.4 数组的使用1.4.1 数组中的元素访问1.4.2 遍历数组运行结果运行结果 2 数组是引用类型2.1 初始 JVM 的内存分布2.2 基本类型变…

https://tieba.baidu.com/p/9247698007

微深节能的库区智能化无人天车管理系统结合了格雷母线技术&#xff0c;提供了一种高精度的定位解决方案。格雷母线系统能够实现连续或断续的位置检测&#xff0c;精度高达≤5mm&#xff0c;适用于需要高精度作业的场景&#xff0c;如货物搬运和堆放。这种系统通过实时交互&…