当前位置: 首页 > news >正文

TensorRT笔记(2):解析样例中Logger日志类的设计

在https://blog.csdn.net/ouliten/article/details/154490047?spm=1001.2014.3001.5502#t5

我把Logger这个类给贴出来了,但是关键的实现机制并没有详细讲解

这个Logger类继承了nvinfer1::ILogger,最关键的重写log方法实现如下

void log(Severity severity, const char* msg) noexcept override{LogStreamConsumer(mReportableSeverity, severity) << "[TRT] " << std::string(msg) << std::endl;}

很明显这个log方法委托给了LogStreamConsumer这个类取实现。而且LogStreamConsumer这个类样子上就很明显像std::ostream的使用方法。

下面就从头开始分析这个类的实现

LogStreamConsumerBuffer

using Severity=nvinfer1::ILogger::Severity;
class LogStreamConsumerBuffer:public std::stringbuf{
public:LogStreamConsumerBuffer(std::ostream& stream, const std::string& prefix, bool shouldLog): mOutput(stream), mPrefix(prefix), mShouldLog(shouldLog){}LogStreamConsumerBuffer(LogStreamConsumerBuffer&& other) noexcept: mOutput(other.mOutput), mPrefix(other.mPrefix), mShouldLog(other.mShouldLog){}LogStreamConsumerBuffer(const LogStreamConsumerBuffer& other) = delete;LogStreamConsumerBuffer() = delete;LogStreamConsumerBuffer& operator=(const LogStreamConsumerBuffer&) = delete;LogStreamConsumerBuffer& operator=(LogStreamConsumerBuffer&&) = delete;~LogStreamConsumerBuffer() override{// std::streambuf::pbase() gives a pointer to the beginning of the buffered part of the output sequence// std::streambuf::pptr() gives a pointer to the current position of the output sequence// if the pointer to the beginning is not equal to the pointer to the current position,// call putOutput() to log the output to the streamif (pbase() != pptr()){putOutput();}}//!//! synchronizes the stream buffer and returns 0 on success//! synchronizing the stream buffer consists of inserting the buffer contents into the stream,//! resetting the buffer and flushing the stream//!//C++ 流机制在每次 flush 或 std::endl 时会调用int32_t sync() override{putOutput();return 0;}//将缓存内容输出到 mOutput。//自动添加时间戳和前缀。//清空缓冲区并 flush。void putOutput(){if(mShouldLog){std::time_t timestamp=std::time(nullptr);tm *tm_local=std::localtime(&timestamp);mOutput << "[";mOutput << std::setw(2) << std::setfill('0') << 1 + tm_local->tm_mon << "/";mOutput << std::setw(2) << std::setfill('0') << tm_local->tm_mday << "/";mOutput << std::setw(4) << std::setfill('0') << 1900 + tm_local->tm_year << "-";mOutput << std::setw(2) << std::setfill('0') << tm_local->tm_hour << ":";mOutput << std::setw(2) << std::setfill('0') << tm_local->tm_min << ":";mOutput << std::setw(2) << std::setfill('0') << tm_local->tm_sec << "] ";// std::stringbuf::str() gets the string contents of the buffer// insert the buffer contents pre-appended by the appropriate prefix into the streammOutput << mPrefix << str();}// set the buffer to emptystr("");// flush the streammOutput.flush();}void setShouldLog(bool shouldLog){mShouldLog = shouldLog;}
private:std::ostream &mOutput;//真正输出日志的流std::string mPrefix;//日志前缀,比如 [E] 或 [I]bool mShouldLog{};//是否实际输出日志,用于根据 severity 筛选日志
};

这个类对std::ostream做了一个包装。实现了具体的输出的功能

LogStreamConsumerBase

//提供线程安全的日志缓冲
class LogStreamConsumerBase
{
public:LogStreamConsumerBase(std::ostream& stream, const std::string& prefix, bool shouldLog): mBuffer(stream, prefix, shouldLog){}protected:std::mutex mLogMutex;LogStreamConsumerBuffer mBuffer;
}; // class LogStreamConsumerBase

这个类比较简单,为LogStreamConsumerBuffer提供了一个锁。目的是防止多线程同时调用log,导致输出混乱。

LogStreamConsumer

//继承顺序很重要:先 LogStreamConsumerBase 初始化 mBuffer,再把 mBuffer 的地址传给 std::ostream 构造函数。
class LogStreamConsumer:protected LogStreamConsumerBase,public std::ostream{
public:LogStreamConsumer(nvinfer1::ILogger::Severity reportableSeverity,nvinfer1::ILogger::Severity severity):LogStreamConsumerBase(severityOstream(severity),severityPrefix(severity),severity<=reportableSeverity),std::ostream(&mBuffer),mShouldLog(severity<=reportableSeverity),//根据 Severity 决定是否输出日志mSeverity(severity){}LogStreamConsumer(LogStreamConsumer&& other) noexcept: LogStreamConsumerBase(severityOstream(other.mSeverity), severityPrefix(other.mSeverity), other.mShouldLog), std::ostream(&mBuffer) // links the stream buffer with the stream, mShouldLog(other.mShouldLog), mSeverity(other.mSeverity){}LogStreamConsumer(const LogStreamConsumer& other) = delete;LogStreamConsumer() = delete;~LogStreamConsumer() override = default;LogStreamConsumer& operator=(const LogStreamConsumer&) = delete;LogStreamConsumer& operator=(LogStreamConsumer&&) = delete;void setReportableSeverity(Severity reportableSeverity){mShouldLog = mSeverity <= reportableSeverity;mBuffer.setShouldLog(mShouldLog);}std::mutex& getMutex(){return mLogMutex;}bool getShouldLog() const{return mShouldLog;}
private:static std::ostream& severityOstream(Severity severity){//自动选择输出流:return severity>=Severity::kINFO?std::cout:std::cerr;}static std::string severityPrefix(Severity severity){switch (severity){case Severity::kINTERNAL_ERROR: return "[F] ";case Severity::kERROR: return "[E] ";case Severity::kWARNING: return "[W] ";case Severity::kINFO: return "[I] ";case Severity::kVERBOSE: return "[V] ";default: assert(0); return "";}}bool mShouldLog;Severity mSeverity;
};// class LogStreamConsumer

重载<<方法


//模板实现,可以像普通 ostream 一样使用:
template <typename T>
LogStreamConsumer& operator<<(LogStreamConsumer& logger, const T& obj)
{if (logger.getShouldLog()){std::lock_guard<std::mutex> guard(logger.getMutex());//使用互斥锁保证多线程安全。auto& os = static_cast<std::ostream&>(logger);os << obj;}return logger;
}
//!
//! Special handling std::endl
//!
inline LogStreamConsumer& operator<<(LogStreamConsumer& logger, std::ostream& (*f)(std::ostream&) )
{if (logger.getShouldLog()){std::lock_guard<std::mutex> guard(logger.getMutex());auto& os = static_cast<std::ostream&>(logger);os << f;}return logger;
}
inline LogStreamConsumer& operator<<(LogStreamConsumer& logger, const nvinfer1::Dims& dims)
{if (logger.getShouldLog()){std::lock_guard<std::mutex> guard(logger.getMutex());auto& os = static_cast<std::ostream&>(logger);for (int32_t i = 0; i < dims.nbDims; ++i){os << (i ? "x" : "") << dims.d[i];}}return logger;
}

找到关键点,在具体实现的时候

auto& os = static_cast<std::ostream&>(logger);
os << obj;

我们将LogStreamConsumer& logger强制转换成std::ostream&类型,用来模拟<<操作

而logger的std::ostream父类,绑定了&mBuffer。

mBuffer是LogStreamConsumerBuffer类,继承自std::stringbuf

std::ostream 有一个构造函数:

explicit ostream(streambuf* sb);
  • 也就是说,std::ostream 可以接受一个 指向 std::streambuf 的指针 来初始化输出缓冲区。

  • 所有写入 ostream 的操作(<<write()flush() 等)都会最终调用 streambuf虚函数

    • overflow() → 当缓冲区满时写入。

    • xsputn() → 写入连续字符。

    • sync() → flush 同步缓冲区。

当 flush 或 std::endl 触发:

mBuffer.sync()
  • 调用 LogStreamConsumerBuffer::sync(),内部调用 putOutput()

  • putOutput() 会:

    • 生成时间戳

    • 添加前缀([I]、[E] 等)

    • 将缓冲区的字符串写入 mOutput(也就是 std::cout / std::cerr

    • 清空 stringbuf 并 flush mOutput

核心设计理念

  1. 继承 + 委托

    • LogStreamConsumer 继承 std::ostream 提供流式接口

    • 输出实现委托给 mBuffer,分离接口与逻辑

  2. 组合 + 初始化顺序控制

    • mBufferLogStreamConsumerBase 中初始化,保证构造顺序正确

    • std::ostream 构造时拿到已初始化的缓冲区

  3. 最终输出到指定流

    • mBuffer 内部的 mOutput 决定真正输出的设备

    • 允许根据 severity 动态选择 std::coutstd::cerr

LogStreamConsumerBuffer设计解析

它继承 std::stringbuf,又持有一个 std::ostream&,看起来像“自己写自己还要交给别人写”,其实逻辑如下:

① 继承 std::stringbuf
  • 目的:利用 stringbuf 的缓冲功能。

    • stringbuf 内部有一个可写字符串缓冲区。

    • 当你做 sputn()operator<< 写入时,数据先写到这个缓冲区。

    • 你可以重载 sync(),在 flush 的时候把缓冲区的内容输出到外部。

  • 也就是:stringbuf 用作缓存层

② 内部维护 std::ostream& mOutput
  • 目的:决定最终输出流去向。

    • 日志可能要输出到 std::coutstd::cerr 或文件流。

    • mOutput 保存这个目标。

  • 当缓冲区 flush(调用 sync() 或析构)时:

    1. 取出缓冲区内容 str()

    2. 添加时间戳、前缀

    3. 写入 mOutput

    4. flush mOutput

感觉怪怪的?

LogStreamConsumerBuffer 既继承了 std::stringbuf(给 std::ostream 提供缓冲区),又自己内部持有一个 std::ostream& mOutput。表面上看好像重复了,其实逻辑上是合理的,而且在 C++ 日志系统里是一个典型模式

  • std::ostream 本身不存储数据,它只是一个流接口。

  • std::ostream 需要一个 std::streambuf* 来实际存储和输出数据:

  • 当你写入 os << "msg"

  • 数据写入 buffer(调用 sputn()overflow()

  • flush/析构时,调用 buffer.sync()

发现了吗,如果是你自己实现的std::ostream子类对象,调用<<,数据只是放在了buffer里,并没有输出。

那究竟谁能输出呢?答案是std::cout/std::cerr.

LogStreamConsumerBuffer 内部有:

std::ostream& mOutput; // 最终输出目标

它不是用来缓冲的,而是决定最终输出流(例如 std::coutstd::cerr)。

LogStreamConsumerBuffer 设计了 中间缓存 + 格式化 + 最终输出

  1. 继承 stringbuf:拦截 ostream 写入,允许缓存和格式化

  2. 内部 std::ostream& mOutput:决定写到哪里

  3. 如果内部没有std::ostream& mOutput,那么你依然要在其他地方创建一个std::ostream&然后输出stringbuf的内容

为什么LogStreamConsumer要继承LogStreamConsumerBase

其实在逻辑上看,LogStreamConsumer与LogStreamConsumerBase没那么像父子同类的关系。所以如果把这两改成组合关系,也不是不可以?

不过因为std::ostream的父类要求stringbuf指针。通过按序继承的方式,能保证父类初始化的顺序。

http://www.dtcms.com/a/599102.html

相关文章:

  • 南京领动做网站怎么样佛山制作网站公司推荐
  • 苏州建网站哪个好做定制的网站
  • 江苏网站建设流程朔州市建设监理公司网站
  • AI销冠是什么?熊猫智汇公司如何利用它提升企业效率?
  • 并发编程 | 提高程序效率的关键技术
  • 电子商务网站建设 项目规划书iis网站
  • 网站规划与建设实验心得企业网络营销策划必须以什么为核心
  • Bash Glob 通配符详细指南:从 POSIX 标准到高级用法
  • 景观设计论文seo关键词优化排名推广
  • 小米手机网站的风格设计莱州教育网站
  • 上海自适应网站设计网络营销策划书实施计划
  • 自己怎做网站后台正规医院看男科大概多少钱
  • NebulaChat 框架学习笔记
  • 网站规划建设方案模板网站建设和美工
  • 怎样用自己电脑做网站c2c电子商务平台有哪些?
  • 专业设计网站有哪些建设部的官方网站
  • CottonCloudsProcreate软笔刷套装打造柔和棉云质感日漫风插画创作资源
  • 山儿网站建设公司小程序 制作公司
  • 做暧昧网站无锡做网站的企业
  • 中堂做网站wordpress下载软件
  • 天津微外卖网站建设郑州大型网站建设电话
  • 《编程工具上架应用商店的避坑+引流全攻略》
  • 淮安网站网站建设京津冀协同发展10周年
  • 如何制作一个网站网站建设完整代码
  • 网站群建设规划方案wordpress同步微博插件
  • 网站底部关键词指向怎么做网站 教学
  • 企业网站建设范文辽宁工程招标网信息平台
  • SAP FICO资产批量导入功能
  • 大型企业门户网站能力建设探索与实践包头企业做网站
  • 什么都能买到的网站wordpress怎么做手机端