文章详情

短信预约信息系统项目管理师 报名、考试、查分时间动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

时序数据库Influx-IOx源码学习三(命令行及配置)

2014-12-30 07:35

关注

时序数据库Influx-IOx源码学习三(命令行及配置)

欢迎关注公众号:

上篇介绍到:InfluxDB-IOx的环境搭建,详情见:https://my.oschina.net/u/3374539/blog/5016798

本章开始,讲解启动的主流程!

打开src/main.rs文件可以找到下面的代码

fn main() -> Result<(), std::io::Error> {
    // load all environment variables from .env before doing anything
    load_dotenv();

    let config = Config::from_args();
    println!("{:?}", config);
   
    //省略
    .....
    
    Ok(())
}

main方法中映入眼帘的第一行就是load_dotenv()方法,然后是Config::from_args()接下来就分别跟踪这两个方法,看明白是怎么工作的。

加载配置文件

README文件中,我们可以看到这样一行:

Should you desire specifying config via a file, you can do so using a .env formatted file in the working directory. You can use the provided example as a template if you want:

cp docs/env.example .env

意思就是这个工程使用的配置文件,名字是.env。了解这个特殊的名字之后,我们看代码src/main.rs:276

fn load_dotenv() {
    //调用dotenv方法,并对其返回值进行判断
    match dotenv() {
        //如果返回成功,程序什么都不做,继续执行。
        Ok(_) => {}
        //返回的是错误,那么判断一下是否为"未找到"错误,
        //如果是未找到,那么就什么都不做(也就是有默认值填充)
        Err(dotenv::Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
        }
        //这里就是真真正正必须要处理的错误了,直接退出程序
        Err(e) => {
            eprintln!("FATAL Error loading config from: {}", e);
            eprintln!("Aborting");
            std::process::exit(1);
        }
    };
}

然后跟踪dotenv()方法看看如何执行(这里就进入了dotenv这个crate了): 为了方便写,我就直接把所有调用,从上到下的顺序全都写出来了

//返回一个PathBuf的Result,之后再看这个Result
pub fn dotenv() -> Result {
    //new一个Finder结构并调用find方法
    //?代表错误的时候直接抛出错误
    let (path, iter) = Finder::new().find()?;
    //返回一个自定义的Iter结构,并调用load方法
    iter.load()?;
    //成功返回
    Ok(path)
}
//创建一个Finder结构体,filename使用`.env`填充
 pub fn new() -> Self {
        Finder {
            filename: Path::new(".env"),
        }
 }
//返回一个元组,多个返回值,(路径,文件读取相关记录)
pub fn find(self) -> Result<(PathBuf, Iter)> {
        //使用标准库中的current_dir()方法得到当前的路径
        //出错就返回Error::Io错误,正常就调用find方法
        let path = find(&env::current_dir().map_err(Error::Io)?, self.filename)?;
        //如果找到了.env文件就打开,打开错误就返回Error::Io错误
        let file = File::open(&path).map_err(Error::Io)?;
        //使用打开的文件创建一个Iter的结构
        let iter = Iter::new(file);
        //返回
        Ok((path, iter))
 }
 //递归查找.env文件
 pub fn find(directory: &Path, filename: &Path) -> Result {
    //拼装一个全路径
    let candidate = directory.join(filename);
    //尝试打开这个文件
    match fs::metadata(&candidate) {
        //成功打开了,说明找到了.env文件,就返回成功
        //但我有个疑问文件内容为啥不校验一下呢?
        Ok(metadata) => if metadata.is_file() {
            return Ok(candidate);
        },
        //除了没找到文件的错误之外,其它错误都直接返回异常
        Err(error) => {
            if error.kind() != io::ErrorKind::NotFound {
                return Err(Error::Io(error));
            }
        }
    }
    //没找到的时候,就返回到父级文件夹里,继续找,一直到根文件夹
    if let Some(parent) = directory.parent() {
        find(parent, filename)
    } else {
        //一直到根文件夹,还没找到就返回一个NotFound的IO错误,
        //这个在上面的代码中提到,这个错误会被忽略
        Err(Error::Io(io::Error::new(io::ErrorKind::NotFound, "path not found")))
    }
}

  //对应的iter.load()?;方法实现
  pub fn load(self) -> Result<()> {
        //可以使用for是因为实现了Iterator 这个trait
        for item in self {
            //获取读取出来的一行一行的配置项
            let (key, value) = item?;
            //验证key没有什么问题,就放到env中
            if env::var(&key).is_err() {
                env::set_var(&key, value);
            }
        }
        Ok(())
    }
// 为了能够for循环,实现的Iterator
impl Iterator for Iter {
    type Item = Result<(String, String)>;

