ZLToolKit(一) Logger

文章介绍了C++11实现的日志模块,支持console、文件和广播输出,具备异步输出、日志清理和切片功能。LogContext用于存储日志信息,避免重复打印。Logger单例管理输出通道和写日志。还讨论了默认构造函数、禁止拷贝和移动的基类以及enable_shared_from_this的使用。


我是FFZero,一个10年开发经验的cpper。

ZLToolkit是著名流媒体开源库ZLMediakit的底层工具库,封装了日志,时间通知,IO复用等能力,对我们学习实践C++有很大参考价值。
本文中使用的C++语法(模板,基本语法,类)都可以参考我的专栏 C++补完计划,哪里不会点哪里。

本文介绍ZLToolkit的Logger模块,一起看看如何实现一个实用的Logger类吧。

Logger介绍

Logger类位于ZLToolkit/src/Util 路径下,是典型的工具类,负责所有的日志打印。在这里插入图片描述Logger的基类std::enable_shared_frome_this和noncopyable都可以在 C++补完计划中找到对应的文章进行学习。

Logger具有下面的能力:

  1. 支持控制台,文件,广播三种输出通道,支持异步输出(单独线程写日志)
  2. 支持按时间、文件数量,文件大小进行清理和切片
  3. 支持三种输出风格(cout, printf, 可变长模板)。
  4. 支持重载<<的类。

Logger使用示例

    #include "Util/logger.h"
    int main() {
       // Initialize the logging system
        Logger::Instance().add(std::make_shared<ConsoleChannel> ());//输出日志到终端
        Logger::Instance().add(std::make_shared<FileChannel>());  ////输出日志到文件,默认路径:exeDir() + "log/"
        Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>()); //设置日志写文件为异步写入(单独线程写文件) 
        
        TraceL << "int"<< (int)1  << endl;    //cout输出风格
        PrintT("this is a %s test:%d", "printf trace", 124);  //printf输出风格
        LogT(1, "+", "2", '=', 3);            //可变长模板输出风格
        return 0;
    }

上面的代码初始化了日志系统,日志同时输出到终端和文件,设置了异步写文件。后面展示了输出文件的三种方式。

源码分析

类图

请添加图片描述
Logger类是单例类,负责管理所有的输出通道(LogChannel),设置日志等级和是否开启异步写文件。
LogContext是日志上下文类,继承自std::ostringstream,operator<<存储内容,str函数输出日志内容字符串。

类分析

  1. LogContext
    存储日志的打印信息,继承了ostringstream用<<存储内容和str()输出日志内容的字符串。
    _repeat 成员变量用来记录重复日志的次数,避免短时间重复打印同样的日志。

使用如下:

void Logger::writeChannels(const LogContextPtr &ctx) {
         if (ctx->_line == _last_log->_line && ctx->_file == _last_log->_file && ctx->str() == _last_log->str()) {
            //重复的日志每隔500ms打印一次,过滤频繁的重复日志
            ++_last_log->_repeat;
            if (timevalDiff(_last_log->_tv, ctx->_tv) > 500) {  //超过500ms,打印一条
                ctx->_repeat = _last_log->_repeat;  //ctx->_repeat要继承过来
                writeChannels_l(ctx); 
            }
            return;
        }
        if (_last_log->_repeat) {    //上一条日志短时间内一直重复,没有打印出来,最后打印一下
            writeChannels_l(_last_log);
        }
        writeChannels_l(ctx);
    }
  1. Logger
    单例,核心类,所有的重要操作几乎都是由这个类完成。

    • add() 添加输出通道
    • setWriter() 设置异步写日志线程
    • setLevel() 设置所有日志通道的log等级
    • write() 写日志,如果调用过setWriter,则异步写,否则直接同步写
  2. LogChannel
    日志通道,负责格式化日志并打印到输出流
    void format(const Logger &logger, std::ostream &ost, const LogContextPtr &ctx, bool enable_color, bool enable_detail)函数负责将LogContex的内容进行格式化并输出ost中。

    • ConsoleChannel: format函数的输出参数设置位std::cout。
    • FileChannelBase: 文件输出通道的基类。
    • FileChannel: 文件输出实际使用的类,自动清理的日志文件通道。主要是在write前检测文件夹和文件大小,并创建新文件、更新index信息、删除过期的日志文件。最后调用FileChannelBase的write函数写文件。
    • EventChannel: 事件通道。向注册在“kBroadcastLogEvent”的listener emit日志上下文信。
  3. LogWriter/AsyncLogWriter
    LogWriter只是作为一个基类,几乎没有实现。AsyncLogWriter是一个使用率很高的类,这个类开启了一个线程用于写日志。
    write函数用于将日志暂存进_pending对象,并唤醒阻塞的信号量刷新日志。
    ** flushAll**函数用于刷新日志。

    void AsyncLogWriter::flushAll() {
        decltype(_pending) tmp;
        {
            lock_guard<mutex> lock(_mutex);
            tmp.swap(_pending);
        }
    
        tmp.for_each([&](std::pair<LogContextPtr, Logger *> &pr) {
            pr.second->writeChannels(pr.first);
        });
    }
    

    这个函数将_pending的日志先交换到tmp对象再进行打印。可以优先减少锁的时间,提高效率。

  4. LogContextCapture
    每次打印的时候会生成该类的临时对象,在构造时生成LogContext对象并与logger对象进行关联,结束后主要是生成LogContext对象并将logger对象于其进行关联。比较有意思的是LogContextCapture &operator<<(std::ostream &(*f)(std::ostream &))这个函数,会在参数为std::endl(回车符)的时候是调用,这个函数会调用_logger.write(_ctx)将日志立即输出。

  5. LoggerWrapper包装类
    这个类用于支持printf和可变参数格式。实现比较简单,但是对模板的使用很巧妙。

    template<typename First, typename ...ARGS>
    static inline void printLogArray(Logger &logger, LogLevel level, const char *file, const char *function, int line, First &&first, ARGS &&...args) {
        LogContextCapture log(logger, level, file, function, line);
        log << std::forward<First>(first);
        appendLog(log, std::forward<ARGS>(args)...);
    }
    
     template<typename Log, typename First, typename ...ARGS>
    static inline void appendLog(Log &out, First &&first, ARGS &&...args) {
        out << std::forward<First>(first);
        appendLog(out, std::forward<ARGS>(args)...);
    }
    
     template<typename Log>
    static inline void appendLog(Log &out) {}
    

    将第一个参数用First &&first和其余参数区分开。First &&first和std::forward(first)是完美转发,不改变first的左值或者右值属性。printLogArray创建了一个LogContextCapture对象,交给appendLog进行<<处理,直到最后一次空调用。

