05-面向人与机器的输出
人类可读输出与机器可解析输出
译文 · 基于 Command Line Applications in Rust
面向人与机器的输出
原文链接: https://rust-cli.github.io/book/tutorial/output.html
打印 “Hello World”
1
| println!("Hello World");
|
嗯,挺简单。
很好!下一话题。
使用 println!
用 println! 宏几乎可以打印你喜欢的所有东西。
这个宏能力相当惊人,
但也有一套特殊语法。
它期望第一个参数是包含占位符的字符串字面量。
后面参数的值会填进
这个字符串。
例如:
1
2
| let x = 42;
println!("My lucky number is {}.", x);
|
会打印:
上面字符串里的花括号({})就是一种占位符。
这是默认占位符类型,
会尽量以人类可读的方式打印给定值。
对数字和字符串效果很好,
但并非所有类型都能这样做。
因此还有一种“调试表示”,
可以通过把占位符写成 {:?} 来获得。
例如:
1
2
| let xs = vec![1, 2, 3];
println!("The list is: {:?}", xs);
|
会打印:
若希望自己的数据类型也能用于调试和日志打印,
通常可以在定义上方加上 #[derive(Debug)]。
打印错误
打印错误应通过 stderr,
以便用户
和其它工具
更容易把输出管道到文件
或更多工具。
在 Rust 中,这通过
println! 和 eprintln! 实现,
前者打印到 stdout,
后者打印到 stderr。
1
2
| println!("This is information");
eprintln!("This is an error! :(");
|
往终端打印出奇地慢!
如果在循环里调用 println! 之类的东西,
很容易成为原本很快的程序里的瓶颈。
要加速,
可以做两件事。
第一,
你可能想减少真正“刷新”到终端的写入次数。
println! 会每次告诉系统刷新到终端,
因为常见做法是每行打印一次。
若不需要这样,
可以把 stdout 句柄包在 BufWriter 里,
默认最多缓冲 8 kB。
仍可在想立即打印时对这个 BufWriter
调用 .flush()。
1
2
3
4
5
| use std::io::{self, Write};
let stdout = io::stdout(); // 获取全局 stdout
let mut handle = io::BufWriter::new(stdout); // 可选:用缓冲区包住该句柄
writeln!(handle, "foo: {}", 42); // 若关心错误,在此加 `?`
|
第二,
获取 stdout(或 stderr)上的锁,
并用 writeln! 直接打印会有帮助。
这能避免系统反复锁定和解锁 stdout。
1
2
3
4
5
| use std::io::{self, Write};
let stdout = io::stdout(); // 获取全局 stdout
let mut handle = stdout.lock(); // 获取其上的锁
writeln!(handle, "foo: {}", 42); // 若关心错误,在此加 `?`
|
也可以把两种做法结合起来。
显示进度条
有些 CLI 应用运行不到一秒,
有些则要几分钟或几小时。
如果你写的是后一类程序,
可能希望让用户看到正在发生什么。
为此,应尽量打印有用的状态更新,
最好是易于消化的形式。
使用 indicatif crate,
可以为程序添加进度条
和小小的旋转指示器。
下面是一个快速示例:
1
2
3
4
5
6
7
8
9
| fn main() {
let pb = indicatif::ProgressBar::new(100);
for i in 0..100 {
do_hard_work();
pb.println(format!("[+] finished #{}", i));
pb.inc(1);
}
pb.finish_with_message("done");
}
|
更多信息见文档
和示例。
日志
为了更容易理解程序在做什么,
我们可能想加一些日志语句。
写应用时这通常很容易,
半年后再跑这个程序时会非常有用。
在某些方面,
打日志和用 println! 一样,
只不过你可以指定消息的重要性。
通常可用的级别是 error、warn、info、debug 和 trace
(error 优先级最高,trace 最低)。
要为应用添加简单日志,
需要两样东西:
log crate(包含以日志级别命名的宏)
以及一个真正把日志写到有用地方的适配器。
能使用日志适配器非常灵活:
例如,你不仅可以写到终端,
还可以写到 syslog 或中央日志服务器。
既然我们只关心写 CLI 应用,
一个易用的适配器是 env_logger。
之所以叫 “env” logger,是因为你可以用
环境变量指定想记录应用的哪些部分,
以及在哪个级别记录。
它会在日志消息前加上时间戳
以及消息来自的模块。
由于库也可以使用 log,
你也能轻松配置它们的日志输出。
快速示例如下:
1
2
3
4
5
6
7
| use log::{info, warn};
fn main() {
env_logger::init();
info!("starting up");
warn!("oops, nothing implemented!");
}
|
假定该文件是 src/bin/output-log.rs,
在 Linux 和 macOS 上可以这样运行:
1
2
3
4
5
| $ env RUST_LOG=info cargo run --bin output-log
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/output-log`
[2018-11-30T20:25:52Z INFO output_log] starting up
[2018-11-30T20:25:52Z WARN output_log] oops, nothing implemented!
|
在 Windows PowerShell 中可以这样运行:
1
2
3
4
5
6
| $ $env:RUST_LOG="info"
$ cargo run --bin output-log
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/output-log.exe`
[2018-11-30T20:25:52Z INFO output_log] starting up
[2018-11-30T20:25:52Z WARN output_log] oops, nothing implemented!
|
在 Windows CMD 中可以这样运行:
1
2
3
4
5
6
| $ set RUST_LOG=info
$ cargo run --bin output-log
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/output-log.exe`
[2018-11-30T20:25:52Z INFO output_log] starting up
[2018-11-30T20:25:52Z WARN output_log] oops, nothing implemented!
|
RUST_LOG 是用来设置日志配置的环境变量名。
env_logger 也包含一个 builder,
以便以编程方式调整这些设置,
例如默认显示 info 级别消息。
外面还有很多其它日志适配器,
以及对 log 的替代与扩展。
如果你知道应用会打很多日志,
务必审阅它们,
让用户的生活更轻松。