首页 > 编程语言 >C++日志管理从基础到完善

C++日志管理从基础到完善

时间:2024-12-28 12:26:45浏览次数:7  
标签:std 完善 log LogLevel C++ _. 日志 include

万古教员有名言,自信人生二百年。
个人主页:oioihoii
喜欢内容的话欢迎关注、点赞、收藏!感谢支持,祝大家祉猷并茂,顺遂无虞

在这里插入图片描述

版本一:基础日志代码

在设计C++日志系统时,我们需要考虑以下几个关键点:

  1. 易用性:日志系统应该易于使用,开发者应该能够轻松地添加日志条目。
  2. 性能:日志系统应该尽可能地减少对应用程序性能的影响。这意味着日志记录的过程应该尽可能地快速,以减少对应用程序的延迟。
  3. 灵活性:日志系统应该能够支持不同级别的日志(如错误、警告、信息、调试等),并能够在运行时动态地更改日志级别。
  4. 可配置性:日志系统应该允许开发者配置日志的各种参数,如日志文件的位置、日志的格式等。
  5. 线程安全:如果应用程序是多线程的,那么日志系统也必须是线程安全的。

下面是一个简单的C++日志系统的设计,它考虑了上述的所有因素:

#include <iostream>
#include <fstream>
#include <mutex>
#include <memory>

enum class LogLevel {
    ERROR,
    WARNING,
    INFO,
    DEBUG
};

class Logger {
public:
    Logger(const std::string& file_name) {
        file_stream_.open(file_name, std::ios::out | std::ios::app);
    }

    ~Logger() {
        file_stream_.close();
    }

    void Log(LogLevel level, const std::string& message) {
        std::lock_guard<std::mutex> lock(mutex_);
        switch(level) {
            case LogLevel::ERROR:
                file_stream_ << "[ERROR] ";
                break;
            case LogLevel::WARNING:
                file_stream_ << "[WARNING] ";
                break;
            case LogLevel::INFO:
                file_stream_ << "[INFO] ";
                break;
            case LogLevel::DEBUG:
                file_stream_ << "[DEBUG] ";
                break;
        }
        file_stream_ << message << std::endl;
    }

private:
    std::ofstream file_stream_;
    std::mutex mutex_;
};

int main() {
    auto logger = std::make_shared<Logger>("log.txt");
    logger->Log(LogLevel::INFO, "This is an info message");
    logger->Log(LogLevel::ERROR, "This is an error message");
    return 0;
}

在这个设计中,我们使用了一个std::ofstream对象来写入日志文件,使用了一个std::mutex对象来确保线程安全,使用了一个枚举类LogLevel来表示不同的日志级别。我们的Log函数接受一个日志级别和一个消息,然后将它们写入日志文件。

这只是一个基础的日志系统设计,实际的日志系统可能会更复杂,例如,它可能会支持将日志发送到多个目的地(如文件、网络、控制台等),支持多种日志格式,支持日志轮转等。

版本二:考虑性能的进阶日志

对于大量日志的生成,确实需要考虑性能问题。频繁地打开和关闭文件会导致性能下降。在这种情况下,我们可以考虑以下优化:

  1. 缓冲日志消息:我们可以在内存中缓冲日志消息,然后在缓冲区满时一次性写入文件,而不是每次都打开和关闭文件。这可以大大提高性能,但是需要注意的是,如果程序崩溃,缓冲区中的日志消息可能会丢失。

  2. 异步写入:我们可以在一个单独的线程中写入日志文件,这样就不会阻塞主程序的执行。这需要更复杂的线程同步机制,但是可以进一步提高性能。

下面是一个改进的版本,使用了缓冲区和异步写入:

#include <iostream>
#include <fstream>
#include <mutex>
#include <memory>
#include <thread>
#include <condition_variable>
#include <queue>

enum class LogLevel {
    ERROR,
    WARNING,
    INFO,
    DEBUG
};

class Logger {
public:
    Logger(const std::string& file_name) : exit_(false) {
        file_stream_.open(file_name, std::ios::out | std::ios::app);
        worker_ = std::thread(&Logger::writeLog, this);
    }

    ~Logger() {
        exit_ = true;
        cv_.notify_all();
        worker_.join();
        file_stream_.close();
    }

