microLog 后端开发指南

microLog 后端开发指南

概述

microLog 采用抽象工厂模式门面模式进行架构设计,能够方便地扩展新的日志后端。本文档将逐步说明如何开发自定义后端实现,并提供完整的示例与最佳实践。

文中示例代码仅为展示原理,并没有进行编译测试

架构设计

#mermaid-svg-1AlTiiOoMoNhxPmb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1AlTiiOoMoNhxPmb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1AlTiiOoMoNhxPmb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1AlTiiOoMoNhxPmb .error-icon{fill:#552222;}#mermaid-svg-1AlTiiOoMoNhxPmb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1AlTiiOoMoNhxPmb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1AlTiiOoMoNhxPmb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1AlTiiOoMoNhxPmb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1AlTiiOoMoNhxPmb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1AlTiiOoMoNhxPmb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1AlTiiOoMoNhxPmb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1AlTiiOoMoNhxPmb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1AlTiiOoMoNhxPmb .marker.cross{stroke:#333333;}#mermaid-svg-1AlTiiOoMoNhxPmb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1AlTiiOoMoNhxPmb p{margin:0;}#mermaid-svg-1AlTiiOoMoNhxPmb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1AlTiiOoMoNhxPmb .cluster-label text{fill:#333;}#mermaid-svg-1AlTiiOoMoNhxPmb .cluster-label span{color:#333;}#mermaid-svg-1AlTiiOoMoNhxPmb .cluster-label span p{background-color:transparent;}#mermaid-svg-1AlTiiOoMoNhxPmb .label text,#mermaid-svg-1AlTiiOoMoNhxPmb span{fill:#333;color:#333;}#mermaid-svg-1AlTiiOoMoNhxPmb .node rect,#mermaid-svg-1AlTiiOoMoNhxPmb .node circle,#mermaid-svg-1AlTiiOoMoNhxPmb .node ellipse,#mermaid-svg-1AlTiiOoMoNhxPmb .node polygon,#mermaid-svg-1AlTiiOoMoNhxPmb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1AlTiiOoMoNhxPmb .rough-node .label text,#mermaid-svg-1AlTiiOoMoNhxPmb .node .label text,#mermaid-svg-1AlTiiOoMoNhxPmb .image-shape .label,#mermaid-svg-1AlTiiOoMoNhxPmb .icon-shape .label{text-anchor:middle;}#mermaid-svg-1AlTiiOoMoNhxPmb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1AlTiiOoMoNhxPmb .rough-node .label,#mermaid-svg-1AlTiiOoMoNhxPmb .node .label,#mermaid-svg-1AlTiiOoMoNhxPmb .image-shape .label,#mermaid-svg-1AlTiiOoMoNhxPmb .icon-shape .label{text-align:center;}#mermaid-svg-1AlTiiOoMoNhxPmb .node.clickable{cursor:pointer;}#mermaid-svg-1AlTiiOoMoNhxPmb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1AlTiiOoMoNhxPmb .arrowheadPath{fill:#333333;}#mermaid-svg-1AlTiiOoMoNhxPmb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1AlTiiOoMoNhxPmb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1AlTiiOoMoNhxPmb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1AlTiiOoMoNhxPmb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1AlTiiOoMoNhxPmb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1AlTiiOoMoNhxPmb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1AlTiiOoMoNhxPmb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1AlTiiOoMoNhxPmb .cluster text{fill:#333;}#mermaid-svg-1AlTiiOoMoNhxPmb .cluster span{color:#333;}#mermaid-svg-1AlTiiOoMoNhxPmb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1AlTiiOoMoNhxPmb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1AlTiiOoMoNhxPmb rect.text{fill:none;stroke-width:0;}#mermaid-svg-1AlTiiOoMoNhxPmb .icon-shape,#mermaid-svg-1AlTiiOoMoNhxPmb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1AlTiiOoMoNhxPmb .icon-shape p,#mermaid-svg-1AlTiiOoMoNhxPmb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1AlTiiOoMoNhxPmb .icon-shape .label rect,#mermaid-svg-1AlTiiOoMoNhxPmb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1AlTiiOoMoNhxPmb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1AlTiiOoMoNhxPmb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1AlTiiOoMoNhxPmb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 持有 mutex、threadPool,格式化在调用线程完成
用户代码

auto log = createLog<tinyLogType::CUSTOM>(args...);

