mediasoup源码分析(三)--日志模块

概述

mediasoup是一个基于WebRTC的sfu服务器框架,它允许开发人员构建实时通信应用程序。它包含许多模块和组件,其中之一是日志模块。
mediasoup的日志模块用于记录系统的运行日志和调试信息。它可以帮助开发人员在开发和调试过程中跟踪和分析问题。日志模块可以记录各种级别的日志,如错误日志、警告日志、信息日志和调试日志。
使用mediasoup的日志模块,开发人员可以:

  1. 配置日志级别:可以设置日志级别,只记录特定级别的日志。例如,只记录错误和警告日志,而不记录信息和调试日志。

  2. 记录日志到文件:可以将日志记录到文件中,以便长期存档和分析。

  3. 自定义日志输出:可以自定义日志输出格式和方式。开发人员可以选择将日志输出到控制台、文件还是其他位置。

  4. 进行远程日志记录:可以将日志发送到远程服务器进行集中管理和分析。

日志模块是mediasoup的一个重要组成部分,它为开发人员提供了丰富的日志记录和分析功能,以帮助他们构建稳定和可靠的实时通信应用程序。使用日志模块,开发人员可以更轻松地诊断和解决问题,并提供更好的用户体验。

日志level

enum class LogLevel : uint8_t
{LOG_DEBUG = 3,LOG_WARN  = 2,LOG_ERROR = 1,LOG_NONE  = 0
};
ValueDescription
“debug”Log all severities.
“warn”Log “warn” and “error” severities.
“error”Log “error” severity.
“none”Do not log anything

日志LogTags

	struct LogTags{bool info{ false };bool ice{ false };bool dtls{ false };bool rtp{ false };bool srtp{ false };bool rtcp{ false };bool rtx{ false };bool bwe{ false };bool score{ false };bool simulcast{ false };bool svc{ false };bool sctp{ false };bool message{ false };};
ValueDescription
“info”Logs about software/library versions, configuration and process information.
“ice”Logs about ICE.
“dtls”Logs about DTLS.
“rtp”Logs about RTP.
“srtp”Logs about SRTP encryption/decryption.
“rtcp”Logs about RTCP.
“rtx”Logs about RTP retransmission, including NACK/PLI/FIR.
“bwe”Logs about transport bandwidth estimation.
“score”Logs related to the scores of Producers and Consumers.
“simulcast”Logs about video simulcast.
“svc”Logs about video SVC.
“sctp”Logs about SCTP (DataChannel).
“message”Logs about messages (can be SCTP messages or direct messages).

mediasoup生成的所有日志都有一个以“mediasoup”加冒号开头的命名空间,后面是以大写字母加冒号表示的日志严重性(就像“warn”或“error”一样),后面是内部组件名称(如果有)和日志消息。

media worker 子进程生成的日志消息应该得到特殊处理。如果它们的严重性等于或高于给定的logLevel,就会生成它们,但如果DEBUG环境变量的值与其命名空间匹配,就会记录它们。
media worker 子进程生成的日志可以具有以下命名空间(其中NNNNN是子进程PID):

“mediasoup:worker[pid:NNNNNN]”
“mediasoup:WARN:worker[pid:NNNNNN]”
“mediasoup:ERROR:worker[pid:NNNNNN]

mediasoup启动日志

