在程序中使用日志功能

在应用中,需要记录程序运行过程中的一些关键信息以及异常输出等。这些信息用来排查程序故障或者其他用途。

日志模块可以自己实现或者是借用第三方库,之前写过一个类似的使用Qt的打印重定向将打印输出到文件:Qt将打印信息输出到文件_qt log输出到文件-CSDN博客

第三方日志库有plog、glog、spdlog、log4qt等等。主要介绍以下plog和spdlog这两个只需要包含对应文件而不需要编译生成库的第三方日志模块。

PLOG

下载链接:Releases · SergiusTheBest/plog · GitHub

下载之后直接引入对应文件即可,写一个简单的例子:

#include "mainwindow.h"
#include "plog/Appenders/RollingFileAppender.h"
#include "plog/Formatters/TxtFormatter.h"
#include "plog/Initializers/ConsoleInitializer.h"
#include "plog/Log.h"
#include <QApplication>int main(int argc, char *argv[]) {QApplication a(argc, argv);plog::RollingFileAppender<plog::TxtFormatter> file_logger("app.log",1000000, 3);plog::ColorConsoleAppender<plog::TxtFormatter> console_logger;plog::init(plog::debug, &file_logger).addAppender(&console_logger);PLOGD << "Debug message";PLOGI << "Info message";PLOGW << "Warning message";PLOGE << "Error message";MainWindow w;w.show();return a.exec();
}

函数plog::RollingFileAppender 时,需要提供三个参数,这些参数决定了日志文件的滚动和格式化方式。下面介绍对应三个参数的含义:

第一个参数是日志文件的名称,这是一个字符串,用于指定要写入的日志文件的名称。例如,我设设置的日志文件名是app.log。

第二个参数是日志文件的大小限制。当日志文件的大小达到这个限制时,Plog 将自动滚动日志文件并创建新的日志文件。这个大小通常以字节为单位,例如 1000000 表示 1MB。

第三个参数是滚动文件的数量限制。当日志文件达到大小限制并滚动时,Plog 将保留多少个滚动文件。例如,如果将其设置为 3,则在滚动后将保留最多 3 个滚动文件,旧的滚动文件将被删除。

函数plog::ColorConsoleAppender将日志以彩色形式打印到控制台进行输出。

plog::init(plog::debug, &file_logger).addAppender(&console_logger)进行日志初始化,将日志等级设置为debug。编译运行查看:

对应日志打印到了控制台。然后查看是否有日志文件生成:

查看日志文件内容:

SPDLOG

下载链接:GitHub - gabime/spdlog: Fast C++ logging library.

spdlog与plog一样,只需要包含对应文件即可无需额外编译。

写一个简单的例子:

#include "mainwindow.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/stdout_sinks.h"
#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);auto console_logger = spdlog::stdout_logger_mt("console_logger");auto file_logger = spdlog::basic_logger_mt("file_logger", "log.txt");console_logger->info("console_logger info");file_logger->error("file_logger error");MainWindow w;w.show();return a.exec();
}

初始化了两个logger,console_logger将会打印到控制台,file_logger将会将日志内容生成到文件log.txt中,编译运行:

console_logger已经将日志打印到控制台,查看是否有日志文件生成以及日志文件中是否有内容:

查看log.txt却是空的:

关闭程序之后log.txt中有文本生成:

需要设置日志刷新

file_logger->flush_on(spdlog::level::debug);

这样设置之后日志级别在debug及以上的日志都会实施刷新在文件中。另外日志显示的格式也是可以通过set_pattern设置的,例如我是这样设置的

spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e][%t][%s %! %#]%v");

即[时间][线程id][文件名 函数 行号]日志内容。

可以看到文件名、函数、行号没有正确打印,如果要正确打印这三者需要用到对应宏SPDLOG_LOGGER:

       SPDLOG_LOGGER_INFO(console_logger,"console_logger info");SPDLOG_LOGGER_ERROR(file_logger,"file_logger error");

可以看到文件名、函数、行号有正常打印:

简单封装一下。

头文件:

#ifndef LOG_H
#define LOG_H#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/spdlog.h"#define LOGD(...) \SPDLOG_LOGGER_DEBUG(Log::instance().GetDebugLogger(), __VA_ARGS__);
#define LOGI(...) \SPDLOG_LOGGER_INFO(Log::instance().GetInfoLogger(), __VA_ARGS__);
#define LOGW(...) \SPDLOG_LOGGER_WARN(Log::instance().GetWarnLogger(), __VA_ARGS__);
#define LOGE(...) \SPDLOG_LOGGER_ERROR(Log::instance().GetErrorLogger(), __VA_ARGS__);class Log {
private:std::shared_ptr<spdlog::logger> m_DebugLogger;std::shared_ptr<spdlog::logger> m_InfoLogger;std::shared_ptr<spdlog::logger> m_WarnLogger;std::shared_ptr<spdlog::logger> m_ErrorLogger;public:static Log &instance();auto GetDebugLogger() -> decltype(m_DebugLogger) { return m_DebugLogger; }auto GetInfoLogger() -> decltype(m_InfoLogger) { return m_InfoLogger; }auto GetWarnLogger() -> decltype(m_WarnLogger) { return m_WarnLogger; }auto GetErrorLogger() -> decltype(m_ErrorLogger) { return m_ErrorLogger; }void init(const std::string &fileName);private:Log();
};#endif // LOG_H

源文件:

#include "log.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"Log &Log::instance() {static Log log;return log;
}void Log::init(const std::string &fileName) {spdlog::set_pattern("[%Y-%m-%d %H:%M:%S.%e][%t][%s %! %#]%v");m_DebugLogger =spdlog::basic_logger_mt("debug_logger", fileName + "_debug.log");m_DebugLogger->set_level(spdlog::level::debug);m_DebugLogger->flush_on(spdlog::level::debug);m_InfoLogger =spdlog::basic_logger_mt("info_logger", fileName + "_info.log");m_InfoLogger->set_level(spdlog::level::info);m_InfoLogger->flush_on(spdlog::level::info);m_WarnLogger =spdlog::basic_logger_mt("warn_logger", fileName + "_warn.log");m_WarnLogger->set_level(spdlog::level::warn);m_WarnLogger->flush_on(spdlog::level::warn);m_ErrorLogger =spdlog::basic_logger_mt("error_logger", fileName + "_error.log");m_ErrorLogger->set_level(spdlog::level::err);m_ErrorLogger->flush_on(spdlog::level::err);
}Log::Log() {}

 调用示例:

#include "log.h"
#include "mainwindow.h"
#include <QApplication>
#include <QDateTime>int main(int argc, char *argv[]) {QApplication a(argc, argv);Log::instance().init(QString("log/%1_%2").arg("Test").arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")).toStdString());LOGD("debug");LOGI("info");LOGW("warn");LOGE("error");MainWindow w;w.show();return a.exec();
}

注意修改spdlog源码中的这部分:

每次程序启动后调用init初始化日志后都会生成对应四个日志文件。

最后贴一个纯C++实现的简单的日志功能:

#ifndef LOG_H
#define LOG_H#include <ctime>
#include <fstream>
#include <iostream>
#include <mutex>
#include <sstream>
#include <string>// 日志级别
enum class LogLevel { DEBUG, INFO, WARNING, ERROR };// 日志类
class Logger {
public:Logger() {// 打开日志文件logFile.open("app.log", std::ios::app);}~Logger() {// 关闭日志文件if (logFile.is_open()) { logFile.close(); }}// 日志接口void Log(const std::string &message, LogLevel level) {std::lock_guard<std::mutex> lock(mtx); // 线程安全// 获取当前时间std::time_t now = std::time(nullptr);char buf[100] = {0};std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S",std::localtime(&now));// 构造日志消息std::ostringstream logStream;logStream << "[" << buf << "] [" << ToString(level) << "] " << message<< std::endl;// 输出到控制台std::cout << logStream.str();// 输出到文件if (logFile.is_open()) { logFile << logStream.str(); }}private:std::ofstream logFile; // 日志文件std::mutex mtx;        // 互斥锁// 将日志级别转换为字符串std::string ToString(LogLevel level) {switch (level) {case LogLevel::DEBUG: return "DEBUG";case LogLevel::INFO: return "INFO";case LogLevel::WARNING: return "WARNING";case LogLevel::ERROR: return "ERROR";default: return "UNKNOWN";}}
};// 日志类的单例
class LoggerSingleton {
public:static Logger &GetInstance() {static Logger instance; // 实例化一个Logger对象return instance;}// 删除拷贝构造函数和赋值操作LoggerSingleton(const LoggerSingleton &) = delete;LoggerSingleton &operator=(const LoggerSingleton &) = delete;private:LoggerSingleton() {} // 私有构造函数
};// 定义宏简化日志调用
#define LOG_DEBUG(msg) LoggerSingleton::GetInstance().Log(msg, LogLevel::DEBUG)
#define LOG_INFO(msg) LoggerSingleton::GetInstance().Log(msg, LogLevel::INFO)
#define LOG_WARNING(msg) \LoggerSingleton::GetInstance().Log(msg, LogLevel::WARNING)
#define LOG_ERROR(msg) LoggerSingleton::GetInstance().Log(msg, LogLevel::ERROR)#endif // LOG_H

调用示例:

#include "log.h"
#include <iostream>using namespace std;int main() {// 记录不同级别的日志LOG_DEBUG("This is a debug message.");LOG_INFO("This is an info message.");LOG_WARNING("This is a warning message.");LOG_ERROR("This is an error message.");cout << "Hello World!" << endl;return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaEE】_HTML常用标签

目录 1.HTML结构 2. HTML常用标签 2.1 注释标签 2.2 标题标签&#xff1a;h1~h6 2.3 段落标签&#xff1a;p 2.4 换行标签&#xff1a;br 2.5 格式化标签 2.6 图片标签&#xff1a;img 2.7 超链接标签&#xff1a;a 2.8 表格标签 2.9 列表标签 2.10 表单标签 2.10…

C++继承(二):菱形继承、virtual菱形虚拟继承

目录 一、了解菱形继承 二、菱形继承的问题 三、虚拟继承virtual 3.1virtual 3.2虚拟继承解决数据冗余和二义性的原理 四、总结/继承和组合 一、了解菱形继承 单继承&#xff1a;一个子类只有一个直接父类时称这个继承关系为单继承 多继承&#xff1a;一个子类有两个或…

C++重新入门-C++ 函数

函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数&#xff0c;即主函数 main() &#xff0c;所有简单的程序都可以定义其他额外的函数。 您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的&#xff0c;但在逻辑上&#xff0c;划分通常…

春节折腾了4天,终于用上了win11和matlab2023b

这个春节折腾了4天&#xff0c;终于与时俱进用上了win11和matlab2023b。 新购的硬盘&#xff0c;顺丰快递给力2天半到手。 先折腾硬盘&#xff0c;连线&#xff0c;没有盘符&#xff0c;使用管理&#xff0c;初始化&#xff0c;格式化&#xff0c;新建卷。下载win11&#xff0…

导数的定义【高数笔记】

【含义】可以抽象成&#xff0c;在一个极其短的时间段内&#xff0c;温度差 / 时间差 【本质】瞬间的平均值 【分类】可以分成几类&#xff1f;每类需要注意的点 【导数存在的必要条件】 【导数与极限的关系】可以参考导数的定义的式子 【题型解法】分几个题型&#xff1f;每个…

2.12作业

程序代码&#xff1a; #include<stdlib.h> #include<string.h> #include<stdio.h>//递归实现n! int n(int element) {if(0element)return 1;return element*n(element-1); }//递归实现0-n的和 int sub_sum(int element) {if(0element)return 0;return eleme…

system V——进程间通信

上一篇博客中我介绍了system V进程间通信中的内存共享&#xff0c;但是其中还有两 种通信方式&#xff1a;消息队列、和信号量&#xff0c;接下来我将简单介绍一下&#xff0c;消息队列和 信号量以及操作系统是如何看待system V进程间通信的。1. 消息队列 a. 大致介绍 消息队…

用HTML5 + JavaScript绘制花、树

用HTML5 JavaScript绘制花、树 <canvas>是一个可以使用脚本 (通常为JavaScript) 来绘制图形的 HTML 元素。 <canvas> 标签/元素只是图形容器&#xff0c;必须使用脚本来绘制图形。 HTML5 canvas 图形标签基础https://blog.csdn.net/cnds123/article/details/112…

腾讯云4核8G服务器性能如何?支持多少用户访问?

腾讯云4核8G服务器支持多少人在线访问&#xff1f;支持25人同时访问。实际上程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&#xff0c;假设公网带宽太小&#xff0c;流量直接卡在入口&#xff0c;4核8G配置的CPU内存也会造成计算…

{}初始化和初始化列表

C98标准中允许使用花括号对数组和自定义类型的变量进行初始化&#xff0c;C11扩展了大括号的用途&#xff0c;允许使用花括号对所有的内置类型和自定义类型进行初始化&#xff0c;使用时&#xff0c;可以加号&#xff0c;也可以不加。 对于自定义类型&#xff0c;当花括号中的常…

windows 下安装gin

go install 执行命令&#xff0c;执行不了的参考一下 https://blog.csdn.net/weixin_42592326/article/details/135946806 Golang 中没法下载第三方包解决办法-CSDN博客 go install github.com/gin-gonic/ginlatest 还是安装不了的话&#xff0c;用手机开热点&#xff0c;电…

Avaddon勒索病毒解密工具

前言 Avaddon勒索病毒被笔者称为2020年全球十大流行勒索病毒之一&#xff0c;其首次出现于2020年6月在俄罗斯某地下黑客论坛开始出售&#xff0c;该勒索病毒使用C语言进行编写&#xff0c;采用RSA-2048和AES-256加密算法对文件进行加密&#xff0c;该勒索病毒的传播方式多种多…

自动化AD域枚举和漏洞检测脚本

linWinPwn 是一个 bash 脚本&#xff0c;可自动执行许多 Active Directory 枚举和漏洞检查。该脚本基于很多现有工具实现其功能&#xff0c;其中包括&#xff1a;impacket、bloodhound、netexec、enum4linux-ng、ldapdomaindump、lsassy、smbmap、kerbrute、adidnsdump、certip…

第75讲Avatar头像FooterHome实现

Avatar头像实现 avatar&#xff1a; <template><el-dropdown><span class"el-dropdown-link"><el-avatar shape"square" :size"40" :src"squareUrl" /></span><template #dropdown><el-drop…

MySQL基本操作之数据库的操作

一.创建数据库 1.基本语法 create database 数据库名&#xff1b; 注意别忘记加分号。 2.if not exists 数据库名字是唯一的&#xff0c;所以不可以创建已存在的数据库&#xff0c;如下&#xff1a; 重复创建就会报错 所以有了if not exists这个语法&#xff0c;加上之后&…

JAVA设计模式之访问模式详解

访问者模式 1 访问者模式介绍 访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式. 访问者模式(Visitor Pattern) 的原始定义是&#xff1a;允许在运行时将一个或多…

数据分析基础之《pandas(8)—综合案例》

一、需求 1、现在我们有一组从2006年到2016年1000部最流行的电影数据 数据来源&#xff1a;https://www.kaggle.com/damianpanek/sunday-eda/data 2、问题1 想知道这些电影数据中评分的平均分&#xff0c;导演的人数等信息&#xff0c;我们应该怎么获取&#xff1f; 3、问题…

vue3 之 商城项目—layout静态模版结构搭建

layout—模块静态模版搭建 一般情况下我们会有nav区域&#xff0c;header区域&#xff0c;二级路由出口区域以及footer区域&#xff0c;如图 我们在开发的时候先把大模块搭建起来&#xff0c;再一步一步填充小模块 在layout下建文件&#xff0c;目录如下 在index.vue中把上…

DP读书:《openEuler操作系统》(九)从IPC到网卡到卡驱动程序

DP读书&#xff1a;《openEuler操作系统》从IPC到网卡到卡驱动程序&#xff09; 上章回顾_SPI上节回顾_TCP 网卡驱动程序简介1.设备驱动2.总线与设备3.网卡及其抽象 驱动程序的注册与注销1. 注册2. 注销 设备初始化1. 硬件初始化2. 软件初始化 设备的打开与关闭1. 设备的打开2.…

阿里云带宽计费模式怎么选?如何收费的?

阿里云服务器带宽计费模式分为“按固定带宽”和“按使用流量”&#xff0c;有什么区别&#xff1f;按固定带宽是指直接购买多少M带宽&#xff0c;比如1M、5M、10M、100M等&#xff0c;阿里云直接分配用户所购买的带宽值&#xff0c;根据带宽大小先付费再使用&#xff1b;按使用…