    fn next(&mut self) -> Option {
        loop {
           //一行一行的读取文件内容
            let line = match self.lines.next() {
                Some(Ok(line)) => line,
                Some(Err(err)) => return Some(Err(Error::Io(err))),
                None => return None,
            };
            //解析配置项目,这里就不在深入跟了
            match parse::parse_line(&line, &mut self.substitution_data) {
                Ok(Some(result)) => return Some(Ok(result)),
                Ok(None) => {}
                Err(err) => return Some(Err(err)),
            }
        }
    }
}

研究这里的时候,我发现了一个比较好玩儿的东西就是返回值的Result。标准库的定义中,Result是有两个值,分别是

自定义的类型,节省了Error这个模板代码
pub type Result = std::result::Result;

//Error也自己定义
pub enum Error {
    LineParse(String, usize),
    Io(io::Error),
    EnvVar(std::env::VarError),
    #[doc(hidden)]
    __Nonexhaustive
}

//实现一个not_found()的方法来判断是否为not_found的一个错误类型
impl Error {
    pub fn not_found(&self) -> bool {
        if let Error::Io(ref io_error) = *self {
            return io_error.kind() == io::ErrorKind::NotFound;
        }
        false
    }
}

//实现标准库中的error::Error这个trait
impl error::Error for Error {
    //追踪错误的上一级,应该是打印堆栈这种功能
    //如果内部有错误类型Err返回:Some(e),如果没有返回:None
    //关于"static这个生命周期的标注,我也不是很理解
    //是指存储的错误生命周期足够长还是什么?
    fn source(&self) -> Option<&(dyn error::Error + "static)> {
        match self {
            Error::Io(err) => Some(err),
            Error::EnvVar(err) => Some(err),
            _ => None,
        }
    }
}
//实现错误的打印
impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::Io(err) => write!(fmt, "{}", err),
            Error::EnvVar(err) => write!(fmt, "{}", err),
            Error::LineParse(line, error_index) => write!(fmt, "Error parsing line: "{}", error at line index: {}", line, error_index),
            _ => unreachable!(),
        }
    }
}

更详细的rust错误处理,可以参见:https://zhuanlan.zhihu.com/p/109242831

命令行参数

在main方法中我们可以看到第二行,

let config = Config::from_args();

这是influx使用了structopt这个crate,调用该方法后,程序会根据结构体上的#[structopt()]中的参数进行执行命令行解析。

#[derive(Debug, StructOpt)]
#[structopt(
//cargo的crate名字
name = "influxdb_iox",
//打印出来介绍
about = "InfluxDB IOx server and command line tools",
long_about = // 省略 ...
)]
struct Config {
    // from_occurrences代表出现了几次,就是-vvv的时候v出现的次数
    #[structopt(short, long, parse(from_occurrences))]
    verbose: u64,
    #[structopt(
    short,
    long,
    global = true,
    env = "IOX_ADDR",
    default_value = "http://127.0.0.1:8082"
    )]
    host: String,
    #[structopt(long)]
    num_threads: Option,
    //subcommand代表是一个子类型的,
    //具体还有什么命令行要去子类型里继续解析,
    //这个字段不展示在命令行中
    #[structopt(subcommand)]
    command: Command,
}

//在influx的命令行中提供了8个主要的命令,
//在上一章中使用到的run参数就是属于Run(Box)里的调用。
//这里都是subcommand,需要继续解析,这个在以后学习每个具体功能的时候再分析
#[derive(Debug, StructOpt)]
enum Command {
    Convert { // 省略 ...},
    Meta {// 省略 ...},
    Database(commands::database::Config),
    Run(Box),
    Stats(commands::stats::Config),
    Server(commands::server::Config),
    Writer(commands::writer::Config),
    Operation(commands::operations::Config),
}

下面通过打印出来的例子来对应structopt中的内容。

$ ./influxdb_iox -vvvv run
Config { verbose: 4, host: "http://127.0.0.1:8082", num_threads: None, command: Run(Config { rust_log: None, log_format: None, verbose_count: 0, writer_id: None, http_bind_address: 127.0.0.1:8080, grpc_bind_address: 127.0.0.1:8082, database_directory: None, object_store: None, bucket: None, aws_access_key_id: None, aws_secret_access_key: None, aws_default_region: "us-east-1", google_service_account: None, azure_storage_account: None, azure_storage_access_key: None, jaeger_host: None }) }

可以看到,我们执行了Run这个变体的Subcommand,并且指定了Config结构体中的verbose 4 次,IOx也成功的识别了。

后面继续学习程序的启动过程,祝玩儿的开心!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