C++11

List及const成员函数

template<typename T>
class List : public std::list<T> {
public:
    template<typename ... ARGS>
    List(ARGS &&...args) : std::list<T>(std::forward<ARGS>(args)...) {};

    ~List() = default;

    void append(List<T> &other) {
        if (other.empty()) {
            return;
        }
        this->insert(this->end(), other.begin(), other.end());
        other.clear();
    }

    template<typename FUNC>
    void for_each(FUNC &&func) {
        for (auto &t : *this) {
            func(t);
        }
    }

    template<typename FUNC>
    void for_each(FUNC &&func) const {  //const List<int>& listRef = list; const List* listPtr = &list调用的版本
        for (auto &t : *this) {
            func(t);
        }
    }
};
<think>好的,用户需要查找ZLToolKit的编译方法和相关指南。让我结合之前的引用信息来整理下。 用户之前提供了四个引用内容,其中引用[1]提到项目文档和社区支持,引用[2]说明项目没有单启动文件而是通过示例集成,引用[3]详细描述了vs2017以下版本的编译步骤,引用[4]则是关于移动端适配的指南。用户当前明确询问编译方法,所以重点在引用[3]。 首先需要确定用户的操作系统和编译器版本。如果是Windows用户且使用较旧版本的Visual Studio(2017以下),引用[3]的cmake-gui方案完全适用。但用户可能使用更新的工具链,需要补充其他情况。 接着考虑跨平台编译需求。虽然用户的主要引用是关于Windows的,但ZLToolKit作为C++库应该支持Linux/macOS。需要补充通用编译流程,比如使用CMake命令行编译的标准步骤。 然后联想到引用[2]提到的示例程序。编译完成后用户可能需要测试功能,应该指导运行examples下的测试用例验证编译成果。 最后是移动端需求。引用[4]专门讲iOS/Android适配,虽然用户当前没提移动端,但作为开发者可能后续需要,应该简要说明交叉编译的方法作为前瞻性指引。 编译问题容易遇到子模块依赖问题,特别要注意git submodule update --init这步(引用[3]提到),很多编译失败源于此。需要特别强调。 可能用户实际环境比引用描述的更复杂,比如存在OpenSSL依赖问题。虽然引用未提及,但根据同类项目经验,应该预警常见编译错误及解决方向(如依赖库缺失),并建议查阅项目文档的FAQ部分。 需要把Windows传统方法、现代CMake通用方法、移动端方案分层说明,保持步骤清晰。每个环节要标注引用来源(如引用[3]的步骤),同时补充引用未覆盖但必要的技术细节(如Linux下安装依赖库的命令)。 </think>以下是根据提供的引用信息整理的 **ZLToolKit编译指南**,涵盖不同平台和开发环境: --- ### **、Windows 平台编译(VS2017 及以下版本)** [^3] 1. **克隆代码与初始化子模块** ```bash git clone https://github.com/ZLMediaKit/ZLToolKit.git cd ZLToolKit git submodule update --init # 关键!下载依赖项 ``` 2. **使用 CMake-GUI 生成工程** - 打开 CMake-GUI,设置源码路径(`ZLToolKit 目录`)和构建路径(新建 `build` 目录) - 点击 **Configure** → 选择 Visual Studio 版本 → **Generate** 生成 `.sln` 工程 3. **编译与运行** - 双击生成的 `ZLToolKit.sln` 用 VS 打开 - 选择 `Release` 配置 → 右键解决方案点击 **生成** - 在 `bin/Release` 目录下找到编译出的库文件(`.lib`/`.dll`)及示例程序 --- ### **二、通用编译方法(Linux/macOS/现代VS)** [^2] 1. **命令行编译(推荐)** ```bash mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release # 开启优化 make -j4 # 并行编译,数字按CPU核心数调整 ``` 2. **运行测试用例** ```bash cd bin ./test_http # 运行HTTP模块测试程序(其他示例类推) ``` --- ### **三、移动端编译(iOS/Android)** [^4] 1. **iOS 交叉编译** ```bash mkdir build_ios && cd build_ios cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/ios.toolchain.cmake -DIOS_PLATFORM=OS make ``` - 生成静态库 `.a` 文件,集成到 Xcode 工程 2. **Android 编译** - 使用 NDK 工具链,指定 `-DANDROID_NDK=/path/to/ndk` - 示例: ```bash cmake .. -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a ``` --- ### **四、常见问题解决** 1. **子模块初始化失败** 重复执行 `git submodule update --init --recursive` 确保依赖拉取完整。 2. **依赖库缺失(如 OpenSSL)** - Linux: `sudo apt install libssl-dev` - macOS: `brew install openssl` 3. **更详细指南** 参考项目文档:[ZLToolKit GitHub Wiki](https://github.com/ZLMediaKit/ZLToolKit/wiki) [^1] ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值