mediasoup:worker[pid:80653] mediasoup-worker::main() | starting mediasoup-worker process [version:3.0.0-dev] +0ms
mediasoup:worker[pid:80653] mediasoup-worker::main() | little-endian CPU detected +0ms
mediasoup:worker[pid:80653] mediasoup-worker::main() | 64 bits architecture detected +1ms
mediasoup:worker[pid:80653] Settings::PrintConfiguration() | <configuration> +0ms
mediasoup:worker[pid:80653] Settings::PrintConfiguration() |   logLevel            : debug +0ms
mediasoup:worker[pid:80653] Settings::PrintConfiguration() |   logTags             : info,simulcast +0ms
mediasoup:worker[pid:80653] Settings::PrintConfiguration() |   rtcMinPort          : 40000 +0ms
mediasoup:worker[pid:80653] Settings::PrintConfiguration() |   rtcMaxPort          : 49999 +0ms
mediasoup:worker[pid:80653] Settings::PrintConfiguration() | </configuration> +0ms
mediasoup:worker[pid:80653] DepLibUV::PrintVersion() | libuv version: "1.27.0" +0ms
mediasoup:worker[pid:80653] DepOpenSSL::ClassInit() | openssl version: "OpenSSL 1.1.1b  26 Feb 2019" +0ms
mediasoup:worker[pid:80653] DepLibSRTP::ClassInit() | libsrtp version: "libsrtp 2.0.0" +0ms
mediasoup:Worker worker process running [pid:80653] +28ms
mediasoup:Worker createRouter() +1m
mediasoup:Channel[pid:80653] request() [method:worker.createRouter, id:1] +1m
mediasoup:Channel[pid:80653] request succeeded [method:worker.createRouter, id:1] +4ms
mediasoup:Router constructor() +0ms
mediasoup:Channel[pid:80653] request() [method:router.createWebRtcTransport, id:3] +360ms
mediasoup:Channel[pid:80653] request succeeded [method:router.createWebRtcTransport, id:3] +4ms
mediasoup:Transport constructor() +0ms
mediasoup:WebRtcTransport constructor() +0ms
mediasoup:Transport setMaxIncomingBitrate() [bitrate:1500000] +4ms
mediasoup:Channel[pid:80653] request() [method:transport.setMaxIncomingBitrate, id:4] +8ms
mediasoup:Channel[pid:80653] request succeeded [method:transport.setMaxIncomingBitrate, id:4] +2ms

日志相关接口定义

