spdlog库学习(五):其他小功能

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


周期刷新

示例中,周期性刷新时间的设置只演示了一个接口

spdlog::flush_every(std::chrono::seconds(3));

接口定义在spdlog.h 实现在spdlog-inl.h

SPDLOG_INLINE void flush_every(std::chrono::seconds interval)
{
    details::registry::instance().flush_every(interval);
}

还是调用了registry的接口,我们关注得是这个值有什么用
registry中的该接口实现如下

SPDLOG_INLINE void registry::flush_every(std::chrono::seconds interval)
{
    std::lock_guard<std::mutex> lock(flusher_mutex_);
    auto clbk = [this]() { this->flush_all(); };
    periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval);
}

从代码中看到,只是创建了一个periodic_flusher_这个对象,类型为periodic_worker。看名字也知道是周期性的执行一个函数,函数就是clbk。
peridoic_worker是如何实现的呢?定义在periodic_worker.h。定义比较短,这里全贴出来

class SPDLOG_API periodic_worker
{
public:
    periodic_worker(const std::function<void()> &callback_fun, std::chrono::seconds interval);
    periodic_worker(const periodic_worker &) = delete;
    periodic_worker &operator=(const periodic_worker &) = delete;
    // stop the worker thread and join it
    ~periodic_worker();

private:
    bool active_;
    std::thread worker_thread_;
    std::mutex mutex_;
    std::condition_variable cv_;
};

周期性执行的实现

可以看到,这里是新建了一个线程。我比较好奇的是,周期性执行一个函数,这里是采用什么方式实现的。当然通过类内的条件变量和锁,也能大致猜到。其在构造函数中实现,如下:

SPDLOG_INLINE periodic_worker::periodic_worker(const std::function<void()> &callback_fun, std::chrono::seconds interval)
{
    active_ = (interval > std::chrono::seconds::zero()); // 时间间隔大于0秒才激活线程。
    // 该变量是为停止该线程而设计的。如果没有该变量,线程将没有恰当的时机被终止。该变量无需保护。
    if (!active_)
    {
        return;
    }

    worker_thread_ = std::thread([this, callback_fun, interval]() {  //线程执行函数体
        for (;;)
        {
         //先加锁,这是使用条件变量的标准流程,并不是为了保护哪个临界区 
            std::unique_lock<std::mutex> lock(this->mutex_);
            // 等待interval秒,期间如果有人notify,会提前返回。
            // 当然我们知道就是为了让它等interval秒而已,正常情况下不会有人notify的
            // 条件变量返回后,会判断后面的lamba表达式是否为真,如果是真,直接return。
            // 这是用于判断线程是否该结束。
            if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) 
            {
                return; // active_ == false, so exit this thread
            }
            callback_fun();
        }
    });
}

registry::flush_all()

SPDLOG_INLINE void registry::flush_all()
{
    std::lock_guard<std::mutex> lock(logger_map_mutex_);
    for (auto &l : loggers_)
    {
        l.second->flush();
    }
}

可以看到只是调用了logger的flush()方法。
意料之中的是:logger又调用了sink的flush方法。记得logger并不直接处理日志输出目标,而是由sink代劳。
普通logger是由sink直接调用操作系统接口刷新的,例如

  • ansicolor_sink< ConsoleMutex>直接调用了fflush();
  • base_sink< Mutex> 交给了基类来实现,因为刷新哪个文件是未知的。
    其派生类有的为空实现比如:系统日志就无需刷新;有的直接调用了文件辅助类的flush方法file_helper_.flush()

文件辅助类后续再学习
uml图

trace功能

spdlog::enable_backtrace(32);
spdlog::debug("Backtrace message {}", i);
spdlog::dump_backtrace();

可以看到,trace功能是需要开启的,trace的记录数量也是需要设置的。
enable_trace同样也是调用了registry的接口,实现如下

SPDLOG_INLINE void registry::enable_backtrace(size_t n_messages)
{
    std::lock_guard<std::mutex> lock(logger_map_mutex_);
    backtrace_n_messages_ = n_messages;

    for (auto &l : loggers_)
    {
        l.second->enable_backtrace(n_messages);
    }
}

可以看到,registry的接口,通过调用logger的enable_trace,把他保存的所有logger都设置了一样的trace。

而logger内部有一个tracer_类专门用于处理trace消息

// create new backtrace sink and move to it all our child sinks
SPDLOG_INLINE void logger::enable_backtrace(size_t n_messages)
{
    tracer_.enable(n_messages);
}

而在输出日志时,会先询问tracer_要不要输出trace

log(....) {
 bool traceback_enabled = tracer_.enabled();
 ...
}

那么默认情况下,logger会不会启用trace功能呢?
看代码发现,tracer_一般是默认初始化的,其类型为backtracer,实现在backetracer.h

class logger{
...
	details::backtracer tracer_;
...
}

backtracer.h
class SPDLOG_API backtracer
{
...
    std::atomic<bool> enabled_{false};
    circular_q<log_msg_buffer> messages_;
public:
    void enable(size_t size);
    bool enabled() const;
    ...
};
SPDLOG_INLINE bool backtracer::enabled() const
{
    return enabled_.load(std::memory_order_relaxed);
}

判断trace_是否启用的变量是原子的,默认为false。这里的使用很微妙,为什么是原子的呢?有待后续思考、学习。
其enabled()问询实现,也是没见过的用法:

backtracer-inl.h
SPDLOG_INLINE bool backtracer::enabled() const
{
    return enabled_.load(std::memory_order_relaxed);
}

stopwatch功能

stopwatch应该是一个性能工具,可以方便的统计代码执行的用时

    spdlog::stopwatch sw;    
    spdlog::debug("Elapsed {}", sw);
    spdlog::debug("Elapsed {:.3}", sw);       

stopwatch实现很简单,其中的时间戳大量使用了c++:chrono库的工具。c++对chrono时间工具的设计很全面,全面到有些复杂。这里贴两篇别人的介绍文档。
c++11 chrono全面解析(最高可达纳秒级别的精度)
C++11中的时间库std::chrono(引发关于时间的思考)

class stopwatch
{
    using clock = std::chrono::steady_clock;
    std::chrono::time_point<clock> start_tp_;

public:
    stopwatch()
        : start_tp_{clock::now()}
    {}
    // 到现在过去了多久。
    std::chrono::duration<double> elapsed() const 
    {
        return std::chrono::duration<double>(clock::now() - start_tp_);
    }
   // 重置一下时间检查点
    void reset()
    {
        start_tp_ = clock::now();
    }
};

根据其接口,容易理解其功能,已添加注释。
核心在于,该类如何与format进行交互。

系统日志

这个我自己不太懂,所以贴在这里。看看系统日志到底是个什么东西。

auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID);

系统日志实现在:syslog_sink.h

template<typename Mutex>
class syslog_sink : public base_sink<Mutex> {};

构造函数
    {
        // set ident to be program name if empty
        ::openlog(ident_.empty() ? nullptr : ident_.c_str(), syslog_option, syslog_facility);
    }
析构函数
    {
        ::closelog();
    }
sink_it_ 函数
    {
        ....
        ::syslog(syslog_prio_from_level(msg), "%.*s", static_cast<int>(length), payload.data());
    }

原来系统日志就是调用了syslog.h中的接口。该头文件是The Single UNIX ® Specification, Version 2标准中规定的。文档

好奇心驱使,看看android日志怎么打印的。

auto android_logger = spdlog::android_logger_mt("android", tag);

android_sink实现在android_sink.h。其也是用了android提供的系统日志工具:android/log.h。不过它好像可以直接使用,不需要open和close。不过它好像可能输出失败,添加了重试的逻辑。

sink_it_(...) {
...
android_log(priority, tag_.c_str(), msg_output);
while ((ret == -11 /*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES))
{
    details::os::sleep_for_millis(5);
    ret = android_log(priority, tag_.c_str(), msg_output);
    retry_count++;
}
...
}

android_log根据android的版本不同,提供了两个重载。

    // There might be liblog versions used, that do not support __android_log_buf_write. So we only compile and link against
    // __android_log_buf_write, if user explicitely provides a non-default log buffer. Otherwise, when using the default log buffer, always
    // log via __android_log_write.
    template<int ID = BufferID>
    typename std::enable_if<ID == static_cast<int>(log_id::LOG_ID_MAIN), int>::type android_log(int prio, const char *tag, const char *text)
    {
        return __android_log_write(prio, tag, text);
    }

    template<int ID = BufferID>
    typename std::enable_if<ID != static_cast<int>(log_id::LOG_ID_MAIN), int>::type android_log(int prio, const char *tag, const char *text)
    {
        return __android_log_buf_write(ID, prio, tag, text);
    }
    

此外,为了统一spdlog的日志等级和android的日志等级,使用convert_to_android_来转换这两种日志等级

static android_LogPriority convert_to_android_(spdlog::level::level_enum level)

总结

接下来的学习:
文件辅助类
文本格式化:fmt工具
线程池实现
等等,想到再补充

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值