Linux_C++_日志实例

Logger.hpp

cpp 复制代码
#ifndef __LOGGER_HPP
#define __LOGGER_HPP

#include <iostream>
#include <cstdio>
#include <string>
#include <memory>
#include <sstream>
#include <ctime>
#include <sys/time.h>
#include <unistd.h>
#include <filesystem> // C++17
#include <fstream>
#include "Mutex.hpp"

namespace NS_LOG_MODULE
{
    enum class LogLevel
    {
        INFO,
        WARNING,
        ERROR,
        FATAL,
        DEBUG
    };
    std::string LogLevel2Message(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        case LogLevel::DEBUG:
            return "DEBUG";
        default:
            return "UNKNOWN";
        }
    }

    // 1. 时间戳 2. 日期+时间
    std::string GetCurrentTime()
    {
        struct timeval current_time;
        int n = gettimeofday(&current_time, nullptr);
        (void)n;

        // current_time.tv_sec; current_time.tv_usec;
        struct tm struct_time;
        localtime_r(&(current_time.tv_sec), &struct_time); // r: 可重入函数
        char timestr[128];
        snprintf(timestr, sizeof(timestr), "%04d-%02d-%02d %02d:%02d:%02d.%ld",
                 struct_time.tm_year + 1900,
                 struct_time.tm_mon + 1,
                 struct_time.tm_mday,
                 struct_time.tm_hour,
                 struct_time.tm_min,
                 struct_time.tm_sec,
                 current_time.tv_usec);
        return timestr;
    }

    // 输出角度 -- 刷新策略
    // 1. 显示器打印
    // 2. 文件写入
    // 策略模式,策略接口
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };
    // 控制台日志刷新策略, 日志将来要向显示器打印
    class ConsoleStrategy : public LogStrategy
    {
    public:
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cerr << message << std::endl; // ??
        }
        ~ConsoleStrategy()
        {
        }

    private:
        Mutex _mutex;
    };

    const std::string defaultpath = "./log";
    const std::string defaultfilename = "log.txt";


    // 文件策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &name = defaultfilename)
            : _logpath(path),
              _logfilename(name)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_logpath))
                return;
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const std::string &message) override
        {
            {
                LockGuard lockguard(_mutex);
                if (!_logpath.empty() && _logpath.back() != '/')
                {
                    _logpath += "/";
                }
                std::string targetlog = _logpath + _logfilename; // "./log/log.txt"
                std::ofstream out(targetlog, std::ios::app);     // 追加方式写入
                if (!out.is_open())
                {
                    std::cerr << "open " << targetlog << "failed" << std::endl;
                    return;
                }
                out << message << "\n";
                out.close();
            }
        }

        ~FileLogStrategy()
        {
        }

    private:
        std::string _logpath;
        std::string _logfilename;
        Mutex _mutex;
    };

    // 交给大家
    // const std::string defaultfilename = "log.info";
    // const std::string defaultfilename = "log.warning";
    // const std::string defaultfilename = "log.fatal";
    // const std::string defaultfilename = "log.error";
    // const std::string defaultfilename = "log.debug";
     // 文件策略&&分日志等级来进行保存
    // class FileLogLevelStrategy : public LogStrategy
    // {
    // public:
    // private:
    // };


    // 日志类:
    // 1. 日志的生成
    // 2. 根据不同的策略,进行刷新
    class Logger
    {
        // 日志的生成:
        // 构建日志字符串
    public:
        Logger()
        {
            UseConsoleStrategy();
        }
        void UseConsoleStrategy()
        {
            _strategy = std::make_unique<ConsoleStrategy>();
        }
        void UseFileStrategy()
        {
            _strategy = std::make_unique<FileLogStrategy>();
        }
        // 内部类, 标识一条完整的日志信息
        //  一条完整的日志信息 = 做半部分固定部分 + 右半部分不固定部分
        //  LogMessage RAII风格的方式,进行刷新
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, std::string &filename, int line, Logger &logger)
                : _level(level),
                  _curr_time(GetCurrentTime()),
                  _pid(getpid()),
                  _filename(filename),
                  _line(line),
                  _logger(logger)
            {
                // 先构建出来左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << LogLevel2Message(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _filename << "] "
                   << "[" << _line << "] "
                   << " - ";

                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this; // 返回当前LogMessage对象,方便下次继续进行<<
            }

            ~LogMessage()
            {
                if (_logger._strategy)
                {
                    _logger._strategy->SyncLog(_loginfo);
                }
            }

        private:
            LogLevel _level;
            std::string _curr_time;
            pid_t _pid;
            std::string _filename;
            int _line;
            std::string _loginfo; // 一条完整的日志信息

            // 一个引用,引用外部的Logger类对象
            Logger &_logger; // 方便我们后续进行策略式刷新
        };

        // 这里已经不是内部类了
        // 故意采用拷贝LogMessage
        LogMessage operator()(LogLevel level, std::string filename, int line)
        {
            return LogMessage(level, filename, line, *this);
        }

        ~Logger()
        {
        }

    private:
        std::unique_ptr<LogStrategy> _strategy; // 刷新策略
    };

    // 日志对象,全局使用
    Logger logger;

