码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 【跟小嘉学 Rust 编程】三十一、Rust的日志与追踪


    系列文章目录

    【跟小嘉学 Rust 编程】一、Rust 编程基础
    【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
    【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
    【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
    【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据
    【跟小嘉学 Rust 编程】六、枚举和模式匹配
    【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目
    【跟小嘉学 Rust 编程】八、常见的集合
    【跟小嘉学 Rust 编程】九、错误处理(Error Handling)
    【跟小嘉学 Rust 编程】十、泛型(Generic Type)、特征(Trait)和生命周期(Lifetimes)
    【跟小嘉学 Rust 编程】十一、编写自动化测试
    【跟小嘉学 Rust 编程】十二、构建一个命令行程序
    【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包
    【跟小嘉学 Rust 编程】十四、关于 Cargo 和 Crates.io
    【跟小嘉学 Rust 编程】十五、智能指针(Smart Point)
    【跟小嘉学 Rust 编程】十六、无畏并发(Fearless Concurrency)
    【跟小嘉学 Rust 编程】十七、面向对象语言特性
    【跟小嘉学 Rust 编程】十八、模式匹配(Patterns and Matching)
    【跟小嘉学 Rust 编程】十九、高级特性
    【跟小嘉学 Rust 编程】二十、进阶扩展
    【跟小嘉学 Rust 编程】二十一、网络编程
    【跟小嘉学 Rust 编程】二十二、常用 API
    【跟小嘉学 Rust 编程】二十三、Cargo 使用指南
    【跟小嘉学 Rust 编程】二十四、内联汇编(inline assembly)
    【跟小嘉学 Rust 编程】二十五、Rust命令行参数解析库(clap)
    【跟小嘉学 Rust 编程】二十六、Rust的序列化解决方案(Serde)
    【跟小嘉学 Rust 编程】二十七、Rust 异步编程(Asynchronous Programming)
    【跟小嘉学 Rust 编程】二十八、Rust中的日期与时间
    【跟小嘉学 Rust 编程】二十九、Rust 中的零拷贝序列化解决方案(rkyv)
    【跟小嘉学 Rust 编程】三十、Rust 使用 Slint UI
    【跟小嘉学 Rust 编程】三十一、Rust的日志与追踪
    【跟小嘉学 Rust 编程】三十二、Rust的设计模式(Design Patterns)
    【跟小嘉学 Rust 编程】三十三、Rust的Web开发框架之一: Actix-Web的基础
    【跟小嘉学 Rust 编程】三十四、Rust的Web开发框架之一: Actix-Web的进阶

    文章目录

    • 系列文章目录
      • @[TOC](文章目录)
    • 前言
    • 一、日志详解
      • 1.1、 日志级别
      • 1.2、输出位置
      • 1.3、日志的查看
      • 1.4、日志采集
    • 二、日志门面(logging facade)
      • 2.1、日志门面介绍
      • 2.1、Log 特征
      • 2.2、日志宏
      • 2.3、日志输出
        • 2.3.1、env_logger
        • 2.3.2、实现 log 特征
    • 三、追踪(tracing)
      • 3.1、追踪(tracing)
      • 3.2、使用
        • 3.2.1、添加依赖
        • 3.2.1、使用
      • 3.3、异步编程中使用
      • 3.4、tracing的核心概念
        • 3.4.1、span
        • 3.4.2、event
        • 3.4.3、collector
      • 3.5、tracing-subscriber
      • 3.6、#[instrument]

    前言

    本章节讲解日志与监控,系统的可观测性(Observability) 通常由三个维度:日志(Logging)、指标(Metric)和追踪(Tracing)。

    • 日志:离散的错误信息和状态信息
    • 指标:记录和呈现可聚合的数据
    • 追踪:单个请求的一系列事件;

    主要教材参考 《The Rust Programming Language》
    主要教材参考 《Rust For Rustaceans》
    主要教材参考 《The Rustonomicon》
    主要教材参考 《Rust 高级编程》
    主要教材参考 《Cargo 指南》
    主要教材参考 《Rust 异步编程》


    一、日志详解

    1.1、 日志级别

    日志级别是对基本的“滚动文本”式日志记录的一个重要补充。每条日志消息都回基于其重要性或严重程度分配到一个日志级别。

    • Fatal:程序发生致命错误,此类错误往往来自于程序逻辑的严重异常,例如无法分配足够的硬盘空间、内存不足等,建议立即退出或重启程序
    • Error:错误,一般指的是程序级别的错误或严重的业务错误,但是这种错误并不会影响程序的运行。
    • Warn:警告,说明这条记录需要注意,但是不确定是否发生了错误,因粗需要相关的开发人员来辨别。
    • Info:信息,此类往往用于记录程序的运行信息,例如用户操作或状态的变化;
    • Debug:调试信息,是给开发者用的,应用了解程序当前的详细运行状况,例如请求信息追踪等;

    1.2、输出位置

    一般而言,日志可以输出的两个地方:终端控制台(标准输出/标准错误)和文件。

    如果没有日志持久化的需求,你只是为了调试程序,建议输出到控制台即可。悄悄的说一句,我们还可以为不同的级别设定不同的输出位置,例如 Debug 日志输出到控制台,既方便开发查看,但又不会占用硬盘,而 Info 和 Warning 日志可以输出到文件 info.log 中,至于 Error、Fatal 则可以输出到 error.log 中。

    1.3、日志的查看

    • 可以使用 tail、cat、grep等命令
    • 打开文件搜索
    • 在可视化界面,配合日志采集工具到 ElasticSearch 或其他搜索平台,然后通过 kibana、grafana 等进行搜索查看。

    1.4、日志采集

    我们可以使用日志采集工具去控制台等标准输出读取日志数据,然后将读取到的数据发送给到日志存储平台(ElasticSearch).

    目前常用的日志采集工具有:filebeat、vector等,他们都是以 agent 的形式运行在你的应用程序旁边;

    二、日志门面(logging facade)

    2.1、日志门面介绍

    就如同 slf4j 是 Java 的日志门面库,log 也是 Rust 的日志门面库,目前由官方积极维护。

    cargo add log
    
    • 1

    既然是门面,log 自然定义了一套统一的日志特征和API,将日志的操作进行了抽象定义。

    2.1、Log 特征

    pub trait Log: Sync + Send {
        fn enabled(&self, metadata: &Metadata<'_>) -> bool;
        fn log(&self, record: &Record<'_>);
        fn flush(&self);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • enabled:用于判断某条带有元数据的日志是否能被记录,它对于 log_enabled!宏特别有用
    • log:回记录 record 所代表的日志
    • flush:回将缓存中的日志刷到输出中,例如标准输出或文件;

    2.2、日志宏

    log 为我们不同的日志级别提供了不同的日志宏

    use log::{info, trace, warn};
    
    pub fn shave_the_yak(yak: &mut Yak) {
        trace!("Commencing yak shaving");
    
        loop {
            match find_a_razor() {
                Ok(razor) => {
                    info!("Razor located: {}", razor);
                    yak.shave(razor);
                    break;
                }
                Err(err) => {
                    warn!("Unable to locate a razor: {}, retrying", err);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.3、日志输出

    log 仅仅是日志门面库,不具备完整的日志库功能。如果你是 rust 库的开发者,只要在库中使用门面库即可,具体的日志库交由用户选择和绑定

    2.3.1、env_logger

    1、添加依赖

    cargo add env_logger
    
    • 1

    2、使用

    use log::{debug, error, log_enabled, info, Level};
    
    fn main() {
        // 注意,env_logger 必须尽可能早的初始化
        env_logger::init();
    
        debug!("this is a debug {}", "message");
        error!("this is printed by default");
    
        if log_enabled!(Level::Info) {
            let x = 3 * 4; // expensive computation
            info!("the answer was: {}", x);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    此时i可以通过环境变量(RUST_LOG)设置日志级别

    默认情况下,env_logger 会输出到标错误,如果你要输出到标准输出 ,可以使用 builder 来改变日志对象(target);

    2.3.2、实现 log 特征

    use log::{Record, Level, Metadata};
    struct SimpleLogger;
    impl log::Log for SimpleLogger {
        fn enabled(&self, metadata: &Metadata) -> bool {
            metadata.level() <= Level::Info
        }
        fn log(&self, record: &Record) {
            if self.enabled(record.metadata()) {
                println!("{} - {}", record.level(), record.args());
            }
        }
        fn flush(&self) {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    三、追踪(tracing)

    3.1、追踪(tracing)

    随着微服务的流行,现在一个产品有多个系统组成是非常常见的,这种情况下,一条用户请求可能会横跨几个甚至几十个服务。此时再用传统的日志方式去跟踪这条用户请求就变得较为困难,这就是分布式追踪在现代化监控系统中这么炽手可热的原因。

    关于分布式追踪,在后面的监控章节进行详细介绍,大家只要知道:分布式追踪的核心就是在请求的开始生成一个 trace_id,然后将该 trace_id 一直往后透穿,请求经过的每个服务都会使用该 trace_id 记录相关信息,最终将整个请求形成一个完整的链路予以记录下来。

    那么后面当要查询这次请求的相关信息时,只要使用 trace_id 就可以获取整个请求链路的所有信息了,非常简单好用。看到这里,相信大家也明白为什么这个库的名称叫 tracing 了吧?

    至于为何把它归到日志库的范畴呢?因为 tracing 支持 log 门面库的 API,因此,它既可以作为分布式追踪的 SDK 来使用,也可以作为日志库来使用。

    在分布式追踪中,trace_id 都是由 SDK 自动生成和往后透穿,对于用户的使用来说是完全透明的。如果你要手动用日志的方式来实现请求链路的追踪,那么就必须考虑 trace_id 的手动生成、透传,以及不同语言之间的协议规范等问题

    3.2、使用

    3.2.1、添加依赖

    cargo add tracing
    
    • 1

    3.2.1、使用

    use log;
    use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
    
    fn main() {
        // 只有注册 subscriber 后, 才能在控制台上看到日志输出
        tracing_subscriber::registry()
            .with(fmt::layer())
            .init();
        
        // 调用 `log` 包的 `info!`
        log::info!("Hello world");
        
        let foo = 42;
        // 调用 `tracing` 包的 `info!`
        tracing::info!(foo, "Hello from tracing");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    tracing 在 API上使用了 log 的规范。

    3.3、异步编程中使用

    在异步编程中,异步任务的执行没有确定的顺序,那么输出的日志将没有确定的日志并混在一起,无法按照我们想要的逻辑顺序串联起来。

    归根到底,在于日志只能针对某个时间点进行记录,缺乏上下文信息,而线程间的执行顺序又是不确定的,因此日志就有些无能为力。而 tracing 为了解决这个问题,引入了 span 的概念( 这个概念也来自于分布式追踪 ),一个 span 代表了一个时间段,拥有开始和结束时间,在此期间的所有类型数据、结构化数据、文本数据都可以记录其中。

    大家发现了吗? span 是可以拥有上下文信息的,这样就能帮我们把信息按照所需的逻辑性串联起来了。

    3.4、tracing的核心概念

    3.4.1、span

    相比起日志只能记录在某个时间点发生的事件,span 最大的意义就在于它可以记录一个过程,也就是在某一段时间内发生的事件流。既然是记录时间段,那自然有开始和结束:

    use tracing::{span, Level};
    fn main() {
        let span = span!(Level::TRACE, "my_span");
    
        // `enter` 返回一个 RAII ,当其被 drop 时,将自动结束该 span
        let enter = span.enter();
        // 这里开始进入 `my_span` 的上下文
        // 下面执行一些任务,并记录一些信息到 `my_span` 中
        // ...
    } // 这里 enter 将被 drop,`my_span` 也随之结束
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.4.2、event

    Event 代表了某个时间点发生的事件,这方面它跟日志类似,但是不同的是,Event 还可以产生在 span 的上下文中。

    use tracing::{event, span, Level};
    use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
    
    fn main() {
        tracing_subscriber::registry().with(fmt::layer()).init();
        // 在 span 的上下文之外记录一次 event 事件
        event!(Level::INFO, "something happened");
    
        let span = span!(Level::INFO, "my_span");
        let _guard = span.enter();
    
        // 在 "my_span" 的上下文中记录一次 event
        event!(Level::DEBUG, "something happened inside my_span");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    虽然 event 在哪里都可以使用,但是最好只在 span 的上下文中使用:用于代表一个时间点发生的事件,例如记录 HTTP 请求返回的状态码,从队列中获取一个对象,等等。

    3.4.3、collector

    当 Span 或 Event 发生时,它们会被实现了 Collect 特征的收集器所记录或聚合。这个过程是通过通知的方式实现的:当 Event 发生或者 Span 开始/结束时,会调用 Collect 特征的相应方法通知 Collector。

    3.5、tracing-subscriber

    只有使用了 tracing-subscriber 日志才会输出到控制台

    cargo add tracing-subscriber
    
    • 1

    3.6、#[instrument]

    如果想要将某个函数的整个函数体都设置为 span 的范围,最简单的方法就是为函数标记上 #[instrument],此时 tracing 会自动为函数创建一个 span,span 名跟函数名相同,在输出的信息中还会自动带上函数参数。

    use tracing::{info, instrument};
    use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
    
    #[instrument]
    fn foo(ans: i32) {
        info!("in foo");
    }
    
    fn main() {
        tracing_subscriber::registry().with(fmt::layer()).init();
        foo(42);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    对于没有内置 tracing 支持或者无法使用 #instrument 的函数,例如外部库的函数,我们可以使用 Span 结构体的 in_scope 方法,它可以将同步代码包裹在一个 span 中

  • 相关阅读:
    c语言进阶篇:指针(一)
    手把手教你在项目中引入Excel报表组件
    ATtiny88初体验(四):看门狗
    猿创征文|我的半年算法学习成长之路~
    Oracle19c数据库安装 - 基于Linux环境
    工具及方法 - 编辑二进制文件(使用VSCode和Notepad++的插件Hex Editor)
    玩转 Scrapy 框架 (二):Scrapy 架构、Request和Response介绍
    lenovo联想笔记本电脑ThinkPad X13 AMD Gen2(20XH,20XJ)原装出厂Windows10系统镜像
    智能汽车安全:保护车辆远程控制和数据隐私
    当我点击桌面App的图标时发生了什么-浅析Activity启动流程(代码基于Android-12)
  • 原文地址:https://blog.csdn.net/fj_Author/article/details/133008408
  • 最新文章
  • 攻防演习之三天拿下官网站群
    数据安全治理学习——前期安全规划和安全管理体系建设
    企业安全 | 企业内一次钓鱼演练准备过程
    内网渗透测试 | Kerberos协议及其部分攻击手法
    0day的产生 | 不懂代码的"代码审计"
    安装scrcpy-client模块av模块异常,环境问题解决方案
    leetcode hot100【LeetCode 279. 完全平方数】java实现
    OpenWrt下安装Mosquitto
    AnatoMask论文汇总
    【AI日记】24.11.01 LangChain、openai api和github copilot
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
正则表达式工具 cron表达式工具 密码生成工具

京公网安备 11010502049817号