当前位置: 首页> 财经> 金融 > 网络广告策划流程_石家庄发布最新公告_裂变营销_海外新闻发布

网络广告策划流程_石家庄发布最新公告_裂变营销_海外新闻发布

时间:2025/7/9 16:05:40来源:https://blog.csdn.net/Wzs040810/article/details/141302962 浏览次数:0次
网络广告策划流程_石家庄发布最新公告_裂变营销_海外新闻发布

日志系统第四弹:日志落地模块

  • 一、设计
    • 1.需求
    • 2.设计
  • 二、实现框架
    • 1.标准输出日志落地类
    • 2.指定文件日志落地类
    • 3.按大小进行滚动【滚动文件日志落地类】
    • 4.抽象父类
  • 三、正式实现
    • 1.StdoutSink的完善
    • 2.FileSink的完善
    • 3.RollSinkBySize
  • 四、工厂类的实现
  • 五、测试
    • 1.文本信息测试
    • 2.二进制文件测试
    • 3.滚动文件测试
  • 六、演示扩展并测试
    • 1.扩展版本的实现
    • 2.测试

一、设计

1.需求

日志落地模块主要负责将格式化之后的日志消息输出到指定的落地方向

关键是支持多落地方向:

标准输出
指定文件
滚动文件(按大小进行滚动)
而且还支持用户灵活扩展:比如按时间进行滚动

2.设计

既然我们的日志落地模块要支持用户灵活扩展,而且日志落地类具有共性,因此我们可以借助多态来实现日志落地类的灵活扩展

因此需要搞一个抽象父类,实现具体子类
为了将对象的创建和使用分离,我们可以引入工厂模式

二、实现框架

1.标准输出日志落地类

标准输出不需要任何成员,直接用cout打印对应数据即可
因为数据不一定都是string,因此我们按char*来打印

class StdoutSink
{
public:void log(const char* data,size_t len);
};

为了实现高内聚低耦合,日志落地模块只需要负责提供日志落地的接口即可,因此 日志落地模块只需要对外提供这一个函数即可

2.指定文件日志落地类

因为一个FileSink对象只会向一个指定文件当中打印日志,所以为了防止每次打印频繁打开和关闭文件,因此我们对指定文件的ofstream对象采用长连接的做法

所以势必就需要将其搞成成员变量

class FileSink
{
public:FileSink(const std::string& filename);~FileSink();void log(const char* data,size_t len);
private:std::string _filename;std::ofstream _ofs;
};

3.按大小进行滚动【滚动文件日志落地类】

允许/需要 用户自行传入最大大小,因此需要一个max_size成员变量和cur_size文件当前大小
当然,basename(滚动文件基础文件名)和ofstream是少不了的,
因为文件是需要滚动的,因此我们需要给一个编号,又因为滚动文件对象会被多线程访问

因此编号的++需要是原子的,但是加锁又会降低多线程的并发效率,因此我们将编号设为原子类型

class RollBySize
{
public:RollBySize(const std::string &basename, size_t max_size);~RollBySize();void log(const char *data, size_t len);private:void check();std::string createNewFile();std::string _basename;std::ofstream _ofs;std::atomic<size_t> _name_count;size_t _max_size;size_t _cur_size;
};

check是用来检查并更新滚动文件
createNewFile是用来返回新的滚动文件名

滚动文件的命名方式:basename + 时间戳(年月日时分秒)+ 编号 + “.log”

4.抽象父类

前面说过,日志落地类对外提供的服务接口就是void log(const char* data,size_t len);

因此,我们只需要把他搞成纯虚函数即可
注意:因为我们后续要采用工厂设计模式来完成多态,所以不要忘了给抽象父类的析构函数加virtual

class LogSink
{
public:using ptr = std::shared_ptr<LogSink>;virtual ~LogSink() {}virtual void log(const char *data, size_t len) = 0;
};

三、正式实现

1.StdoutSink的完善