#ifdef MS_LOG_TRACE#define MS_TRACE() \do \{ \if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG) \{ \const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "D(trace) " _MS_LOG_STR, _MS_LOG_ARG); \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \} \} \while (false)#define MS_TRACE_STD() \do \{ \if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG) \{ \std::fprintf(stdout, "(trace) " _MS_LOG_STR _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG); \std::fflush(stdout); \} \} \while (false)
#else#define MS_TRACE() {}#define MS_TRACE_STD() {}
#endif#define MS_HAS_DEBUG_TAG(tag) \(Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED(tag))#define MS_HAS_WARN_TAG(tag) \(Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED(tag))#define MS_DEBUG_TAG(tag, desc, ...) \do \{ \if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED(tag)) \{ \const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "D" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \} \} \while (false)#define MS_DEBUG_TAG_STD(tag, desc, ...) \do \{ \if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED(tag)) \{ \std::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \std::fflush(stdout); \} \} \while (false)#define MS_WARN_TAG(tag, desc, ...) \do \{ \if (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED(tag)) \{ \const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "W" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \} \} \while (false)#define MS_WARN_TAG_STD(tag, desc, ...) \do \{ \if (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED(tag)) \{ \std::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \std::fflush(stderr); \} \} \while (false)#define MS_DEBUG_2TAGS(tag1, tag2, desc, ...) \do \{ \if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED_2(tag1, tag2)) \{ \const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "D" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \} \} \while (false)#define MS_DEBUG_2TAGS_STD(tag1, tag2, desc, ...) \do \{ \if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED_2(tag1, tag2)) \{ \std::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \std::fflush(stdout); \} \} \while (false)#define MS_WARN_2TAGS(tag1, tag2, desc, ...) \do \{ \if (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED_2(tag1, tag2)) \{ \const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "W" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \} \} \while (false)#define MS_WARN_2TAGS_STD(tag1, tag2, desc, ...) \do \{ \if (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED_2(tag1, tag2)) \{ \std::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \std::fflush(stderr); \} \} \while (false)#if MS_LOG_DEV_LEVEL == 3#define MS_DEBUG_DEV(desc, ...) \do \{ \const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "D" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \} \while (false)#define MS_DEBUG_DEV_STD(desc, ...) \do \{ \std::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \std::fflush(stdout); \} \while (false)
#else#define MS_DEBUG_DEV(desc, ...) {}#define MS_DEBUG_DEV_STD(desc, ...) {}
#endif#if MS_LOG_DEV_LEVEL >= 2#define MS_WARN_DEV(desc, ...) \do \{ \const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "W" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \} \while (false)#define MS_WARN_DEV_STD(desc, ...) \do \{ \std::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \std::fflush(stderr); \} \while (false)
#else#define MS_WARN_DEV(desc, ...) {}#define MS_WARN_DEV_STD(desc, ...) {}
#endif#define MS_DUMP(desc, ...) \do \{ \const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "X" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \} \while (false)#define MS_DUMP_STD(desc, ...) \do \{ \std::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \std::fflush(stdout); \} \while (false)#define MS_DUMP_DATA(data, len) \do \{ \const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "X(data) " _MS_LOG_STR, _MS_LOG_ARG); \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \size_t bufferDataLen{ 0 }; \for (size_t i{0}; i < len; ++i) \{ \if (i % 8 == 0) \{ \if (bufferDataLen != 0) \{ \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(bufferDataLen)); \bufferDataLen = 0; \} \const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, "X%06X ", static_cast<unsigned int>(i)); \bufferDataLen += loggerWritten; \} \const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, "%02X ", static_cast<unsigned char>(data[i])); \bufferDataLen += loggerWritten; \} \if (bufferDataLen != 0) \{ \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(bufferDataLen)); \} \} \while (false)#define MS_DUMP_DATA_STD(data, len) \do \{ \std::fprintf(stdout, "(data) " _MS_LOG_STR _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG); \size_t bufferDataLen{ 0 }; \for (size_t i{0}; i < len; ++i) \{ \if (i % 8 == 0) \{ \if (bufferDataLen != 0) \{ \Logger::buffer[bufferDataLen] = '\0'; \std::fprintf(stdout, "%s", Logger::buffer); \bufferDataLen = 0; \} \const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, "\n%06X ", static_cast<unsigned int>(i)); \bufferDataLen += loggerWritten; \} \const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, "%02X ", static_cast<unsigned char>(data[i])); \bufferDataLen += loggerWritten; \} \if (bufferDataLen != 0) \{ \Logger::buffer[bufferDataLen] = '\0'; \std::fprintf(stdout, "%s", Logger::buffer); \} \std::fflush(stdout); \} \while (false)#define MS_ERROR(desc, ...) \do \{ \if (Settings::configuration.logLevel >= LogLevel::LOG_ERROR || MS_LOG_DEV_LEVEL >= 1) \{ \const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "E" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \Logger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \} \} \while (false)#define MS_ERROR_STD(desc, ...) \do \{ \if (Settings::configuration.logLevel >= LogLevel::LOG_ERROR || MS_LOG_DEV_LEVEL >= 1) \{ \std::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \std::fflush(stderr); \} \} \while (false)#define MS_ABORT(desc, ...) \do \{ \std::fprintf(stderr, "(ABORT) " _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \std::fflush(stderr); \std::abort(); \} \while (false)#define MS_ASSERT(condition, desc, ...) \if (!(condition)) \{ \MS_ABORT("failed assertion `%s': " desc, #condition, ##__VA_ARGS__); \}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/838578.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【scikit-learn】DBSCAN基于密度聚类ML模型实战及经验总结(更新中)

1.一直以来想写下基于scikit-learn训练AI算法的系列文章&#xff0c;作为较火的机器学习框架&#xff0c;也是日常项目开发中常用的一款工具&#xff0c;最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下scikit-learn框架DBSCAN密度聚类机器学习模型相关知识…

Java的NIO提供了非阻塞I/O机制的包

Java的NIO&#xff08;New I/O&#xff09;是一种提供了替代性、非阻塞I/O机制的包。它的引入主要是为了解决传统I/O机制在处理大量连接或大数据量时所带来的性能瓶颈和可扩展性问题。下面详细介绍NIO的一些关键概念和特性&#xff1a; 1.通道&#xff08;Channels&#xff09…

管理Kubernetes平台的工具Rancher

目录 一、特性二、使用方法2.1、安装 Rancher2.2、创建 Kubernetes 集群2.3、管理和部署应用 Rancher 是一个开源的容器管理平台&#xff0c;它提供了企业级的 Kubernetes 管理解决方案&#xff0c;使得部署和管理 Kubernetes 集群变得更加简单。Rancher 提供了一个统一的控制面…

使用pyautogui制作点击器怎么输入中文

通过咨询国内一些大厂的问答软件&#xff0c;发现基本都是让我们使用pinyin库来输入中文&#xff0c;我使用了一下&#xff0c;很难达到我的需求&#xff0c;我就发现可以使用复制、粘贴这两个快捷键来达到这个效果。 它们给的代码基本都是这个模式&#xff0c;但是拼音和需要的…

Python 白底黑字图片去除红色水印

Python 白底黑字图片去除红色水印 import os from PIL import Imagedef remove_color(image_path, new_image_path):"""初始化:param image_path: 图片路径:param new_image_path: 新图片路径"""# 打开图片并转换为RGBA格式img Image.open(imag…

java入门1.1.2

前言&#xff1a; 第一&#xff1a;一坨垃圾的迭代&#xff0c;还是垃圾 第二&#xff1a;本内容为对类&#xff0c;对象&#xff0c;构造函数的最新抽象理解 正片 先将类&#xff0c;对象&#xff0c;还要构造函数翻译成英文 class&#xff0c;object&#xff0c;construc…

汇中 SCL-61D2超声水表汇中通讯协议

RS-485串行通讯接口设置表 通用代码注释 读取正向仪表数据 DD的内容为 通讯示例 主机命令&#xff1a;2A 41 4A 仪表响应&#xff1a;26 41 4A 00 00 13 63 00 00 07 72 00 00 10 34 00 33 读取负向仪表数据&#xff1a;&#xff08;单向型仪表无此命令&#xff09; DD的内容…

用户研究方法论中定性研究的优缺点分析

定性研究是一种探索性研究方法&#xff0c;它侧重于理解用户的感受、态度、动机和行为背后的原因。以下是定性研究的一些优缺点&#xff1a; 优点&#xff1a; 深入理解&#xff1a;定性研究能够提供对用户行为和态度的深入理解&#xff0c;帮助研究者捕捉到用户的真实感受和动…

selenium发展史

Selenium Core 2004 年&#xff0c;Thoughtworks 的工程师 Jason Huggins 正在负责一个 Web 应用的测试工作&#xff0c;由于这个项目需要频繁回归&#xff0c;这导致他不得不每天做着重复且低效的工作。为了解决这个困境&#xff0c;Jason 开发了一个运行在 JavaScript 沙箱中…

PDF 生成目录和页码 点击跳转(新)

为啥又写一篇&#xff1f; 因为之前 用 Anchor 写的&#xff0c;这东西 放到Paragraph 里就不好使了 。 这回 目录里 和 跳转的地方 用的都是 Chunk 添加 目录条目 返回跳转的标记 public String addMenuTag (List<Pair<Chunk, String>> chunks, String[] men…

代码随想录算法训练营第二十九天| LeetCode491.递增子序列* 、LeetCode46.全排列*、LeetCode47.全排列 II

#LeetCode 491. Non-decreasing Subsequences #LeetCode 491. 视频讲解&#xff1a;回溯算法精讲&#xff0c;树层去重与树枝去重 | LeetCode&#xff1a;491.递增子序列_哔哩哔哩_bilibili 首先&#xff0c;本题不能考虑首先对数组排序&#xff0c;排序会导致数组直接变为一个…

2024-5-16

今日安排&#xff1a; 完结 nf_tables 模块的基本学习&#xff0c;然后开始审计源码mount 的使用&#xff0c;学习 namespace (昨昨昨昨天残留的任务)&#xff08;&#xff1a;看我能搁到什么时候静不下心学习新知识就做 CTF 题目&#x1f991;&#x1f991;&#x1f991; 今…

2010-2024年各地级市社会信用体系建设匹配DID数据

2010-2024年各地级市社会信用体系建设匹配DID数据 1、时间&#xff1a;2010-2024年 2、指标&#xff1a;行政区划代码、年份、所属省份、地区、社会信用体系建设示范区 3、范围&#xff1a;310个地级市 4、来源&#xff1a;国家发改委 5、指标解释&#xff1a; 社会信用体…

YOLO系列笔记(十五)—— Python 文件与目录操作指南:掌握 os 模块的常用命令

掌握 os 模块的常用命令 前言文件操作1. 检查文件是否存在&#xff1a;os.path.exists2. 删除文件&#xff1a;os.remove3. 重命名文件&#xff1a;os.rename4. 获取文件大小&#xff1a;os.path.getsize5. 读取文件内容&#xff1a;with open&r6. 写入文件内容&#xff1a…

【重学C语言】十四、结构体

【重学C语言】十四、结构体 结构体初始化和使用声明注意点结构体嵌套结构体数组字节对齐对齐数(Alignment)对齐要求对齐规则结构体成员的对齐示例编译器指令和属性为什么要对齐跨平台兼容性位段(位域)结构体 在C语言中,结构体(struct&#x

跨平台应用开发进阶(五十四)cordova自定义插件

文章目录 一、前言二、cordova 自定义插件2.1 cordova 安装2.2 cordova 创建 android 工程2.3 使用 cordova 官方提供的插件2.4 创建自定义插件 三、拓展阅读 一、前言 在前期博文《ReactNative进阶&#xff08;一&#xff09;&#xff1a;ReactNative 学习资料汇总》中&#…

C++基础与函数解析 | 函数的声明与定义 | 函数调用 | 函数详解 | 函数重载 | 重载解析 | 递归函数 | 内联函数 | 函数指针

文章目录 一、函数基础1.基本函数定义2.函数的声明与定义3.函数调用 二、函数详解1.参数2.函数体3.返回类型 三、函数重载与重载解析1.函数重载2.重载解析 四、函数相关的其他内容1.递归函数2.内联函数3.constexpr函数&#xff08;C11起&#xff09;4.consteval 函数 (C20 起 )…

Redis - hiredis源码安装和接口使用介绍

一、hiredis源码安装说明 本文创作基于 hiredisv1.2.0版本 1.简介 hiredis是一个用于与Redis交互的C语言客户端库。它提供了一组简单易用的API&#xff0c;使开发人员可以轻松地连接到Redis服务器&#xff0c;并执行各种操作&#xff0c;如设置和获取键值对、执行命令、订阅和…

C语言 | Leetcode C语言题解之第92题反转链表II

题目&#xff1a; 题解&#xff1a; struct ListNode *reverseBetween(struct ListNode *head, int left, int right) {// 因为头节点有可能发生变化&#xff0c;使用虚拟头节点可以避免复杂的分类讨论struct ListNode *dummyNode malloc(sizeof(struct ListNode));dummyNode…

案例实践 | 招商局集团基于长安链的双循环航运贸易应用

案例名称-招商局双循环航运贸易联盟链 ■ 建设单位 招商局集团 ■ 用户群体 货主企业、物流企业、基础设施运营商等各参与主体 ■ 应用成效 已赋能产业链上下游超1.2万家中小微企业&#xff0c;累计提供普惠金融超830亿元 案例背景 作为全球贸易大国&#xff0c;我国约…