Log API文档

概述

mc::log 是一个灵活、高性能的日志系统,为应用程序提供结构化日志记录能力。该模块采用模块化设计,支持多种日志级别、多种输出目标和自定义格式化,针对资源受限场景进行了优化。

主要特性

  • 多级日志:支持trace、debug、info、warn、error、fatal等多种日志级别
  • 结构化日志:支持键值对方式的结构化日志记录,便于分析和处理
  • 多目标输出:支持控制台、文件等多种输出目标
  • 可扩展的架构:支持自定义appender,可扩展到各种输出目标
  • 上下文信息:自动记录文件名、行号、函数名等上下文信息
  • 便捷的宏接口:提供简洁易用的宏,简化日志记录

基本用法

记录日志

// 使用全局日志宏(推荐)
tlog("详细调试信息");
dlog("调试信息: ${value}", ("value", 42));
ilog("用户 ${user} 登录成功", ("user", "admin"));
nlog("一般通知: ${event}", ("event", "周期性检查完成"));
wlog("磁盘空间不足: ${available}GB", ("available", 1.2));
elog("操作失败: ${code}, ${message}", ("code", 404)("message", "资源不存在"));
flog("系统崩溃: ${reason}", ("reason", "内存访问错误"));

使用指定日志记录器

// 获取指定名称的日志记录器
auto logger = mc::get_logger("database");

// 使用指定日志记录器记录日志
mc_ilog(logger, "数据库连接已建立: ${host}:${port}",
       ("host", "localhost")("port", 3306));

结构化日志格式

// 基本格式:格式字符串中的 ${key} 会被后面的键值对替换
ilog("处理请求: ${method} ${path}, 耗时: ${time}ms",
    ("method", "GET")("path", "/api/users")("time", 45.8));

// 支持复合数据类型
std::vector<int> ids = {1, 2, 3};
ilog("用户ID列表: ${ids}", ("ids", ids));

// 支持嵌套数据结构
mc::dict user = {
    {"name", "张三"},
    {"age", 30},
    {"roles", mc::variants{"admin", "user"}}
};
ilog("用户信息: ${user}", ("user", user));

扩展属性 (attrs)

  • 在消息体之外附加键值信息,不参与 ${key} 替换,输出时以 key=value 形式追加到同一条日志行末尾;value 为嵌套 dict 时递归为 key={ k2=v2 ... }
  • 使用方式:最后一个参数是 mc::dict 类型时,自动识别为 attrs
mc::dict attrs;
attrs["trace_id"] = std::string("tid-123");
attrs["span_id"]  = std::string("span-1");
attrs["count"]    = 42;

// 可选 attrs - 最后一个参数是 dict,自动作为 attrs
// 无模板 + attrs
ilog(logger, "request done", attrs);

// 有模板 + attrs
ilog(logger, "用户 ${user} 登录", ("user", "alice"), attrs);

// 多个格式参数 + attrs
ilog(logger, "count=${i}, value=${a}", ("i", 1)("a", 2), attrs);

输出示例:

// 输入
ilog(logger, "count=${i}", ("i", 1), attrs);
// 输出: count=1 trace_id=tid-123 span_id=span-1 count=42

日志系统配置

配置日志级别

// 设置默认日志记录器的级别
mc::log::default_logger().set_level(mc::log::level::debug);

// 获取并配置特定日志记录器
auto logger = mc::get_logger("network");
logger.set_level(mc::log::level::info);

添加日志追加器

// 创建控制台追加器
auto console = mc::log::log_manager::instance().create_appender("console");
console->init({{"color", true}}); // 启用彩色输出

// 添加到默认日志记录器
mc::log::default_logger().add_appender(console);

// 创建文件追加器
auto file = mc::log::log_manager::instance().create_appender("file");
file->init({
    {"filename", "app.log"},
    {"max_size", 10 * 1024 * 1024}, // 10MB
    {"rotation", true}
});

// 添加到特定日志记录器
auto logger = mc::get_logger("database");
logger.add_appender(file);

API 详细说明

日志级别

mc::log::level 枚举定义了支持的日志级别:

级别 描述
trace 最详细的跟踪信息
debug 调试信息
info 普通信息
warn 警告信息
error 错误信息
fatal 致命错误
notice 一般通知
off 关闭日志
all 所有级别

mdbctl_log

将日志内容输出到 mdbctl 终端(需先通过 mdbctl attach)。使用 mdbctl_logger,仅含 socket_appender,不含 console/file。

宏定义:

mdbctl_log(...)           // 使用 mdbctl_logger
mc_mdbctl_log(LOGGER, ...) // 使用指定 logger

示例:

#include <mc/log.h>

mdbctl_log("调试信息");
mdbctl_log("当前状态: ${status}", ("status", "正常运行"));

日志宏

全局日志宏

宏 描述
tlog(format, args...) 记录 trace 级别日志
dlog(format, args...) 记录 debug 级别日志
ilog(format, args...) 记录 info 级别日志
wlog(format, args...) 记录 warn 级别日志
elog(format, args...) 记录 error 级别日志
flog(format, args...) 记录 fatal 级别日志
nlog(format, args...) 记录 notice 级别日志

指定日志记录器的宏

宏 描述
mc_tlog(logger, format, args...) 使用指定记录器记录 trace 级别日志
mc_dlog(logger, format, args...) 使用指定记录器记录 debug 级别日志
mc_ilog(logger, format, args...) 使用指定记录器记录 info 级别日志
mc_wlog(logger, format, args...) 使用指定记录器记录 warn 级别日志
mc_elog(logger, format, args...) 使用指定记录器记录 error 级别日志
mc_flog(logger, format, args...) 使用指定记录器记录 fatal 级别日志
mc_nlog(logger, format, args...) 使用指定记录器记录 notice 级别日志

核心类

logger 类

方法 描述
static logger get(const char* name) 获取指定名称的日志记录器
logger(const std::string& name) 构造指定名称的日志记录器
void set_level(level lvl) 设置日志级别
level get_level() const 获取日志级别
bool is_enabled(level lvl) const 检查指定日志级别是否启用
logger& system(int system_id) 设置系统 ID(multihost 场景),链式调用
logger& period(int period_s) 设置日志打印间隔(秒),只接受正整数;小于或等于 0 时抛出 invalid_arg_exception,返回当前 logger 引用
logger& condition(bool cond) 设置 condition,控制是否输出日志,链式调用
logger clone() const 克隆 logger 实例

注意:以上 API 表格需要更新为完整的 6 列格式:| 方法 | 参数 | 返回值 | 异常 | 线程安全 | 说明 |。

| void log(message msg) | 记录日志消息 | | void add_appender(const appender_ptr& a) | 添加日志追加器 | | bool remove_appender(const std::string& name) | 删除指定名称的追加器 | | appender_ptr find_appender(const std::string& name) const | 查找指定名称的追加器 | | const std::vector<appender_ptr>& get_appenders() const | 获取所有追加器 | | void clear_appenders() | 清除所有追加器 |

message 类

消息类表示一条日志消息,包含级别、格式、参数等信息:

方法 描述
message(level lvl, const std::string& format) 构造函数
level get_level() const 获取日志级别
const context& get_context() const 获取上下文信息
const std::string& get_message() const 获取格式化后的消息
const std::chrono::system_clock::time_point& get_timestamp() const 获取时间戳

appender 类

追加器接口类,定义了日志输出目标的基本接口:

方法 描述
virtual bool init(const variant& args) 初始化追加器
virtual void append(const message& msg) 追加日志消息
const std::string& get_name() const 获取追加器名称
void set_name(const std::string& name) 设置追加器名称

log_manager 类

日志管理器类,负责管理日志记录器和追加器:

方法 描述
static log_manager& instance() 获取单例实例
logger get_logger(const char* name) 获取指定名称的日志记录器
appender_ptr create_appender(const std::string& type) 创建指定类型的追加器
bool load_appender(const std::string& lib_path, const std::string& appender_name) 从动态库加载追加器
bool apply_config(const logging_config& config) 以 best effort 应用日志配置;全部显式 appender 与 logger 绑定成功时返回 true

apply_config 先按 lib_path 动态加载显式 appender 插件,再创建或重新初始化进程级同名 appender,最后更新 logger。任一显式插件加载、appender 初始化或 logger 引用解析失败时返回 false;其他有效项仍尽量应用,不执行事务回滚。调用者应检查返回值:false 表示存在未成功应用的配置,不代表已应用的有效项被回滚。与之前仅记录部分失败日志相比,缺失 appender 引用现在也返回 false;成功配置仍返回 true,函数签名不变。appender_dirs 的目录扫描保持 best effort。同名 appender 会复用并重新初始化已有对象;重新初始化失败同样返回 false。

内置追加器

追加器类型 描述 配置项
console 输出到控制台 color: 是否启用彩色输出
file 输出到文件 filename: 文件名;max_size: 最大文件大小;rotation: 是否启用轮转
rotating_file 按大小轮转文件 filename: 文件名;max_size: 单个文件最大大小;max_files: 最大文件数
daily_file 按日期轮转文件 filename: 文件名;hour: 轮转小时;minute: 轮转分钟
null 丢弃所有日志 无

最佳实践

  1. 使用结构化日志:优先使用键值对格式记录日志,而不是简单字符串拼接

  2. 日志级别使用:

    • 谨慎使用 trace 级别,仅用于非常详细的调试信息
    • debug 级别用于开发调试
    • info 级别用于记录正常操作
    • warn 级别用于潜在问题警告
    • error 级别用于错误但不影响程序继续运行
    • fatal 级别用于导致程序终止的严重错误
  3. 性能考虑:

    • 在高性能关键路径上,先检查日志级别是否启用再构造复杂日志消息
    • 对于频繁记录的日志点,考虑使用采样或限流
  4. 分类记录:使用不同名称的日志记录器对日志进行分类,便于管理和过滤

  5. 状态跟踪:在长时间操作的开始和结束都记录日志,便于跟踪操作状态和耗时

✅ 正例

// 1. 使用结构化日志,携带上下文信息便于检索
void handle_login(const std::string& user, const std::string& ip) {
    ilog("用户登录: ${user}, IP: ${ip}, 时间: ${time}",
         ("user", user)("ip", ip)("time", std::time(nullptr)));
    // 业务逻辑...
}

// 2. 在构造复杂日志消息前检查日志级别,避免不必要的开销
auto logger = mc::get_logger("perf");
if (logger.is_enabled(mc::log::level::debug)) {
    // 仅在 debug 级别启用时才执行昂贵的序列化
    auto snapshot = generate_debug_snapshot();  // 开销大
    mc_dlog(logger, "调试快照: ${snap}", ("snap", snapshot));
}

❌ 反例

// 1. 使用字符串拼接代替结构化日志
void handle_login(const std::string& user, const std::string& ip) {
    // ❌ 字符串拼接,难以检索和解析
    ilog("用户 " + user + " 从 " + ip + " 登录");
}

// 2. 在生产环境使用 trace 级别记录高频日志
void process_packet(const packet& pkt) {
    // ❌ trace 级别应仅用于开发调试,生产环境会产生海量日志
    tlog("处理数据包: seq=${seq}, len=${len}, data=${data}",
         ("seq", pkt.seq)("len", pkt.len)("data", pkt.dump()));
}