Boost log库

Boost.log库使用方法 做服务器开发首要做的事情,就是将日志系统建立起来。这个系统也是方便我们检查服务器的问题。 初始化日志系统这是一个初始化日志系统的代码。我这里是使用了一个配置文件来做boost.log的设置的。所以代码写起来是非常的简洁。#include #include #include #includ... 阅读详情

本文首先介绍了boost.log的几个重要的概念,然后分析其框架结构,最后详细解析了一段示例代码,并总结了将boost.log应用到自己的程序中时的步骤。

 

1. 几个概念

  • 日志记录:一个独立的消息包,这个消息包还不是实际写到日志里的消息,它只是一个候选的消息。
  • 属性:日志记录中的一个消息片。
  • 属性值:那就是上面所说的属性的值了,可以是各种数据类型。
  • 日志槽(LOG SINK):日志写向的目标,它要定义日志被写向什么地方,以及如何写。
  • 日志源:应用程序写日志时的入口,其实质是一个logger对象的实例。
  • 日志过滤器:决定日志记录是否要被记录的一组判断。
  • 日志格式化:决定日志记录输出的实际格式。
  • 日志核心:维护者日志源、日志槽、日志过滤器等之间的关系的一个全局中的实体。主要在初始化logging library时用到。

2. 框架结构

如图,

(1). 应用程序在图的右侧,通过一个或多个logger实例发送日志消息。

(2). 应用程序也可以出现在左侧,那就是一个日志的显示实例了。

(3). 一个日志记录的数据中会包括许多属性。属性基本上是一个函数,它的返回值就是属性值。比如时间不是一个函数(也是一个属性)。

(4). 有三种类型的属性集:全局的,特定线程的,特定源的。前两个是由logging core来维护的,所以不用再初始化。

(4.1). 全局属性集中的属性被连接到所以的日志对象上。

(4.2). 线程属性集中的属性会连接到把它注册到属性集时的那个线程。

(4.3). 源属性集由初始化日志的源来维护的,它会连接到一个特定的源上。

(4.4). 当一个源初始化日志对象的时候,它会从上述的三个属性集的所有属性中得到属性值。这些值会在将来处理。

如果在不同的属性集中有相同的属性名字的时候就会造成冲突,解决冲突的方法是全局属性集的优先级最低,源属性集的优先级最高。高优先级的属性会覆盖低优先级的属性。

(5). 当组合属性值的时候,logging core来决定一个属性是否要被送到sink中,这就是过滤。有两层过滤,首先应用的是全局中过滤,全局过滤用来快速的过滤掉那些不需要的日志记录。然后就是sink指定的过滤了。每个sink都有单独的过滤器。sink过滤器允许将一个日志记录定向到一个指定的sink。

(6). 如果一个日志记录至少通过了一个sink的话,它就可以用了。这时候就是日志消息格式化的时候了。格式化完成的日志消息和属性值一起被送到接收它们的sink中。

(7). 如上图所示,sink被分为前端和后端两个部分。这是为了抽象sink的通用功能,如过滤和线程同步。前端由日志库提供,用户不大可能再去实现它。而后端很可能是在日志库的外面,它来实现对日志记录的处理。如写文件,发送到网络等。日志库提供了大部分通常用到的后端程序。

3. 示例代码解析

这个示例代码在boost.log的basic_usage里,文件前面include就省略了。

namespace logging = boost::log;
namespace fmt = boost::log::formatters;
namespace flt = boost::log::filters;
namespace sinks = boost::log::sinks;
namespace attrs = boost::log::attributes;
namespace src = boost::log::sources;

using boost::shared_ptr;

// 这里定义了一个日志级别的enum,后面在日志输出时会是一个属性值
enum severity_level
{
    normal,
    notification,
    warning,
    error,
    critical
};