log->info("hello world");
LOG<Backend> 门面层(模板类)
后端层(自定义实现)

  • 继承 logItfc

  • 实现 vlog / get_fd__ / is_valid__
    logFactoryHelper 抽象工厂

注册所有后端类型,通过索引创建实例

核心接口

logItfc 基类

所有后端必须继承 logItfc 并实现以下纯虚函数:

cpp 复制代码
class logItfc {
public:
    enum class level_t { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3 };

    // 必须实现:日志记录核心方法
    virtual void vlog(level_t level, const char* fmt, va_list args) = 0;

    // 必须实现:获取文件描述符(无则返回 -1)
    virtual int get_fd__() const = 0;

    // 必须实现:检查后端是否有效
    virtual bool is_valid__() const = 0;

protected:
    // 可用的工具方法
    const char* level_2_string__(level_t level);   // 级别转字符串
    std::string nowStr();                          // 获取当前时间字符串
    std::shared_ptr<wheels::threadPool> pt_runner__; // 线程池指针
};

开发步骤

步骤 1:定义后端枚举类型

include/log.hpptinyLogType 枚举中添加新类型:

cpp 复制代码
enum class tinyLogType {
    UDP        = 0,
    LOCAL      = 1,
    PIPE       = 2,
    UNIXSOCK   = 3,
    TERMINAL   = 4,
    TCP        = 5,
    SQLITE3    = 6,
    MYSQL      = 7,
    POSTGRESQL = 8,
    CUSTOM     = 9,  // 添加新类型,按顺序递增
};

步骤 2:创建后端头文件

创建 include/logDetail/custom_log.hpp

cpp 复制代码
#pragma once

#include <string>
#include <cstdint>
#include "logDetail/itfc.hpp"

namespace microLog {

class customLog : public logItfc {
private:
    // 自定义成员变量
    std::string m_config__;
    bool        m_inited__;

public:
    // 构造函数:接收自定义参数
    customLog(const std::string& config);
    ~customLog();

    // 必须实现的接口
    virtual int  get_fd__()   const override;
    virtual bool is_valid__() const override;
    virtual void vlog(level_t level, const char* fmt, va_list args) override;

    // 自定义方法(可选)
    bool init();
};

} // namespace microLog

步骤 3:实现后端

创建 src/logDetail/custom_log.cpp

cpp 复制代码
#include <cstdarg>
#include <cstdio>
#include "logDetail/custom_log.hpp"

namespace microLog {

customLog::customLog(const std::string& config)
    : m_config__(config), m_inited__(false)
{
    if (!init()) {
        throw std::runtime_error("customLog 初始化失败");
    }
}

customLog::~customLog() {
    // 清理资源
}

bool customLog::init() {
    // 初始化逻辑:连接资源、创建对象等
    m_inited__ = true;
    return true;
}

int customLog::get_fd__() const {
    // 如果后端使用文件描述符,返回 fd;否则返回 -1
    return -1;
}

bool customLog::is_valid__() const {
    // 返回后端是否有效
    return m_inited__;
}

void customLog::vlog(level_t level, const char* fmt, va_list args) {
    if (!is_valid__()) { return; }

    // 1. 获取时间戳
    std::string ts = nowStr();

    // 2. 获取级别字符串
    const char* level_str = level_2_string__(level);

    // 3. 格式化日志内容
    char buf[4096] = {0};
    va_list args_copy;
    va_copy(args_copy, args);
    vsnprintf(buf, sizeof(buf), fmt, args_copy);
    va_end(args_copy);

    // 4. 输出到自定义目标(示例:控制台)
    printf("[%s] [%s] %s\n", ts.c_str(), level_str, buf);
}

} // namespace microLog

步骤 4:注册到抽象工厂

include/log.hpplogFactoryHelper 中添加新后端:

cpp 复制代码
#include "logDetail/custom_log.hpp"  // 添加头文件

namespace microLog {
    using logFactoryHelper = wheels::dm::abstractFactory<
        LOG<udpLog>,
        LOG<localLog>,
        LOG<pipeLog>,
        LOG<unixSock>,
        LOG<terminal>,
        LOG<tcpLog>,
        LOG<sqlite3Log>,
        LOG<mysqlLog>,
        LOG<postgresqlLog>,
        LOG<customLog>  // 添加新后端
    >;
}

步骤 5:配置 CMakeLists.txt

src/logDetail/CMakeLists.txt 中添加新源文件:

cmake 复制代码
set(LOG_DETAIL_SRC
    ...
    custom_log.cpp
    ...
)

如果新后端依赖外部库,需添加相应的 find_packagetarget_link_libraries

使用示例

cpp 复制代码
#include "log.hpp"

// 创建自定义后端实例
auto log = createLog<tinyLogType::CUSTOM>(4, "my_config");

// 使用日志
log->info("系统启动");
log->debug("调试信息: %d", 42);
log->warn("警告: 资源不足");
log->error("错误: %s", "连接失败");

完整示例:Redis 后端

头文件 redis_log.hpp

cpp 复制代码
#pragma once

#include <string>
#include <cstdint>
#include "logDetail/itfc.hpp"

#ifdef HAVE_REDIS
#   include <hiredis/hiredis.h>
#endif

namespace microLog {

class redisLog : public logItfc {
private:
#ifdef HAVE_REDIS
    redisContext* m_ctx__;
#endif
    std::string m_host__;
    uint16_t    m_port__;
    std::string m_key__;
    bool        m_inited__;

public:
    redisLog(const std::string& host = "localhost",
             uint16_t port = 6379,
             const std::string& key = "microLog");
    ~redisLog();

    virtual int  get_fd__()   const override;
    virtual bool is_valid__() const override;
    virtual void vlog(level_t level, const char* fmt, va_list args) override;
};

} // namespace microLog

实现文件 redis_log.cpp

cpp 复制代码
#include <cstdarg>
#include <cstdio>
#include <string>
#include "logDetail/redis_log.hpp"

namespace microLog {

redisLog::redisLog(const std::string& host, uint16_t port, const std::string& key)
    : m_host__(host), m_port__(port), m_key__(key), m_inited__(false)
{
#ifdef HAVE_REDIS
    m_ctx__ = redisConnect(host.c_str(), port);
    if (!m_ctx__ || m_ctx__->err) {
        if (m_ctx__) {
            std::cerr << "Redis 连接失败: " << m_ctx__->errstr << std::endl;
            redisFree(m_ctx__);
            m_ctx__ = nullptr;
        } else {
            std::cerr << "Redis 连接失败: 内存分配错误" << std::endl;
        }
        return;
    }
    m_inited__ = true;
#else
    throw std::runtime_error("Redis 支持未启用");
#endif
}

redisLog::~redisLog() {
#ifdef HAVE_REDIS
    if (m_ctx__) {
        redisFree(m_ctx__);
        m_ctx__ = nullptr;
    }
#endif
}

int redisLog::get_fd__() const {
    return -1;
}

bool redisLog::is_valid__() const {
#ifdef HAVE_REDIS
    return m_inited__ && m_ctx__ && !m_ctx__->err;
#else
    return false;
#endif
}

void redisLog::vlog(level_t level, const char* fmt, va_list args) {
#ifdef HAVE_REDIS
    if (!is_valid__()) { return; }

    char buf[4096] = {0};
    std::string ts = nowStr();
    snprintf(buf, sizeof(buf), "[%s] [%s] ", ts.c_str(), level_2_string__(level));
    
    char msg[4096] = {0};
    va_list args_copy;
    va_copy(args_copy, args);
    vsnprintf(msg, sizeof(msg), fmt, args_copy);
    va_end(args_copy);

    std::string log_entry = std::string(buf) + msg;
    
    redisReply* reply = (redisReply*)redisCommand(m_ctx__, "LPUSH %s %s", 
                                                   m_key__.c_str(), log_entry.c_str());
    if (reply) {
        freeReplyObject(reply);
    }
#endif
}

} // namespace microLog

最佳实践

1. 线程安全

若后端包含共享状态,应使用互斥锁保护:

cpp 复制代码
class customLog : public logItfc {
private:
    std::mutex m_mutex__;
    // ...

    void vlog(level_t level, const char* fmt, va_list args) {
        std::lock_guard<std::mutex> lock(m_mutex__);
        // ...
    }
};

2. 资源管理

在析构函数中正确释放资源:

cpp 复制代码
customLog::~customLog() {
    close(m_fd__);
    delete m_object__;
    // ...
}

3. 错误处理

使用异常或返回值处理初始化失败:

cpp 复制代码
customLog::customLog() {
    if (!init()) {
        throw std::runtime_error("customLog 初始化失败");
    }
}

4. 条件编译

对于依赖外部库的后端,使用条件编译:

cpp 复制代码
#ifdef HAVE_REDIS
#   include <hiredis/hiredis.h>
#endif

void vlog(...) {
#ifdef HAVE_REDIS
    // 实现逻辑
#endif
}

