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 时创建,并在程序的剩余生命周期内复用。这样减少了对象创建和销毁的开销,同时保留了简洁的日志记录语法。