ZLToolKit(一)日志模块


功能描述

ZLToolkit的日志模块。

  1. 支持console,文件,广播三种输出通道,支持异步输出。
  2. 支持按时间、文件数量,文件大小进行清理和切片。
  3. 支持三种输出风格(cout, printf, 可变长模板)。
  4. 支持重载<<的类。
  5. 参考test_logger测试代码。

使用示例

#include "Util/logger.h"
int main()
{
    Logger::Instance().add(std::make_shared<FileChannel>());
    Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
    TraceL << "int"<< (int)1  << endl;
    PrintT("this is a %s test:%d", "printf trace", 124);
    LogT(1, "+", "2", '=', 3);
    return 0;
}

源码分析

类图

请添加图片描述

源码分析

  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

禁止拷贝和移动的基类

//禁止拷贝基类
class noncopyable {
protected:
    noncopyable() {}
    ~noncopyable() {}
private:
    //禁止拷贝
    noncopyable(const noncopyable &that) = delete;
    noncopyable(noncopyable &&that) = delete;
    noncopyable &operator=(const noncopyable &that) = delete;
    noncopyable &operator=(noncopyable &&that) = delete;
};

将拷贝函数声明为delete,当你尝试调用这些函数时,编译器会抛出一个错误, 而且派生类不能重写这个函数。

default成员函数的作用

class A
{
public:
    // ...
    A() {}          // 定义一个空的默认构造函数
    A() = default;  // 主动让编译器生成默认构造函数
    // ...
};

这两种方式的区别:
C++ 中有概念叫 trivial type ,可以简单翻译成“普通(平凡)类型”。
当一个类型(class / struct )同时满足以下几个条件时,它就是 trivial type:

  1. 没有虚函数或虚基类。
  2. 没有具有相应的非平凡构造函数/运算符/析构函数的基类
  3. 没有具有相应的非平凡构造函数/运算符/析构函数的类类型的数据成员

所以,上面的例子中,如果使用了 A() {} 则 class A是non-trivial type;如果使用的是 A() = default; 则class A是trivial type。编译器可以对平凡构造函数进行各种优化,可能比用户空实现的构造函数性能更好。

https://learn.microsoft.com/en-us/cpp/cpp/trivial-standard-layout-and-pod-types?view=msvc-170

定义了带参数的构造函数时还希望使用默认构造函数

    class MyClass {
     public:
      MyClass() = default;  // 提供缺省的构造函数
      MyClass(int v):x(y) {}
      MyClass(const MyClass& other) = default;  // 提供缺省的拷贝构造函数
      MyClass& operator=(const MyClass& other) = default;  // 提供缺省的拷贝赋值运算符
      // 其他成员函数
     private:
      int x;
    };

MyClass(int v):x(y) {} 编译器就不自动为你生成默认的那些构造函数了,因为它觉得你想根据自己的需求定义构造函数,这时候使用=default可以简化定义默认的构造函数并且
使其满足trivial type的定义。。

判断trivial

#include <type_traits>

class A
{
public:
    A(int i) : n(i) {}
    A() {}
private:
    int n;
};

class B
{
public:
    B(int i) : n(i) {}
    B() = default;
private:
    int n;
};

static_assert(!std::is_trivial<A>::value);
static_assert(std::is_trivial<B>::value);

enable_shared_from_this

说明:
若一个类T继承了std::enable_shared_from_this,则会为该类T提供成员函数:shared_from_this.当T类型对象t被一个为名为 pt的std::shared_ptr 类对象管理时,调用 T::shared_from_this 成员函数,将会返回一个新的std::shared_ptr 对象,它与pt共享t的所有权。

示例:

class A : public std::enable_shared_from_this<A> 
{
public:
	std::shared_ptr<A> getptr() {
		return shared_from_this();
	}
	~A() { std::cout << "A::~A() called" << std::endl; }
};
 
int main()
{
	{
		std::shared_ptr<A> a1(new A());
		std::shared_ptr<A> a2 = a1->getptr();
		std::cout << "a1.use_count() = " << a1.use_count() << std::endl;  //2 
		std::cout << "a2.use_count() = " << a2.use_count() << std::endl;  //2 
	}
}

使用场景:
在异步调用中,存在一个保活机制,异步函数执行的时间点我们是无法确定的,然而异步函数可能会使用到异步调用之前就存在的变量。为了保证该变量在异步函数执期间一直有效,我们可以传递一个指向自身的share_ptr给异步函数,这样在异步函数执行期间share_ptr所管理的对象就不会析构,所使用的变量也会一直有效了(保活)。

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);
        }
    }
};
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值