简介
Log4cplus 是 log4j 的 C++ 实现,其接口和使用逻辑与 log4j 基本保持一致。
- log4cplus 具有线程安全、灵活、以及多粒度控制的特点
- 可以通过将日志划分优先级使其可以面向程序调试、运行、测试、和维护等全生命周期
- 可以选择将日志输出到控制台、调试器、文件、服务器
- 可以通过指定策略对日志进行定期备份
许可协议
Log4cplus 的每个文件是使用二级BSD许可协议(Two clause BSD license)或者 Apache license 2.0 许可协议,其中的线程池(ThreadPool.h)又是使用另外的协议。
重要组成
类 | 说明 |
---|---|
Filter | 过滤器,过滤输出消息 |
Layout | 布局器,控制输出消息的格式 |
Appender | 附加器,将日志输出到所附加的设备终端如控制台、调试器、文件、远程服务器等等 |
Logger | 记录器,保存并跟踪对象日志信息变更的实体,当你需要对一个对象进行记录时,就需要生成一个logger |
Hierarchy | 分类器,层次化的树型结构,用于对被记录信息的分类,层次中每一个节点维护一个logger的所有信息 |
LogLevel | 优先权,包括TRACE, DEBUG, INFO, WARNING, ERROR, FATAL |
关系
- Hierarchy -> Logger -> Appender(Layout) -> Filter
- InternalLoggingEvent -> LogLevel
源码
Filter
过滤器,用于过滤日志项,可继承Filter自定义过滤器,也可用自带的过滤器
- Filter
- DenyAllFilter:全部过滤
- LogLevelMatchFilter:等级过滤
- LogLevelRangeFilter:等级范围过滤
- StringMatchFilter:字符串过滤
- FunctionFilter:方法函数过滤
- NDCMatchFilter
- MDCMatchFilter
Layout
布局器,控制输出日志消息的格式
- Layout
- SimpleLayout: DEBUG - Hello world
- TTCCLayout(time、thread、category、context):[0x60004b030] INFO SlowObject \
- Actually doing something - PatternLayout
class PatternLayout
- %%a – Abbreviated weekday name
- %%A – Full weekday name
- %%b – Abbreviated month name
- %%B – Full month name
- %%c – Standard date and time string
- %%d – Day of month as a decimal(1-31)
- %%H – Hour(0-23)
- %%I – Hour(1-12)
- %%j – Day of year as a decimal(1-366)
- %%m – Month as decimal(1-12)
- %%M – Minute as decimal(0-59)
- %%p – Locale’s equivalent of AM or PM
- %%q – milliseconds as decimal(0-999) – Log4CPLUS specific
- %%Q – fractional milliseconds as decimal(0-999.999) – Log4CPLUS specific
- %%S – Second as decimal(0-59)
- %%U – Week of year, Sunday being first day(0-53)
- %%w – Weekday as a decimal(0-6, Sunday being 0)
- %%W – Week of year, Monday being first day(0-53)
- %%x – Standard date string
- %%X – Standard time string
- %%y – Year in decimal without century(0-99)
- %%Y – Year including century as decimal
- %%Z – Time zone name
- %% – The percent sign
1 | new PatternLayout(LOG4CPLUS_TEXT("[%D{%Y-%m-%d %H:%M:%S.%q}] [%t] %-5p [%M] %m%n")); |
1 | [2020-08-24 01:30:43.650] [14168] DEBUG [main] log test |
Appender
附加器,将日志输出到所附加的设备终端如控制台、调试器、文件、远程服务器等等
class Appender
1 | //! Asynchronous append. |
1 | log4cplus::helpers::Properties properties; |
class AsyncAppender
1 | log4cplus::helpers::Properties properties; |
class RollingFileAppender
1 | RollingFileAppender(const log4cplus::tstring& filename, |
Logger
记录器,保存并跟踪对象日志信息变更的实体,当你需要对一个对象进行记录时,就需要生成一个logger
1 | void log(LogLevel ll, const log4cplus::tstring& message, |
Hierarchy
分类器,层次化的树型结构,用于对被记录信息的分类,层次中每一个节点维护一个logger的所有信息
1 | typedef std::vector<Logger> ProvisionNode; |
LogLevel
1 | typedef int LogLevel; |
1 | const LogLevel OFF_LOG_LEVEL = 60000; |
Properties
属性,用于配置参数
1 | log4cplus::helpers::Properties properties; |
可变参数格式化打印日志
示例
1 | LOG4CPLUS_INFO(logger, LOG4CPLUS_TEXT("Hello world")); |
原理
LOG4CPLUS_INFO_FMT1
2
LOG4CPLUS_MACRO_FMT_BODY (logger, INFO_LOG_LEVEL, __VA_ARGS__)
LOG4CPLUS_MACRO_FMT_BODY1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LOG4CPLUS_SUPPRESS_DOWHILE_WARNING() \
do { \
log4cplus::Logger const & _l \
= log4cplus::detail::macros_get_logger (logger); \
if (LOG4CPLUS_MACRO_LOGLEVEL_PRED ( \
_l.isEnabledFor (log4cplus::logLevel), logLevel)) { \
LOG4CPLUS_MACRO_INSTANTIATE_SNPRINTF_BUF (_snpbuf); \
log4cplus::tchar const * _logEvent \
= _snpbuf.print (__VA_ARGS__); \
log4cplus::detail::macro_forced_log (_l, \
log4cplus::logLevel, _logEvent, \
LOG4CPLUS_MACRO_FILE (), __LINE__, \
LOG4CPLUS_MACRO_FUNCTION ()); \
} \
} while(0) \
LOG4CPLUS_RESTORE_DOWHILE_WARNING()
LOG4CPLUS_MACRO_INSTANTIATE_SNPRINTF_BUF1
2
3
log4cplus::helpers::snprintf_buf & var \
= log4cplus::detail::get_macro_body_snprintf_buf ()
snprintf_buf::print1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19tchar const *
snprintf_buf::print (tchar const * fmt, ...)
{
assert (fmt);
tchar const * str = nullptr;
int ret = 0;
std::va_list args;
do
{
va_start (args, fmt);
ret = print_va_list (str, fmt, args);
va_end (args);
}
while (ret == -1);
return str;
}
snprintf_buf::print_va_list1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43int
snprintf_buf::print_va_list (tchar const * & str, tchar const * fmt,
std::va_list args)
{
int printed;
std::size_t const fmt_len = std::char_traits<tchar>::length (fmt);
std::size_t buf_size = buf.size ();
std::size_t const output_estimate = fmt_len + fmt_len / 2 + 1;
if (output_estimate > buf_size)
buf.resize (buf_size = output_estimate);
//...
printed = vsntprintf (&buf[0], buf_size - 1, fmt, args);
if (printed == -1)
{
if (errno == EILSEQ)
{
LogLog::getLogLog ()->error (
LOG4CPLUS_TEXT ("Character conversion error when printing"));
// Return zero to terminate the outer loop in
// snprintf_buf::print().
return 0;
}
buf_size *= 2;
buf.resize (buf_size);
}
else if (printed >= static_cast<int>(buf_size - 1))
{
buf_size = printed + 2;
buf.resize (buf_size);
printed = -1;
}
else
buf[printed] = 0;
str = &buf[0];
return printed;
}
其实就是使用了C语音的可变参数宏实现参数可变
- va_start
- va_arg
- va_end
vadefs.h1
2
3
4
5
6
7
使用了 _vswprintf_p(sprintf) 做格式化
1 | int _vswprintf_p( |
C语言的函数是从右往左压入栈的,比如一下内存分布
1 | void print_args(int count, ...) { |
1 | | 5 | // 高位地址 |
日志打印流程
流程
- Logger::log
- LoggerImpl::log:等级
- LoggerImpl::forcedLog:获取 InternalLoggingEvent
- LoggerImpl::callAppenders:遍历父子附加器
- AppenderAttachableImpl::appendLoopOnAppenders:遍历附加器列表
- Appender::doAppend:同步异步
- Appender::syncDoAppend:检查阈值、过滤器、锁
- FileAppenderBase::append:文件打开、锁定(进程同步)、格式化附加、刷新
- SimpleLayout::formatAndAppend:附加
原理
Logger::log1
2
3
4
5
6void
Logger::log (LogLevel ll, const log4cplus::tstring& message, const char* file,
int line, const char* function) const
{
value->log (ll, message, file, line, function ? function : "");
}
LoggerImpl::log:等级1
2
3
4
5
6
7
8
9
10
11void
LoggerImpl::log(LogLevel loglevel,
const log4cplus::tstring& message,
const char* file,
int line,
const char* function)
{
if(isEnabledFor(loglevel)) {
forcedLog(loglevel, message, file, line, function ? function : "");
}
}
LoggerImpl::forcedLog:获取 InternalLoggingEvent1
2
3
4
5
6
7
8
9
10
11
12
13void
LoggerImpl::forcedLog(LogLevel loglevel,
const log4cplus::tstring& message,
const char* file,
int line,
const char* function)
{
spi::InternalLoggingEvent & ev = internal::get_ptd ()->forced_log_ev;
assert (function);
ev.setLoggingEvent (this->getName(), loglevel, message, file, line,
function);
callAppenders(ev);
}
LoggerImpl::callAppenders:遍历父子附加器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22void
LoggerImpl::callAppenders(const InternalLoggingEvent& event)
{
int writes = 0;
for(const LoggerImpl* c = this; c != nullptr; c=c->parent.get()) {
writes += c->appendLoopOnAppenders(event);
if(!c->additive) {
break;
}
}
// No appenders in hierarchy, warn user only once.
if(!hierarchy.emittedNoAppenderWarning && writes == 0) {
helpers::getLogLog().error(
LOG4CPLUS_TEXT("No appenders could be found for logger (")
+ getName()
+ LOG4CPLUS_TEXT(")."));
helpers::getLogLog().error(
LOG4CPLUS_TEXT("Please initialize the log4cplus system properly."));
hierarchy.emittedNoAppenderWarning = true;
}
}
AppenderAttachableImpl::appendLoopOnAppenders:遍历附加器列表1
2
3
4
5
6
7
8
9
10
11
12int
AppenderAttachableImpl::appendLoopOnAppenders(const spi::InternalLoggingEvent& event) const
{
int count = 0;
thread::MutexGuard guard (appender_list_mutex);
for (auto & appender : appenderList)
{
++count;
appender->doAppend(event);
}
return count;
}
Appender::doAppend:同步异步1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23void
Appender::doAppend(const log4cplus::spi::InternalLoggingEvent& event)
{
if (async)
{
event.gatherThreadSpecificData ();
std::atomic_fetch_add_explicit (&in_flight, std::size_t (1),
std::memory_order_relaxed);
try
{
enqueueAsyncDoAppend (SharedAppenderPtr (this), event);
}
catch (...)
{
subtract_in_flight ();
throw;
}
}
else
#endif
syncDoAppend (event);
}
Appender::syncDoAppend:检查阈值、过滤器、锁1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33void
Appender::syncDoAppend(const log4cplus::spi::InternalLoggingEvent& event)
{
thread::MutexGuard guard (access_mutex);
if(closed) {
helpers::getLogLog().error(
LOG4CPLUS_TEXT("Attempted to append to closed appender named [")
+ name
+ LOG4CPLUS_TEXT("]."));
return;
}
// Check appender's threshold logging level.
if (! isAsSevereAsThreshold(event.getLogLevel()))
return;
// Evaluate filters attached to this appender.
if (checkFilter(filter.get(), event) == spi::DENY)
return;
// Lock system wide lock.
helpers::LockFileGuard lfguard;
if (useLockFile && lockFile.get ())
{
try
{
lfguard.attach_and_lock (*lockFile);
}
catch (std::runtime_error const &)
{
return;
}
}
// Finally append given event.
append(event);
}
FileAppenderBase::append:文件打开、锁定(进程同步)、格式化附加、刷新1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22void
FileAppenderBase::append(const spi::InternalLoggingEvent& event)
{
if(!out.good()) {
if(!reopen()) {
getErrorHandler()->error( LOG4CPLUS_TEXT("file is not open: ")
+ filename);
return;
}
// Resets the error handler to make it
// ready to handle a future append error.
else
getErrorHandler()->reset();
}
if (useLockFile)
out.seekp (0, std::ios_base::end);
layout->formatAndAppend(out, event);
if(immediateFlush || useLockFile)
out.flush();
}
SimpleLayout::formatAndAppend:附加1
2
3
4
5
6
7
8
9void
SimpleLayout::formatAndAppend(log4cplus::tostream& output,
const log4cplus::spi::InternalLoggingEvent& event)
{
output << llmCache.toString(event.getLogLevel())
<< LOG4CPLUS_TEXT(" - ")
<< event.getMessage()
<< LOG4CPLUS_TEXT("\n");
}
源码编译
默认编译
编译成动态库,带有很多例子项目
去除例子(只编译库)
修改记录
https://github.com/huihut/log4cplus/commit/5d7e51ac6a43e1eaa623e5d2272651458edf85c6
修改内容
./CMakeLists.txt
1 | option(LOG4CPLUS_BUILD_TESTING "Build the test suite." OFF) |
编译成静态库
修改记录
https://github.com/huihut/log4cplus/commit/4e02f06a5549afca1183801a1424eee221a36bb5
修改内容
./src/CMakeLists.txt
1 | add_compile_definitions (LOG4CPLUS_STATIC) |
使用
将日志输出到控制台
1 |
|
将日志输出到控制台并写入文件
1 |
|
log4cplusplus
https://github.com/huihut/log4cplusplus
简介
log4cplusplus 是 log4cplus 的包装库
- 线程安全
- 支持异步
- 支持中文路径和内容
- 支持输出到文件、控制台、调试器
- 支持格式化打印
接口
1 | enum Log4CPlusPlusLevel |
使用
1 |
|