# log **Repository Path**: bscrl/log ## Basic Information - **Project Name**: log - **Description**: 日志组件 - **Primary Language**: C++ - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-05-05 - **Last Updated**: 2024-09-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

日志系统

# 一、依赖的组件模块 # 二、提高代码灵活性和可移植性 ## 2.1 指定日志输出位置 ```c++ enum LoggingDestination { LOG_TO_NONE = 0, // 不记录日志 LOG_TO_FILE = 1 << 0, // 将日志记录到文件 LOG_TO_SYSTEM_DEBUG_LOG = 1 << 1, // 将日志记录到系统调试日志 // 这是一个组合值,表示同时记录到文件和系统调试日志。 LOG_TO_ALL = LOG_TO_FILE | LOG_TO_SYSTEM_DEBUG_LOG, // 在Windows上(OS_WIN),默认是将日志输出到文件(LOG_DEFAULT = LOG_TO_FILE)。 // 在POSIX兼容的操作系统上(如Linux、Mac等,OS_POSIX),由于可能无法轻松定位到可执行文件的位置, // 因此默认是将日志通过 OutputDebugString 输出到系统调试日志(LOG_DEFAULT = LOG_TO_SYSTEM_DEBUG_LOG)。 #if defined(OS_WIN) LOG_DEFAULT = LOG_TO_FILE, #elif defined(OS_POSIX) LOG_DEFAULT = LOG_TO_SYSTEM_DEBUG_LOG, #endif }; ``` LoggingDestination枚举类型用于指定日志记录的目标位置。 这样的设计允许在编译时通过定义不同的宏来指定日志记录的目标,提高了代码的灵活性和可移植性。同时,使用位掩码来表示不同的日志目标也允许在运行时通过位运算来动态地组合或修改日志目标。 - 灵活性: 枚举的定义本身就是一种灵活性的体现,而代码中又通过位运算符的方式来为运行时通过位运算来动态地组合或修改日志目标提供可能。 - 可移植性: 看到对平台检查的部分,采用`#if defined....#elif....#endif`的方式而非`#if defined ...#else...endif`就是为了以后可以更方便的扩展平台。 ## 2.2 通过预编译支持各种平台 ```c++ #if defined(OS_WIN) typedef HANDLE PlatformFile; #elif defined(OS_POSIX) typedef int PlatformFile; #if defined(OS_BSD) || defined(OS_MACOSX) || defined(OS_NACL) typedef struct stat stat_wrapper_t; #else typedef struct stat64 stat_wrapper_t; #endif #endif // defined(OS_POSIX) ``` # 三、控制日志输出 ## 3.1、解决跨平台情况下ERROR冲突 1. Windows SDK中的ERROR宏: 在Windows编程中,wingdi.h(或其他Windows SDK头文件) 可能会定义一个名为ERROR的宏,通常用于指示某个函数或操作失败。在这个上下文中,ERROR被定义为0。 2. 日志记录宏冲突: 假设现有的代码库使用了一个名为LOG(ERROR)的日志记录宏。 当在Windows环境下编译这段代码时,ERROR会被替换为0,因此LOG(ERROR)会被扩展为BAIDU_COMPACK_LOG(0)。 但是,我们自定义的错误级别不是0,而是3 3. 解决冲突: 为了解决这个问题,代码首先使用#undef ERROR取消了ERROR的原始定义。 然后,它定义了一个新的ERROR宏,其值为0,但这只是为了与Windows SDK保持一致, 而不是为了实际用于日志记录。 4. 定义新的日志严重性级别: 为了仍然能够使用LOG(ERROR)这种语法,同时确保它映射到正确的日志严重性级别, 代码定义了一个常量BLOG_0,并将其设置为BLOG_ERROR。 这样,当代码尝试使用LOG(ERROR)时,它实际上会映射到BAIDU_COMPACK_LOG(BLOG_ERROR), 而不是BAIDU_COMPACK_LOG(0)。 ```c++ #undef ERROR #define ERROR 0 // Needed for LOG_IS_ON(ERROR). const LogSeverity BLOG_0 = BLOG_ERROR; #endif ``` ## 3.2、支持运行时调整哪些日志应该被执行 通过使用 Google 的 gFlags库来定义命令行参数, 在程序运行时通过命令行参数控制哪些日志应该执行 >gFlags 是一个用于处理命令行标志(flags)和配置参数的库。 譬如: ```c++ # define BAIDU_VLOG_IS_ON(verbose_level, filepath) \ ({ static const int* vlocal = &::logging::VLOG_UNINITIALIZED; \ const int saved_verbose_level = (verbose_level); \ (saved_verbose_level >= 0)/*VLOG(-1) is forbidden*/ && \ (*vlocal >= saved_verbose_level) && \ ((vlocal != &::logging::VLOG_UNINITIALIZED) || \ (::logging::add_vlog_site(&vlocal, filepath, __LINE__, \ saved_verbose_level))); }) #else // GNU extensions not available, so we do not support --vmodule. // Dynamic value of FLAGS_verbose always controls the logging level. # define BAIDU_VLOG_IS_ON(verbose_level, filepath) \ (::logging::FLAGS_v >= (verbose_level)) #endif #define VLOG_IS_ON(verbose_level) BAIDU_VLOG_IS_ON(verbose_level, __FILE__) DECLARE_int32(v); extern const int VLOG_UNINITIALIZED; ``` DECLARE_int32(v); 这条语句声明了一个名为 v 的整数型(32位)命令行标志。 在运行时决定某个日志调用是否应该执行,这可以帮助优化性能,因为不必要的日志调用可以被避免。 宏允许你在运行时动态地控制日志记录的详细程度,而不必修改和重新编译源代码。 # 四、日志存储的底层缓冲区设计 给出一个反面案例: ```c++ std::ostringstream os; os<<"first log message"; os<<"second log message"; cout<)通过直接在这个数组上操作来避免 std::string 的内存分配。 使用底层缓冲区也需要更多的注意和谨慎,因为你需要手动管理内存和确保缓冲区不会溢出。此外,当你需要将日志内容转换为 std::string 或其他类型时,你可能需要执行额外的复制操作。 在设计日志系统时,一种常见的策略是使用一个循环缓冲区(circular buffer)或类似的机制来存储日志条目。每个日志条目可以有一个固定的最大大小,并且当缓冲区满时,较旧的日志条目可以被新的条目覆盖。这样可以确保在任意时刻,你都有一定数量的最新日志可供查看,而不会无限制地增长内存使用。 # 五、设计巧思 ```c++ // 这个宏的主要目的是帮助开发者识别和理解哪些代码路径尚未被实现。 // 当设置为在运行时记录错误日志时,它还可以帮助在测试或生产环境中捕获这些问题。 // 这个宏用于标记尚未实现的代码路径。其实现取决于NOTIMPLEMENTED_POLICY的值: // 0 -- 不执行任何操作(可能会被编译器优化掉) // 1 -- 在编译时发出警告 // 2 -- 在编译时失败(这通常是通过静态断言或其他编译器特定的技巧实现的) // 3 -- 在运行时失败(使用`DCHECK`或其他类似的运行时断言) // 4 -- [default]:在运行时记录一个错误日志(使用`LOG(ERROR)`) // 5 -- LOG(ERROR) at runtime, 每个调用点只记录一次 #endif // BRPC_WITH_GLOG #ifndef NOTIMPLEMENTED_POLICY #if defined(OS_ANDROID) && defined(OFFICIAL_BUILD) #define NOTIMPLEMENTED_POLICY 0 #else // Select default policy: LOG(ERROR) #define NOTIMPLEMENTED_POLICY 4 #endif #endif /*********************************** * 设计巧思: * 这样的设计使得当NOTIMPLEMENTED()宏被触发时,日志或错误信息中能够包含更多关于出错位置的上下文信息, * 特别是当使用GCC编译时。这对于快速定位和修复问题是非常有帮助的。 * 例如,如果你在函数void MyClass::MyFunction(int a, double b)中触发了NOTIMPLEMENTED()宏, * 并且你的代码是用GCC编译的,那么你将看到的错误信息可能类似于: * Not implemented reached in void MyClass::MyFunction(int, double) * 而如果你不是用GCC编译的,那么错误信息将简单地是: * NOT IMPLEMENTED * 这样的设计考虑了跨平台和跨编译器的兼容性,同时尽可能地提供了有用的调试信息。 * ************************************/ #if defined(COMPILER_GCC) // __PRETTY_FUNCTION__是GCC提供的一个宏, // 它可以展开为当前函数的签名,包括函数名、返回类型以及参数类型,这使得在调试或记录日志时能够提供更详细的信息。 #define NOTIMPLEMENTED_MSG "Not implemented reached in " << __PRETTY_FUNCTION__ #else #define NOTIMPLEMENTED_MSG "NOT IMPLEMENTED" #endif #if NOTIMPLEMENTED_POLICY == 0 #define NOTIMPLEMENTED() BAIDU_EAT_STREAM_PARAMS #elif NOTIMPLEMENTED_POLICY == 1 // TODO, figure out how to generate a warning #define NOTIMPLEMENTED() COMPILE_ASSERT(false, NOT_IMPLEMENTED) #elif NOTIMPLEMENTED_POLICY == 2 #define NOTIMPLEMENTED() COMPILE_ASSERT(false, NOT_IMPLEMENTED) #elif NOTIMPLEMENTED_POLICY == 3 #define NOTIMPLEMENTED() NOTREACHED() #elif NOTIMPLEMENTED_POLICY == 4 #define NOTIMPLEMENTED() LOG(ERROR) << NOTIMPLEMENTED_MSG #elif NOTIMPLEMENTED_POLICY == 5 #define NOTIMPLEMENTED() do { \ static bool logged_once = false; \ LOG_IF(ERROR, !logged_once) << NOTIMPLEMENTED_MSG; \ logged_once = true; \ } while(0); \ BAIDU_EAT_STREAM_PARAMS #endif #if defined(NDEBUG) && defined(OS_CHROMEOS) #define NOTREACHED() LOG(ERROR) << "NOTREACHED() hit in " \ << __FUNCTION__ << ". " #else #define NOTREACHED() DCHECK(false) #endif ``` 这段代码定义了一个名为NOTIMPLEMENTED()的宏,用于标记尚未实现的代码路径。它的实现受NOTIMPLEMENTED_POLICY控制,该策略定义了当NOTIMPLEMENTED()宏被触发时应该执行的操作。 以下是NOTIMPLEMENTED_POLICY各值的含义和相应的NOTIMPLEMENTED()宏的行为: 0: 什么也不做(被编译器优化掉)。 1: 在编译时发出警告。但注意,这里的COMPILE_ASSERT实际上并不在GCC中产生编译时警告,而是编译时错误(因为COMPILE_ASSERT通常用于在编译时检查某个条件是否为真)。 2: 在编译时失败(使用COMPILE_ASSERT)。 3: 在运行时失败(使用NOTREACHED(),这通常是DCHECK失败时的一个宏)。 4: 在运行时记录一个错误日志(使用LOG(ERROR)),这是默认行为。 5: 在运行时记录一个错误日志,但每个调用点只记录一次。 注意,这里还定义了一个NOTIMPLEMENTED_MSG宏,它包含当前函数名的去混淆版本(在GCC编译器下)或一个简单的字符串"NOT IMPLEMENTED"。 另外,BAIDU_EAT_STREAM_PARAMS这个宏在代码中被使用,但它在这段代码中没有定义。我猜测它可能是一个用于处理或忽略任何传递给NOTIMPLEMENTED()宏的额外参数(比如流操作符<<后面的内容)的宏。 在代码的最后,有一个条件编译块,它特定于NDEBUG(非调试模式)和OS_CHROMEOS(Chrome OS操作系统)。在这个条件下,NOTREACHED()宏被定义为一个记录日志的宏,类似于NOTIMPLEMENTED_POLICY为4时的行为,但日志消息略有不同。 但是,需要注意的是,代码中的NOTREACHED()宏在条件编译块的末尾被切断了,这可能导致编译错误。你应该确保这个宏的完整定义包含在你的代码库中。 这段代码提供了一种灵活的方式来处理未实现的代码路径,允许开发者根据他们的需要选择不同的处理策略。