Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rust常用库练习 #201

Open
vislee opened this issue Mar 13, 2024 · 0 comments
Open

rust常用库练习 #201

vislee opened this issue Mar 13, 2024 · 0 comments

Comments

@vislee
Copy link
Owner

vislee commented Mar 13, 2024

命令行解析

默认:env::args().collect()

使用clap解析

参数分为 可选参数(Options)位置参数(Arguments)

使用Builder模式构建命令行参数

use clap::{Arg, Command, ArgAction};

fn main() {
    let matches = Command::new("My Test Program")
        .version("0.1.0")
        .author("Hackerman Jones <[email protected]>")
        .about("Teaches argument parsing")
        .arg(Arg::new("file")    // 用于后续的查找
                 .short('f')             // 短
                 .long("file")         // 长
                .value_name("FILE")         //  help时的提示,类似于--file <FILE>
                 .action(ArgAction::Set)    // 指定解析参数时的方法
                 .help("A cool file"))          // help的帮助
        .arg(Arg::new("num")  // 当没设置.short('n')  和  .long("num")   时,为位置参数。
                 .action(ArgAction::Set)
                 .value_parser([1, 2, 3, 4, 5])    // 参数限制
                 .help("Five less than your favorite number"))
        .get_matches();

    let fi = "input.txt".to_string();
    let myfile = matches.get_one::<String>("file").unwrap_or(&fi);
    println!("The file passed is: {}", myfile);

    let num_str = matches.get_one::<String>("num");
    match num_str {
        None => println!("No idea what your favorite number is."),
        Some(s) => {
            match s.parse::<i32>() {
                Ok(n) => println!("Your favorite number is {}.", n),
                Err(_) => println!("That's not a number! {}", s),
            }
        }
    }
}

使用衍生宏构建命令行参数

///开始的 是--help时的解释。
#[arg(short, long)]#[arg(long="xyz", short='x')]#[arg(short, long, default_value_t = 1)] 标注的为可选参数。

  • 参数验证:
    • 枚举值验证
    • 参数值验证
    • 参数关联
    • 自定义验证
use clap::Parser;

#[derive(Parser,Debug)]
#[command(name = "test")]
#[command(author = "louivis")]
#[command(version = "1.0")]
#[command(about = "clap", long_about = "a tutorial of crate clap")]
struct Args {
    /// Name of the person to greet
    #[arg(short='a', long="abc")]
    name: String,

    /// Number of times to greet
    #[arg(short, long, default_value_t = 1)]
    count: u8,

    /// phone
    phone: Option<String>,
}

fn main() {
    let args = Args::parse();

    for _ in 0..args.count {
        println!("Hello {}!", args.name)
    }
}

json解析

使用serde_json解析json。
Cargo.toml文件添加:

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

例子:

use serde::{Deserialize, Serialize};
use serde_json::{Result, Value};

#[derive(Serialize, Deserialize, Debug)]
struct Person {
    name: String,
    age: u8,
    #[serde(default)]
    phones: Vec<String>,
}

fn test_too_struct() -> Result<()> {
    let data = r#"
        {
            "name": "vislee", "age": 28
        }
    "#;

    let p: Person = serde_json::from_str(data)?;
    println!("=Deserialize={:?}", p);

    let s: String = serde_json::to_string(&p)?;
    println!("=Serialize={}", s);

    Ok(())
}

fn test_too_val() -> Result<()> {
    let data = r#"
        {
            "name": "vislee", "age": 18, "phones": ["158666666", "182333333"]
        }
    "#;
    let p: Value = serde_json::from_str(data)?;
    println!("=Deserialize={:#?}", p);
    println!(
        "===name={}",
        *p.get("name").unwrap_or(&Value::String("abc".to_string()))
    );

    println!("=Serialize={}", p.to_string());
    Ok(())
}

fn main() {
    let x = test_too_struct();
    println!("{:?}", x);
    let x = test_too_val();
    println!("{:?}", x);
}

HTTP

使用reqwest发送请求
Cargo.toml文件添加:

[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }

简单请求:

use std::collections::HashMap;

#[tokio::main]
async fn main() {
    let resp = match reqwest::get("http://127.0.0.1:8989/ip").await {
        Ok(resp) => resp,
        Err(e) => {
            eprintln!("Error1: {}", e);
            return;
        }
    };

    let json_result: HashMap<String, String> = match resp.json::<HashMap<String, String>>().await {
        Ok(json_result) => json_result,
        Err(e) => {
            eprintln!("Error2: {}", e);
            return;
        }
    };

    println!("{:#?}", json_result);
}

