Log4cplus 日志库

简介

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
2
//! Asynchronous append.
bool async;
1
2
log4cplus::helpers::Properties properties;
properties.setProperty(LOG4CPLUS_TEXT("AsyncAppend"), LOG4CPLUS_TEXT("true"));

class AsyncAppender

1
2
3
4
log4cplus::helpers::Properties properties;
log4cplus::SharedAppenderPtr fileAppend(new log4cplus::RollingFileAppender(properties));
log4cplus::SharedAppenderPtr asyncAppender(new log4cplus::AsyncAppender(fileAppend, 100));
m_logger.addAppender(asyncAppender);

class RollingFileAppender

1
2
3
4
5
6
RollingFileAppender(const log4cplus::tstring& filename,
long maxFileSize = 10*1024*1024,
int maxBackupIndex = 1,
bool immediateFlush = true,
bool createDirs = false);
RollingFileAppender(const log4cplus::helpers::Properties& properties);

Logger

记录器,保存并跟踪对象日志信息变更的实体,当你需要对一个对象进行记录时,就需要生成一个logger

1
2
3
4
5
6
7
8
9
10
11
12
void log(LogLevel ll, const log4cplus::tstring& message,
const char* file = LOG4CPLUS_CALLER_FILE (),
int line = LOG4CPLUS_CALLER_LINE (),
const char* function = LOG4CPLUS_CALLER_FUNCTION ()) const;
void log(spi::InternalLoggingEvent const &) const;

void forcedLog(LogLevel ll, const log4cplus::tstring& message,
const char* file = LOG4CPLUS_CALLER_FILE (),
int line = LOG4CPLUS_CALLER_LINE (),
const char* function = LOG4CPLUS_CALLER_FUNCTION ()) const;

void forcedLog(spi::InternalLoggingEvent const &) const;

Hierarchy

分类器,层次化的树型结构,用于对被记录信息的分类,层次中每一个节点维护一个logger的所有信息

1
2
3
4
5
6
7
typedef std::vector<Logger> ProvisionNode;
typedef std::map<log4cplus::tstring, ProvisionNode> ProvisionNodeMap;
typedef std::map<log4cplus::tstring, Logger> LoggerMap;

ProvisionNodeMap provisionNodes;
LoggerMap loggerPtrs;
Logger root;

LogLevel

1
typedef int LogLevel;
1
2
3
4
5
6
7
8
9
const LogLevel OFF_LOG_LEVEL     = 60000;
const LogLevel FATAL_LOG_LEVEL = 50000;
const LogLevel ERROR_LOG_LEVEL = 40000;
const LogLevel WARN_LOG_LEVEL = 30000;
const LogLevel INFO_LOG_LEVEL = 20000;
const LogLevel DEBUG_LOG_LEVEL = 10000;
const LogLevel TRACE_LOG_LEVEL = 0;
const LogLevel ALL_LOG_LEVEL = TRACE_LOG_LEVEL;
const LogLevel NOT_SET_LOG_LEVEL = -1;

Properties

属性,用于配置参数

1
2
3
4
5
6
7
8
9
10
log4cplus::helpers::Properties properties;
properties.setProperty(LOG4CPLUS_TEXT("File"), logFile.c_str());
properties.setProperty(LOG4CPLUS_TEXT("CreateDirs"), LOG4CPLUS_TEXT("true"));
properties.setProperty(LOG4CPLUS_TEXT("ImmediateFlush"), LOG4CPLUS_TEXT("true"));
properties.setProperty(LOG4CPLUS_TEXT("MaxFileSize"), std::to_wstring(max_file_size).c_str());
properties.setProperty(LOG4CPLUS_TEXT("MaxBackupIndex"), std::to_wstring(max_file_count).c_str());
properties.setProperty(LOG4CPLUS_TEXT("AsyncAppend"), s_async.c_str());
properties.setProperty(LOG4CPLUS_TEXT("Locale"), LOG4CPLUS_TEXT("en_US.UTF-8"));

log4cplus::SharedAppenderPtr fileAppend(new log4cplus::RollingFileAppender(properties));

可变参数格式化打印日志

示例

1
LOG4CPLUS_INFO(logger, LOG4CPLUS_TEXT("Hello world"));

原理

LOG4CPLUS_INFO_FMT

1
2
#define LOG4CPLUS_INFO_FMT(logger, ...)  \
LOG4CPLUS_MACRO_FMT_BODY (logger, INFO_LOG_LEVEL, __VA_ARGS__)

LOG4CPLUS_MACRO_FMT_BODY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define LOG4CPLUS_MACRO_FMT_BODY(logger, logLevel, ...)                 \
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_BUF

1
2
3
#  define LOG4CPLUS_MACRO_INSTANTIATE_SNPRINTF_BUF(var)     \
log4cplus::helpers::snprintf_buf & var \
= log4cplus::detail::get_macro_body_snprintf_buf ()

snprintf_buf::print

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
tchar 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_list

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
33
34
35
36
37
38
39
40
41
42
43
int
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 defined (EILSEQ)
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;
}
#endif

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;

#endif

str = &buf[0];
return printed;
}

其实就是使用了C语音的可变参数宏实现参数可变

  • va_start
  • va_arg
  • va_end

vadefs.h

1
2
3
4
5
6
7
#elif defined _M_IX86 && !defined _M_HYBRID_X86_ARM64

#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))

#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))

使用了 _vswprintf_p(sprintf) 做格式化

1
2
3
4
5
6
7
8
9
10
int _vswprintf_p(
wchar_t *buffer,
size_t count,
const wchar_t *format,
va_list argptr
);
int sprintf (
char * str,
const char * format,
... );

C语言的函数是从右往左压入栈的,比如一下内存分布

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void print_args(int count, ...) {
int i, value;
va_list arg_ptr;
va_start(arg_ptr, count);
for(i=0; i<count; i++) {
value = va_arg(arg_ptr,int);
printf("position %d = %d\n", i+1, value);
}
va_end(arg_ptr);
}
int main(int argc, char* argv[]) {
print_args(5,1,2,3,4,5);
return 0;
}
1
2
3
4
5
6
| 5 |   // 高位地址
| 4 |
| 3 |
| 2 |
| 1 | // arg_ptr
| 5 | // count

日志打印流程

流程

  • Logger::log
  • LoggerImpl::log:等级
  • LoggerImpl::forcedLog:获取 InternalLoggingEvent
  • LoggerImpl::callAppenders:遍历父子附加器
  • AppenderAttachableImpl::appendLoopOnAppenders:遍历附加器列表
  • Appender::doAppend:同步异步
  • Appender::syncDoAppend:检查阈值、过滤器、锁
  • FileAppenderBase::append:文件打开、锁定(进程同步)、格式化附加、刷新
  • SimpleLayout::formatAndAppend:附加

原理

Logger::log

