此前写代码,习惯性
std::cout
和std::cerr
这种直接可视化的打印,然后满屏密密麻麻的打印信息,而如果不需要了,就会在代码大段大段的注释,非常不美观,甚至可以说是一堆堆💩。。。从代码美学的逻辑,应该是需要集中管理,然后根据需要选择输出“警告”,“调试”,“错误”等调试信息。
这里选择一个比较容易上手的工具:Spdlog
安装和项目中使用 Spdlog
在 Ubuntu Linux 系统:
sudo apt-get install libspdlog-dev
在 C++ 文件中包含 Spdlog 头文件:
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h> // 对于控制台彩色日志
#include <spdlog/sinks/basic_file_sink.h> // 对于文件日志
CMakeLists 中使用方式:
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# 添加Spdlog
find_package(spdlog REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE spdlog::spdlog)
实际使用
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/basic_file_sink.h>
int main() {
auto logger = spdlog::stdout_color_mt("console_logger");
auto file_logger = spdlog::basic_logger_mt("file_logger", "logs.txt");
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%l] %v");
// 使用 {} 作为占位符
logger->info("Hello, {}!", "World");
file_logger->warn("This is a warning in a file.");
// 16进制
logger->debug("Frame header: 0x{:02x} 0x{:02x}", recv_buffer[0], recv_buffer[1]);
return 0;
}
设置日志级别:
Spdlog 支持的日志级别包括:trace
, debug
, info
, warn
, err
, critical
, off
。可以设置全局日志级别或针对单个 logger:
spdlog::set_level(spdlog::level::info); // 全局设置,只记录 info 及以上级别的日志
console->set_level(spdlog::level::debug); // 对 "console" 进行设置
日志格式设置:
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%l] %v");
console->info("This is a formatted log");
异步日志记录:
为了增加日志记录的性能,可以使用 spdlog 的异步日志功能:
#include <spdlog/async.h>
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "async_log.txt");
async_file->info("This is an async log message");
项目中的使用技巧
比如在main函数中:
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
int main() {
auto logger = spdlog::stdout_color_mt("console");
logger->set_level(spdlog::level::debug); // 设置日志级别
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%^%L%$] %v"); // 设置全局格式
// 应用程序代码...
return 0;
}
这时如果在类的实现里也需要使用到spdlog,就可以这样:
#include <spdlog/spdlog.h>
class TCPFrameClient {
public:
TCPFrameClient() {
logger_ = spdlog::get("console"); // 注意用"console"来定位
if (!logger_) {
// Fallback or re-initialize if not found
logger_ = spdlog::stdout_color_mt("console");
}
}
void doSomething() {
logger_->info("Doing something");
}
private:
std::shared_ptr<spdlog::logger> logger_;
};
也可以通过传递指针的方式(稍微复杂一些):
class TCPFrameClient {
public:
TCPFrameClient(std::shared_ptr<spdlog::logger> logger)
: logger_(logger) {
if (!this->logger_) {
// Fallback initialization
this->logger_ = spdlog::stdout_color_mt("console");
}
}
void connect() {
logger_->info("Connecting to server");
}
private:
std::shared_ptr<spdlog::logger> logger_;
};
// 主程序
int main() {
auto logger = spdlog::stdout_color_mt("console");
TCPFrameClient client(logger);
client.connect();
return 0;
}
🌟一个更好的实践
一个好的工程,其结构应该像下面这样的:
├── Utils
│ ├── CMakeLists.txt
│ ├── logger.cpp
│ └── logger.h
├── Parser
│ ├── CMakeLists.txt
│ ├── data_frame_parser.cpp
│ └── data_frame_parser.h
├── TCPClient
│ ├── CMakeLists.txt
│ ├── tcp_frame_client.cpp
│ └── tcp_frame_client.h
具体而言就是单独管理Logger:
Logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <memory>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
class Logger {
public:
static std::shared_ptr<spdlog::logger> get_instance(const std::string& logger_name);
};
#endif // LOGGER_H
Logger.cpp
#include "logger.h"
std::shared_ptr<spdlog::logger> Logger::get_instance(const std::string& logger_name) {
auto logger = spdlog::get(logger_name);
if (!logger) {
logger = spdlog::stdout_color_mt(logger_name);
logger->set_level(spdlog::level::info);
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%^%l%$] %v");
}
return logger;
}
修改 CMakeLists.txt
对于 Utils
目录的 CMakeLists.txt
,需要添加以下内容来确保 Logger
类被正确编译和链接:
add_library(Utils
logger.h
logger.cpp
)
target_include_directories(Utils PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
# 链接 spdlog
find_package(spdlog REQUIRED)
target_link_libraries(Utils
spdlog::spdlog
)
确保其他模块的 CMakeLists.txt
文件更新为链接到 Utils
库:
target_link_libraries(Parser
Utils # 添加这个
# 其他依赖
)
target_link_libraries(TCPClient
Utils # 添加这个
# 其他依赖
)
在实际使用时,就可以这样:
MainProcessor::MainProcessor(const std::string& path, int mode)
: path_(path), mode_(mode), logger_(Logger::get_instance("DataFrameParser")) {}
TCPFrameClient::TCPFrameClient(DataFrameParser& parser, int port)
: sock_(-1), server_port_(port), parser_(parser) {
parser_.initializeDecoder();
logger_ = Logger::get_instance("DataFrameParser");
}
DataFrameParser::DataFrameParser(int width, int height)
: width_(width), height_(height) {
initDisparityPseudoColorTable();
initializeDecoder();
logger_ = Logger::get_instance("DataFrameParser");
}