Rust 的 Logging 推荐
内容整理自 Reddit 的讨论:What is the current recommendation for logging in Rust? : rust。
问题简述:除了标准的 log,还有不少选择:env_logger,tracing,slog,simplelog 等等,最佳实践是什么?
来自 Koxiaet 的答复:通常有两类与日志相关的 crate:日志接口和日志消费者。接口提供了想要记录某些东西时调用的函数,消费者处理将结构化日志数据格式化到某个地方(stderr 或文件)。两个主要的接口是 log 和 tracing,后者功能更强大因为它支持结构化日志记录,但前者更普遍。还有另一个结构化日志接口 slog,比 tracing 更古老但用的较少。每个日志接口都有自己生态系统,可以根据自己的需要选择。如果在写一个库,log 是个不错的选择,因为所有的日志记录接口都与它兼容。但如果你确实需要结构化日志记录,则可以改用 tracing,这取决于你的需求,比如你是需要写到文件还是只是终端。
其他网友的推荐:
- File Logging:emabee/flexi_logger: A flexible logger for rust programs that can write to stderr or to log files。(来自 cfsamson)
- tracing 的接口:tracing_log - Rust,有多个同时操作交错日志消息时特别方便,可以按某些属性对它们进行分组并单独查看它们。(来自 class_two_perversion)
- estk/log4rs: A highly configurable logging framework for Rust,log4rs 是一个高度可配置的日志框架,以 Java 的 Logback 和 log4j 库为模型。通过 Yaml 配置,到 sdout 和文件,带有文件大小限制选项,还可以配置不同级别的日志。(来自 tms102)
- tracing-appender - crates.io: Rust Package Registry,推荐者所知道的唯一线程外日志记录解决方案,不仅适用于异步应用程序。(来自 Pand9)
- daboross/fern: Simple, efficient logging for Rust,像 Python 的logging 和 JS 的 Winston。(来自 RapBeautician)
Rust 全栈
本文是一篇博客翻译,来自:Full Stack Rust - Blog。
一年前,我的首选语言如下:
- Python 用于高级代码快速原型设计,或用于需要第三方功能的代码
- C/C++ 用于长期的 low-level 项目
当时只听过 Rust 并简单使用过,我的经验来自用 Rust 写了一个处理大文件(>4GB)的事务并从中挖掘一些统计信息的小工具。我用了一个库将文件映射到内存,缤瑞按照顺序对其进行分析。有一些很酷的概念,比如编译器静态地强制内存映射在它被取消映射后无法访问——如果你不小心,C++ 中可能就会发生这种错误。
不过当时并没有真正吸引我,因为那只是一个小新奇。当我向 pdblister 添加新功能以并行获取数千个 PDB 文件时诀窍来了。由于 GIL,在 CPython 中几乎不可能,而在 C/C++ 中做到不面临并行错误是极其困难的。然而 Rust 让这变得容易。我添加了 tokio 驱动的异步,使用 tokio::spawn 生成新任务来下载 PDB,并修复了编译器报的错误,它可以正常工作了。Rust 编译器输出一个二进制文件,它可以在任何地方运行,没有运行时依赖。
取代 Python
这是第一点,Rust 是 Python 作为中长期工具语言的绝佳替代品。Python 的好处是庞大的库和生态系统,通过 pip 可以直接拿到,想要快速制作与 API 交互的原型,可以使用 requests,只要 import requests 就可以使用了。Rust 的 reqwest 也是如此,只要输入 cargo add reqwest 就可以在代码中使用它。
然而当进入更长期的生命周期时,Python 就显示出劣势,requests 是程序的依赖,用户需要后去后才能使用。此外,由于弱类型和错误处理能力(与 Rust 比),Python 变得更加劣势。这一点上,我可以使用 Rust 比使用 Python 更快地编写原型工具,并且我可以自信地知道我的工具比等效的 Python 更易于维护且寿命更长。但是,对于短期工具,Python 可能仍然更好,因为它不需要启动项目即可在 VSCode 中获得智能感知支持。 Rust 的 cargo-script 接近将 Rust 推入脚本语言的领域,但不幸的是,我还没有在 VSCode 中找到与之集成的插件。
取代 C
Rust 也是 C 的直接替代品,它在各方面都更好,并且可以与遗留 C 代码原生互操作以进行增量替换。Rust 最大的改进是生态系统:如上所述,利用 Rust 生态中已有的库是很容易的。如果你从未使用过 C,那很幸运,实际上 C 中使用高级功能的最佳方法是自己写。
C 生态系统是支离破碎的,而且很脆弱。ABI 或构建系统没有一致的标准:
- 由于缺乏 ABI 一致性,你不能跨平台或操作系统使用相同的二进制文件。 所以你必须从源代码构建。
- 由于缺乏一致的构建系统,你不能简单地和应用程序一起构建 C 库,必须修补或重写要使其与你的库兼容的库的构建系统。
- C 库很少跨平台兼容,因为它们缺乏可以依赖的共享抽象。
然后还有 Rust 最特色的安全改进——我就不展开了。但根据我的经验 - 安全性在很大程度上是一种工具,可以让第三方库开发人员更容易强迫我正确使用他们的库,这是 C 库不能做的事情。
全栈 Rust
总而言之,在过去的一年中,我一直在堆栈的所有部分使用 Rust,而我之前使用过其他语言。我已经使用 Rust 来实现引导加载程序:xenia-project/xell-rs: Xell Bootloader, rewritten in Rust because ¯_(ツ)_/¯,我已经使用它通过 pdblister 和 panamax 中的高级 HTTP/HTTPS 和其他技术来镜像文件。我利用并贡献了优秀的 gdbstub 库,用于控制由自定义 VMM 运行的 VM。这些项目都是在堆栈的不同级别完成的,而 Rust 非常适合所有级别。 我已经开始在我的个人项目中专门使用 Rust,并在适合的时候推动它在我的工作中使用。
tagged_cell:快速、可初始化和线程安全的静态变量
通过 TaggedCell 和 Tag 类型实现,为了安全操作,TaggedCell 的每个实例都必须是唯一的。然后必须通过 TaggedCell::init () 初始化 TaggedCell,它使用用户提供的函数或闭包初始化底层数据,然后返回一个特殊的零大小的 Init<Tag> 用于访问 Cell 的数据。为了确保每个单元格使用唯一的标签类型,tagged_cell! 提供宏。该宏根据变量的名称创建一个新的标记类型,并将其应用到声明中。
use tagged_cell::tagged_cell;
tagged_cell!{
static BAR: TaggedCell<Vec<usize>, _> = TaggedCell::new();
}
let tag = BAR.init(|| vec![0, 10, 20]);
let vec = BAR.get(tag);
assert_eq!(vec[2], 20);
为了允许跨线程使用,只有第一次调用 TaggedCell::init 才会初始化 Cell 的数据。所有未来的 TaggedCell::init 调用都将返回一个新标签。未确定哪个线程将初始化 Cell 的数据。
use std::thread;
use tagged_cell::tagged_cell;
tagged_cell!{
static TABLE: TaggedCell<Vec<usize>, _> = TaggedCell::new();
}
thread::spawn(move || {
let tag = TABLE.init(|| vec![0, 10, 20]);
let table = TABLE.get(tag);
assert_eq!(table[2], 20);
});
thread::spawn(move || {
let tag = TABLE.init(|| vec![0, 10, 20]);
let table = TABLE.get(tag);
assert_eq!(table[1], 10);
});
GitHub:Dasch0/tagged_cell: Fast, initializable, and thread safe static variables
ukanren-rs:µKanren 的 Rust 实现
µKanren 是一种轻量级关系编程语言
- 原始的 Schema 实现在这里:jasonhemann/microKanren: The implementation of microKanren, a featherweight relational programming language
- 相关参考:miniKanren.org
use ukanren::*;
fn appendo(first: Value, second: Value, out: Value) -> BoxedGoal<impl Iterator<Item = State>> {
eq(&first, &())
.and(eq(&second, &out))
.or(fresh(move |a: Value, d: Value, res: Value| {
eq(&(a.clone(), d.clone()), &first)
.and(eq(&(a.clone(), res.clone()), &out))
.and(appendo(d.clone(), second.clone(), res))
}))
.boxed()
}
let goal = fresh(|x, y| appendo(x, y, [1, 2, 3, 4, 5].to_value()));
assert_eq!(
goal.run(2).collect::<Vec<_>>(),
vec![
state![(), [1, 2, 3, 4, 5]],
state![[1], [2, 3, 4, 5]],
state![[1, 2], [3, 4, 5]],
state![[1, 2, 3], [4, 5]],
state![[1, 2, 3, 4], [5]],
state![[1, 2, 3, 4, 5], ()],
],
);
GitHub:ekzhang/ukanren-rs: Rust implementation of µKanren, a featherweight relational programming language.
rust-counter-strings:快速定位字符串位置
字符串中的每个星号都出现在由紧接前面的数字指定的位置。因此,29 后面的星号是该字符串中的第 29 个字符。可以在任何地方砍掉字符串的末尾,并且确切地知道它在哪里被剪掉了。比如不用数就知道字符串 2*4*6*8*11*14*17*2 正好有 18 个字符。当处理 50 万个字符时会比较省事。
$ ./rust-counter-strings 50
# 2*4*6*8*11*14*17*20*23*26*29*32*35*38*41*44*47*50*
这就是个小工具,代码也只有几十行。
GitHub:thomaschaplin/rust-counter-strings: