前景引入

当我们运行多线程/进程程序的时候,由于输出操作不具有原子性,于是就会出现多线程竞态访问的现象,导致控制台打印混乱、消息重叠等现象

日志的引入可以很好的解决此类问题,并且可以对程序的运行过程进行分类记录,灵活对文件/控制台进行原子性写入。

日志的作用

日志是软件系统运行过程中产生的结构化、可追溯的运行记录,是保障系统稳定、可观测、可维护的核心基础设施。其主要作用包括:

1. 故障定位与问题排查

日志记录程序执行流程、关键变量、异常信息与错误堆栈,是线上问题排查的核心依据,能够快速定位故障发生的时间、位置、原因与上下文,大幅降低问题修复成本。

2. 系统运行状态监控

通过日志可以实时观测服务启动、接口调用、请求耗时、资源使用等运行状态,为监控告警、性能统计、健康检查提供数据支撑,保障系统稳定运行。

3. 业务流程追溯与审计

日志可完整记录用户行为、操作记录、业务流转与关键决策,实现全链路可追溯,满足业务审计、合规检查、责任界定等需求。

4. 性能分析与系统优化

基于日志统计接口耗时、并发量、调用频次、资源瓶颈等指标,帮助开发者识别性能热点、优化逻辑、提升系统吞吐量与响应速度。

5. 安全监控与风险防范

记录登录、授权、敏感操作等行为,用于识别异常访问、暴力破解、越权操作等安全风险,为安全事件分析、入侵检测、事后溯源提供证据链。

6. 辅助开发与调试

在开发与测试阶段,日志替代传统打印方式,提供结构化、分级、可持久化的调试信息,提升开发效率与代码质量。

绝大部分语言都对日志进行了库函数封装,如C++的spdlog、C语言zlog等等,我们这里进行伪代码封装自己的基础日志系统。

1.预期效果

日志的核心目标是:按照预设格式原子性地写入目标文件,其核心用途是对多线程 / 多进程环境下的程序运行事件,进行完整、有序的过程记录。

2.使用接口与大致思路

头文件及接口

C++17原生文件系统库:

头文件<filesystem>

检查路径是否存在:std::filesystem::exists(path)判断文件 / 目录是否存在

创建多级目录:std::filesystem::create_directories(path)递归创建多级目录(如/home/uer/...)

C++原生内存管理库:

头文件<memory>

std::unique_ptr独占式智能指针:一个指针独占一个对象,自动释放内存(析构时调用 delete

std::make_unique安全创建 std::unique_ptr(C++14 引入):避免裸 new,减少异常安全问题

字符串流处理库:

头文件<sstream>

std::stringstream可读写的字符串流:既可以往里面写数据(拼接),也可以从里面读数据(解析)。

文件流操作库:

std::ofstream输出文件流:仅用于写文件。

大致思路

程序采用 “策略模式 + RAII 机制 + 流式拼接” 三大核心设计思想,实现 “可切换输出目标、自动释放资源、支持任意类型日志拼接” 的通用日志模块:

  1. 策略模式:把 “控制台输出” 和 “文件输出” 封装成独立的策略类,通过统一接口切换,扩展新输出方式(如网络日志)时无需修改核心逻辑;
  2. RAII 机制:利用 Logmessage 临时对象的析构函数自动输出日志,无需手动调用 “刷新 / 写入” 接口;
  3. 流式拼接:重载 operator<< 运算符,支持像 cout 一样拼接任意类型的日志内容;
  4. 线程安全:所有输出操作加互斥锁,避免多线程下日志乱序 / 残缺;
  5. 自动资源管理:用 std::unique_ptr 管理策略对象、std::ofstream 管理文件句柄,避免内存 / 文件句柄泄漏。

3.完整代码展示

策略模式

// 基类
class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &info) = 0;
    };
    // 子类,显示器打印
    class ScreenStratey : public LogStrategy
    {
    public:
        void SyncLog(const std::string &info) override
        {
            /* 输出逻辑控制代码块 */
        }

    private:
    };

    // 子类,指定文件打印
    std::string defaultpath = "./Log";
    std::string defaultfile = "Mylog.log";
    class FileStrategy : public LogStrategy
    {
    public:
        FileStrategy() {};
        void SyncLog(const std::string &message) override
        {
            /* 输出逻辑控制代码块 */
        }
        ~FileStrategy() {};

    private:
    };

RAII资源管理模式

class Logger
{
    /* ... */
    class Logmessage
    {
        /* ... */
        ~Logmessage()
        {
            _logger.ffulsh_strategy->SyncLog(finfo); // 析构函数调用输出策略
        };
    }