#define ENABLE_CONSOLE_LOG_STRATEGY() logger.UseConsoleStrategy();
#define ENABLE_FILE_LOG_STRATEGY() logger.UseFileStrategy();

#define LOG(level) logger(level, __FILE__, __LINE__)

}

#endif

Mutex.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <pthread.h>
#include "Logger.hpp"


class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_lock, nullptr);
    }
    void Lock()
    {
        pthread_mutex_lock(&_lock);
    }
    pthread_mutex_t *Ptr()
    {
        return &_lock;
    }
    void Unlock()
    {
        pthread_mutex_unlock(&_lock);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&_lock);
    }
private:
    pthread_mutex_t _lock;
};

class LockGuard // RAII风格代码
{
public:
    LockGuard(Mutex &lock):_lockref(lock)
    {
        _lockref.Lock();
    }
    ~LockGuard()
    {
        _lockref.Unlock();
    }
private:
    Mutex &_lockref;
};

Main.cc

cpp 复制代码
#include "Logger.hpp"
#include <thread>
#include <vector>

using namespace NS_LOG_MODULE;

void TestConsoleLog()
{
    std::cout << "========= 测试控制台输出 =========" << std::endl;
    // 默认是 ConsoleStrategy
    ENABLE_CONSOLE_LOG_STRATEGY();
    LOG(LogLevel::INFO) << "这是一条普通信息";
    LOG(LogLevel::WARNING) << "这是一个警告, 错误码: " << 404;
    LOG(LogLevel::DEBUG) << "调试信息: 变量 x = " << 3.14;
    sleep(1); // 暂停一下,方便观察时间戳变化
}

void TestFileLog()
{
    std::cout << "\n========= 测试文件输出 =========" << std::endl;
    
    // 切换到文件策略
    // 这会自动创建 ./log 目录和 log.txt 文件
    ENABLE_FILE_LOG_STRATEGY();
    
    std::cout << "策略已切换为文件,请查看 ./log/log.txt" << std::endl;

    LOG(LogLevel::INFO) << "这条信息应该出现在文件中";
    LOG(LogLevel::ERROR) << "数据库连接失败";
    LOG(LogLevel::FATAL) << "系统即将崩溃!!";
    
    // 模拟批量写入
    for(int i = 0; i < 5; i++) {
        LOG(LogLevel::DEBUG) << "文件日志计数: " << i;
    }
}

// 模拟多线程写日志,测试线程安全
void ThreadTask(int id)
{
    LOG(LogLevel::INFO) << "我是线程 " << id << ", 正在运行...";
}

void TestMultiThread()
{
    std::cout << "\n========= 测试多线程并发写入 =========" << std::endl;
    // 此时策略依然是文件策略
    std::vector<std::thread> threads;
    for(int i = 0; i < 5; i++)
    {
        threads.emplace_back(ThreadTask, i);
    }

    for(auto& t : threads)
    {
        t.join();
    }
    std::cout << "多线程测试完成,请检查日志文件是否有乱序或缺失。" << std::endl;
}

int main()
{
    // 1. 测试屏幕打印
    TestConsoleLog();

    // 2. 测试文件写入
    TestFileLog();

    // 3. 测试多线程并发
    TestMultiThread();

    // 4. 切回控制台(可选,演示灵活性)
    std::cout << "\n========= 切回控制台 =========" << std::endl;
    ENABLE_CONSOLE_LOG_STRATEGY();
    LOG(LogLevel::INFO) << "我又回到了屏幕上";

    return 0;
}

Makefile

bash 复制代码
CXX = g++
# 定义编译选项
# -Wall:  开启警告
# -g:     生成调试信息
# -MMD:   关键参数!自动生成依赖文件(.d),但在生成的依赖中忽略系统头文件(<iostream>等)
# -MP:    为头文件生成伪目标,防止删除头文件后导致 make 报错
CXXFLAGS = -MMD 
TARGET = main
SRCS = Main.cc

$(TARGET):$(SRCS)
	$(CXX) $(SRCS) -o $(TARGET)

.PHONY:clean
clean:
	rm -rf $(TARGET)
相关推荐
麦聪聊数据1 小时前
金融级数据库运维的“零信任”实践:如何在合规与效率间寻找平衡点?
运维·数据库·后端·sql·金融
头发还没掉光光1 小时前
C语言贪吃蛇:基于Linux中ncurses库实的贪吃蛇小游戏
linux·c语言·开发语言
为什么要做囚徒1 小时前
Docker实战系列之Root目录迁移指南:单机环境下的完整实践
运维·docker·容器
invicinble1 小时前
对于后端要和linux打交道要掌握的点
linux·运维·python
_Johnny_1 小时前
ubuntu将磁盘剩余空间自动分配指南
linux·运维·ubuntu
leiming61 小时前
linux 进程学习之信号
linux·运维·学习
若风的雨1 小时前
linux Page Table 和 TLB 操作总结
linux
AlenTech1 小时前
如何解决Ubuntu中使用系统pip报错的问题,error: externally-managed-environment
linux·ubuntu·pip
梵尔纳多2 小时前
第一个 3D 图像
c++·图形渲染·opengl