日志与策略模式

什么是设计模式

IT行业这么火,涌入的人很多,俗话说林子大了啥鸟都有。大佬和菜鸡们两极分化的越来越严重。为了让菜鸡们不太拖大佬的后腿,于是大佬们针对一些经典的常见的场景,给定了一些对应的解决方案,这个就是设计模式。

认识日志

计算机中的日志是记录系统和软件运行中发生事件的文件,主要作用是监控运行状态、记录异常信息,帮助快速定位问题并支持程序员进行问题修复。它是系统维护、故障排查和安全管理的重要工具。

日志已有现成的解决方案,如:spdlog、glog、Boost.Log等等。

日志的格式有以下的标准:

1.时间戳 2.日志的等级 3.日志的内容 还可以加入: 1.文件名 2.行号 3.线程or进程的id

这里我们采用设计模式 - 策略模式来进行日志的设计!

我们想要实现的日志格式如下:

代码语言:javascript

AI代码解释

[可读性很好的时间] [⽇志等级] [进程pid] [打印对应⽇志的⽂件名][⾏号] - 消息内容,⽀持可变参数

[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [17] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [18] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [20] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [21] - hello world
[2024-08-04 12:27:03] [WARNING] [202938] [main.cc] [23] - hello world

前提

实现日志需要使用到锁,这里我们可以使用上一篇文章中封装好了的锁接口。【Linux系统】深入理解线程同步,实现生产消费模型-CSDN博客

代码语言:javascript

AI代码解释

Mutex.hpp

// 封装锁接口
#pragma once
#include <pthread.h>

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&mutex, nullptr);
    }

    ~Mutex()
    {
        pthread_mutex_destroy(&mutex);
    }

    void Lock()
    {
        pthread_mutex_lock(&mutex);
    }

    void Unlock()
    {
        pthread_mutex_unlock(&mutex);
    }

private:
    pthread_mutex_t mutex;
};

class LockGuard
{
public:
    LockGuard(Mutex &mutex)
        : _Mutex(mutex)
    {
        _Mutex.Lock();
    }

    ~LockGuard()
    {
        _Mutex.Unlock();
    }

private:
    // 为了保证锁的底层逻辑,锁是不能够拷贝的,并且也是没有拷贝构造函数的
    //  避免拷贝,应该引用
    Mutex &_Mutex;
};

日志刷新策略实现

日志的刷新策略可以分为两种:向屏幕刷新、向指定文件刷新

第一步,先实现日志中的刷新策略:

我们可以通过C++中的继承与多态来实现日志选择不同的刷新策略。

基类作为抽象类没有任何方法实现,不同的子类来继承后实现不同的刷新方式,其中不论是显示器刷新还是指定文件刷新,其都是临界资源,不能同时访问写入数据,否则将会导致信息错乱。所有刷新的过程全部都要加锁!

向显示器刷新没什么好说的

向文件刷新:其中对文件的路径要进行判断,如果文件的路径不存在,则需要我们创建对应的路径。其中需要使用的接口如下:

代码语言:javascript

AI代码解释

#include <filesystem>    //C++17标准
命名空间:std::filesystem 位于filesystem命名空间中

判断路径p是否存在,返回bool:exists(const path& p)
创建对应的路径:            create_directories(const path& p)

打开对应的路径:

代码语言:javascript

AI代码解释

#include <fstream>
命名空间:std 位于std命名空间中

创建对象时打开文件: ofstream ofs(path,openmode)

打开模式设置为追加模式:ios::app

具体实现:

代码语言:javascript

AI代码解释

const string end = "\r\n";

// 实现刷新策略:a.向显示器刷新 b.向指定文件刷新

// 利用多态机制实现
// 包含至少一个纯虚函数的类称为抽象类,不能实例化,只能被继承
class LogStrategy // 基类
{
public:
    //"=0"声明为纯虚函数。纯虚函数强制派生类必须重写该函数
    virtual void SyncLog(const string& message) = 0;
};

// 向显示器刷新:子类
class ConsoleLogStrategy : public LogStrategy
{
public:
    void SyncLog(const string& message) override
    {
        // 加锁,访问显示器,显示器也是临界资源
        LockGuard lockguard(_mutex);
        cout << message << end;
    }

private:
    Mutex _mutex;
};

// 向指定文件刷新:子类
const string defaultpath = "./log";
const string defaultfile = "my.log";
class FileLogStrategy : public LogStrategy
{
public:
    FileLogStrategy(const string& path = defaultpath, const string& file = defaultfile)
        : _path(path), _file(file)
    {
        LockGuard lockguard(_mutex);
        // 判断路径是否存在,如果不存在就创建对应的路径
        if (!(filesystem::exists(_path)))
            filesystem::create_directories(_path);
    }

    void SyncLog(const string& message) override
    {
        // 合成最后路径
        string Path = _path + (_path.back() == '/' ? "" : "/") + _file;
        // 打开文件
        ofstream out(Path, ios::app);
        //以流方式向文件写入数据
        out << message << end;
    }

private:
    string _path;
    string _file;
    Mutex _mutex;
};

日志文件生成与选择刷新策略实现

完成日志文件的刷新策略,下面实现日志文件的形成与刷新策略的选择。

第二步,实现日志文件对刷新策略的选择:

利用多态机制灵活选择刷新策略

代码语言:javascript

AI代码解释

class Logger
{
public:
    Logger()
    {
        // 默认选择显示器刷新
        Strategy = make_unique<ConsoleLogStrategy>();
    }
    
    //选择在显示器刷新
    void EnableConsoleLogStrategy()
    {
        Strategy = make_unique<ConsoleLogStrategy>();
    }

    //选择向指定文件刷新
    void EnableFileLogStrategy()
    {
        Strategy = make_unique<FileLogStrategy>();
    }
private:
    //智能指针
    unique_ptr<LogStrategy> Strategy;
};

第三步,实现日志文件的生成:

我们选择Logger类中实现LogMessage类的方式,来实现日志信息。

补充:

外部类只能通过内部类的实例化对象,来访问内部类中的方法与成员,且受修饰符限制。 内部类可以直接访问外部类的方法以及成员,没有限制

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