5. 批量写入优化

对于网络或数据库后端,建议实现批量提交:

cpp 复制代码
class customLog : public logItfc {
private:
    std::vector<std::string> m_buffer__;
    size_t                   m_batch_size__;
    std::mutex               m_mutex__;

    void flush() {
        // 批量提交所有缓冲数据
    }

    void vlog(...) {
        {
            std::lock_guard<std::mutex> lock(m_mutex__);
            m_buffer__.push_back(entry);
            if (m_buffer__.size() >= m_batch_size__) {
                flush();
            }
        }
    }
};

6. 连接重连

对于网络后端,实现自动重连机制:

cpp 复制代码
bool customLog::check_connection() {
    if (!m_connected__) {
        reconnect();
    }
    return m_connected__;
}

void vlog(...) {
    if (!check_connection()) { return; }
    // 写入逻辑
}

调试技巧

1. 验证编译

bash 复制代码
mkdir -p build && cd build
cmake .. -DBUILD_TESTS=ON
make -j4

2. 添加测试

创建 test/test_custom.cpp

cpp 复制代码
#include "log.hpp"

int main() {
    auto log = createLog<tinyLogType::CUSTOM>(4, "test_config");
    if (!log) {
        std::cerr << "创建失败" << std::endl;
        return 1;
    }
    
    log->info("测试信息");
    log->debug("测试调试");
    log->warn("测试警告");
    log->error("测试错误");
    
    std::cout << "测试通过" << std::endl;
    return 0;
}

3. 启用调试模式

bash 复制代码
cmake .. -DBUILD_DEBUG=ON

已有后端参考

后端 文件路径 特点
LOCAL logDetail/local.hpp 环形文件,嵌入式友好
UDP logDetail/udp.hpp 无连接,高性能
TCP logDetail/tcp.hpp 可靠传输
PIPE logDetail/pipe.hpp 进程间通信
UNIXSOCK logDetail/unixSock.hpp 本地套接字
TERMINAL logDetail/terminal.hpp 控制台输出,彩色支持
SQLITE3 logDetail/sqlite3_log.hpp 嵌入式数据库
MYSQL logDetail/mysql_log.hpp 双连接 + 事务批量提交
POSTGRESQL logDetail/postgresql_log.hpp 双连接 + 事务批量提交

常见问题

Q1: 如何处理阻塞操作?

A : 在 vlog 中不要执行长时间阻塞操作。如果必须阻塞,可考虑使用异步线程处理。

Q2: 是否需要继承 std::enable_shared_from_this

A : 若需要在回调中获取自身的 shared_ptr,建议继承:

cpp 复制代码
class customLog : public logItfc, public std::enable_shared_from_this<customLog> {
    // 使用 shared_from_this() 获取 shared_ptr
};

Q3: 如何传递配置参数?

A : 通过构造函数传递。createLog 使用完美转发:

cpp 复制代码
auto log = createLog<tinyLogType::CUSTOM>(4, arg1, arg2, arg3);

Q4: 线程池如何使用?

A : pt_runner__ 是线程池指针,可用于异步任务:

cpp 复制代码
void vlog(...) {
    if (pt_runner__) {
        pt_runner__->pushTask([this, entry]() {
            // 异步处理
        });
    }
}
相关推荐
Esaka_Forever1 小时前
Python 完整内存管理机制详解
开发语言·python·spring
星空露珠2 小时前
迷你世界UGc3.0脚本Wiki[剧情动画模块管理接口 Timeline]
开发语言·数据结构·算法·游戏·lua
汉克老师2 小时前
GESP2026年6月认证C++二级( 第三部分编程题(2、菱形))精讲
c++·找规律·绘制图形·对角线·双重循环
未来之窗软件服务2 小时前
计算机考试-C语言 应用题—东方仙盟
c语言·开发语言·仙盟创梦ide·东方仙盟·计算机考试
想你依然心痛2 小时前
AtomCode在后端开发中的实战体验:Go微服务从零搭建
开发语言·微服务·golang
我是一颗柠檬2 小时前
【Java项目技术亮点】EXPLAIN深度分析与慢查询治理
android·java·开发语言
luj_17682 小时前
草酸与烟酸对消化及糖代谢的影响解析
服务器·c语言·开发语言·经验分享·算法
fei_sun2 小时前
【SystemVerilog】SystemVerilog与C语言的接口
c语言·开发语言
W是笔名2 小时前
python___容器类型的数据___序列
开发语言·python