    void Log(LogLevel level, const std::string& message) {
        std::lock_guard<std::mutex> lock(mutex_);
        log_queue_.push(std::make_pair(level, message));
        cv_.notify_all();
    }

private:
    void writeLog() {
        while (true) {
            std::unique_lock<std::mutex> lock(mutex_);
            cv_.wait(lock, [this]{ return !log_queue_.empty() || exit_; });
            while (!log_queue_.empty()) {
                auto log = log_queue_.front();
                log_queue_.pop();
                switch(log.first) {
                    case LogLevel::ERROR:
                        file_stream_ << "[ERROR] ";
                        break;
                    case LogLevel::WARNING:
                        file_stream_ << "[WARNING] ";
                        break;
                    case LogLevel::INFO:
                        file_stream_ << "[INFO] ";
                        break;
                    case LogLevel::DEBUG:
                        file_stream_ << "[DEBUG] ";
                        break;
                }
                file_stream_ << log.second << std::endl;
            }
            if (exit_ && log_queue_.empty()) break;
        }
    }

    std::ofstream file_stream_;
    std::mutex mutex_;
    std::condition_variable cv_;
    std::queue<std::pair<LogLevel, std::string>> log_queue_;
    std::thread worker_;
    bool exit_;
};

int main() {
    auto logger = std::make_shared<Logger>("log.txt");
    logger->Log(LogLevel::INFO, "This is an info message");
    logger->Log(LogLevel::ERROR, "This is an error message");
    return 0;
}

在这个版本中,我们使用了一个std::queue来缓冲日志消息,使用了一个std::condition_variable来同步主线程和工作线程,使用了一个std::thread来执行日志写入。我们的Log函数将日志消息添加到队列中,然后通知工作线程。工作线程在收到通知后,会将队列中的所有日志消息写入文件。

版本三:添加定时删除的完整日志管理

要实现日志文件的定时清除功能,我们需要添加一些额外的代码来管理日志文件。以下是一个改进的版本,它会在每天的特定时间(例如午夜)创建一个新的日志文件,并删除30天前的日志文件:

#include <iostream>
#include <fstream>
#include <mutex>
#include <memory>
#include <thread>
#include <condition_variable>
#include <queue>
#include <chrono>
#include <filesystem>
#include <iomanip>

enum class LogLevel {
    ERROR,
    WARNING,
    INFO,
    DEBUG
};

class Logger {
public:
    Logger(const std::string& dir, int days) : exit_(false), dir_(dir), days_(days) {
        worker_ = std::thread(&Logger::writeLog, this);
    }

    ~Logger() {
        exit_ = true;
        cv_.notify_all();
        worker_.join();
    }

    void Log(LogLevel level, const std::string& message) {
        std::lock_guard<std::mutex> lock(mutex_);
        log_queue_.push(std::make_pair(level, message));
        cv_.notify_all();
    }

private:
    void writeLog() {
        while (true) {
            std::unique_lock<std::mutex> lock(mutex_);
            cv_.wait(lock, [this]{ return !log_queue_.empty() || exit_; });
            auto now = std::chrono::system_clock::now();
            auto date = std::chrono::system_clock::to_time_t(now);
            std::tm tm;
            localtime_s(&tm, &date);
            std::ostringstream oss;
            oss << dir_ << "/" << std::put_time(&tm, "%Y%m%d") << ".log";
            if (!file_stream_.is_open() || oss.str() != file_name_) {
                file_stream_.close();
                file_name_ = oss.str();
                file_stream_.open(file_name_, std::ios::out | std::ios::app);
                deleteOldLogs();
            }
            while (!log_queue_.empty()) {
                auto log = log_queue_.front();
                log_queue_.pop();
                switch(log.first) {
                    case LogLevel::ERROR:
                        file_stream_ << "[ERROR] ";
                        break;
                    case LogLevel::WARNING:
                        file_stream_ << "[WARNING] ";
                        break;
                    case LogLevel::INFO:
                        file_stream_ << "[INFO] ";
                        break;
                    case LogLevel::DEBUG:
                        file_stream_ << "[DEBUG] ";
                        break;
                }
                file_stream_ << log.second << std::endl;
            }
            if (exit_ && log_queue_.empty()) break;
        }
    }

    void deleteOldLogs() {
        auto now = std::chrono::system_clock::now();
        auto date = std::chrono::system_clock::to_time_t(now);
        std::tm tm;
        localtime_s(&tm, &date);
        tm.tm_mday -= days_;
        mktime(&tm);
        std::ostringstream oss;
        oss << dir_ << "/" << std::put_time(&tm, "%Y%m%d") << ".log";
        std::filesystem::remove(oss.str());
    }

    std::ofstream file_stream_;
    std::mutex mutex_;
    std::condition_variable cv_;
    std::queue<std::pair<LogLevel, std::string>> log_queue_;
    std::thread worker_;
    bool exit_;
    std::string dir_;
    std::string file_name_;
    int days_;
};

int main() {
    auto logger = std::make_shared<Logger>("logs", 30);
    logger->Log(LogLevel::INFO, "This is an info message");
    logger->Log(LogLevel::ERROR, "This is an error message");
    return 0;
}

在这个版本中,我们在writeLog函数中检查当前的日期,如果日期改变了,我们就创建一个新的日志文件,并删除30天前的日志文件。我们使用了C++17的std::filesystem库来删除文件。注意,这个代码只在支持C++17的编译器上有效。

这个代码的一个限制是,它假设日志文件的名字是按照"YYYYMMDD.log"的格式命名的。如果你的日志文件的命名规则不同,你需要修改writeLogdeleteOldLogs函数中的代码。

进一步探讨交流以及更多惊喜关注公众号联系我!再次欢迎关注、点赞、收藏,系列内容可以点击专栏目录订阅,感谢支持,祝大家祉猷并茂,顺遂无虞
在这里插入图片描述

若将文章用作它处,请一定注明出处,商用请私信联系我!

标签:std,完善,log,LogLevel,C++,_.,日志,include
From: https://blog.csdn.net/Z_oioihoii/article/details/144654583

相关文章

  • 《 C++ 点滴漫谈: 十三 》C++ 中的虚拟函数革命:virtual、override 和 final 如何改变你
    摘要这篇博客深入探讨了C++中virtual、override和final关键字的核心概念与使用技巧。我们从虚函数和多态的基本概念出发,讲解了如何通过virtual实现动态绑定,使程序能够在运行时根据对象类型调用适当的函数。接着,我们深入分析了override的使用,帮助开发者避免重写错......
  • C++大内存分配错误
    支持一对一答疑的购买网址C++无法分配大内存当影像较大时,m和n是int类型时,char*a=newchar[m*n]可能出现无法分配内存的错误原因分析:由于早期数据处理需求对内存需要较小,例如早期影像较小,影像长宽的积较小,char*a=newchar[m*n]不会出错。时代变化,影像体积变大,老代码仍旧使......
  • c++入门
    ⦁C++基础1.数据类型主要有五类数据类型:布尔类型,字符型,整型,浮点型和无类型。部分数据类型及所占位数:数据类型C++语言表示所占位数范围字符型char8b(1字节)-128~127或0~255无符号字符型unsignedchar8b(1字节)0~255整型int......
  • 只谈C++11新特性 - 删除函数
    删除函数背景在C++11之前,C++的类默认会生成拷贝构造函数和赋值运算符。这在某些情况下会引发问题,尤其是在我们希望明确禁止某些操作时。假设我们有一个类,它不希望被拷贝,但未明确声明拷贝构造函数和赋值运算符,这时编译器会自动生成默认实现,导致程序员可能无意间拷贝了该......
  • 基于java的SpringBoot/SSM+Vue+uniapp的员工日志管理信息系统的详细设计和实现(源码+l
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 期末复习c++时 发现以前没注意的点
    期末复习因为没有往年卷做现在闲得无聊导致的......
  • 13C++循环结构-for循环(3)
    一、回文数问题:“地满红花红满地,天连碧水碧连天”是一副回文联,用回文形式写成的对联,既可以顺读,也可以倒读,意思不变。在数学中也存在这样特征的一类数,称为回文数。设n是一任意自然数,将n各个数位上的数字反向排列所得自然数m,若m等于n,则n为回文数。例如,1234321是回文数,1234567不是......
  • C#使用Tesseract C++ API过程记录
    TesseractTesseract是一个开源的光学字符识别(OCR)引擎,最初由Hewlett-Packard(惠普)实验室开发,后来由Google收购并继续维护和开源贡献。Tesseract可以识别多种语言的文字,广泛应用于将图片或扫描文档中的文本内容转换成可编辑的文本格式。随着深度学习技术的发展,Tesseract......
  • C#使用Tesseract C++ API过程记录
    TesseractTesseract是一个开源的光学字符识别(OCR)引擎,最初由Hewlett-Packard(惠普)实验室开发,后来由Google收购并继续维护和开源贡献。Tesseract可以识别多种语言的文字,广泛应用于将图片或扫描文档中的文本内容转换成可编辑的文本格式。随着深度学习技术的发展,Tesseract也整合......
  • C#调用C++代码,以OpenCV为例
    前言使用C#调用C++代码是一个很常见的需求,因此本文以知名的C++机器视觉库OpenCV为例,说明在C#中如何通过使用P/Invoke(平台调用)来调用C++代码。只是以OpenCV为例,实际上在C#中使用OpenCV可以使用OpenCVSharp这个项目,这是一个很优秀的项目,GitHub地址:https://github.com/shimat/opencv......