    operator()(LEVEL level, std::string name, int line) // 返回临时变量,结束自动调用析构
    {
        return Logmessage(level, name, line, *this);
    }
};

流式拼接

template<typename T> // 利用模版支持不同参数
Logmessage &operator<<(const T &info)
{
    std::stringstream ss;
    ss << info;
    finfo += ss.str();
    return *this; // 返回指针,实现链式调用实现可变参数
}

完整代码

#include <string>
#include <iostream>
#include "Mutex.hpp"
#include <cstdio>
#include <sstream>
#include <fstream>
#include <unistd.h>
#include <sys/types.h>
#include <memory>
#include <filesystem>
using namespace MutexModule;
namespace LogModule
{
#define gsep "\r\n"
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &info) = 0;
    };
    // 显示器打印
    class ScreenStratey : public LogStrategy
    {
    public:
        void SyncLog(const std::string &info) override
        {
            LockGuard _lock(_mutex);
            std::cout << info << gsep;
        }

    private:
        Mutex _mutex;
    };

    // 指定文件打印
    std::string defaultpath = "./Log";
    std::string defaultfile = "Mylog.log";
    class FileStrategy : public LogStrategy
    {
    public:
        FileStrategy(const std::string path = defaultpath, const std::string filename = defaultfile)
            : _path(path),
              _filename(filename)
        {
            if (std::filesystem::exists(_path))
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);

            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _filename; // "./log/" + "my.log"
            std::ofstream out(filename, std::ios::app);                                  // 追加写入的 方式打开
            if (!out.is_open())
            {
                return;
            }
            out << message << gsep;
            out.close();
        }
        ~FileStrategy() {};

    private:
        std::string _path;
        std::string _filename;
        Mutex _mutex;
    };
    enum class LEVEL
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    std::string leveltos(const LEVEL &level)
    {
        switch (level)
        {
        case LEVEL::DEBUG:
            return "DEBUG";
        case LEVEL::INFO:
            return "INFO";
        case LEVEL::WARNING:
            return "WARNING";
        case LEVEL::ERROR:
            return "ERROR";
        case LEVEL::FATAL:
            return "FATAL";
        default:
            return "UNKNOW";
        }
    }
    std::string GetTime()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm;
        localtime_r(&curr, &curr_tm);
        char timebuffer[128];
        snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",
                 curr_tm.tm_year + 1900,
                 curr_tm.tm_mon + 1,
                 curr_tm.tm_mday,
                 curr_tm.tm_hour,
                 curr_tm.tm_min,
                 curr_tm.tm_sec);
        return timebuffer;
    }
    // 默认执行类
    class Logger
    {
    public:
        Logger()
        {
            Enable_Screen_log_Strategy();
        }
        void Enable_Screen_log_Strategy()
        {
            ffulsh_strategy = std::make_unique<ScreenStratey>();
        }
        void Enable_File_log_Strategy()
        {
            ffulsh_strategy = std::make_unique<FileStrategy>();
        }
        class Logmessage
        {
        public:
            Logmessage(const LEVEL &level, std::string filename, int line, Logger &logger)
                : _ctime(GetTime()),
                  _level(level),
                  _pid(getpid()),
                  _filename(filename),
                  _line(line),
                  _logger(logger)

            {
                std::stringstream ss;
                ss << "[" << _ctime << "] "
                   << "[" << leveltos(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _filename << "] "
                   << "[" << _line << "] "
                   << "- ";
                finfo = ss.str();
            }
            template <typename T>
            Logmessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;
                finfo += ss.str();
                return *this;
            }
            ~Logmessage()
            {
                _logger.ffulsh_strategy->SyncLog(finfo);
            }

        private:
            std::string _ctime;
            LEVEL _level;
            pid_t _pid;
            std::string _filename;
            int _line;
            Logger &_logger;
            std::string finfo; // 完整信息
        };
        Logmessage operator()(LEVEL level, std::string name, int line)
        {
            return Logmessage(level, name, line, *this);
        }
        ~Logger()
        {
        }

    private:
        std::unique_ptr<LogStrategy> ffulsh_strategy;
    };
    Logger logger;
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Screen_log_Strategy logger.Enable_Screen_log_Strategy()
#define Enable_File_log_Strategy logger.Enable_File_log_Strategy()
}
// 日志格式:[ctime][level][pid][filename][line]

注:编译时需要添加-std=c++17选项

补充:互斥锁封装MUTEX.hpp

// Mutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>

namespace MutexModule
{
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }
        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
            (void)n;
        }
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
            (void)n;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }

    private:
        pthread_mutex_t _mutex;
    };

    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex) : _mutex(mutex)
        {
            _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.Unlock();
        }

    private:
        Mutex &_mutex;
    };
}

 

 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