惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
Forbes - Security
Forbes - Security
WordPress大学
WordPress大学
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
Spread Privacy
Spread Privacy
D
Darknet – Hacking Tools, Hacker News & Cyber Security
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
P
Privacy International News Feed
A
About on SuperTechFans
T
Tailwind CSS Blog
I
InfoQ
S
Securelist
云风的 BLOG
云风的 BLOG
罗磊的独立博客
Recent Announcements
Recent Announcements
T
The Exploit Database - CXSecurity.com
B
Blog RSS Feed
V
Visual Studio Blog
Know Your Adversary
Know Your Adversary
The GitHub Blog
The GitHub Blog
Jina AI
Jina AI
腾讯CDC
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
博客园 - 【当耐特】
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Full Disclosure
S
Secure Thoughts
博客园 - 司徒正美
J
Java Code Geeks
Y
Y Combinator Blog
Google Online Security Blog
Google Online Security Blog
GbyAI
GbyAI
N
News and Events Feed by Topic
Help Net Security
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Project Zero
Project Zero
T
Tenable Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Tor Project blog
MyScale Blog
MyScale Blog
Scott Helme
Scott Helme
小众软件
小众软件
K
Kaspersky official blog

I'm OWenT

国产大模型(GLM 5.1、Kimi K2.6)真实场景效果和 Coding Plan 额度测试 新版本libatapp的连接管理——从etcd服务发现到拓扑驱动的自动重连 新版本libatbus的设计变更——从树形路由到拓扑驱动 Protobuf又一坑 - C++标准和ABI兼容性 AI真好用-给Blog主题统一加mermaid,chart.js,excalidraw,draw.io的多种引入方式支持 给内网部署Squid-通用HTTP下载缓存 UE使用CodeChecker和clang-tidy生成静态分析报告 找出UE的循环依赖 C++小协程栈和临时变量及作用域的栈溢出问题分析 游戏服务的可观测性能力建设(C++生态) 指标上报的多线程优化和多拉取源点优化 协程(libcopp)的Channel功能和CPU命中率优化 通用RPC代码生成器 实现strong_rc_ptr(比shared_ptr更快的引用计数智能指针) 手夯一个STL allocator和对象内存分析组件 std::condition_variable 的信号丢失问题 踩坑一处(GCC)STL std::async 实现BUG导致的crash问题 GCC 14的一个warning to error BUG 给xresloader(Excel导表工具)增强UE读表支持(包含蓝图,Blueprint) Opentelemetry社区在gRPC的几个链接问题(静态库和动态库混用,musl工具链,符号裁剪) Excel转表工具(xresloader)的新验证器(验证外部Excel和文本数据,唯一性和自定义规则) protobuf v22和gRPC v1.55版本升级的依赖变化和upb适配 关于protobuf近期版本(v20/v3.20+)和 gRPC v1.54版本在某些编译环境下的一些链接和编译问题 xresloader-Excel导表工具链的近期变更汇总 打通游戏服务端框架的C++20协程改造的最后一环 给cmake-toolset和工具链(curl等)加HTTP/2和HTTP/3支持 又开新坑之 coredns 插件: nftables和filter 关于opentelemetry-cpp社区对于C++ Head Only组件单例和符号可见性的讨论小记 填个转表工具 xresloader 去年的坑(数组尾部裁剪) 集成 upb 和 lua binding 的踩坑小记 libcopp对C++20协程的接入和接口设计 再度优化GCC、LLVM、Clang、libc++、libc++abi等套件的构建脚本 游戏服务的分布式事务优化(二)- 事务管理 游戏服务的分布式事务优化(一)- Write Ahead Log(WAL) 模块 记录一些bazel适配用编译选项 测试现代化硬件C++浮点数性能和一致性 适配Boringssl和OpenSSL 3.0 近期cmake-toolset的一些适配问题 C++20 Text Formatting/fmtlib 适配问题小记 再次重构LLVM+Clang+libcxx+libc++abi+其他相关工具的构建流程 重构基于CMake的构建工具链 新版GCC和LLVM+Clang终于Release啦 折腾一下nftables下的双拨 [C++20] Module partitions和符号交叉引用(声明和实现分离) [Rust] 实现一个线程安全且迭代器可以保存的链表 基于protobuf的代码生成 几个使用protobuf中C++接口的Arena的坑 Amazon Aurora DB存储引擎论文阅读小记 近期对libatapp的一些优化调整(增加服务发现和连接管理,支持yaml等) xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等) 在游戏服务器中使用分布式事务 libcopp接入C++20 Coroutine和一些过渡期的设计 libatbus 的大幅优化 nftables初体验 容器配置开发环境小计 PALM Tree - 适合多核并发架构的B+树 - 论文阅读小记 跨平台协程库 - libcopp 简介 C++20 Coroutine 性能测试 (附带和libcopp/libco/libgo/goroutine/linux ucontext对比) 尝鲜Github Action 一些xresloader(转表工具)的改进 protobuf、flatbuffer、msgpack 针对小数据包的简单对比 协程框架(libcopp) 小幅优化 Excel转表工具(xresloader) 增加protobuf插件功能和集成 UnrealEngine 支持 Anna(支持任意扩展和超高性能的KV数据库系统)阅读笔记 C++20 Coroutine libcopp merge boost.context 1.69.0 Google去中心化分布式系统论文三件套(Percolator、Spanner、F1)读后感 Rust玩具-企业微信机器人通用服务 使用ELK辅助监控开发测试环境服务质量和问题定位 2018年的新通用伪随机数算法(xoshiro / xoroshiro)的C++(head only)实现 Webpack+vue+boostrap+ejs构建Web版GM工具 Rust的第二次接触-写个小服务器程序 理解和适配AEAD加密套件 atsf4g-co的进化:协程框架v2、对象路由系统和一些其他细节优化 协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比 可执行文件压缩 初识Rust 使用restructedtext编写xresloader文档 atframework的etcd模块化重构 C++的backtrace ECDH椭圆双曲线(比DH快10倍的密钥交换)算法简介和封装 protobuf-net的动态Message实现 pbc的proto3接入 atgateway内置协议流程优化-加密、算法协商和ECDH 整理一波软件源镜像同步工具+DevOps工具 Blog切换到Hugo libcopp v2的第一波优化完成 libcopp(v2) vs goroutine性能测试 libcopp的线程安全、栈池和merge boost.context 1.64.0 GCC 7和LLVM+Clang+libc++abi 4.0的构建脚本 libatbus的几个藏得很深的bug 用cmake交叉编译到iOS和Android 开源项目得一些小维护 atapp的c binding和c#适配 对象路由系统设计 2016年总结 近期的一个协程流程BUG 重写了llvm+clang+libc++和libc++abi的构建脚本 atsf4g完整游戏工程示例|I'm OWenT atframework基本框架已经完成|I'm OWenT
Opentelemetry-cpp的Logs模块标准更新(涉及近期版本:1.8-1.9的BREAK CHANGES)
owent · 2023-02-25 · via I'm OWenT

blog-website

背景

Opentelemetry-cpp 是可观测领域,opentelemetry (CNCF基金会孵化项目)的C++ SDK接入层opentelemetry 里面主要是分链路跟踪(Trace)、指标(Metrics)、日志(Logs)三大块。 同时 opentelemetry 有一个标准规范文档 opentelemetry-specification ,而SDK实现主要就是来对这个标准规范文档的特定语言实现。 由于日志(Logs)这一块一直处于Experimental阶段,所以很长时间以来 C++ SDK接入层 都没有及时更新跟进规范的变化。

去年底的时候,我抽时间来更新了一波规范实现。但是拖到今天才来写这篇分享( * T _ T * ),也是想着把一些向前不兼容的变化列举出来,方便可能有些同学升级的时候可能会碰到一些问题可以对照解决。

首先简单介绍一下 opentelemetry-cpp 的内部模块结构,主要是三部分: API, SDK, Exporter 。然后还有其他一些扩展和辅助性功能。

  • API模块: 主要用于各类组件接入创建链路跟踪(Trace)、指标(Metrics)、日志(Logs),这部分必须是Head-Only的,ABI稳定的。
  • SDK模块: 主要用于应用框架层来接入如何实际产生和处理数据的实现层,和API模块搭配可以做出类似热插拔的效果。
  • Exporter模块: 决定如何导出数据,用什么协议导出。
  • 其他: 包含HTTP封装,插件模块等周边支持组件

Logs的规范 层面,大部分的变化只是名称上的变化,还有少量的功能性增强和一些仍然处于非常早期的新功能草案。

命名变更

首先,规范定义了一个 LogRecord 类型,并且要求在API组件中实现的导出接口全部以这个 LogRecord 为基准,LogRecord 要求实现所有字段的 setter 接口。 同时,导出接口 从原来的 Logger::Log(...) 重命名为了 Logger::EmitLogRecord(...) ,并且在继承关系中,仅需要实现 Logger::EmitLogRecord(nostd::unique_ptr<LogRecord>)

这部分的变化,由于原先的 SDK模块中也有个 LogRecord 结构,这里其实是不同含义,但是重名了。所以我们废弃了 SDK中的 LogRecord 。 新的规范中在SDK里也加入了 ReadableLogRecordReadWriteLogRecord 两个接口,要求 ReadableLogRecord 必须实现 getter 接口,而 ReadWriteLogRecord setter 和 getter 都有。 其实这里的 ReadWriteLogRecord 就很像之前 SDK 里的 LogRecord 。 另外由于SDK里所有模块的数据传递都是通过各自内部的一个叫 Recordable 的对象,并且这个对象还会包含一些SDK内的独有的内容,比如Resource和InstrumentationScope。 所以我们最终的继承关系改成了如下所示:

classDiagram LogRecord <|– Recordable Recordable <|– ReadableLogRecord Recordable <|– 特定Exporter的Recordable ReadableLogRecord <|– ReadWriteLogRecord LogRecord: +SetTimestamp() LogRecord: +SetObservedTimestamp() LogRecord: +SetSeverity() LogRecord: +SetBody() LogRecord: +SetAttribute() LogRecord: +SetTraceId() LogRecord: +SetSpanId() LogRecord: +SetTraceFlags() class Recordable { +SetResource() +SetInstrumentationScope() } class ReadableLogRecord { GetTimestamp() GetObservedTimestamp() GetSeverity() GetSeverityText() GetBody() GetTraceId() GetSpanId() GetTraceFlags() GetAttributes() GetResource() GetInstrumentationScope() } class 特定Exporter的Recordable { 位于Exporter中,比如 OtlpLogRecordable }

另外所有的 Processor、Exporter、Factory的名字都由 *Log*Processor*Log*Exporter*Log*Factory 变为了 *LogRecord*Processor*LogRecord*Exporter*LogRecord*Factory 。 对应的头文件的文件名也做了相应修改。

对于 Processor 接口, OnReceive 重命名为了 OnEmit

我们还移除了老 Logger::Log(...) 接口中的 name 字段。在非常古老的版本中,Logs的协议里有 name 字段,所以 Logger::Log(...) 接口里也有个 name 参数。 但是实际上很早期的一次协议更新就已经移除这个字段了(从v1.4.0版本开始),我们在接口层保留了相当长的时间,并且设置为了 deprecated 就是为了给用户一段时间去迁移。 现在这次改造就顺带移除了这个废弃的接口,以防误用。

API接口中一些STL类型的接口换成了 nostd 的版本,比如 std::unique_ptr -> nostd::unique_ptr 。这部分主要是和其他模块保持一致,管理ABI兼容性。

新增功能

规范变化也包含一些新功能,比如说,我们现在可以通过 LoggerProvider::GetLogger() 接口来设置自动从链路跟踪(Trace)模块的 RuntimeContext 中自动提取和关联当前的调用链路信息。 这样可以把日志自动关联到链路上,当然这需要使用链路跟踪(Trace)模块的模块启用里面的 Scope 组件。我们仍然支持手动设置链路信息。

Logs的数据字段方面,增加了一个 observed_timestamp ,这个字段会默认填写为上报创建 LogRecord 时的时间。当然也是可以手动设置的。

我们现在也允许在 LoggerProvider::GetLogger() 创建Logger的时候指定InstrumentationScope的属性,不过目前的版本中只是接口合入了,下个Release才会真正进入到Exporter的数据中。 详见: https://github.com/open-telemetry/opentelemetry-cpp/pull/2004 这个PR的合入进展。

新的规范有一个还处于非常初期的 EventLog 的草案,主要是包装了一下普通版本的 Logger ,在要设置log的关联事件的时候规范化 event.nameevent.domain 的使用。这个可能未来还会变化比较大,暂时不推荐使用。

由于新增了一些属性,Logs的一键调用接口(包括 EmitLogRecord, Debug, Error, Info 等)的参数越多了。并且大部分都是可选的,这对接口的维护带来了极大得不变。为了缓解这个问题,我用type traits的方式重构了这部分的重载。相当于把原来的按类型+参数顺序的重载改成了仅仅按类型的重载,无所谓传参的顺序。然后根据参数类型来决定如何填充字段。比如参考其中一个接口注释:

/**
  * Emit a Log Record object with arguments
  *
  * @param log_record Log record
  * @tparam args Arguments which can be used to set data of log record by type.
  *  Severity                                -> severity, severity_text
  *  string_view                             -> body
  *  AttributeValue                          -> body
  *  SpanContext                             -> span_id,tace_id and trace_flags
  *  SpanId                                  -> span_id
  *  TraceId                                 -> tace_id
  *  TraceFlags                              -> trace_flags
  *  SystemTimestamp                         -> timestamp
  *  system_clock::time_point                -> timestamp
  *  KeyValueIterable                        -> attributes
  *  Key value iterable container            -> attributes
  *  span<pair<string_view, AttributeValue>> -> attributes(return type of MakeAttributes)
  */
template <class... ArgumentType>
void EmitLogRecord(nostd::unique_ptr<LogRecord> &&log_record, ArgumentType &&... args);

这上面列出了如何按参数类型会影响哪些字段。那么如何实现呢?首先由于传入的参数 ArgumentType 是个通用引用,它既能匹配左值引用,又能匹配右值,还能匹配是否带 constviolate ,所以第一层模板类型提取我们可以用 std::decay<ArgumentType>::type 来移除这些类型修饰。然后通过一个特殊类型的特化来实现不同的功能,参数传递走完美转发就可以了。比如:

template <>
struct LogRecordSetterTrait<trace::SpanContext>
{
  template <class ArgumentType>
  inline static LogRecord *Set(LogRecord *log_record, ArgumentType &&arg) noexcept
  {
    log_record->SetSpanId(arg.span_id());
    log_record->SetTraceId(arg.trace_id());
    log_record->SetTraceFlags(arg.trace_flags());

    return log_record;
  }
};

template <>
struct LogRecordSetterTrait<trace::SpanId>
{
  template <class ArgumentType>
  inline static LogRecord *Set(LogRecord *log_record, ArgumentType &&arg) noexcept
  {
    log_record->SetSpanId(std::forward<ArgumentType>(arg));

    return log_record;
  }
};

可以看到,如果参数类型是 trace::SpanContext 我们会同时设置 trace_id 、 span_id 和 trace_flags 。但是如果传入的是 trace::SpanId 我们就只会设置 trace_id

这里还有一处和之前不太兼容的特殊的地方,就是通用引用(Universal Reference),就是 ArgumentType && 不能匹配 std::initializer_list<T> 。而我们之前是可以通过 std::initializer_list<std::pair<nostd::string_view, common::AttributeValue>> 来传递attributes的。 这里为了适配,我们提供了一组 MakeAttributes(...) 接口,通过重载来适配attributes的传递。整个实现大概是这样:

inline static nostd::span<const std::pair<nostd::string_view, common::AttributeValue>>
MakeAttributes(std::initializer_list<std::pair<nostd::string_view, common::AttributeValue>>
                   attributes) noexcept
{
  return nostd::span<const std::pair<nostd::string_view, common::AttributeValue>>{
      attributes.begin(), attributes.end()};
}

inline static nostd::span<const std::pair<nostd::string_view, common::AttributeValue>>
MakeAttributes(
    nostd::span<const std::pair<nostd::string_view, common::AttributeValue>> attributes) noexcept
{
  return attributes;
}

inline static const common::KeyValueIterable &MakeAttributes(
    const common::KeyValueIterable &attributes) noexcept
{
  return attributes;
}

template <
    class ArgumentType,
    nostd::enable_if_t<common::detail::is_key_value_iterable<ArgumentType>::value> * = nullptr>
inline static common::KeyValueIterableView<ArgumentType> MakeAttributes(
    const ArgumentType &arg) noexcept
{
  return common::KeyValueIterableView<ArgumentType>(arg);
}

使用起来可以类似这种 logger->EmitLogRecord(Severity::kError, MakeAttributes({ {"key1", "value 1"}, {"key2", 2} }));logger->Debug("Test log message", MakeAttributes(std::map<std::string, std::string>())); 。应该也不会造成太大困扰。

总结

主要的改动也就这么多,也欢迎有兴趣的小伙伴们互相交流。 如有遗漏,也欢迎小伙伴们指出和补充。