跳转至

最佳实践

本文档总结了使用 CFLogger 的推荐做法和常见模式。

日志级别使用

级别选择指南

级别 使用场景 示例
TRACE 详细的执行流程 "进入函数 processRequest()", "循环迭代 i=5"
DEBUG 调试信息 "变量 x = 42", "缓存未命中"
INFO 重要状态变化 "服务启动", "用户登录成功"
WARNING 潜在问题 "配置文件缺失,使用默认值", "内存使用率 80%"
ERROR 错误和异常 "数据库连接失败", "文件不存在"

推荐原则

// ✅ 好的做法
void process_user_login(const std::string& username) {
    debug("尝试登录用户: " + username, "Auth");

    if (authenticate(username)) {
        info("用户登录成功: " + username, "Auth");
    } else {
        warning("用户登录失败: " + username, "Auth");
    }
}

// ❌ 不好的做法
void process_user_login(const std::string& username) {
    trace("开始执行 process_user_login 函数", "Auth");
    trace("参数 username = " + username, "Auth");
    // ... 过度日志
}

标签使用

标签命名规范

// ✅ 推荐的标签
info("连接成功", "Database");     // 大写首字母
info("请求处理", "HTTP");         // 全大写缩写
info("缓存更新", "cache");        // 小写也可以,保持一致

// ❌ 不推荐的标签
info("连接成功", "db");           // 过于简短
info("请求处理", "HttpRequestHandler");  // 过于详细
info("缓存更新", "c");            // 意义不明

按模块划分标签

class UserService {
public:
    void login(const std::string& username) {
        info("用户登录: " + username, "User");
    }
};

class DatabaseService {
public:
    void connect() {
        info("数据库连接", "Database");
    }
};

class NetworkService {
public:
    void send(const std::string& data) {
        debug("发送数据: " + data, "Network");
    }
};

常用标签建议

模块类型 推荐标签
用户认证 Auth, User, Login, Session
数据访问 Database, DB, SQL, Cache, Redis
网络通信 Network, HTTP, API, WebSocket
文件操作 FileIO, FileSystem, Storage
配置管理 Config, Settings, Preferences
UI 渲染 UI, Render, Paint, Widget
后台任务 Background, Worker, Job, Task
性能监控 Performance, Metrics, Stats

消息内容

消息编写原则

// ✅ 好的消息 - 包含上下文
error("无法打开配置文件: " + path + ", 原因: " + strerror(errno), "FileIO");
info("用户 " + username + " 从 " + ip_address + " 登录", "Auth");
warning("查询耗时 " + std::to_string(duration_ms) + "ms 超过阈值", "Database");

// ❌ 差的消息 - 缺少上下文
error("文件打开失败", "FileIO");
info("用户登录", "Auth");
warning("查询慢", "Database");

结构化消息

// ✅ 使用结构化格式
info("请求处理 | method=POST | path=/api/users | duration=50ms | status=200", "API");

// ✅ 使用键值对
error("数据库错误 | code=" + std::to_string(err.code) +
      " | msg=" + err.message + " | query=" + query, "Database");

避免敏感信息

// ❌ 不要记录敏感信息
info("用户登录: user=admin&password=123456", "Auth");

// ✅ 脱敏处理
info("用户登录: user=admin&password=****", "Auth");
info("信用卡支付: ****-****-****-" + last4, "Payment");

性能考虑

避免过度日志

// ❌ 在热路径中过度日志
void process_data(const std::vector<Data>& items) {
    for (const auto& item : items) {
        trace("处理项目: " + item.to_string());  // 每个项目都记录
    }
}

// ✅ 批量记录
void process_data(const std::vector<Data>& items) {
    debug("开始处理 " + std::to_string(items.size()) + " 个项目");
    for (const auto& item : items) {
        // 处理...
    }
    info("完成处理 " + std::to_string(items.size()) + " 个项目");
}

延迟计算

// ❌ 每次都计算
trace("调试信息: " + expensive_computation());  // 即使 TRACE 被过滤也会计算

// ✅ 使用条件判断
if (should_log(level::TRACE)) {
    trace("调试信息: " + expensive_computation());
}

字符串拼接

// ❌ 多次拼接
std::string msg = "用户: ";
msg += username;
msg += ", 操作: ";
msg += action;
info(msg);

// ✅ 单次拼接
info("用户: " + username + ", 操作: " + action);

线程安全

多线程环境

// ✅ CFLogger 是线程安全的,可以直接使用
void worker_thread(int id) {
    info("工作线程 " + std::to_string(id) + " 启动", "Worker");
    // ... 工作逻辑 ...
    info("工作线程 " + std::to_string(id) + " 完成", "Worker");
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) {
        threads.emplace_back(worker_thread, i);
    }
    for (auto& t : threads) {
        t.join();
    }
}

共享资源的日志

// ✅ 记录共享资源访问
class ThreadSafeCounter {
public:
    void increment() {
        std::lock_guard<std::mutex> lock(mutex_);
        counter_++;
        trace("计数器递增到 " + std::to_string(counter_), "Counter");
    }

private:
    std::mutex mutex_;
    int counter_ = 0;
};

错误处理

记录异常

// ✅ 记录异常信息
try {
    process_data();
} catch (const std::exception& e) {
    error(std::string("处理失败: ") + e.what(), "Process");
    throw;  // 重新抛出
}

// ✅ 记录异常和上下文
try {
    connect_database(url);
} catch (const DatabaseException& e) {
    error("数据库连接失败 | url=" + url +
          " | error=" + std::string(e.what()), "Database");
}

关键操作前后

// ✅ 关键操作前后记录
void save_to_file(const std::string& path, const Data& data) {
    info("开始保存文件: " + path, "FileIO");

    try {
        write_file(path, data);
        info("文件保存成功: " + path, "FileIO");
    } catch (...) {
        error("文件保存失败: " + path, "FileIO");
        throw;
    }
}

启动和关闭

应用启动

int main(int argc, char** argv) {
    // 1. 尽早初始化日志
    setup_logging();

    using namespace cf::log;
    info("=" * 50, "Main");
    info("应用程序启动", "Main");
    info("版本: " + std::string(VERSION), "Main");
    info("命令行参数: " + join_args(argc, argv), "Main");

    // 2. 应用逻辑...

    return 0;
}

应用关闭

int main(int argc, char** argv) {
    setup_logging();

    // 应用逻辑...

    using namespace cf::log;
    info("应用程序正常退出", "Main");
    Logger::instance().flush_sync();  // 确保所有日志写入

    return 0;
}

配置管理

环境感知配置

enum class Environment {
    Development,
    Testing,
    Production
};

void setup_logging(Environment env) {
    using namespace cf::log;

    switch (env) {
        case Environment::Development:
            Logger::instance().setMininumLevel(level::TRACE);
            break;
        case Environment::Testing:
            Logger::instance().setMininumLevel(level::DEBUG);
            break;
        case Environment::Production:
            Logger::instance().setMininumLevel(level::INFO);
            break;
    }
}

动态调整

class LoggingController {
public:
    void increase_verbosity() {
        auto current = Logger::instance().getMininumLevel();
        int new_level = std::max(0, as<int>(current) - 1);
        Logger::instance().setMininumLevel(static_cast<level>(new_level));
        info("日志级别调整为: " + std::string(to_string(static_cast<level>(new_level))));
    }

    void decrease_verbosity() {
        auto current = Logger::instance().getMininumLevel();
        int new_level = std::min(4, as<int>(current) + 1);
        Logger::instance().setMininumLevel(static_cast<level>(new_level));
    }
};

日志轮转

虽然需要自定义实现,但建议:

// ✅ 按大小轮转
class RotatingFileSink : public ISink {
    // 参见 sinks.md 中的实现
};

// ✅ 按时间轮转
class DailyFileSink : public ISink {
    // 每天创建新文件
    // app_20260316.log, app_20260317.log, ...
};

单元测试

测试中的日志

// ✅ 测试时可以降低日志级别
TEST(MyTest, TestSomething) {
    // 临时设置更高日志级别用于调试
    Logger::instance().setMininumLevel(level::TRACE);

    // 测试代码...

    // 测试结束恢复
    Logger::instance().setMininumLevel(level::WARNING);
}

Mock Sink

// ✅ 使用 Mock Sink 验证日志
class MockSink : public ISink {
public:
    std::vector<std::string> messages;

    bool write(const LogRecord& record) override {
        if (formatable() && formatter_) {
            messages.push_back(formatter_->format_me(record));
        } else {
            messages.push_back(record.msg);
        }
        return true;
    }

    bool flush() override { return true; }
};

TEST(LoggingTest, ErrorLogged) {
    auto mock = std::make_shared<MockSink>();
    Logger::instance().add_sink(mock);

    error("Test error");

    ASSERT_FALSE(mock->messages.empty());
    ASSERT_TRUE(mock->messages[0].find("Test error") != std::string::npos);
}

检查清单

在发布前检查:

  • [ ] 生产环境日志级别不低于 INFO
  • [ ] 没有记录敏感信息(密码、令牌等)
  • [ ] 热路径中没有过度日志
  • [ ] 关键操作前后都有日志
  • [ ] 使用一致的标签命名
  • [ ] 日志消息包含足够的上下文
  • [ ] 文件 sink 使用追加模式
  • [ ] 应用退出前调用 flush_sync()

下一步

相关文档