复杂请求:

use reqwest::header::{self, HeaderMap, HeaderName};
use reqwest::{Client, Url};

#[tokio::main]
async fn main() {
    let url = match Url::parse_with_params(
        "http://127.0.0.1:8989/header",
        &[("lang", "rust"), ("browser", "servo")],
    ) {
        Ok(u) => u,
        Err(e) => {
            println!("error: {:?}", e);
            return;
        }
    };

    let mut headers = HeaderMap::new();
    headers.insert(header::HOST, "example.com".parse().unwrap());
    headers.insert(
        HeaderName::from_lowercase(b"foo").unwrap(),
        "bar".parse().unwrap(),
    );

    let resp = match Client::new().get(url).headers(headers).send().await {
        Ok(r) => r,
        Err(e) => {
            println!("error: {:?}", e);
            return;
        }
    };
    println!("status: {}", resp.status());
    println!("headers: {:#?}", resp.headers());
    println!("body: {:?}", resp.text().await);
}

解释上述用到的几个方法:

  • parse_with_params
    定义了一个名为parse_with_params的函数,它接受一个字符串切片input和一个实现了IntoIterator trait的迭代器iter。函数的目的是解析带有参数的URL,并返回一个Url类型的结果或者一个ParseError错误。其中:<I as IntoIterator>::Item: Borrow<(K, V)> :指定了I迭代器的元素类型必须实现Borrow trait,以便可以借用一个元组 (K, V)。这里<I as IntoIterator>::Item表示从 I 类型转换成的迭代器的元素类型; K: AsRef<str> : 指定了泛型参数 K 必须实现 AsRef<str> trait,这意味着 K 的类型可以被转换为字符串切片的引用。

日志记录

日志库通常会支持:日志分级、日志过滤、日志输出格式化、日志回滚等功能。
Log是Rust的轻量级日志接口。它抽象了日志实现,很好的兼容不同日志库。有大量日志库基于它实现的,最常用env_looger,log4rs等。
定义了如下等级:

  • Error: 严重错误
  • Warn: 危险状况
  • Info: 有用的信息
  • Debug: 低优先级的调试信息
  • Trace: 非常低的优先级,很详细的信息
    Log只是个接口,实际还需要设置具体的日志库。如果没有设置具体的日志库,那么log门面库就退化成noop实现,也就是忽略所有的日志。
[dependencies]
log = "0.4"

具体实现

env_logger

[dependencies]
log = "0.4"
env_logger = "0.11.3"

例子:

use log::info;

fn main() {
    env_logger::init();
    info!("starting up");

    println!("hello world");
}

通过环境变量RUST_LOG控制日志等级。
env_logger 也可以通过Builder的format方法自定义日志格式:

env_logger::builder()
    .format(|buf, record| {
        writeln!(buf, "{}: {}", record.level(), record.args())
    })
    .init();

也可以通过target方法自定义输出目标。

use log::info;
use env_logger::fmt::Target;
use std::fs::OpenOptions;

fn main() {
    let file = OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(true)
        .open("app.log")
        .expect("Failed to open log file");

    env_logger::builder()
    .default_format()
    .target(Target::Pipe( Box::new(file)))
    .init();

    info!("starting up");

    println!("hello world");
}

log4rs

log4j组件:

  • appender:控制输出到什么地方去,例子添加输出到 文件 和 控制台。
  • logger:添加 appender 配置,比如一条日志既要输出到控制台也要持久化到日志文件中,就可以在logger中同时绑定 ConsoleAppender 和 FileAppender
  • build: 构建根记录器配置

能输出日志到文件和控制台两种方式。配置方式:可以使用外部 yaml 文件,也可以通过生成器配置。
日志格式说明:https://docs.rs/log4rs/1.3.0/log4rs/encode/pattern/index.html#formatters

  • 配置文件
    创建target/config/config.yaml文件
# Scan this file for changes every 30 seconds
refresh_rate: 30 seconds

appenders:
  # An appender named "stdout" that writes to stdout
  stdout:
    kind: console

  error:
    kind: file
    path: "log/error.log"
    encoder:
      pattern: "{d(%Y-%m-%d %H:%M:%S)} - {m}{n}"

  # An appender named "requests" that writes to a file with a custom pattern encoder
  requests:
    kind: file
    path: "log/requests.log"
    encoder:
      pattern: "{d} - {m}{n}"

