cmake 选项
以下是一个Markdown表格,展示了在编译时可能用到的CMake参数及其作用:
Parameter | Description |
CMAKE_BUILD_TYPE |
设置构建类型。可以是“Release”或“Debug”。如果未设置,默认为“Release”。 |
SPDLOG_USE_STD_FORMAT |
如果设置为ON,则使用std::format 而非fmt库。 |
CMAKE_CXX_STANDARD |
设置C++标准版本。如果SPDLOG_USE_STD_FORMAT 为ON,使用C++20;否则,默认为C++11。 |
MSVC |
针对Microsoft Visual C++特有的编译器标志,比如/Zc:__cplusplus /MP 。 |
CMAKE_CXX_EXTENSIONS |
设置是否使用编译器特定扩展。在CYGWIN或MSYS系统上为ON,其他系统默认为OFF。 |
SPDLOG_MASTER_PROJECT |
如果spdlog是主项目,则设置为ON;如果作为子项目通过add_subdirectory 包含,则设置为OFF。 |
SPDLOG_BUILD_SHARED |
设置为ON以构建共享库;否则构建静态库。 |
SPDLOG_ENABLE_PCH |
设置为ON以使用预编译头来加速编译时间。 |
SPDLOG_BUILD_PIC |
设置为ON以构建位置独立代码(-fPIC)。 |
SPDLOG_BUILD_EXAMPLE |
如果设置为ON,将构建示例。 |
SPDLOG_BUILD_EXAMPLE_HO |
如果设置为ON,将构建仅头文件版本的示例。 |
SPDLOG_BUILD_TESTS |
设置为ON以构建测试。 |
SPDLOG_BUILD_TESTS_HO |
设置为ON以构建测试使用的仅头文件版本。 |
SPDLOG_BUILD_BENCH |
设置为ON以构建基准测试(需安装Google Benchmark库)。 |
SPDLOG_SANITIZE_ADDRESS |
设置为ON以在测试中启用地址消毒器。 |
SPDLOG_BUILD_WARNINGS |
设置为ON以启用编译器警告。 |
SPDLOG_INSTALL |
设置为ON以生成安装目标。 |
SPDLOG_USE_STD_FORMAT |
设置为ON以使用std::format 而非fmt库。 |
SPDLOG_FMT_EXTERNAL |
设置为ON以使用外部fmt库而非捆绑的版本。 |
SPDLOG_FMT_EXTERNAL_HO |
设置为ON以使用外部fmt库的头文件版本。 |
SPDLOG_NO_EXCEPTIONS |
设置为ON以编译时禁用异常处理,改为在spdlog异常时调用abort()。 |
SPDLOG_WCHAR_SUPPORT |
Windows专用,设置为ON以支持宽字符API。 |
SPDLOG_WCHAR_FILENAMES |
Windows专用,设置为ON以支持宽字符文件名。 |
SPDLOG_CLOCK_COARSE |
Linux专用,设置为ON以使用CLOCK_REALTIME_COARSE 而非常规时钟。 |
SPDLOG_PREVENT_CHILD_FD |
设置为ON以防止子进程继承日志文件描述符。 |
SPDLOG_NO_THREAD_ID |
设置为ON以阻止spdlog在每次日志调用时查询线程ID(如果线程ID不需要)。 |
SPDLOG_NO_TLS |
设置为ON以阻止spdlog使用线程本地存储。 |
SPDLOG_NO_ATOMIC_LEVELS |
设置为ON以防止spdlog使用std::atomic日志级别(仅当代码不会并发修改日志级别时使用)。 |
SPDLOG_DISABLE_DEFAULT_LOGGER |
设置为ON以禁用默认日志器的创建。 |
SPDLOG_TIDY |
如果CMake版本大于3.5,设置为ON以运行clang-tidy。 |
这个表格涵盖了在CMake中构建spdlog时可能会用到的主要参数,以及它们对构建过程的影响。
编译步骤(按需选择)
可以使用以下CMake命令行参数来构建spdlog
:
- 进入构建目录:首先,您需要创建一个
build
目录(如果还没有的话),然后进入该目录。例如:
mkdir build cd build
- 运行CMake:使用下面的命令来配置和生成构建系统。请确保替换
[PathToYourToolchainFile]
为您的ToolchainFile.cmake
文件的实际路径。
cmake .. -DCMAKE_BUILD_TYPE=Release \ -DSPDLOG_MASTER_PROJECT=ON \ -DSPDLOG_BUILD_SHARED=ON \ -DSPDLOG_BUILD_PIC=ON \ -DSPDLOG_BUILD_WARNINGS=ON \ -DCMAKE_TOOLCHAIN_FILE=[PathToYourToolchainFile]
- 这条命令做了以下几点:
- 设置构建类型为
Release
。 - 将
SPDLOG_MASTER_PROJECT
设置为ON
,表示spdlog
是主项目。 - 将
SPDLOG_BUILD_SHARED
设置为ON
,以构建共享库。 - 将
SPDLOG_BUILD_PIC
设置为ON
,以生成位置独立的代码。 - 将
SPDLOG_BUILD_WARNINGS
设置为ON
,以启用编译器警告。 - 指定交叉编译的工具链文件。
- 构建项目:在配置完成后,使用以下命令来构建项目:
cmake --build .
指定安装目录
如果您想指定一个自定义的安装目录,您可以使用CMAKE_INSTALL_PREFIX变量来设置这个目录。这个变量定义了项目安装的根目录。例如,如果您想将spdlog安装到/my/custom/path目录,您可以在CMake命令中加入-DCMAKE_INSTALL_PREFIX=/my/custom/path
代码测试
简单的spdlog使用实例
当然可以。以下是一个简单的spdlog使用示例。这个例子展示了如何初始化一个基本的日志记录器,并使用它来记录不同级别的消息。
首先,确保您已经安装了spdlog。如果您还没有安装,可以通过类似这样的命令进行安装(取决于您的系统和包管理器):
# 使用vcpkg(Windows/Linux/macOS) vcpkg install spdlog # 使用Homebrew(macOS) brew install spdlog # 使用Conan conan install spdlog/1.12.0@
接下来,创建一个C++文件(例如main.cpp
),并添加以下代码:
#include <spdlog/spdlog.h> #include <spdlog/sinks/stdout_color_sinks.h> int main() { // 创建并初始化一个控制台日志记录器 auto console = spdlog::stdout_color_mt("console"); // 设置日志级别为debug(这将记录所有级别的日志) console->set_level(spdlog::level::debug); // 记录不同级别的日志 console->error("这是一条错误信息"); console->warn("这是一条警告信息"); console->info("这是一条普通信息"); console->debug("这是一条调试信息"); console->trace("这是一条追踪信息"); return 0; }
然后,您需要编译这个程序。在编译时,确保链接到spdlog库。如果您使用的是CMake,您的CMakeLists.txt
可能看起来像这样:
cmake_minimum_required(VERSION 3.1) project(spdlog_example) # 查找spdlog库 find_package(spdlog REQUIRED) # 创建可执行文件 add_executable(spdlog_example main.cpp) # 链接spdlog库 target_link_libraries(spdlog_example PRIVATE spdlog::spdlog)
运行这个程序将在控制台上输出不同级别的日志信息,每条信息都有不同的颜色以区分级别。这是spdlog的一个基本示例,但spdlog也支持更高级的功能,如文件日志记录、日志格式自定义、多线程和异步日志记录等。
宏定义 LOG(INFO)
在 spdlog
中,日志输出通常不是通过 LOG(INFO)
这种宏来实现的,而是通过直接调用具体的日志级别函数,如 info()
, warn()
, error()
等。但是,你可以通过定义自己的宏来模拟类似 LOG(INFO)
这样的语法。
例如,你可以定义一系列简单的宏来封装 spdlog
的日志调用:
#include <spdlog/spdlog.h> // 定义宏 #define LOG_INFO(logger, ...) logger->info(__VA_ARGS__) #define LOG_WARN(logger, ...) logger->warn(__VA_ARGS__) #define LOG_ERROR(logger, ...) logger->error(__VA_ARGS__) // 以此类推... int main() { // 创建一个日志记录器 auto logger = spdlog::stdout_color_mt("logger"); // 使用宏进行日志记录 LOG_INFO(logger, "这是一条信息消息"); LOG_WARN(logger, "这是一条警告消息"); LOG_ERROR(logger, "这是一条错误消息"); return 0; }
这种方法允许你用类似 LOG(INFO)
的方式来记录日志,只不过你需要指定要使用的日志记录器。这样做的好处是你可以根据需要轻松地定义不同类型的日志记录器,并在应用程序中灵活使用它们。
spdlog
本身没有提供 LOG(INFO)
这样的宏,因为它倾向于更明确的函数调用风格,这在C++中是更常见的做法。但是,通过自定义宏,你可以很容易地实现类似的功能。
spdlog包裹类
可以为您创建一个头文件,以封装 spdlog
的日志接口,使其可以以 LOG(INFO)
和 LOG(ERROR)
的形式使用。这个头文件将定义必要的宏,并设置一个默认的日志记录器。
创建一个名为 SpdlogWrapper.h
的头文件,并添加以下内容:
#ifndef SPDLOG_WRAPPER_H #define SPDLOG_WRAPPER_H #include <spdlog/spdlog.h> #include <spdlog/sinks/stdout_color_sinks.h> #include <memory> #include <sstream> // 初始化默认日志记录器 inline std::shared_ptr<spdlog::logger> InitDefaultLogger() { auto logger = spdlog::stdout_color_mt("default_logger"); logger->set_level(spdlog::level::debug); // 设置默认日志级别 return logger; } // 获取默认日志记录器 inline std::shared_ptr<spdlog::logger>& DefaultLogger() { static auto logger = InitDefaultLogger(); return logger; } // 定义LOG宏 enum class LogLevel { INFO, WARN, ERROR, DEBUG, TRACE }; class LogMessage { public: LogMessage(LogLevel level) : level_(level) {} ~LogMessage() { auto logger = DefaultLogger(); switch (level_) { case LogLevel::INFO: logger->info(stream_.str()); break; case LogLevel::WARN: logger->warn(stream_.str()); break; case LogLevel::ERROR: logger->error(stream_.str()); break; case LogLevel::DEBUG: logger->debug(stream_.str()); break; case LogLevel::TRACE: logger->trace(stream_.str()); break; } } std::ostringstream& stream() { return stream_; } private: LogLevel level_; std::ostringstream stream_; }; #define LOG(level) LogMessage(LogLevel::level).stream() #endif // SPDLOG_WRAPPER_H
包裹类的改进
在提供的示例中,每次使用 LOG
宏时,都会创建一个 LogMessage
实例,这确实会带来一定的开销。不过,由于 spdlog
的设计非常高效,对于大多数场景,这种额外开销通常不会成为性能瓶颈。
但是,如果您的应用程序对性能要求非常高,尤其是在高频日志记录的场景下,这种方法可能不是最优的。在这种情况下,可以考虑更直接地使用 spdlog
的 API,或者设计一种更高效的宏实现。
为了改进性能,您可以考虑以下策略:
- 使用全局或静态日志记录器:减少创建和销毁日志记录器的次数,使用全局或静态实例。
- 直接调用
spdlog
的函数:直接使用spdlog
的info
,warn
,error
等函数,而不是通过宏和中间类。 - 条件编译:对于调试或追踪日志,可以通过预处理器指令(如
#ifdef DEBUG
)在编译时禁用它们,以减少生产环境中的开销。
如果性能是一个关键因素,建议进行适当的基准测试,以评估不同方法在您的具体用例中的性能表现。在大多数情况下,spdlog
的性能已经足够优秀,不太可能成为瓶颈。
静态日志记录器
要将 LogMessage
类修改为使用静态的日志记录器,我们需要改变日志记录器的初始化和存储方式。这样可以确保日志记录器在应用程序的整个生命周期内只被创建一次。下面是修改后的 SpdlogWrapper.h
文件内容:
#ifndef SPDLOG_WRAPPER_H #define SPDLOG_WRAPPER_H #include <spdlog/spdlog.h> #include <spdlog/sinks/stdout_color_sinks.h> #include <memory> #include <sstream> // 初始化并获取静态日志记录器 inline std::shared_ptr<spdlog::logger>& GetStaticLogger() { static auto logger = spdlog::stdout_color_mt("default_logger"); logger->set_level(spdlog::level::debug); // 设置默认日志级别 return logger; } // 定义LOG宏 enum class LogLevel { INFO, WARN, ERROR, DEBUG, TRACE }; class LogMessage { public: LogMessage(LogLevel level) : level_(level) {} ~LogMessage() { auto logger = GetStaticLogger(); switch (level_) { case LogLevel::INFO: logger->info(stream_.str()); break; case LogLevel::WARN: logger->warn(stream_.str()); break; case LogLevel::ERROR: logger->error(stream_.str()); break; case LogLevel::DEBUG: logger->debug(stream_.str()); break; case LogLevel::TRACE: logger->trace(stream_.str()); break; } } std::ostringstream& stream() { return stream_; } private: LogLevel level_; std::ostringstream stream_; }; #define LOG(level) LogMessage(LogLevel::level).stream() #endif // SPDLOG_WRAPPER_H
在这个修改中:
GetStaticLogger
函数负责初始化和返回一个静态的日志记录器实例。LogMessage
类在其析构函数中使用这个静态日志记录器来记录消息。LOG
宏的行为保持不变。
这种方式确保日志记录器作为一个静态局部变量初始化,从而在第一次调用 GetStaticLogger
时创建,并在程序的剩余生命周期内复用。这样减少了对象创建和销毁的开销,同时保留了简洁的日志记录语法。
结语
在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。
这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。
我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。