|
我们在使用日志的时候,为了方便调试,会希望日志中包含打日志的代码行号,文件等信息。如图所示。

通常的做法是通过内置的宏__FUNCTION__输出函数名,__FILE__输出文件名。如果想要在调用日志输出的地方输出这些信息,
就需要加上这些宏,为了方便,通常的做法是将日志封装成宏函数的形式,进行日志输出。

对于宏,有很多的优点,但是我个人不喜欢宏,调试不友好,只是进行简单的字符串替换,没有语法检查。
如果不使用宏,又没法在程序运行的时候获取需要的信息,因此,问题的关键是如何在非运行期获取行号等信息。
现代C++新增的constexpr关键字,并可以用于函数,因此我们可以使用这个特性,实现一个编译期的函数。以下是实现代码。
改代码基于C++17,C++20新增了source_location,可以获取行号,文件名等信息,但是可以使用C++17自己实现一个,
如果编译器支持C++20,可以进行简单改造。
class SourceLocation
{
public:
constexpr SourceLocation(const char *fileName = __builtin_FILE(), const char *funcName = __builtin_FUNCTION(),
std::uint32_t lineNum = __builtin_LINE()) noexcept
: _fileName(fileName), _funcName(funcName), _lineNum(lineNum)
{
}
[[nodiscard]] constexpr const char *FileName() const noexcept
{
return _fileName;
}
[[nodiscard]] constexpr const char *FuncName() const noexcept
{
return _funcName;
}
[[nodiscard]] constexpr std::uint32_t LineNum() const noexcept
{
return _lineNum;
}
private:
const char *_fileName;
const char *_funcName;
const std::uint32_t _lineNum;
};
inline constexpr auto GetLogSourceLocation(const SourceLocation &location)
{
return spdlog::source_loc{location.FileName(), static_cast<int>(location.LineNum()), location.FuncName()};
}
template <typename Mutex>
class HtmlFormatSink final : public spdlog::sinks::base_sink<Mutex>
{
public:
HtmlFormatSink(spdlog::filename_t baseFileName, std::size_t maxSize,
std::size_t maxFiles, bool rotateOnOpen = false,
const spdlog::file_event_handlers &eventHandlers = {})
: _mBaseFilename(std::move(baseFileName)),
_maxSize(maxSize), _maxFiles(maxFiles), _fileHelper(eventHandlers)
{
if (maxSize == 0)
{
spdlog::throw_spdlog_ex(&#34;rotating sink constructor: max_size arg cannot be zero&#34;);
}
if (maxFiles > 200000)
{
spdlog::throw_spdlog_ex(&#34;rotating sink constructor: max_files arg cannot exceed 200000&#34;);
}
_fileHelper.open(calc_filename(_mBaseFilename, 0));
// 写入html头
spdlog::memory_buf_t htmlHeader;
const char *pHtmlHeader =
R&#34;(<html>
<head>
<meta http-equiv=&#34;content-type&#34; content=&#34;text/html; charset-gb2312&#34;>
<title>Html Output</title>
</head>
<body>
<font face=&#34;Fixedsys&#34; size=&#34;2&#34; color=&#34;#0000FF&#34;>)&#34;;
htmlHeader.append(pHtmlHeader, pHtmlHeader + std::strlen(pHtmlHeader));
_fileHelper.write(htmlHeader);
_currentSize = _fileHelper.size(); // expensive. called only once
if (rotateOnOpen && _currentSize > 0)
{
rotate_();
_currentSize = 0;
}
}
static spdlog::filename_t
calc_filename(const spdlog::filename_t &fileName, std::size_t index)
{
// if (index == 0u)
//{
// return fileName;
// }
spdlog::filename_t basename;
spdlog::filename_t ext;
std::tie(basename, ext) = spdlog::details::file_helper::split_by_extension(fileName);
std::time_t timeVar = 0;
std::time(&timeVar);
char pTimeStr[64] = {0};
std::strftime(pTimeStr, sizeof(pTimeStr), &#34;%Y_%m_%d_%H_%M_%S&#34;, std::localtime(&timeVar));
return spdlog::fmt_lib::format(SPDLOG_FILENAME_T(&#34;{}{}.{}{}&#34;), basename, pTimeStr, index, ext);
}
spdlog::filename_t filename()
{
std::lock_guard<Mutex> lock(spdlog::sinks::base_sink<Mutex>::mutex_);
return _fileHelper.filename();
}
protected:
void sink_it_(const spdlog::details::log_msg &msg) override
{
spdlog::memory_buf_t formatted;
const char *pPrefix = GetLogLevelHtmlPrefix(msg.level);
// 填充html前缀
formatted.append(pPrefix, pPrefix + std::strlen(pPrefix));
spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted);
// 填充后缀
const char *pSuffix = R&#34;(<br></font>)&#34;;
formatted.append(pSuffix, pSuffix + std::strlen(pSuffix));
auto newSize = _currentSize + formatted.size();
// rotate if the new estimated file size exceeds max size.
// rotate only if the real size > 0 to better deal with full disk (see issue #2261).
// we only check the real size when new_size > max_size_ because it is relatively expensive.
if (newSize > _maxSize)
{
_fileHelper.flush();
if (_fileHelper.size() > 0)
{
rotate_();
newSize = formatted.size();
}
}
_fileHelper.write(formatted);
_currentSize = newSize;
}
void flush_() override
{
_fileHelper.flush();
}
private:
void rotate_()
{
using namespace spdlog;
using details::os::filename_to_str;
using details::os::path_exists;
_fileHelper.close();
for (auto i = _maxFiles; i > 0; --i)
{
filename_t src = calc_filename(_mBaseFilename, i - 1);
if (!path_exists(src))
{
continue;
}
filename_t target = calc_filename(_mBaseFilename, i);
if (!rename_file_(src, target))
{
// if failed try again after a small delay.
// this is a workaround to a windows issue, where very high rotation
// rates can cause the rename to fail with permission denied (because of antivirus?).
// todo 是否需要写入头?
details::os::sleep_for_millis(100);
if (!rename_file_(src, target))
{
_fileHelper.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit!
_currentSize = 0;
throw_spdlog_ex(&#34;rotating_file_sink: failed renaming &#34; +
filename_to_str(src) + &#34; to &#34; + filename_to_str(target),
errno);
}
}
}
_fileHelper.reopen(true);
// 写入html头
spdlog::memory_buf_t htmlHeader;
const char *pHtmlHeader =
R&#34;(<html>
<head>
<meta http-equiv=&#34;content-type&#34; content=&#34;text/html; charset-gb2312&#34;>
<title>Html Output</title>
</head>
<body>
<font face=&#34;Fixedsys&#34; size=&#34;2&#34; color=&#34;#0000FF&#34;>)&#34;;
htmlHeader.append(pHtmlHeader, pHtmlHeader + std::strlen(pHtmlHeader));
_fileHelper.write(htmlHeader);
}
bool rename_file_(const spdlog::filename_t &srcFileName,
const spdlog::filename_t &targetFileName)
{
// try to delete the target file in case it already exists.
(void)spdlog::details::os::remove(targetFileName);
return spdlog::details::os::rename(srcFileName, targetFileName) == 0;
}
constexpr const char *GetLogLevelHtmlPrefix(spdlog::level::level_enum level)
{
const char *pPrefix = &#34;&#34;;
switch (level)
{
case spdlog::level::trace:
pPrefix = R&#34;(<font color=&#34; #DCDFE4&#34;>)&#34;;
break;
case spdlog::level::debug:
pPrefix = R&#34;(<font color=&#34; #56B6C2&#34;>)&#34;;
break;
case spdlog::level::info:
pPrefix = R&#34;(<font color=&#34; #98C379&#34;>)&#34;;
break;
case spdlog::level::warn:
pPrefix = R&#34;(<font color=&#34; #E5C07B&#34;>)&#34;;
break;
case spdlog::level::err:
pPrefix = R&#34;(<font color=&#34; #E06C75&#34;>)&#34;;
break;
case spdlog::level::critical:
pPrefix = R&#34;(<font color=&#34; #DCDFE4&#34; style=&#34;background-color:#E06C75;&#34;>)&#34;;
break;
case spdlog::level::off:
case spdlog::level::n_levels:
break;
}
return pPrefix;
}
private:
spdlog::filename_t _mBaseFilename;
std::size_t _maxSize;
std::size_t _maxFiles;
std::size_t _currentSize;
spdlog::details::file_helper _fileHelper;
};
using HtmlFormatSink_mt = HtmlFormatSink<std::mutex>;
using HtmlFormatSink_st = HtmlFormatSink<spdlog::details::null_mutex>;
inline constexpr std::string_view GetDefaultLogPattern()
{
#if defined(_DEBUG) || defined(DEBUG)
return &#34;%^[%Y-%m-%d %T%e] (%s:%# %!) %l: %v%$&#34;;
#else
return &#34;%^[%Y-%m-%d %T%e] %l: %v%$&#34;;
#endif
}
class CLogger final
{
public:
static CLogger &GetLogger()
{
static CLogger logger;
return logger;
}
CLogger(const CLogger &) = delete;
CLogger &operator=(const CLogger &) = delete;
CLogger(CLogger &&) = delete;
// void SetLevel(spdlog::level::level_enum level)
// {
// _logger->set_level(level);
// }
void InitLogger(std::string_view fileName, size_t level, size_t maxFileSize, size_t maxFiles,
std::string_view pattern = GetDefaultLogPattern())
{
auto fileSink = std::make_shared<HtmlFormatSink_mt>(std::string(fileName),
maxFileSize * 1024 * 1024, maxFiles);
fileSink->set_level(static_cast<spdlog::level::level_enum>(level));
fileSink->set_pattern(std::string(pattern));
auto consoleSink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
consoleSink->set_level(static_cast<spdlog::level::level_enum>(level));
consoleSink->set_pattern(std::string(pattern));
std::vector<spdlog::sink_ptr> sinks{fileSink, consoleSink};
_logger = std::make_shared<spdlog::logger>(&#34;MultiLogger&#34;, std::begin(sinks), std::end(sinks));
_logger->set_level(static_cast<spdlog::level::level_enum>(level));
spdlog::set_default_logger(_logger);
}
private:
CLogger() = default;
~CLogger() = default;
private:
std::shared_ptr<spdlog::logger> _logger;
};
// trace
template <typename... Args>
struct trace
{
constexpr trace(fmt::format_string<Args...> fmt, Args &&...args, SourceLocation location = {})
{
spdlog::log(GetLogSourceLocation(location), spdlog::level::trace, fmt, std::forward<Args>(args)...);
}
};
template <typename... Args>
trace(fmt::format_string<Args...> fmt, Args &&...args) -> trace<Args...>;
// debug
template <typename... Args>
struct debug
{
constexpr debug(fmt::format_string<Args...> fmt, Args &&...args, SourceLocation location = {})
{
spdlog::log(GetLogSourceLocation(location), spdlog::level::debug, fmt, std::forward<Args>(args)...);
}
};
template <typename... Args>
debug(fmt::format_string<Args...> fmt, Args &&...args) -> debug<Args...>;
// info
template <typename... Args>
struct info
{
constexpr info(fmt::format_string<Args...> fmt, Args &&...args, SourceLocation location = {})
{
spdlog::log(GetLogSourceLocation(location), spdlog::level::info, fmt, std::forward<Args>(args)...);
}
};
template <typename... Args>
info(fmt::format_string<Args...> fmt, Args &&...args) -> info<Args...>;
// warn
template <typename... Args>
struct warn
{
constexpr warn(fmt::format_string<Args...> fmt, Args &&...args, SourceLocation location = {})
{
spdlog::log(GetLogSourceLocation(location), spdlog::level::warn, fmt, std::forward<Args>(args)...);
}
};
template <typename... Args>
warn(fmt::format_string<Args...> fmt, Args &&...args) -> warn<Args...>;
// error
template <typename... Args>
struct error
{
constexpr error(fmt::format_string<Args...> fmt, Args &&...args, SourceLocation location = {})
{
spdlog::log(GetLogSourceLocation(location), spdlog::level::err, fmt, std::forward<Args>(args)...);
}
};
template <typename... Args>
error(fmt::format_string<Args...> fmt, Args &&...args) -> error<Args...>;
// critical
template <typename... Args>
struct critical
{
constexpr critical(fmt::format_string<Args...> fmt, Args &&...args, SourceLocation location = {})
{
spdlog::log(GetLogSourceLocation(location), spdlog::level::critical, fmt, std::forward<Args>(args)...);
}
};
template <typename... Args>
critical(fmt::format_string<Args...> fmt, Args &&...args) -> critical<Args...>;
上面代码使用了spdlog进行封装,同时为了日志输出保留文本的等级颜色,我封装了一个html文件输出格式,可以达到终端与文件两个地方进行输出。
测试结果展示

参考: |
|