在程序中使用日志功能

news2024/11/24 7:35:25

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

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

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

相关文章

PyCharm2023.3.2配置conda环境

重点在于Path to conda这一步&#xff0c;需要找到conda.bat这个文件&#xff0c;PyCharm才能识别出现有的conda环境。

配置VMware实现从服务器到虚拟机的一键启动脚本

正文共&#xff1a;1666 字 15 图&#xff0c;预估阅读时间&#xff1a;2 分钟 首先祝大家新年快乐&#xff01;略备薄礼&#xff0c;18000个红包封面来讨个开年好彩头&#xff01; 虽然之前将服务器放到了公网&#xff08;成本增加了100块&#xff0c;内网服务器上公网解决方案…

c语言游戏实战(6):走迷宫之推箱子

前言&#xff1a; 在上一篇文章当中我介绍了一个走迷宫的写法&#xff0c;但是那个迷宫没什么可玩性和趣味性&#xff0c;所以我打算在迷宫的基础上加上一个推箱子&#xff0c;使之有更好的操作空间&#xff0c;从而增强了游戏的可玩性和趣味性。 1. 打印菜单 void menu() {…

【DDD】学习笔记-UML 与彩色建模

如果某个领域已经形成了稳定的分析模式&#xff0c;在设计该领域的分析模型时&#xff0c;这些模式就可以提供有价值的参考。可惜&#xff0c;分析模式需要有人来总结和提炼&#xff0c;最好的分析模式提炼者需要兼具领域知识和软件建模能力。很早以前&#xff0c;Martin Fowle…

nodejs切换版本

sudo n 18.17.0 sudo n然后键盘上下选择

Vue核心基础6:Vue内置指令、自定义指令、生命周期

1 Vue中的内置指令 <script>const vm new Vue({el: #root,data: {n: 1,m: 100,name: Vue,str: <h3>你好</h3>}})</script> 1.1 v-text <div v-text"name"></div>1.2 v-html <div v-html"str"></div> …

SpringCloud-高级篇(二十)

下面我们研究MQ的延迟性问题 &#xff08;1&#xff09;初始死信交换机 死信交换机作用一方面可以向Public的异常交换机一样做异常消息的兜底方案&#xff0c;另一方面&#xff0c;可以处理一些超时消息&#xff0c;功能比较丰富一点 &#xff08;2&#xff09;TTL 上面学习…

Java基础:值传递和引用传递

Java在给方法传递参数时&#xff0c;有值传递和引用传递两种方式。 基本概念 值传递&#xff1a;传递对象的一个副本&#xff0c;即使副本被改变&#xff0c;也不会影响源对象&#xff0c;因为值传递的时候&#xff0c;实际上是将实参的值复制一份给形参。 引用传递&#xf…

猫头虎分享已解决Bug || ValueError: Data cardinality is ambiguous

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

【Cocos入门】物理系统

物理引擎默认是关闭状态以节省资源开销。开启方法和之前的普通碰撞类似:cc.directorgetPhysicsManager().enabled true但有一个区别&#xff0c;物理引擎的开启必须放在onLoad函数内运行&#xff0c;否则不生效。 开启物理引擎后&#xff0c;游戏运行&#xff0c;会发现添加…

C++多态重难点

CSDN上已经有很多关于C多态方面的一些系统介绍了&#xff0c;但是我看了一下一些有关于多态问题的细节问题文章较少&#xff0c;因此我想要出一片文章重点讲一讲我认为比较重点且容易被遗忘的知识点&#xff0c;一些比较基本的知识这里就不过多赘述了&#xff0c;可以参考其他优…

controller-manager学习三部曲之二:源码学习

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 本篇概览 作为《controller-manager学习三部曲》系列的第二篇&#xff0c;前面通过shell脚本找到了程序的入口&#xff0c;接下来咱们来学习controller-mana…

第三百一十八回

文章目录 1. 概念介绍2. 使用方法2.1 本地缓冲2.2 服务器缓冲3. 示例代码4. 内容总结我们在上一章回中介绍了"如何让输入键盘不遮挡屏幕"相关的内容,本章回中将介绍如何有效地缓冲网络图片.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍的…

ArcGIS学习(七)图片数据矢量化

ArcGIS学习(七)图片数据矢量化 通过上面几个任务的学习,大家应该已经掌握了ArcGIS的基础操作,并且学习了坐标系和地理数据库这两个非常重要且稍微难一些的专题。从这一任务开始,让我们进入到实战案例板块。 首先进入第一个案例一一图片数据矢量化。 我们在平时的工作学…

单片机学习笔记---AT24C02数据存储

目录 AT24C02数据存储 准备工作 代码讲解 I2C.c 模拟起始位置的时序 模拟发送一个字节的时序 模拟接收应答的时序 模拟接收一个字节的时序 模拟发送应答的时序 模拟结束位置的时序 I2C.h AT24C02.c 字节写&#xff1a;在WORD ADDRESS&#xff08;字地址&#xff…

探索Nginx:强大的开源Web服务器与反向代理

一、引言 随着互联网的飞速发展&#xff0c;Web服务器在现代技术架构中扮演着至关重要的角色。Nginx&#xff08;发音为“engine x”&#xff09;是一个高性能的HTTP和反向代理服务器&#xff0c;也是一个IMAP/POP3/SMTP代理服务器。Nginx因其卓越的性能、稳定性和灵活性&…

汽车零部件制造业MES系统解决方案

一、​汽车零部件行业现状 随着全球汽车产业不断升级&#xff0c;汽车零部件市场竞争日趋激烈&#xff0c;从上游的钢铁、塑料、橡胶等生产到下游的主机厂配套制造&#xff0c;均已成为全球各国汽车制造大佬战略目标调整的焦点&#xff0c;其意欲在汽车零部件行业快速开疆扩土&…

C++内联函数深入讲解

用法&#xff1a; 在函数的返回值前面加上inline&#xff0c;例如&#xff1a; 作用&#xff1a; 内联函数的存在其实是为了解决c语言中一些问题&#xff0c;比如有一个频繁调用的小函数&#xff0c;每次调用都需要建立栈帧&#xff0c;压栈出栈&#xff0c;减少了效率&#xf…

分享86个鼠标特效,总有一款适合您

分享86个鼠标特效&#xff0c;总有一款适合您 86个鼠标特效下载链接&#xff1a;https://pan.baidu.com/s/12Y_iMqt-7-jyw46k62ySDg?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不…

PKI - 借助Nginx实现_客户端使用自签证书供服务端验证

文章目录 Pre概述在 Nginx 中实现客户端使用自签名证书供服务器验证1. 生成客户端密钥对2. 生成自签名客户端证书3. 配置 Nginx4. 重启 Nginx 修5. 验证 在浏览器中安装客户端证书以便进行访问 Pre PKI - 借助Nginx 实现Https 服务端单向认证、服务端客户端双向认证 PKI - 数…