// 定义上面的级别输出流操作的重载函数,在日志输出时会用到
template< typename CharT, typename TraitsT >
inline std::basic_ostream< CharT, TraitsT >& operator<< (
    std::basic_ostream< CharT, TraitsT >& strm, severity_level lvl)
{
    static const char* const str[] =
    {  // 这里的每一个值要与severity_level enum对应
        "normal",
        "notification",
        "warning",
        "error",
        "critical"
    };
    //如果日志的级别在enum里,则输出相应的文本
    if (static_cast< std::size_t >(lvl) < (sizeof(str) / sizeof(*str))) 
        strm << str[lvl];
    else  //否则直接输出数字值
        strm << static_cast< int >(lvl);
    return strm;
}
// 这个函数用来测试日志嵌套输出的功能
int foo(src::logger& lg)
{
    BOOST_LOG_FUNCTION(); // 这里会在Scope属性中加入“foo”
    BOOST_LOG(lg) << "foo is being called";
    return 10;
}

int main(int argc, char* argv[])
{
    // 创建一个sink: synchronous_sink是sink frontend, text_ostream_backend是sink backend
    // text_ostream_backend可以将日志以文本的方式输出
    // synchronous_sink可以处理线程同步的问题,也就是在多个线程同时使用这个sink时,
    // 我们的应用程序不用再考虑线程同步的问题了。
    typedef sinks::synchronous_sink< sinks::text_ostream_backend > text_sink;
    shared_ptr< text_sink > pSink(new text_sink);
    //好了,现在pSink就是一个text_sink类型的shared_ptr指针了

    { // 这里限定的区域是为了下面的锁
        // 获取一个backend的锁指针.
        // 因为有了synchronous_sink类型的frontend,我们这里只要有这个locked_backend()
        // 就保证在此处操作时不会有其它的线程同时操作.
        text_sink::locked_backend_ptr pBackend = pSink->locked_backend();

        // 既然backend是一个text_ostream类型的,我们就可以加入一些ostream类型的输出流给他
        // 日志会同时输出到这些输出流中

        // 先加一个std::clog给它
        shared_ptr< std::ostream > pStream(&std::clog, logging::empty_deleter());

        // shared_ptr会在指针不再使用时删除它, 但std::clog是不能删除的, 所以加logging::empty_deleter()
        pBackend->add_stream(pStream);

        // 再加一个std::ofstream给它
        shared_ptr< std::ofstream > pStream2(new std::ofstream("sample.log"));
        assert(pStream2->is_open());
        pBackend->add_stream(pStream2);
    }

    // 好了,我们已经做好了一个sink, 现在将它加入到logging library里
    logging::core::get()->add_sink(pSink);

    // 再创建一个logger, 我们就可以用它来输出了.
    src::logger lg;

    // Hello, World 一下, 在sample.log文件和控制台上会同时显示
    BOOST_LOG(lg) << "Hello, World!";

    // 格式化输出, 也是用locked_backend来操作, 此时指定的属性要在后台逐一定义.
    pSink->locked_backend()->set_formatter(fmt::ostrm
        << fmt::attr("LineID") // 这个是指日志文件的行号,不是程序源文件的行号
        << " [" << fmt::date_time< boost::posix_time::ptime >("TimeStamp", "%d.%m.%Y %H:%M:%S.%f") 
        << "] [" << fmt::attr< severity_level >("Severity") // 注意这里的severity_level正是我们前面定义的enum
        << "] [" << fmt::time_duration< boost::posix_time::time_duration >("Uptime") // 这个属性在后面会被定义成一个线程范围的属性
        << "] [" 
        << fmt::attr< std::string >("Tag") // 这个Tag只是一个字符串类型的属性
        << "] [" 
        << fmt::named_scope("Scope", fmt::keywords::scope_iteration = fmt::keywords::reverse) << "] " // 这个Scope属性就是打印嵌套函数的东西了
        << fmt::message()); // 最后,别忘了将最重要的日志内容打印了.

/*
    // 这是另外一种格式化的方法, 好像更简单一些.
    pSink->locked_backend()->set_formatter(
        fmt::format("%1% @ %2% [%3%] >%4%< Scope: %5%: %6%")
            % fmt::attr("LineID")
            % fmt::date_time< boost::posix_time::ptime >("TimeStamp", "%d.%m.%Y %H:%M:%S.%f")
            % fmt::time_duration< boost::posix_time::time_duration >("Uptime")
            % fmt::attr< std::string >("Tag")
            % fmt::named_scope("Scope", fmt::keywords::scope_iteration = fmt::keywords::reverse, fmt::keywords::scope_depth = 2)
            % fmt::message());
*/

    // 下面开始设置属性了.

    // LineID是一个计数器,先创建一个初始值为1的计数器.
    shared_ptr< logging::attribute > pCounter(new attrs::counter< unsigned int >(1));

    // 将它加入到全局属性中,如果要求将不同的内容输出到不同的日志文件中去,这里设置为全局属性可能就是不太合适了.
    logging::core::get()->add_global_attribute("LineID", pCounter);

    // 下面是设置TimeStamp属性
    shared_ptr< logging::attribute > pTimeStamp(new attrs::local_clock());
    logging::core::get()->add_global_attribute("TimeStamp", pTimeStamp);

    // 设置Uptime属性为线程级属性,因为运行时间只能在一个线程内衡量才有意义
    // attrs::timer应该是一个boost::posix_time::time_duration类型的值,会记录上本次调用与上一次调用的时间差。
    BOOST_LOG_SCOPED_THREAD_ATTR("Uptime", attrs::timer);

    // Socpe也是一个线程级的属性,add_thread_attribute是另外一个增加线程级属性的方法
    boost::shared_ptr< logging::attribute > pNamedScope(new attrs::named_scope());
    logging::core::get()->add_thread_attribute("Scope", pNamedScope);

    // 设置日志的Scope,也就是“main”函数
    BOOST_LOG_FUNCTION();

    // 现在再输出两个日志记录,结果是这样的:
    // 1 [08.12.2009 11:16:42.750000] [] [00:00:00.000079] [] [int __cdecl main(int,char *[])] Some log line with a counter
    // 2 [08.12.2009 11:16:42.765625] [] [00:00:00.016310] [] [int __cdecl main(int,char *[])] Another log line with the counter
    BOOST_LOG(lg) << "Some log line with a counter";
    BOOST_LOG(lg) << "Another log line with the counter";

    // 注意到上面有两个空的属性,一个是severity_leve, 另一个是Tag
    // 下面设置一下Tag.
    {
        BOOST_LOG_NAMED_SCOPE("Tagging scope"); // 这里设定一个这个区域的名字为“Tagging scope”,输出scope属性值时会增加这个scope

        // 现在增加给lg增加一个临时的属性.
        // 每一个在当前scope里用lg输出的日志记录,它的“Tag”属性值都是“Tagged line”
        BOOST_LOG_SCOPED_LOGGER_TAG(lg, "Tag", std::string, "Tagged line");

        // 也可以用下面的代码实现:
        // attrs::constant< std::string > TagAttr("Tagged line");
        // logging::scoped_attribute _ =
        //     logging::add_scoped_logger_attribute(lg, "Tag", TagAttr);

        // 再输出两条看一下,结果是这样的, 注意“Tagged line”和“Tagging scope”:
        // 3 [08.12.2009 11:16:42.781250] [] [00:00:00.032886] [Tagged line] [Tagging scope<-int __cdecl main(int,char *[])] Some tagged log line
        // 4 [08.12.2009 11:16:42.812500] [] [00:00:00.051012] [Tagged line] [Tagging scope<-int __cdecl main(int,char *[])] Another tagged log line
        BOOST_LOG(lg) << "Some tagged log line";
        BOOST_LOG(lg) << "Another tagged log line";
    }

    // 这里再输出一行,就没有上面那个区域中的“Tag line”和“Tagging scope”了:
    // 5 [08.12.2009 11:16:42.828125] [] [00:00:00.068013] [] [int __cdecl main(int,char *[])] Now the tag is removed
    BOOST_LOG(lg) << "Now the tag is removed";

    // 现在可以看一下过滤器的使用了。
    // 过滤器的过滤条件是基于属性的。
    // 每一个过滤器其实就是一个返回值为bool型的函数对象.
    // 过滤器可以指定到sink,也可以指定到全局.
    // 像下面这样可以为一个sink设置过滤器:
    //pSink->set_filter(
    //    flt::attr< severity_level >("Severity") >= warning // 输出所有Severity属性值大于等于warning的日志记录
    //    || flt::attr< std::string >("Tag").begins_with("IMPORTANT")); // 或者Tag属性值以“IMPORTANT”开头的

    // 对于std::string或std::wstring类型的属性有一些谓词可以使用:
    // "begins_with", "ends_with", "contains", "matches"
    // 其中matches谓词可以RegEx表达式

    // 下面是设置全局的过滤器
    logging::core::get()->set_filter(
        flt::attr< severity_level >("Severity") >= warning // Write all records with "warning" severity or higher
        || flt::attr< std::string >("Tag").begins_with("IMPORTANT"));

    // 这时候,我们可以用“lg”来输出日志记录了。
    // 
    // 另外,还有一个severity_logger,可以直接使用它来做logger
    // 如果想增加一些功能,可以派生于它的类
    src::severity_logger< severity_level > slg;

    // 由于我们前面设置了过滤器(不论是全局的还是sink的都影响),所以下一行的normal日志记录将不会输出。其它是设置为全局和线程的属性对于slg也同样适用。
    BOOST_LOG_SEV(slg, normal) << "A normal severity message, will not pass to the output";
    BOOST_LOG_SEV(slg, error) << "An error severity message, will pass to the output";

    {
        // 这里设置一个以“IMPORTANT”开头的Tag属性
        BOOST_LOG_SCOPED_THREAD_TAG("Tag", std::string, "IMPORTANT MESSAGES");

        // 下面再用slg输出一个normal日志。
        // 这里没有指定level,但severity_logger默认级别为0,在这个程序里就是normal
        // 也可以指定severity_logger的默认级别
        BOOST_LOG(slg) << "Some really urgent line";
    }
    // reset_filter()了sink的filter,如果前面设置了sink的过滤器,这里会取消掉。但全局的不会被reset
    pSink->reset_filter();

    // 下面会先输出foo里的日志记录,然后再输出这个日志记录
    BOOST_LOG(lg) << "The result of foo is " << foo(lg);

    return 0;
}

