中文版本 | English Education
log4cpp是一个C++日志库, 参照log4j实现
特性:
- 通过JSON文件配置, 无需修改代码即可改变其行为
- 支持输出日志到STDOUT和STDERR
- 支持输出日志到指定文件
- 支持输出日志到TCP客户端
- 支持输出日志到UDP客户端
- 单例模式
- 线程安全
- 支持C++17及以上的C++编译器
- CMake 3.11及以上版本
- Boost >= 1.75
警告: 由于MSVC编译器的一些列bug,本项目不再支持MSVC。任何MSVC平台的错误都不再解决,建议使用MingW64
include(FetchContent)
FetchContent_Declare(log4cpp GIT_REPOSITORY https://github.com/SandroDickens/log4cpp.git GIT_TAG v3.0.5)
FetchContent_MakeAvailable(log4cpp)
target_link_libraries(${YOUR_TARGET_NAME} log4cpp)
{
"layout_pattern": "${yyyy}-${MM}-${dd} ${HH}:${mm}:${ss}:${ms} [${8TH}] [${L}] -- ${W}"
}
说明:
${yy}
: 2位数表示的年份. 如99, 03${yyyy}
: 完整的年份, 至少4位数, 用'-'表示公元前. 如-0055, 0787, 1999, 2003, 10191${M}
: 数字表示的月份, 无补0. 从1到12${MM}
: 数字表示的月份, 有补0的两位数. 从01到12${MMM}
: 月份的缩写, 3个字母. 从Jan到Dec${d}
: 月份中的第几天, 无补0. 从1到31${dd}
: 月份中的第几天, 有补0的两位数. 从01到31${h}
: 12小时制不补0的小时, AM和PM分别表示上午和下午. 从0到12${hh}
: 12小时制补0的小时. 从00到12${H}
: 24小时制不补0的小时. 从0到23${HH}
: 24小时制补0的小时. 从00到23${m}
: 无补0的分钟. 从1到59${mm}
: 有补0的分钟. 从01到59${s}
: 无补0的秒. 从1到59${ss}
: 有补0的秒. 从01到59${ms}
: 有补0的毫米. 从001到999${<n>TN}
: 线程名, 如${8TN}
.<n>
为线程名长度, 左对齐, 默认是16, 最大为16. 如果线程名为空, 使用"T+线程ID"代替, 如" main", "T12345"${<n>TH}
: 线程ID, 如${8TH}
.<n>
为线程ID位数, 左补0, 默认是8, 最大为8. 如"T12345"${L}
: 日志级别, 取值FATAL, ERROR, WARN, INFO, DEBUG, TRACE${W}
: 日志正文, 如"hello world!"
注意: ${\d+TH}
是一个正则表达式, 用于匹配线程id, 最大宽度为16. 某些系统无法设置线程名, 只能通过线程ID区分多线程
配置输出器有四种类型: 控制台输出器(console_appender), 文件输出器(file_appender), TCP输出器(tcp_appender), UDP输出器( udp_appender)
一个简单的配置文件示例:
{
"appenders": {
"console_appender": {
"out_stream": "stdout"
},
"file_appender": {
"file_path": "log/log4cpp.log"
},
"tcp_appender": {
"local_addr": "0.0.0.0",
"port": 9443
},
"udp_appender": {
"local_addr": "0.0.0.0",
"port": 9443
}
}
}
控制台输出器的作用是将日志输出到STDOUT或STDERR. 典型配置如下:
{
"appenders": {
"console_appender": {
"out_stream": "stdout"
}
}
}
说明:
out_stream
: 输出流, 可以是stdout或stderr
文件输出器的作用是将日志输出到指定文件. 典型配置如下:
{
"appenders": {
"file_appender": {
"file_path": "log/log4cpp.log"
}
}
}
说明:
file_path
: 输出文件名
TCP输出器内部会启动一个TCP服务器, 接受TCP连接, 将日志输出到连接的客户端, 用于输出日志到远程设备. 典型配置如下:
{
"appenders": {
"tcp_appender": {
"local_addr": "0.0.0.0",
"port": 9443
}
}
}
说明:
local_addr
: 监听地址. 如0.0.0.0
,::
,127.0.0.1
,::1
port
: 监听端口
注意: 如果有多个TCP客户端, 会便利所有客户端逐个发送日志
注意: 日志为明文传输, 注意隐私和安全问题. 后续不会支持加密传输, 如果需要加密建议先将日志加密后再传递个log4cpp
UDP输出器内部会启动一个UDP服务器, 将日志输出到连接的客户端, 用于输出日志到远程设备
与TCP输出器不同, UDP是无连接的, 需要注意:
- UDP是无连接的, 无法保证日志的完整性
- 需要客户端主动发送"hello"消息, 以便服务器获取客户端地址, 以便发送日志
- 客户端退出时需要发送"bye"消息, 以便服务器清理客户端地址, 否则客户端地址会一直保留直到因为日志发送失败而清理或程序退出
典型配置如下:
{
"appenders": {
"udp_appender": {
"local_addr": "0.0.0.0",
"port": 9443
}
}
}
说明:
local_addr
: 监听地址. 如0.0.0.0
,::
,127.0.0.1
,::1
port
: 监听端口
layout分为命名layout(配置名layouts
)和默认layout(配置名root_layout
), log4cpp::layout_manager::get_layout
时如果没有指定名称的layout, 则返回默认layout
注意: 命名layout可以没有, 但是默认layout必须有
命名layout是一个数组, 每个layout配置包括:
name
: layout名称, 用于获取layout, 不能重复, 不能是root
log_level
: log级别, 只有大于等于此级别的log才会输出appenders
: 输出器, 只有配置的输出器才会输出. 输出器可以是console_appender
,file_appender
,tcp_appender
,udp_appender
默认layout是一个对象, 只有log_level
和appenders
, 没有name
, 内部实现name
为root
{
"layouts": [
{
"name": "console_layout",
"log_level": "INFO",
"appenders": [
"console_appender",
"tcp_appender",
"udp_appender"
]
},
{
"name": "file_layout",
"log_level": "WARN",
"appenders": [
"file_appender"
]
}
],
"root_layout": {
"log_level": "INFO",
"appenders": [
"console_appender",
"file_appender",
"tcp_appender",
"udp_appender"
]
}
}
配置文件有两种加载方式:
- 如果当前路径下存在
log4cpp.json
, 会自动加载此配置文件 - 如果配置文件不在当前路径下, 或者文件名不是
log4cpp.json
, 需要手动加载配置文件
log4cpp::layout_manager::load_config("/config_path/log4cpp.json");
首先需要引入头文件:
#include "log4cpp.hpp"
然后获取layout实例. 通过name
获取配置layout, 如果不存在指定的layout, 则返回默认的root_layout
std::shared_ptr<log4cpp::layout> layout = log4cpp::layout_manager::get_layout("recordLogger");
获取layout后, 可以使用下面的方法输出log:
void trace(const char *__restrict fmt, ...);
void info(const char *__restrict fmt, ...);
void debug(const char *__restrict fmt, ...);
void warn(const char *__restrict fmt, ...);
void error(const char *__restrict fmt, ...);
void fatal(const char *__restrict fmt, ...);
上面的方法内部调用了下面的方法, 也可以直接调用下面的方法:
void log(log_level level, const char *fmt, ...);
其中log级别log_level level
的定义如下:
namespace log4cpp {
enum class log_level {
FATAL = 0, ERROR = 1, WARN = 2, INFO = 3, DEBUG = 4, TRACE = 5
};
}
说明:
FATAL
: 致命错误ERROR
: 错误WARN
: 警告INFO
: 信息DEBUG
: 调试TRACE
: 跟踪
#include <thread>
#ifdef __GNUC__
#include <pthread.h>
#endif
#include "log4cpp.hpp"
void set_thread_name(const char *name) {
#ifdef __GNUC__
pthread_setname_np(pthread_self(), name);
#elif __linux__
prctl(PR_SET_NAME, reinterpret_cast<unsigned long>("child"));
#endif
}
void thread_routine() {
set_thread_name("child");
std::shared_ptr<log4cpp::layout> log = log4cpp::layout_manager::get_layout("recordLayout");
log->trace("this is a trace");
log->info("this is a info");
log->debug("this is a debug");
log->warn("this is an warning");
log->error("this is an error");
log->fatal("this is a fatal");
}
int main() {
std::thread t(thread_routine);
set_thread_name("main");
std::shared_ptr<log4cpp::layout> log = log4cpp::layout_manager::get_layout("console_layout");
log->trace("this is a trace");
log->info("this is a info");
log->debug("this is a debug");
log->warn("this is an warning");
log->error("this is an error");
log->fatal("this is a fatal");
t.join();
return 0;
}
CMakeLists.txt示例:
set(TARGET_NAME demo)
add_executable(${TARGET_NAME} demo.cpp)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
file(COPY ./log4cpp.json DESTINATION ${EXECUTABLE_OUTPUT_PATH})
include(FetchContent)
FetchContent_Declare(log4cpp GIT_REPOSITORY https://github.com/SandroDickens/log4cpp.git GIT_TAG v3.0.5)
FetchContent_MakeAvailable(log4cpp)
target_link_libraries(${TARGET_NAME} log4cpp)
if (CMAKE_HOST_UNIX)
target_link_libraries(demo pthread)
endif ()
输出log示例:
2025-01-02 22:53:04:329 [ main] [INFO ] -- this is a info
2025-01-02 22:53:04:329 [ child] [ERROR] -- this is an error
2025-01-02 22:53:04:329 [ main] [WARN ] -- this is an warning
2025-01-02 22:53:04:329 [ child] [FATAL] -- this is a fatal
2025-01-02 22:53:04:329 [ main] [ERROR] -- this is an error
2025-01-02 22:53:04:329 [ main] [FATAL] -- this is a fatal
配置文件实例:
欢迎提交PR, 再提交PR之前有些事项需了解:
本项目优先使用本地boost库, 如果没找到本地boost库则从github在线拉取boost库
如果CMake没有自动找到Boost路径, 可以手动设置Boost路径:
if (CMAKE_HOST_WIN32)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(BOOST_ROOT "D:/OpenCode/boost/gcc")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
set(BOOST_ROOT "D:/OpenCode/boost/msvc")
endif ()
else ()
set(BOOST_ROOT "/usr/local/boost")
endif ()
cmake -S . -B build -DENABLE_DEMO=ON -DENABLE_TESTS=ON -DENABLE_ASAN=ON
cmake --build build --config=Debug
ctest --test-dir build
选项说明:
-DENABLE_DEMO=ON
: 编译demo, 默认不编译-DENABLE_TEST=ON
: 编译测试, 默认不开启-DENABLE_ASAN=ON
: 开启地址检测, 默认不开启
本项目使用Google Test进行单元测试, 测试代码在test目录下, 欢迎补充测试用例
如果你的代码修改了现有功能, 请确保测试用例覆盖到你的修改
如果你的代码修改了现有功能, 请确保ASAN检测通过, 未经ASAN检测通过的代码不会合并
缺失clang_rt.asan_dynamic-x86_64.dll?
如果设置了"ENABLE_ASAN=ON"
且使用的是MSVC编译器, 可能会遇到此问题. 解决办法是:
复制
"D:\Program Files\Microsoft Visual Studio\<Visual Studio Version>\Professional\VC\Tools\MSVC\<MSVC Version>\bin\Hostx64\x64\clang_rt.asan_dynamic-x86_64.dll"
到cmake-build-debug/bin/
本项目使用LGPLv3许可