1
2
3
4
5
6
void
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
11
void
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:获取 InternalLoggingEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
void
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
22
void
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
12
int
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
23
void
Appender::doAppend(const log4cplus::spi::InternalLoggingEvent& event)
{
#if ! defined (LOG4CPLUS_SINGLE_THREADED)
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
33
void
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
22
void
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
9
void
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
2
3
4
option(LOG4CPLUS_BUILD_TESTING "Build the test suite." OFF)
option(LOG4CPLUS_BUILD_LOGGINGSERVER "Build the logging server." OFF)
...
option(WITH_UNIT_TESTS "Enable unit tests" OFF)

编译成静态库

修改记录

https://github.com/huihut/log4cplus/commit/4e02f06a5549afca1183801a1424eee221a36bb5

修改内容

./src/CMakeLists.txt

1
2
3
add_compile_definitions (LOG4CPLUS_STATIC)
...
add_library (${log4cplus} STATIC ${log4cplus_sources})

使用

将日志输出到控制台

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
#include <log4cplus/log4cplus.h>

int main()
{
//用Initializer类进行初始化
log4cplus::Initializer initializer;

//第1步:创建ConsoleAppender
log4cplus::SharedAppenderPtr appender(new log4cplus::ConsoleAppender());

//第2步:设置Appender的名称和输出格式(SimpleLayout)
appender->setName(LOG4CPLUS_TEXT("console"));
appender->setLayout(std::unique_ptr<log4cplus::Layout>(new log4cplus::SimpleLayout));

//第3步:获得一个Logger实例,并设置其日志输出等级阈值
log4cplus::Logger logger = log4cplus::Logger::getInstance(LOG4CPLUS_TEXT ("test"));
logger.setLogLevel(log4cplus::INFO_LOG_LEVEL);

//第4步:为Logger实例添加ConsoleAppender
logger.addAppender(appender);

//第5步:使用宏将日志输出
LOG4CPLUS_INFO(logger, LOG4CPLUS_TEXT("Hello world"));

return 0;
}

将日志输出到控制台并写入文件

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
33
34
35
36
#include <log4cplus/log4cplus.h>

int main()
{
//用Initializer类进行初始化
log4cplus::Initializer initializer;

//第1步:创建ConsoleAppender和FileAppender(参数app表示内容追加到文件)
log4cplus::SharedAppenderPtr consoleAppender(new log4cplus::ConsoleAppender);
log4cplus::SharedAppenderPtr fileAppender(new log4cplus::FileAppender(
LOG4CPLUS_TEXT("log.txt"),
std::ios_base::app));

//第2步:设置Appender的名称和输出格式
//ConsoleAppender使用SimpleLayout
//FileAppender使用PatternLayout
consoleAppender->setName(LOG4CPLUS_TEXT("console"));
consoleAppender->setLayout(std::unique_ptr<log4cplus::Layout>(new log4cplus::SimpleLayout()));

fileAppender->setName(LOG4CPLUS_TEXT("file"));
log4cplus::tstring pattern = LOG4CPLUS_TEXT("%D{%m/%d/%y %H:%M:%S,%Q} [%t] %-5p %c - %m [%l]%n");
fileAppender->setLayout(std::unique_ptr<log4cplus::Layout>(new log4cplus::PatternLayout(pattern)));

//第3步:获得一个Logger实例,并设置其日志输出等级阈值
log4cplus::Logger logger = log4cplus::Logger::getInstance(LOG4CPLUS_TEXT ("test"));
logger.setLogLevel(log4cplus::INFO_LOG_LEVEL);

//第4步:为Logger实例添加ConsoleAppender和FileAppender
logger.addAppender(consoleAppender);
logger.addAppender(fileAppender);

//第5步:使用宏将日志输出
LOG4CPLUS_INFO(logger, LOG4CPLUS_TEXT("Hello world"));

return 0;
}

log4cplusplus

https://github.com/huihut/log4cplusplus

简介

log4cplusplus 是 log4cplus 的包装库

  • 线程安全
  • 支持异步
  • 支持中文路径和内容
  • 支持输出到文件、控制台、调试器
  • 支持格式化打印

接口

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
33
34
35
36
37
38
enum Log4CPlusPlusLevel
{
LogDebugLevel = 10000,
LogInfoLevel = 20000,
LogWarnLevel = 30000,
LogErrorLevel = 40000
};

class Log4CPlusPlus
{
public:
Log4CPlusPlus() {}
virtual ~Log4CPlusPlus() {}

public:
virtual void Release() = 0;

virtual void AddFileAppender(
const wchar_t *file_path = DEFALT_LOG_FILE_PATH,
const wchar_t *file_name = DEFALT_LOG_FILE_NAME,
unsigned long max_file_size = DEFALT_MAX_FILE_SIZE,
unsigned long max_file_count = DEFALT_MAX_FILE_COUNT,
bool is_async = DEFALT_IS_ASYNC
) = 0;

virtual void EnableDebuggerAppender(bool enable) = 0;
virtual void EnableConsoleAppender(bool enable) = 0;

virtual const wchar_t * GetLogPath() = 0;

virtual void WriteLog(
Log4CPlusPlusLevel logLevel,
const char* file,
int line,
const char* function,
const wchar_t *format, ...) = 0;

};

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "log4cplusplus.h"

int main(int argc, char *argv[])
{
log4cplus::Log4CPlusPlus* log = CreateLog4CPlusPlus();
if (log)
{
log->AddFileAppender();
log->EnableDebuggerAppender(true);
log->EnableConsoleAppender(true);

LOG4CPLUSPLUS_DEBUG(log, L"log test");
LOG4CPLUSPLUS_INFO(log, L"log test %s", L"info");
LOG4CPLUSPLUS_WARN(log, L"log test %s %d", L"warn", 123);
LOG4CPLUSPLUS_ERROR(log, L"log test %f", 3.14);
LOG4CPLUSPLUS_ERROR(log, L"中文日志测试!!");

log->Release();
}
return 0;
}