# Set the default logging level to "warn" and attach the "stdout" appender to the root
root:
  level: info
  appenders:
    - error
    - stdout

loggers:
  # Raise the maximum log level for events sent to the "app::backend::db" logger to "info"
  app::backend::db:
    level: info

  # Route log events sent to the "app::requests" logger to the "requests" appender,
  # and *not* the normal appenders installed at the root
  app::requests:
    level: info
    appenders:
      - requests
    additive: false
use log;
use log4rs;

fn main() {
    log4rs::init_file("target/config/config.yaml", Default::default()).unwrap();

    log::info!("starting up");

    println!("hello world");
}
  • 生成器
use log::{self, LevelFilter};

use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
use log4rs::config::{Appender, Config, Logger, Root};
use log4rs::encode::pattern::PatternEncoder;

fn main() {
    // 创建一个文件输出器,用于将日志写入到 logs/sys.log 文件.
    // 使用 {d} - {m}{n} 模式编码器,其中 {d} 表示日期,{m} 表示日志消息,{n} 表示换行符
    let sys_file = FileAppender::builder()
        .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
        .build("logs/sys.log")
        .unwrap();

    // 创建一个文件输出器,用于将日志写入到 logs/business.log 文件.
    let business_file = FileAppender::builder()
        .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
        .build("logs/business.log")
        .unwrap();

    // 创建一个控制台输出器,用于将日志输出到标准输出(通常是控制台)。
    let stdout = ConsoleAppender::builder().build();

    let config = Config::builder()
        // 将控制台输出器添加到配置中,并命名为 "stdout"
        .appender(Appender::builder().build("stdout", Box::new(stdout)))
        // 将 sys_file 文件输出器添加到配置中,并命名为 "sys"
        .appender(Appender::builder().build("sys", Box::new(sys_file)))
        .appender(Appender::builder().build("business", Box::new(business_file)))
        .logger(
            Logger::builder()
                .appender("sys")
                .build("syslog", LevelFilter::Warn),
        )
        // 将 business_file 文件输出器添加到配置中,并命名为 "business"。
        .logger(
            Logger::builder()
                .appender("business")
                .build("businesslog", LevelFilter::Info),
        )
        // 构建根记录器配置,它将 Warn 级别及以上的日志发送到 "stdout" 输出器。
        // 由于没有将 "sys" 和 "business" 输出器添加到根记录器,因此根记录器不会将日志发送到这些文件。
        .build(Root::builder().appender("stdout").build(LevelFilter::Warn))
        .unwrap();

    let _ = log4rs::init_config(config).unwrap();

    log::info!("Hello, info!!!");
    log::info!(target:"syslog", "File syslog info");
    log::warn!(target:"syslog", "File syslog warn");
    log::info!(target:"businesslog", "File businesslog info");
    log::warn!("hello, warn!!!")
}
$ ./target/debug/test_log4rs
2024-03-19T10:43:17.403894+08:00 WARN syslog - File syslog warn
2024-03-19T10:43:17.403957+08:00 INFO businesslog - File businesslog info
2024-03-19T10:43:17.403967+08:00 WARN my_log - hello, warn!!!
$ cat logs/sys.log
2024-03-19T10:43:17.403633+08:00 - File syslog warn
$ cat logs/business.log
2024-03-19T10:43:17.403922+08:00 - File businesslog info

分布式跟踪

tracing 可以作为一个日志库使用,更重要的是一个分布式跟踪的 SDK,用于采集监控数据的,帮助我们分析程序中存在的问题。
分布式跟踪的核心就是在请求的开始生成一个 trace_id,然后将该 trace_id 一直往后透穿,请求经过的每个服务都会使用该 trace_id 记录相关信息,最终将整个请求形成一个完整的链路予以记录下来。
配合tracing-appender支持输出到文件。

tracing 使用 event 发出日志,使用 subscriber 收集日志。

各个模块:

  • ​tracing​​: 作用域内的结构化日志记录和诊断系统。
  • tracing_appender​: 记录事件和跨度的编写者。也就是将日志写入文件或者控制台。
  • tracing_error​: 增强错误处理跟踪诊断信息的实用工具。
  • ​tracing_flame​: 用于生成折叠堆栈跟踪以生成Inferno火焰图和火焰图表的跟踪订阅者。
  • tracing_log​: 用于连接标准库日志系统和tracing系统的连接器。
  • tracing_subscriber​: 用于实现和组成tracing订阅者的工具集合。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant