在程序中使用日志功能

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

日志模块可以自己实现或者是借用第三方库,之前写过一个类似的使用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://www.mfbz.cn/a/384891.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

随机过程及应用学习笔记(二)随机过程的基本概念

随机过程论就是研究随时间变化的动态系统中随机现象的统计规律的一门数学学科。 目录 前言 一、随机过程的定义及分类 1、定义 2、分类 二、随机过程的分布及其数字特征 1、分布函数 2、数字特征 均值函数和方差函数 协方差函数和相关函数 3、互协方差函数与互相关函…

java微服务面试篇

目录 目录 SpringCloud Spring Cloud 的5大组件 服务注册 Eureka Nacos Eureka和Nacos的对比 负载均衡 负载均衡流程 Ribbon负载均衡策略 自定义负载均衡策略 熔断、降级 服务雪崩 服务降级 服务熔断 服务监控 为什么需要监控 服务监控的组件 skywalking 业务…

【MySQL进阶之路】详解执行计划 type 列

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…

BUUCTF-Real-[Jupyter]notebook-rce

1、简介 Jupyter Notebook&#xff08;此前被称为 IPython notebook&#xff09;是一个交互式笔记本&#xff0c;支持运行 40 多种编程语言。 如果管理员未为Jupyter Notebook配置密码&#xff0c;将导致未授权访问漏洞&#xff0c;游客可在其中创建一个console并执行任意Pytho…

python - 模块使用详解

前言 Python有非常强大的第三方库&#xff0c;也有非常多的内置模块帮助开发人员实现某些功能&#xff0c;无需开发人员自己造轮子。本文介绍Python的模块。 什么是模块 模块简单来说就是一系列功能的集合体&#xff0c;如果将程序的开发比喻成拼图&#xff0c;模块就是各种…

C++STL速查手册

本文参考cppreference&#xff0c;整理了一些常用的STL容器及其内置函数与算法&#xff0c;方便查用。 CSTL速查手册 什么是STL&#xff1f;STL模板 什么是STL&#xff1f; 在C中&#xff0c;STL 是指标准模板库&#xff08;Standard Template Library&#xff09;。STL 是 C 标…

CSS之盒模型

盒模型概念 浏览器盒模型&#xff08;Box Model&#xff09;是CSS中的基本概念&#xff0c;它描述了元素在布局过程中如何占据空间。盒模型由内容&#xff08;content&#xff09;、内边距&#xff08;padding&#xff09;、边框&#xff08;border&#xff09;、和外边距&…

mysql Day05

sql性能分析 sql执行频率 show global status like Com_______ 慢查询日志 执行时间超过10秒的sql语句 profile详情 show profiles帮助我们了解时间都耗费到哪里了 #查看每一条sql的耗时情况 show profiles#查看指定query_id的sql语句各个阶段的耗时情况 show profile fo…

【项目日记(九)】项目整体测试,优化以及缺陷分析

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:项目日记-高并发内存池⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你做项目   &#x1f51d;&#x1f51d; 开发环境: Visual Studio 2022 项目日…

Spring Cloud Gateway 网关路由

一、路由断言 路由断言就是判断路由转发的规则 二、路由过滤器 1. 路由过滤器可以实现对网关请求的处理&#xff0c;可以使用 Gateway 提供的&#xff0c;也可以自定义过滤器 2. 路由过滤器 GatewayFilter&#xff08;默认不生效&#xff0c;只有配置到路由后才会生效&#x…

无人机飞行原理,多旋翼无人机飞行原理详解

多旋翼无人机升空飞行的首要条件是动力&#xff0c;有了动力才能驱动旋粪旋转&#xff0c;才能产生克服重力所必需的升力。使旋翼产生升力&#xff0c;进而推动多旋翼无人机升空飞行的一套设备装置称为动力装置&#xff0c;包括多旋翼无人机的发动机以及保证发动机正常工作所必…

LibreOffice Calc 取消首字母自动大写 (Capitalize first letter of every sentence)

LibreOffice Calc 取消首字母自动大写 [Capitalize first letter of every sentence] 1. Tools -> AutoCorrect Options2. AutoCorrect -> Options -> Capitalize first letter of every sentenceReferences 1. Tools -> AutoCorrect Options 2. AutoCorrect ->…

论文介绍 One-step Diffusion 只需单步扩散生成!

论文介绍 One-step Diffusion with Distribution Matching Distillation 关注微信公众号: DeepGo 源码地址&#xff1a; https://tianweiy.github.io/dmd/ 论文地址&#xff1a; https://arxiv.org/abs/2311.18828 这篇论文介绍了一种新的图像生成方法&#xff0c;名为分布匹配…

C++三剑客之std::optional(一) : 使用详解

相关文章系列 C三剑客之std::optional(一) : 使用详解 C三剑客之std::any(一) : 使用 C之std::tuple(一) : 使用精讲(全) C三剑客之std::variant(一) : 使用 C三剑客之std::variant(二)&#xff1a;深入剖析 目录 1.概述 2.构建方式 2.1.默认构造 2.2.移动构造 2.3.拷贝构…

前端vue 数字 字符串 丢失精度问题

1.问题 后端返回的数据 是这样的 一个字符串类型的数据 前端要想显示这个 肯定需要使用Json.parse() 转换一下 但是 目前有一个问题 转换的确可以 showId:1206381711026823172 有一个这样的字段 转换了以后 发现 字段成了1206381711026823200 精度直接丢了 原本的数据…

假期作业 8

1、若有以下说明语句&#xff1a;int a[12]{1,2,3,4,5,6,7,8,9,10,11,12};char c’a’,d,g;则数值为4的表达式是&#xff08; B&#xff09;。 A&#xff09;a[g-c] B&#xff09;a[4] C&#xff09;a[‘d’-‘c’] D&#xff09;a[‘d’-c] 2、假…

【C++ 02】类和对象 1:初识类和对象

文章目录 &#x1f308; Ⅰ 面向对象介绍&#x1f308; Ⅱ 类的引入&#x1f308; Ⅲ 类的定义格式1. 声明和定义不分离2. 声明和定义分离 &#x1f308; Ⅳ 类的访问限定符&#x1f308; Ⅴ 类的作用域&#x1f308; Ⅵ 类的实例化&#x1f308; Ⅶ this 指针 &#x1f308; Ⅰ…

【Java程序设计】【C00254】基于Springboot的java学习平台(有论文)

基于Springboot的java学习平台&#xff08;有论文&#xff09;&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的学习平台 本系统分为系统功能模块、管理员功能模块、教师功能模块以及学生功能模块。 系统功能模块&#xff1a;在平台…

LLM大模型常见问题解答(3)

简要描述下列概念在大语言模型中的作用 Transformer 架构Attention 机制预训练与微调过拟合和欠拟合 Transformer 架构 Transformer是一种基于自注意力机制的深度学习模型&#xff0c;它在论文“Attention Is All You Need”中首次提出。与此前流行的循环神经网络&#xff0…

第四节 zookeeper集群与分布式锁

目录 1. Zookeeper集群操作 1.1 客户端操作zk集群 1.2 模拟集群异常操作 1.3 curate客户端连接zookeeper集群 2. Zookeeper实战案例 2.1 创建项目引入依赖 2.2 获取zk客户端对象 2.3 常用API 2.4 客户端向服务端写入数据流程 2.5 服务器动态上下线、客户端动态监听 2…
最新文章