4. 总结

boost.log框架主要是由日志源,全局库,sink组成。

  • 在上面的程序中,日志源就是src::logger lg和src::severity_logger< severity_level > slg。
  • 全局库就是logging::core::get()。
  • sink就是pSink,这是一个sinks::synchronous_sink< sinks::text_ostream_backend >类型的指针。

sink的backend可以设置输出流,可以设置输出格式。

属性可以设置为全局的,线程的,日志源的。也可以在一个区域中设置一个临时的属性。

 

问: 说,要将日志放文件里, 总共分几步?

  • 答: 总共分三步
  • 第一步、 创建一个sink, 向sink加入文件输出流;
  • 第二步、将sink加入到logging library里, 并创建一个logger;
  • 第三步、向logger输出日志记录。
  •  

5. 其它

  • 如何限制日志文件的长度?
// 创建一个格式化对象, 另一种格式化的方法
boost::function< void (std::ostream&, logging::attribute_values_view const&, std::string const&) > formatter =
    fmt::ostrm
    << fmt::attr< unsigned int >("LineID", "[%09u] ")
    << fmt::date_time< boost::posix_time::ptime >("TimeStamp") << " *"
    << fmt::message();

// 创建一个sink
boost::shared_ptr< sinks::synchronous_sink< sinks::text_ostream_backend > > sink(
    new sinks::synchronous_sink< sinks::text_ostream_backend >);

// 增加常用属性,也就是LineID和TimeStamp这两个属性。这个方便一点
logging::add_common_attributes();

// 设置sink的输出格式
sink->locked_backend()->set_formatter(formatter);

// 日志文件将1个小时换一个,且文件大小不会超过1MB。文件名从file_00.log开始
sink->locked_backend()->add_stream(boost::make_shared< boost::log::rotating_ofstream >(
    "file_%02N.log", logging::keywords::rotation_interval = 3600, logging::keywords::rotation_size = 1048576));

// 将sink加到logging::core里面
logging::core::get()->add_sink(sink);

Boost研究:Boost Log 日志系统用于记录软件运行过程中的关键信息,是大型CAx软件系统的一个重要的组件。本文拟对Boost.Log模块进行分析,旨在记录其技术要点。 阅读详情

相关推荐

boost log库链接问题

1. 使用动态库时,要定义BOOST_LOG_DYN_LINK或者BOOST_ALL_DYN_LINK 否则会出现如下错误: CMakeFiles/IrcChatData.dir/src/main.cpp.o: In function `boost::log::v2s_mt_posix::sources::basic_logger<char, boost::log::v2s_m...

wxf306989618的博客 3407

带你了解BoostLog库,一篇boost库的总结

设计成非常模块化和可扩展的。该库由三个主要层组成:日志数据收集层、收集数据处理层和连接前两层的中央集线器。

sinat_28294665的博客 4952

【BOOST C++ 19 应用库】(1)Boost.log

Boost.Log 是 Boost 中的日志记录库。它支持众多后端以各种格式记录数据。通过以不同方式捆绑服务和转发日志条目的前端访问后端。例如,有一个前端使用线程异步转发日志条目。前端可以有过滤器来忽略某些日志条目。他们定义了如何将日志条目格式化为字符串。所有这些功能都是可扩展的,这使得 Boost.Log 成为一个强大的库。

gongdiwudu的专栏 5480

boost log简介

boost日志库的使用

大草的博客 3987

计算蛋白质结构中氨基酸之间的方向和方位

方向 (Direction):通过相邻氨基酸的 CαCα​ 原子坐标差值,得到方向向量,描述两个氨基酸之间的几何方向。方位 (Orientation):可以通过二面角(如 Psi)来表示相邻氨基酸之间的旋转关系,或者通过局部坐标系(基于 NN、CαCα​、CC 原子)描述残基的空间取向。这些计算是研究蛋白质结构动力学、折叠过程、以及相互作用的基础,尤其是在使用机器学习和深度学习模型进行蛋白质结构预测时,这些几何信息有助于更好地捕捉残基之间的空间关系。

qq_27390023的博客 1680

在虚拟机上搭建FTP服务器

chroot_list_file=/etc/vsftpd/chroot_list # 对chroot_list下用户限制。[root@localhost ~]# vim /etc/vsftpd/vsftpd.conf #打开配置文件。# 指定用户的shell: -s /usr/bin/nologin。# 在配置文件中找到以下几行代码, 去掉注释(去掉 "#"号)# 指定用户主目录:/data/ftp/ftpuser -M。(2)修改配置文件,路径/etc/vsftpd/ 下的文件。

weixin_70753380的博客 3209

boost log库使用一

首先编译安装最新的boost 1.55库,# wget -O boost_1_55_0.tar.bz2 http://downloads.sourceforge.net/project/boost/boost/1.55.0/boost_1_55_0.tar.bz2?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fboost%2Ffiles%2Fboost%2

Dean Chen的专栏 1万+

pgpool-II安装部署

文章介绍了pgpool-II的核心功能、架构组成以及实际部署的关键配置步骤

weixin_41690116的博客 1424

boost log库使用三

写日志到本地文件

Dean Chen的专栏 1万+

LVGL多线程编程避坑指南:从音乐播放器项目看UI与后台任务如何安全协作

本文深入探讨LVGL在多线程环境下的安全协作问题,以音乐播放器项目为例,解析UI与后台任务(如mplayer音频播放器)的典型冲突场景。提供全局互斥锁规范、任务队列异步处理等核心策略,并分享mplayer进程集成方案与进度条优化技巧,帮助开发者规避界面卡顿、内存损坏等常见问题。

weixin_42531494的博客 131

自动化学报Ctex+texstudio配置方法

自动化学报Latex的模板win10使用方法(CTEX+texstudio)

zxy15924462502的博客 7375

boost log库 使用十

防止退出时crash, 可以重复使用的logger.h

Dean Chen的专栏 5347

WOFOST作物模型(7):6.0.6版本的PCSE使用(多层水分平衡和SNOMIN碳氮模型的集成)

wofost81文件结构如上,其中所需的参数可从github上获取然后input下有两个需要调用的功能函数py文件,需要拖到自己的运行py文件同级目录下在使用土壤模块SNOMIN进行作物生长模型模拟时,需要为土壤剖面的不同层次提供多种输入变量。这些变量通常不随年份变化,因此会统一在土壤文件中定义。本节将说明如何将这些输入变量整合到一个字典中,进而生成SNOMIN能够读取的土壤文件。

所有专家都曾始于新手 638

boost log库使用二

运行时改变过滤设置

Dean Chen的专栏 8152

【逐函数讲解ORB_SLAM3源码】获取普通帧率、关键帧、IMU数据

获得imu的旋转 mRwc * mImuCalib.mTcb.rotationMatrix(); Sophus::SE3 mTwc 创建ORB_SLAM3系统 Create SLAM system. It initializes all system threads and gets ready to process frames. ORB_SLAM3::System SLAM(argv[1],argv[2],ORB_SLAM3::System::IMU_RGBD, true, 0, file_nam

小秋 AI SLAM 入门实战教程 1245

DA14531-蓝牙温度传感器-真实案例

传感器低功耗系统功能需求: 1)平时为关机状态,通过GPIO管脚唤醒,霍尔器件触发拉低IO唤醒系统。 2)唤醒后,进入BLE广播状态,等待主机连接,若一分钟无被主机连接,则进入关机状态。 3)连接后(0.1mA),每秒上报一次温度值,同时主机可以读取设备读电池电量。 4)连接断开后,1分钟后,设备进入关机(1uA)。 5)LED灯在连接时点亮,断开灭,低电量时会闪,提示闪烁。

hejinwu1314的博客 856

boost log库使用六

用两个sink,每个sink用不同的filter一个sink只关注特定信息,并产生csv文件。多线程环境下的线程id试验TRIVIAL宏使用真的可以么?研究例子:/usr/src/boost_1_55_0/libs/log/example

Dean Chen的专栏 6296

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

boost log库_Boost.log库使用方法

做服务器开发首要做的事情,就是将日志系统建立起来。这个系统也是方便我们检查服务器的问题。 初始化日志系统这是一个初始化日志系统的代码。我这里是使用了一个配置文件来做boost.log的设置的。所以代码写起来是非常的简洁。#include #include #include #include #include #include #include bool init_log_environment(s...

weixin_39781930的博客 855

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

boost log库 使用十一

不同的sinke使用不同的旋转日志

Dean Chen的专栏 6867

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Boost Log库使用初步

从Java转到c++后,一直对log4j念念不忘,尝试过log4cplus,可能是版本选择的问题,程序中时常出现因为log而崩溃的问题,后来发现boost.log库出了,而项目中boost库基本是必用的,所以直接拿来用了。 boost.log库的介绍就不在这里啰嗦了,详见官网:http://...

choubianuy44343的博客 291

boost使用log库编译报错

使用boost的log,用以下链接选项:-lboost_system -lboost_thread -lboost_log -lboost_log_setup -lboost_filesystem -lpthread当使用add_console_log函数编译链接时报错:/usr/local/gcc/bin/g++ ../obj/timerServer.o ../obj/CWatchThread....

wxf306989618的博客 1262

boost log库 使用七

1. 定制自己的severity2. serverity, logger如何用在其他文件里面3. 多线程环境下的线程ID问题

Dean Chen的专栏 6097

Boost Log库使用介绍

Log库 Boost C++

Archi-dreamer的博客 1979

boost log库_boost-log-库 使用经历

最近想整一个日志库,以前使用过log4cplus,看到了boost推出了1.54版本之后,就开始有了一个log库。这个log看了介绍,之后开始尝试去使用。主要的应用场景是让多个模块(so,dll)使用。场景是一个程序,多个模块,模块是单独的so,打印出来的日志,往同一个文件中输出。最初的时候,编译的boost库是静态库,编译出来的,多是.a类型文件。当在一个日志模块中出示之后,到另一个模块中去使用...

weixin_29002209的博客 515
上一篇: Boost BOOST_FOREACH的扩展
下一篇: Java Timer和TimerTask
hellosijian
博客等级 码龄16年 35粉丝 23原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值