问题描述

在测试有缓存的日志写入时,在Logger对象的析构函数中写了析构时的处理逻辑,但程序退出后未写满的Carrier总是无法写到文件中。经过调试发现,程序结束时Sinker先于前端Logger对象析构,导致在将日志转发给Sinker时抛出异常。

问题分析

经过排查调试和求助ChatGPT,问题在于对象的析构顺序,在初始的版本中,Logger对象是调用了SinkSplitter这个静态单例对象来绑定后端接收器Sinker和传递日志消息的,问题就出在SinkSplitter这个静态对象的实例化上,分流器对象的实例化是通过instance方法来延迟实例化的,这就导致当第一次调用了绑定接收器的函数时,分流器才会被实例化。C++对于静态对象的析构顺序是按照栈的方式先构造的对象后析构,这就导致了分流器的生命周期短于Logger对象,于是当Logger对象进入析构函数,并开始处理未来得及写入的日志时,分流器中保存的后端接收器已经跟随着分流器的析构而析构了。
归根究底,是没有处理好对象间的依赖关系,分流器的生命周期需要大于等于Logger对象,这样才能保证在Logger对象析构时,未来得及处理的日志信息仍能有机会处理。

解决办法

SinkSplitter的实例化方法不再返回引用,而是返回一个共享智能指针,然后在Logger对象中添加一个指向SinkSplitter的智能指针成员变量,在构造时将其指向分流器的静态对象,可以保证分流器会在Logger对象执行析构函数后再进行析构。