日志器模块是对前述几个模块的整合,实现对日志信息的格式化与落地等功能。这里设计同步和异步两种日志器。
一个日志器所要包含的元素有:
需要提供的对外接口接口有:
void debug(const std::string &file, const size_t &line, const std::string fmt, ...)
void info(const std::string &file, const size_t &line, const std::string fmt, ...)
void warn(const std::string &file, const size_t &line, const std::string fmt, ...)
void error(const std::string &file, const size_t &line, const std::string fmt, ...)
void fatal(const std::string &file, const size_t &line, const std::string fmt, ...)
说明:这五种接口(对应物种日志等级)需要用户传入文件名,文件行号,日志主体信息(其他信息在日志器构造时就会确认),先将日志的主体信息(不定参信息)按照fmt转换为字符串,再用所有信息构造一个LogMsg,格式化后进行日志落地。
需要的对内接口:
void log(const char *data, size_t len) 说明:这是最后用于落地日志的接口,同步异步两周日志器的落地方向可能一致,但是落地方式一定不一致,基于这一点,我们也要创建一个日志器基类,将此接口设置为虚函数,交由子类实现。
serialize(LogLevel::value level, const std::string &file, const size_t &line, char *str) 说明:用于格式化日志信息的接口,在最后调用log进行日志落地。
由于对外的五种接口与serialize接口对于每一种日志器来说都是一样的,因此我们可将其在基类中实现,又因为五种接口的区别只是日志等级的不同,极为相似,所有在下面的代码中只展示出debug的实现细节,其他基本一致。
代码实现:
class Logger
{
public:
using ptr = std::shared_ptr;
Logger(const std::string &logger_name,
LogLevel::value limit_level,
Formatter::ptr &formatter,
std::vector &sinks)
: _logger_name(logger_name),
_limit_level(limit_level),
_formatter(formatter),
_sinks(sinks.begin(), sinks.end())
{
}
void debug(const std::string &file, const size_t &line, const std::string fmt, ...)
{
// 判断当前日志是否达到了输出等级
if (LogLevel::value::DEBUG < _limit_level)
return;
// 对数据进行格式化,得到日志消息字符串
char *res;
va_list vl;
va_start(vl, fmt);
int len = vasprintf(&res, fmt.c_str(), vl);
va_end(vl);
if (len == -1)
{
std::cerr << "vasprintf error!" << std::endl;
return;
}
serialize(LogLevel::value::DEBUG, file, line, res);
free(res);
}
protected:
void serialize(LogLevel::value level, const std::string &file, const size_t &line, char *str)
{
// 构造LogMsg
LogMsg msg(level, line, file, _logger_name, str);
// 格式化LogMsg
std::string message = _formatter->format(msg);
// 日志落地
log(message.c_str(), message.size());
}
virtual void log(const char *data, size_t len) = 0;
protected:
std::mutex _mutex;
std::string _logger_name;
std::atomic _limit_level;
Formatter::ptr _formatter;
std::vector _sinks;
};
同步日志器是指完成业务逻辑的线程与实现日志落地的线程是一个线程,因此它的log实现逻辑十分简单,直接调用_sinks中的落地函数即可。
代码实现:
class SyncLogger : public Logger
{
public:
SyncLogger(const std::string &logger_name,
LogLevel::value limit_level,
Formatter::ptr &formatter,
std::vector &sinks)
: Logger(logger_name, limit_level, formatter, sinks)
{
}
protected:
void log(const char *data, size_t len) override
{
std::unique_lock lock(_mutex);
for (auto &sink : _sinks)
{
sink->log(data, len);
}
}
};
异步日志器是指完成业务逻辑的线程与实现日志落地的线程不是一个线程,可避免日志输出阻塞而影响业务功能的情况,由于涉及到多线程,因此实现起来较为复杂,下一篇中我们再详细讲解。
由于构建一个日志器所需要的部件很多,先让用户自己先创建部件再创建日志器显然不合理,因此我们可以用建造者模式来建造日志器。
可以发现在建造日志器时有两个特点:
因此我们便不需要设计一个指挥者。
下面先给出代码再进行解释:
首先是日志器建造者基类:
enum class LoggerType
{
LOGGER_SYNC,
LOGGER_ASYNC
};
class LoggerBuilder
{
public:
LoggerBuilder() : _logger_type(LoggerType::LOGGER_SYNC),
_limit_level(LogLevel::value::DEBUG)
{
}
void buildLoggerType(LoggerType type) { _logger_type = type; }
void buildLoggerName(const std::string &name) { _logger_name = name; }
void buildLoggerLevel(LogLevel::value level) { _limit_level = level; }
void buildLoggerFormatter(const std::string &pattern)
{
_formatter = std::make_shared(pattern);
}
template
void buildLoggerSink(Args &&...args)
{
//LogSink::ptr sink = std::make_shared(std::forward(args)...);
LogSink::ptr sink = SinkFactory::createSink(std::forward(args)...);
_sinks.push_back(sink);
}
virtual Logger::ptr build() = 0;
protected:
LoggerType _logger_type;
std::string _logger_name;
std::atomic _limit_level;
Formatter::ptr _formatter;
std::vector _sinks;
};
说明:
派生类具体实现:
class LocalLoggerBuilder : public LoggerBuilder
{
public:
Logger::ptr build() override
{
assert(!_logger_name.empty());
if(_formatter == nullptr)
{
_formatter = std::make_shared();
}
if(_sinks.empty())
{
_sinks.push_back(std::make_shared());
}
if(_logger_type == LoggerType::LOGGER_ASYNC)
{
}
return std::make_shared(_logger_name, _limit_level, _formatter, _sinks);
}
};
}
说明: