此前写代码,习惯性 std::coutstd::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");
}