日志器是一条日志从产生到落地过程中的指挥角色,日志器调用方法来获取日志对象,日志器调用方法获取格式化日志字符串,日志器调用方法控制落地对象进行日志打印。
因此日志器类中应具有一个formater格式化对象,落地对象数组(一条日志可以落地于多个方向)。
此外还需要一个互斥锁来保证多线程并发环境下的STL容器安全,一个string对象用来保存日志器的名称(日志器的唯一标识),以及日志限制等级
日志器抽象基类
class Logger //使用logger命名空间进行限定
{
protected:
std::string _name;
std::mutex _mtx;
std::atomic<LogLevel::Level> _limit_level; //对于大多数基本数据类型,如果需要线程安全,可以使用atomic进行修饰,避免管理额外的锁对象,atomic在汇编层面实现原子操作,避免锁竞争,效率更高
std::shared_ptr<Formater> _formater;
std::vector<std::shared_ptr<land::LogLand>> _lands;
public: /*完成日志消息的格式化*/
const std::string &getName() { return _name; }
Logger(const std::string &name, const std::shared_ptr<Formater> &formater,
std::vector<std::shared_ptr<land::LogLand>> &lands, LogLevel::Level limitlevel = LogLevel::DEBUG)
: _name(name), _formater(formater), _lands(lands.begin(), lands.end()), _limit_level(limitlevel)
{}
virtual ~Logger() {}
void debug(const std::string &file, size_t line, const std::string &fmt, ...); // fmt是日志消息正文
//*通过传入的参数构造一个日志消息对象,求其格式化之后的字符进一步输出
void info(const std::string &file, size_t line, const std::string &fmt, ...);
void warn(const std::string &file, size_t line, const std::string &fmt, ...);
void error(const std::string &file, size_t line, const std::string &fmt, ...);
void fatal(const std::string &file, size_t line, const std::string &fmt, ...);
protected:
void serialize(LogLevel::Level level, const std::string &file, size_t line, const char *ret);
virtual void log(const char *data, size_t len) = 0; // 统一输出
};
debug~~fatal一系列方法都是提供给用户进行日志打印的方法,用户调用这些方法后这些方法内部会调用serialize构建日志对象后将其格式化,在调用log方法来落地
debug、serialize方法实现
void logger::Logger::debug(const std::string &file, size_t line, const std::string &fmt, ...) //info~fatal方法类似
{
//*检查是否小于最低日志输出等级
if (LogLevel::DEBUG < _limit_level)
return;
//*对用户传入的日志消息正文进行解析
va_list ap;
va_start(ap, fmt);
char *body = nullptr;
int n = vasprintf(&body, fmt.c_str(), ap);
va_end(ap);
if (n == -1)
{
std::cerr << "正文解析错误\n";
return;
}
serialize(LogLevel::DEBUG, file, line, body);
free(body);
}
void logger::Logger::serialize(LogLevel::Level level, const std::string &file, size_t line, const char *body)
{ // 正文
//*构造日志消息对象
LogMsg msg(level, file, line, body, _name);
//*求出日志消息格式化的字符串
std::stringstream ss;
_formater->formatMsg(ss, msg);
//*将格式化完毕的日志消息字符串落地输出
log(ss.str().c_str(), ss.str().length());
}
同步日志器
class SynLogger : public Logger //! 同步日志器
{
public:
SynLogger(const std::string &name, const std::shared_ptr<Formater> &formater, std::vector<std::shared_ptr<land::LogLand>> &lands, LogLevel::Level limitlevel = LogLevel::DEBUG)
: Logger(name, formater, lands, limitlevel)
{}
protected:
virtual void log(const char *data, size_t len) override
{
std::lock_guard<std::mutex> lock(_mtx); //! 多线程访问STL容器时需要加锁保护
for (auto &lander : _lands)
lander->landing(data, len);
}
};
同步日志器最大的特点在于日志的落地由业务线程自身负责,缺点是在一些例如网络环境下可能会导致业务线程的阻塞。
异步日志器
class AsynLogger : public Logger //! 异步日志器
{
public:
AsynLogger(const std::string &name, const std::shared_ptr<Formater> &formater, std::vector<std::shared_ptr<land::LogLand>> &lands, LogLevel::Level limitlevel = LogLevel::DEBUG)
: Logger(name, formater, lands, limitlevel), _looper(new AsynLooper(std::bind(&AsynLogger::realLog, this, std::placeholders::_1))) //*绑定this参数
{}
protected:
virtual void log(const char *data, size_t len) override
{
_looper->push(data, len); //只需要把消息推入异步缓冲区即可返回
}
private: //_looper是异步日志器中的异步工作器,管理着异步缓冲区,异步写日志线程一系列属性,realLog是对异步缓冲区数据的处理方法
void realLog(Buffer &buf);
std::shared_ptr<AsynLooper> _looper;
};
//关于异步工作器的实现在下一文中给出详细交代
日志器建造者
日志器的构造函数较为复杂,需要传入大量的参数,增加了对用户的难度,容易出错,因此对于日志器这类比较大的类实例化可以采用建造者模式对每一个零部件进行构造
enum struct Mode
{
SYN, /*同步日志器,格式化完毕后由业务线程直接写入磁盘*/
ASYN /*异步日志器,格式化完毕后将字符串放入内存缓冲区,由其他线程负责写入磁盘*/
};
class LoggerBuilder /*各个零部件的初始化没有顺序,不需要指挥者*/
{ /*LoggerBuilder是一个抽象基类,由它派生出局部日志器建造者和全局日志器建造者*/
protected:
Mode _mode = Mode::SYN;
std::string _name;
LogLevel::Level _limit_level = LogLevel::Level::DEBUG;
std::shared_ptr<Formater> _formater;
std::vector<std::shared_ptr<land::LogLand>> _lands;
public:
virtual std::shared_ptr<Logger> build() = 0; // 返回局部日志器or全局日志器
void buildMode(Mode mode);
void buildName(const std::string &name);
void buildLevel(LogLevel::Level level);
void buildFormat(const std::string &format);
template <typename LandType, typename... Args>
void buildLand(Args &&...args)
{
auto item = land::LandFactory::create<LandType>(std::forward<Args>(args)...);
_lands.emplace_back(item);
}
//build系列的函数就是对指定的属性进行赋值,较为简单就不一一给出定义了
};