前言
这是我们构建真实项目的第二部分,我们的目标是构建一个运行WebAssembly
的命令行工具。截止到目前为止,我们将文件路径硬编码在了程序里,并且用的还是println!()
来做日志输出,代码实在是有些僵化死板。再添加更多逻辑之前,我们需要让程序更加灵活
Rust
有命令行参数以及调试输出等解决方案, structopt 让命令行参数管理变得很简单,log + env_logger 会赋予你灵活的日志输出能力
正文
日志输出
许多Node.js
代码库都会依赖 debug 包来进行调试输出,Rust
有一个类似的,许多Rust
程序都会用 log 包来做日志,用
env_logger来做输出,在这里日志和输出是解耦的
log
首先在在代码库中添加log
依赖:
crates/my-lib/Cargo.toml:
[dependencies]
log = "0.4"
crates/cli/Cargo.toml:
[dependencies]
my-lib = { path = "../my-lib" }
log = "0.4"
log
包给了我们trace!()
、debug!()
、warn!()
、info!()
、error!()
五个宏,每种的用法和println!()
一样,每个宏有对应的日志等级。接下来让我们试着打印一些信息来看它们是如何工作的,在my-lib
包的lib.rs
文件里我们添加debug!()
// crates/my-lib/src/lib.rs
pub fn from_file<T: AsRef<Path>>(path: T) -> Result<Self, std::io::Error> {
debug!("Loading wasm file from {:?}", path.as_ref());
Ok(Self {})
}
在cli
包的main.rs
文件中将println!()
方法替换成更合适的方法:
// crates/cli/src/main.rs
match Module::from_file("./module.wasm") {
Ok(_) => {
info!("Module loaded");
}
Err(e) => {
error!("Module failed to load: {}", e);
}
}
现在我们运行代码,会发现什么也没输出
$ cargo run -p cli
[...nothing...]
env_logger
我们用env_logger
将日志输出,添加如下依赖:
crates/cli/Cargo.toml:
[dependencies]
my-lib = { path = "../my-lib" }
log = "0.4"
env_logger = "0.9"
然后我们在main()
中进行初始化,我们紧挨着初始化代码添加了一个debug!()
来确保我们可以观察到结果:
crates/cli/src/main.rs:
fn main() {
env_logger::init();
debug!("Initialized logger");
// ...
}
不过我们运行下代码,会发现什么也没有输出
$ cargo run -p cli
[...nothing...]
这个结果是符合预期的,因为默认情况下env_logger
不输出任何内容,我们需要开启相应的功能,我们可以在代码里开启也可以通过环境变量RUST_LOG
来开启:
$ RUST_LOG=debug cargo run -p cli
[2021-12-21T02:33:24Z DEBUG cli] Initialized logger
[2021-12-21T02:33:24Z DEBUG my_lib] Loading wasm file from "./module.wasm"
[2021-12-21T02:33:24Z INFO cli] Module loaded
留意输出结果中的cli
和library
的定位,你可以控制每个模块或全局的消息类型,如果你指定RUST_LOG=info
参数,那么你只会看到info
类型的信息
我们可以通过[package]=[level]
命令来过滤每个模块的消息类型
$ RUST_LOG=cli=debug cargo run -p cli
[2021-12-21T02:35:30Z DEBUG cli] Initialized logger
[2021-12-21T02:35:30Z INFO cli] Module loaded
命令行参数处理
现在我们已经可以看到调试信息了,现在我们需要将配置信息改为从命令行获取。 clap 是一个很棒的命令行配置工具, structopt 则提供了使用clap
的更简单方式
首先添加依赖
crates/cli/Cargo.toml:
[dependencies]
my-lib = { path = "../my-lib" }
log = "0.4"
env_logger = "0.9"
structopt = "0.3"
利用structopt
创建一个导出StructOpt trait
的结构体:
use structopt::StructOpt;
#[derive(StructOpt)]
struct CliOptions {
}
配置StructOpt
有两个层面,一个是全局,另一个是每个参数。全局配置用到结构体的structopt
属性,下面这段代码给出了程序的名称、一段描述,并用到了clap
的AppSettings
来改变颜色
use structopt::{clap::AppSettings, StructOpt};
#[derive(StructOpt)]
#[structopt(
name = "wasm-runner",
about = "Sample project from https://vino.dev/blog/node-to-rust-day-1-rustup/",
global_settings(&[
AppSettings::ColoredHelp
]),
)]
struct CliOptions {}
结果:
cargo run -p cli -- --help
wasm-runner 0.1.0
Sample project from https://vino.dev/blog/node-to-rust-day-1-rustup/
USAGE:
cli
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
添加命令行参数就像在结构中添加字段一样简单,#[structopt]
属性可以决定参数的默认值、解析方式、长短格式等。下面的代码添加了一个必须的参数file_path
,可以使用String
类型作为文件的路径,不过structopt
支持通过parse()
预处理参数值将其转换为更合适的类型
struct CliOptions {
/// The WebAssembly file to load.
#[structopt(parse(from_os_str))]
pub(crate) file_path: PathBuf,
}
此次外我们还需要添加一行代码,向结构体中增加from_args
函数
let options = CliOptions::from_args();
现在我们的main()
函数完整代码如下:
fn main() {
env_logger::init();
debug!("Initialized logger");
let options = CliOptions::from_args();
match Module::from_file(&options.file_path) {
Ok(_) => {
info!("Module loaded");
}
Err(e) => {
error!("Module failed to load: {}", e);
}
}
}
我们的命令行也做了更新:
$ cargo run -p cli -- --help
wasm-runner 0.1.0
Sample project from https://vino.dev/blog/node-to-rust-day-1-rustup/
USAGE:
cli <file-path>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
ARGS:
<file-path> The WebAssembly file to load
整合
因为我们内置的打印机制,我们可以通过RUST_LOG
看到命令行参数
RUST_LOG=debug ./target/debug/cli ./test_file.wasm
[2021-12-21T03:08:09Z DEBUG cli] Initialized logger
[2021-12-21T03:08:09Z DEBUG my_lib] Loading wasm file from "./test_file.wasm"
[2021-12-21T03:08:09Z INFO cli] Module loaded
现在我们的程序已经可以跑通了,后面我们会继续讲解实现WebAssembly
的更多细节
相关阅读
总结
一个简单的日志系统就是如此,未来你可能需要将其记录到磁盘文件或日志聚合器中,在下一篇教程中我们将会介绍关于WebAssembly
的更多细节
更多
- 写给前端看的Rust教程(1)从nvm到rust
- 写给前端看的Rust教程(2)从npm到cargo
- 写给前端看的Rust教程(3)配置Visual Studio Code
- 写给前端看的Rust教程(4)Hello World
- 写给前端看的Rust教程(5)借用&所有权
- 写给前端看的Rust教程(6)String 第一部分
- 写给前端看的Rust教程(7)语言篇[上]
- 写给前端看的Rust教程(8)语言篇[中]
- 写给前端看的Rust教程(9)语言篇[下]
- 写给前端看的Rust教程(10)从 Mixins 到 Traits
- 写给前端看的Rust教程(11)Module
- 写给前端看的Rust教程(12)String 第二部分
- 写给前端看的Rust教程(13)Results & Options
- 写给前端看的Rust教程(14)Errors
- 写给前端看的Rust教程(15)闭包
- 写给前端看的Rust教程(16)生命周期
- 写给前端看的Rust教程(17)迭代
- 写给前端看的Rust教程(18)异步
- 写给前端看的Rust教程(19)实战 第一部分
- 写给前端看的Rust教程(20)实战 第二部分
- 写给前端看的Rust教程(21)实战 第三部分