class StdoutSink : public LogSink
{
public:virtual void log(const char *data, size_t len){// 注意,不能用流插入直接写,因为写的不一定是字符串,而流插入写字符串只有遇到'\0'才会终止// 因此,我们要用std::cout.write(data,len);std::cout.write(data, len);}
};

2.FileSink的完善

class FileSink : public LogSink
{
public:FileSink(const std::string &filename): _filename(filename){// 1. 创建filename所在目录FileHelper::createDir(FileHelper::getPath(_filename));// 2. 打开文件(二进制 + 追加写)_ofs.open(_filename, std::ios::binary | std::ios::app);if (!_ofs.is_open()){std::cout << "FileSink 打开文件失败, 文件名: " << _filename << "\n";abort();}}~FileSink(){// 防止fd泄露if(_ofs.is_open()) _ofs.close();}virtual void log(const char *data, size_t len){_ofs.write(data,len);if(!_ofs.good()){std::cout << "FileSink 写入文件有问题, 文件名: " << _filename << "\n";abort();}}private:std::string _filename;std::ofstream _ofs;
};

3.RollSinkBySize

class RollSinkBySize : public LogSink
{
public:RollSinkBySize(const std::string &basename, size_t max_size): _basename(basename), _max_size(max_size), _cur_size(0), _name_count(0){check();}~RollSinkBySize(){// 防止fd泄露if (_ofs.is_open())_ofs.close();}virtual void log(const char *data, size_t len){check();_ofs.write(data, len);if (!_ofs.good()){std::cout << "RollSinkBySize 写入文件有问题, 基础文件名: " << _basename << "\n";abort();}_cur_size += len;}private:void check(){if (!_ofs.is_open() || _cur_size >= _max_size){// 0. 关闭旧文件if (_ofs.is_open())_ofs.close();// 1. 拿到新的文件名std::string newFile = createNewFile();// 2. 创建该文件FileHelper::createDir(FileHelper::getPath(newFile));// 3. 打开该文件_ofs.open(newFile, std::ios::app | std::ios::binary);if (!_ofs.is_open()){std::cout << "RollSinkBySize 打开文件有问题, 基础文件名: " << _basename << ", 该文件名: " << newFile << "\n";abort();}// 4. 清空cur_size_cur_size = 0;}}std::string createNewFile(){size_t count = _name_count.fetch_add(1); // 原子性进行++并返回++之前的值std::ostringstream oss;// 基础文件名 + 时间戳(年月日时分秒)+ 序号 + ".log"time_t now = DateHelper::now();struct tm *tm = localtime(&now);oss << _basename << tm->tm_year + 1900 << std::setw(2) << std::setfill('0') << tm->tm_mon + 1 << std::setw(2) << std::setfill('0') << tm->tm_mday<< std::setw(2) << std::setfill('0') << tm->tm_hour<< std::setw(2) << std::setfill('0') << tm->tm_min << std::setw(2) << std::setfill('0') << tm->tm_sec << "-" << count << ".log";return oss.str();}std::string _basename;std::ofstream _ofs;std::atomic<size_t> _name_count;size_t _max_size;size_t _cur_size;
};

四、工厂类的实现

我们采用简单工厂模式,但是因为简单工厂模式不符合开闭原则
且上面三个日志落地类所需参数都不相同
因此,我们采用C++的不定参和模板技术来创建对象

class LogSinkFactory
{
public:using ptr = std::shared_ptr<LogSinkFactory>;// 函数模板template <class SinkType, class... Args>static LogSink::ptr create(Args &&...args){return std::make_shared<SinkType>(std::forward<Args>(args)...);}
};

五、测试

我们读取指定文件的信息,并将其写入另一个新的指定文件,最后用md5值来来检测内容是否相同

1.文本信息测试

void test_sink()
{LogSink::ptr sp = LogSinkFactory::create<FileSink>("./log/data.txt");// 打开文件std::ifstream ifs("./data/data.txt", std::ios::binary | std::ios::in);// 将文件指针移动到末尾ifs.seekg(0, ifs.end);// 拿到文件总大小size_t sz = ifs.tellg();// 将文件指针移动到开头ifs.seekg(0, ifs.beg);// 读出数据来std::vector<char> data_vec(sz, '\0');ifs.read(&data_vec[0], sz);sp->log(&data_vec[0], sz);
}

在这里插入图片描述
完美

2.二进制文件测试

在这里插入图片描述
下面我们读取写入这个图片

void test_sink()
{LogSink::ptr sp = LogSinkFactory::create<FileSink>("./log/image.jpg");// 打开文件std::ifstream ifs("./data/image.jpg", std::ios::binary | std::ios::in);// 将文件指针移动到末尾ifs.seekg(0, ifs.end);// 拿到文件总大小size_t sz = ifs.tellg();// 将文件指针移动到开头ifs.seekg(0, ifs.beg);// 读出数据来std::vector<char> data_vec(sz, '\0');ifs.read(&data_vec[0], sz);sp->log(&data_vec[0], sz);
}

在这里插入图片描述
在这里插入图片描述
完美

3.滚动文件测试

void test_sink2()
{size_t max_size = 1024 * 1024; // 1MB是单个文件最大大小LogSink::ptr sp = LogSinkFactory::create<RollSinkBySize>("./log/roll-", max_size);const std::string data = "Hello -";// 写10MB的内容, 大概会分为10个文件for (size_t i = 0, num = 0; num < 10 * max_size; i++){std::string val = data + std::to_string(i) + "\n";sp->log(&val[0], val.size());num += val.size();}
}

在这里插入图片描述
在这里插入图片描述
可见,编号都是连续的,完美

六、演示扩展并测试

下面我们介绍一下:用户该如何扩展我们的日志落地模块
就拿以时间为单位的滚动文件落地模块为例

其实就是继承我们的LogSink抽象父类,重写其log函数即可

1.扩展版本的实现

为了提高用户的使用体验,我们就不要求用户传入具体的秒数了,而是给用户几个选项:

以秒 / 分钟 / 小时 / 天 为单位进行滚动

下面我们要考虑的是,如何判断是否需要进行文件滚动了

我们可以借助取余的思想,以分钟为单位举例:
当两个时间戳对60取余的结果相同时,就意味着这两个时间戳的差值一定是60的倍数

也就是说,两者之间必定至少经过了60秒

注意:任何数对1取余的结果都是0,因此以秒为单位时,只需要判断两个时间戳是否相等即可

因此我们需要保存并维护一个时间戳

enum class LogTimeGap
{SECOND = 0,MINUTE,HOUR,DAY
};#define FormatAdjust(num, ch) std::setw(num) << std::setfill(ch)class RollSinkByTime : public LogSink
{
public:RollSinkByTime(const std::string &basename, LogTimeGap time_gap): _basename(basename), _name_count(0){if (time_gap == LogTimeGap::SECOND)_time_gap = 1;else if (time_gap == LogTimeGap::MINUTE)_time_gap = 60;else if (time_gap == LogTimeGap::HOUR)_time_gap = 3600;else if (time_gap == LogTimeGap::DAY)_time_gap = 3600 * 12;}virtual void log(const char *data, size_t len){check();_ofs.write(data, len);if (!_ofs.good()){std::cout << "RollSinkByTime写入文件失败,文件基础名:" << _basename << "\n";abort();}}private:void check();std::string createNewFile(){size_t count = _name_count.fetch_add(1);std::ostringstream oss;time_t now = DateHelper::now();struct tm *tm = localtime(&now);oss << _basename << tm->tm_year + 1900 << FormatAdjust(2, '0') << tm->tm_mon + 1 << FormatAdjust(2, '0') << tm->tm_mday<< FormatAdjust(2, '0') << tm->tm_hour << FormatAdjust(2, '0') << tm->tm_min << FormatAdjust(2, '0') << tm->tm_sec<< "-" << count << ".log";return oss.str();}std::string _basename;std::ofstream _ofs;std::atomic<size_t> _name_count;time_t _prev_time;size_t _time_gap;
};

上面没啥好说的,大家单独看一下check即可

void check()
{bool ok = !_ofs.is_open();time_t cur_time = DateHelper::now();if (_time_gap == 1 && _prev_time != cur_time)ok = true;else if (_time_gap != 1 && _prev_time % _time_gap == cur_time % _time_gap)ok = true;if (ok){_prev_time = cur_time;std::string newFile = createNewFile();FileHelper::createDir(FileHelper::getPath(newFile));if (_ofs.is_open())_ofs.close();_ofs.open(newFile, std::ios::binary | std::ios::app);if (!_ofs.is_open()){std::cout << "RollSinkByTime写入文件失败,文件基础名:" << _basename << "\n";abort();}}
}

2.测试

int main()
{LogSink::ptr sp = LogSinkFactory::create<RollSinkByTime>("./log/roll-", LogTimeGap::SECOND);const std::string str = "Hello World\n";time_t now = DateHelper::now();while (DateHelper::now() < now + 5) // 给5s{sp->log(&str[0], str.size());}return 0;
}

在这里插入图片描述
完美

以上就是日志系统第四弹:日志落地模块的全部内容哦

关键字:网络广告策划流程_石家庄发布最新公告_裂变营销_海外新闻发布

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: