diff --git a/README.md b/README.md index 1885ebd..db90ed3 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ See all options: A simple example: ```bash -./dbs-cli \ +./dbs-cli create\ --kernel-path ~/path/to/kernel/vmlinux.bin \ --rootfs ~/path/to/rootfs/rootfs.dmg \ - --boot-args "console=ttyS0 console=ttyS1 earlyprintk=ttyS1 tty0 reboot=k debug panic=1 pci=off root=/dev/vda1" create ; + --boot-args "console=ttyS0 tty0 reboot=k debug panic=1 pci=off root=/dev/vda1" ; ``` Set the log level and log file: @@ -26,11 +26,11 @@ Set the log level and log file: > The log-level argument is case-insensitive: ErrOR and InFO are valid. ```bash -./dbs-cli \ +./dbs-cli create\ --log-file dbs-cli.log --log-level ERROR \ --kernel-path ~/path/to/kernel/vmlinux.bin \ --rootfs ~/path/to/rootfs/bionic.rootfs.ext4 \ - --boot-args "console=ttyS0 console=ttyS1 earlyprintk=ttyS1 tty0 reboot=k debug panic=1 pci=off root=/dev/vda1" create ; + --boot-args "console=ttyS0 tty0 reboot=k debug panic=1 pci=off root=/dev/vda1"; ``` > tips: console=ttyS0 is used to connect to the guest console. If serial path is not defined, Dragonball will use stdio to interact with the guest. @@ -44,12 +44,12 @@ Create a vsock console (communication with sock file) > Otherwise, `dbs-cli` will create a vsock console with a sock file, namely the value of `serial-path`. ``` -./dbs-cli \ +./dbs-cli create\ --log-file dbs-cli.log --log-level ERROR \ --kernel-path ~/path/to/kernel/vmlinux.bin \ --rootfs ~/path/to/rootfs/bionic.rootfs.ext4 \ - --boot-args "console=ttyS0 console=ttyS1 earlyprintk=ttyS1 tty0 reboot=k debug panic=1 pci=off root=/dev/vda1" \ - --serial-path "/tmp/dbs" creare; + --boot-args "console=ttyS0 tty0 reboot=k debug panic=1 pci=off root=/dev/vda1" \ + --serial-path "/tmp/dbs"; ``` Create a virtio-vsock tunnel for Guest-to-Host communication. @@ -60,12 +60,12 @@ Create a virtio-vsock tunnel for Guest-to-Host communication. > specified with the `--vsock` parameter. ``` -./dbs-cli \ +./dbs-cli create\ --log-file dbs-cli.log --log-level ERROR \ --kernel-path ~/path/to/kernel/vmlinux.bin \ --rootfs ~/path/to/rootfs/bionic.rootfs.ext4 \ - --boot-args "console=ttyS0 console=ttyS1 earlyprintk=ttyS1 tty0 reboot=k debug panic=1 pci=off root=/dev/vda1" \ - --vsock /tmp/vsock.sock create; + --boot-args "console=ttyS0 tty0 reboot=k debug panic=1 pci=off root=/dev/vda1" \ + --vsock /tmp/vsock.sock; ``` Create virtio-net devices. @@ -74,13 +74,13 @@ Create virtio-net devices. > format of JSON. ``` -./dbs-cli \ +./dbs-cli create \ --log-file dbs-cli.log --log-level ERROR \ --kernel-path ~/path/to/kernel/vmlinux.bin \ --rootfs ~/path/to/rootfs/bionic.rootfs.ext4 \ --boot-args "console=ttyS0 console=ttyS1 earlyprintk=ttyS1 tty0 reboot=k debug panic=1 pci=off root=/dev/vda1" \ --virnets "[{\"iface_id\":\"eth0\",\"host_dev_name\":\"tap0\",\"num_queues\":2,\"queue_size\":0,\"guest_mac\":\"43:2D:9C:13:71:48\",\"allow_duplicate_mac\":true}]" \ - create; + ; ``` Create virtio-blk devices. @@ -89,13 +89,13 @@ Create virtio-blk devices. > format of JSON. ``` -./dbs-cli \ +./dbs-cli create\ --log-file dbs-cli.log --log-level ERROR \ --kernel-path ~/path/to/kernel/vmlinux.bin \ --rootfs ~/path/to/rootfs/bionic.rootfs.ext4 \ --boot-args "console=ttyS0 console=ttyS1 earlyprintk=ttyS1 tty0 reboot=k debug panic=1 pci=off root=/dev/vda1" \ --virblks '[{"drive_id":"testblk","device_type":"RawBlock","path_on_host":"/path/to/test.img","is_root_device":false,"is_read_only":false,"is_direct":false,"no_drop":false,"num_queues":1,"queue_size":1024}]' \ - create; + ; ``` # 2. Usage @@ -108,7 +108,7 @@ After api socket created, you could use `./dbs-cli --api-sock-path [socket path] Cpu Hotplug via API Server: -`sudo ./dbs-cli --api-sock-path [socket path] --vcpu-resize 2 update` +`sudo ./dbs-cli --api-sock-path [socket path] update --vcpu-resize 2 ` Create hot-plug virtio-net devices via API Server: @@ -117,9 +117,9 @@ Create hot-plug virtio-net devices via API Server: ``` sudo ./dbs-cli \ - --api-sock-path [socket path] + --api-sock-path [socket path] update --hotplug-virnets "[{\"iface_id\":\"eth0\",\"host_dev_name\":\"tap0\",\"num_queues\":2,\"queue_size\":0,\"guest_mac\":\"43:2D:9C:13:71:48\",\"allow_duplicate_mac\":true}]" \ - update + ``` Create hot-plug virtio-blk devices via API Server: @@ -129,11 +129,13 @@ Create hot-plug virtio-blk devices via API Server: ``` sudo ./dbs-cli \ - --api-sock-path [socket path] + --api-sock-path [socket path] update --hotplug-virblks '[{"drive_id":"testblk","device_type":"RawBlock","path_on_host":"/path/to/test.img","is_root_device":false,"is_read_only":false,"is_direct":false,"no_drop":false,"num_queues":1,"queue_size":1024}]' \ - update + ``` +TODO : add document for hot-plug virtio-fs + ## 2. Exit vm > If you want to exit vm, just input `reboot` in vm's console. diff --git a/src/api_client.rs b/src/api_client.rs index c047414..fc5cbd6 100644 --- a/src/api_client.rs +++ b/src/api_client.rs @@ -7,24 +7,24 @@ use std::os::unix::net::UnixStream; use anyhow::{Context, Result}; use serde_json::{json, Value}; -use crate::parser::DBSArgs; +use crate::parser::args::UpdateArgs; -pub fn run_api_client(args: DBSArgs) -> Result<()> { - if let Some(vcpu_resize_num) = args.update_args.vcpu_resize { +pub fn run_api_client(args: UpdateArgs, api_sock_path: &str) -> Result<()> { + if let Some(vcpu_resize_num) = args.vcpu_resize { let request = request_cpu_resize(vcpu_resize_num); - send_request(request, &args.api_sock_path)?; + send_request(request, api_sock_path)?; } - if let Some(config) = args.update_args.hotplug_virnets { + if let Some(config) = args.hotplug_virnets { let request = request_virtio_net(&config); - send_request(request, &args.api_sock_path)?; + send_request(request, api_sock_path)?; } - if let Some(config) = args.update_args.hotplug_virblks { + if let Some(config) = args.hotplug_virblks { let request = request_virtio_blk(&config); - send_request(request, &args.api_sock_path)?; + send_request(request, api_sock_path)?; } - if let Some(config) = args.update_args.patch_fs { + if let Some(config) = args.patch_fs { let request = request_patch_fs(&config); - send_request(request, &args.api_sock_path)?; + send_request(request, api_sock_path)?; } Ok(()) } diff --git a/src/api_server.rs b/src/api_server.rs index ad43fbe..609a8c3 100644 --- a/src/api_server.rs +++ b/src/api_server.rs @@ -3,24 +3,22 @@ // SPDX-License-Identifier: Apache-2.0 // -use std::sync::{Arc, Mutex}; - use std::io::prelude::*; use std::os::unix::net::{UnixListener, UnixStream}; +use std::sync::{Arc, Mutex}; use anyhow::{anyhow, Context, Result}; +use crossbeam_channel::{Receiver, Sender}; +use dragonball::api::v1::{VmmRequest, VmmResponse}; use dragonball::device_manager::blk_dev_mgr::BlockDeviceConfigInfo; use dragonball::device_manager::fs_dev_mgr::FsMountConfigInfo; use dragonball::device_manager::virtio_net_dev_mgr::VirtioNetDeviceConfigInfo; - -use crate::vmm_comm_trait::VMMComm; -use dragonball::api::v1::{VmmRequest, VmmResponse}; use dragonball::vcpu::VcpuResizeInfo; use serde_json::Value; - -use crossbeam_channel::{Receiver, Sender}; use vmm_sys_util::eventfd::EventFd; +use crate::vmm_comm_trait::VMMComm; + pub struct ApiServer { pub to_vmm: Option>, pub from_vmm: Option>>>, @@ -53,9 +51,9 @@ impl ApiServer { } } - pub fn run_api_server(&mut self, api_sock_path: &str) -> Result<()> { + pub fn run_api_server(&mut self, api_sock_path: String) -> Result<()> { + println!("dbs-cli: api server created in api_sock_path {:?}. Start waiting for connections from the client side.", &api_sock_path); let unix_listener = UnixListener::bind(api_sock_path)?; - println!("dbs-cli: api server created in api_sock_path {api_sock_path:?}. Start waiting for connections from the client side."); // put the server logic in a loop to accept several connections loop { diff --git a/src/cli_instance.rs b/src/cli_instance.rs index 0246318..b00341c 100644 --- a/src/cli_instance.rs +++ b/src/cli_instance.rs @@ -9,7 +9,7 @@ use std::{ sync::{Arc, Mutex, RwLock}, }; -use crate::vmm_comm_trait::VMMComm; +use crate::{parser::args::CreateArgs, vmm_comm_trait::VMMComm}; use anyhow::{anyhow, Result}; use crossbeam_channel::{Receiver, Sender}; use seccompiler::BpfProgram; @@ -25,8 +25,6 @@ use dragonball::{ vm::{CpuTopology, VmConfigInfo}, }; -use crate::parser::DBSArgs; - const DRAGONBALL_VERSION: &str = env!("CARGO_PKG_VERSION"); pub struct CliInstance { @@ -70,33 +68,33 @@ impl CliInstance { } } - pub fn run_vmm_server(&self, args: DBSArgs) -> Result<()> { - if args.boot_args.kernel_path.is_none() || args.boot_args.rootfs_args.rootfs.is_none() { + pub fn run_vmm_server(&self, args: CreateArgs) -> Result<()> { + if args.kernel_path.is_none() || args.rootfs_args.rootfs.is_none() { return Err(anyhow!( "kernel path or rootfs path cannot be None when creating the VM" )); } let mut serial_path: Option = None; - if args.create_args.serial_path != "stdio" { - serial_path = Some(args.create_args.serial_path); + if args.serial_path != "stdio" { + serial_path = Some(args.serial_path); } // configuration let vm_config = VmConfigInfo { - vcpu_count: args.create_args.vcpu, - max_vcpu_count: args.create_args.max_vcpu, - cpu_pm: args.create_args.cpu_pm.clone(), + vcpu_count: args.cpu.vcpu, + max_vcpu_count: args.cpu.max_vcpu, + cpu_pm: args.cpu.cpu_pm.clone(), cpu_topology: CpuTopology { - threads_per_core: args.create_args.cpu_topology.threads_per_core, - cores_per_die: args.create_args.cpu_topology.cores_per_die, - dies_per_socket: args.create_args.cpu_topology.dies_per_socket, - sockets: args.create_args.cpu_topology.sockets, + threads_per_core: args.cpu.cpu_topology.threads_per_core, + cores_per_die: args.cpu.cpu_topology.cores_per_die, + dies_per_socket: args.cpu.cpu_topology.dies_per_socket, + sockets: args.cpu.cpu_topology.sockets, }, - vpmu_feature: args.create_args.vpmu_feature, - mem_type: args.create_args.mem_type.clone(), - mem_file_path: args.create_args.mem_file_path.clone(), - mem_size_mib: args.create_args.mem_size, + vpmu_feature: args.cpu.vpmu_feature, + mem_type: args.mem.mem_type.clone(), + mem_file_path: args.mem.mem_file_path.clone(), + mem_size_mib: args.mem.mem_size, // as in crate `dragonball` serial_path will be assigned with a default value, // we need a special token to enable the stdio console. serial_path: serial_path.clone(), @@ -114,9 +112,9 @@ impl CliInstance { // boot source let boot_source_config = BootSourceConfig { // unwrap is safe because we have checked kernel_path in the beginning of run_vmm_server - kernel_path: args.boot_args.kernel_path.unwrap(), - initrd_path: args.boot_args.initrd_path.clone(), - boot_args: Some(args.boot_args.boot_args.clone()), + kernel_path: args.kernel_path.unwrap(), + initrd_path: args.initrd_path.clone(), + boot_args: Some(args.boot_args.clone()), }; // rootfs @@ -124,9 +122,9 @@ impl CliInstance { block_device_config_info = BlockDeviceConfigInfo { drive_id: String::from("rootfs"), // unwrap is safe because we have checked rootfs path in the beginning of run_vmm_server - path_on_host: PathBuf::from(&args.boot_args.rootfs_args.rootfs.unwrap()), - is_root_device: args.boot_args.rootfs_args.is_root, - is_read_only: args.boot_args.rootfs_args.is_read_only, + path_on_host: PathBuf::from(&args.rootfs_args.rootfs.unwrap()), + is_root_device: args.rootfs_args.is_root, + is_read_only: args.rootfs_args.is_read_only, ..block_device_config_info }; @@ -142,12 +140,12 @@ impl CliInstance { self.insert_block_device(block_device_config_info) .expect("failed to set block device"); - if !args.create_args.vsock.is_empty() { + if !args.vsock.is_empty() { // VSOCK config let mut vsock_config_info = VsockDeviceConfigInfo::default(); vsock_config_info = VsockDeviceConfigInfo { guest_cid: 42, // dummy value - uds_path: Some(args.create_args.vsock), + uds_path: Some(args.vsock), ..vsock_config_info }; @@ -156,28 +154,26 @@ impl CliInstance { .expect("failed to set vsock socket path"); } - if !args.create_args.virnets.is_empty() { - let configs: Vec = - serde_json::from_str(&args.create_args.virnets) - .expect("failed to parse virtio-net devices from JSON"); + if !args.virnets.is_empty() { + let configs: Vec = serde_json::from_str(&args.virnets) + .expect("failed to parse virtio-net devices from JSON"); for config in configs.into_iter() { self.insert_virnet(config) .expect("failed to insert a virtio-net device"); } } - if !args.create_args.virblks.is_empty() { - let configs: Vec = - serde_json::from_str(&args.create_args.virblks) - .expect("failed to parse virtio-blk devices from JSON"); + if !args.virblks.is_empty() { + let configs: Vec = serde_json::from_str(&args.virblks) + .expect("failed to parse virtio-blk devices from JSON"); for config in configs.into_iter() { self.insert_virblk(config) .expect("failed to insert a virtio-blk device"); } } - if !args.create_args.fs.is_empty() { - let fs_config: FsDeviceConfigInfo = serde_json::from_str(&args.create_args.fs) + if !args.fs.is_empty() { + let fs_config: FsDeviceConfigInfo = serde_json::from_str(&args.fs) .expect("failed to parse virtio-fs devices from JSON"); self.insert_fs(fs_config) .expect("failed to insert a virtio-fs device"); diff --git a/src/main.rs b/src/main.rs index 61a185c..dafa520 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,52 +2,29 @@ extern crate slog_term; -use std::str::FromStr; -use std::sync::Mutex; - use anyhow::Result; use api_client::run_api_client; use clap::Parser; -use slog::Drain; -use slog::*; -use slog_scope::set_global_logger; - use parser::run_with_cli; -use parser::Commands; -use parser::DBSArgs; + +use crate::parser::args::{Commands, DBSArgs}; mod api_client; mod api_server; mod cli_instance; mod parser; +mod utils; mod vmm_comm_trait; fn main() -> Result<()> { let args: DBSArgs = DBSArgs::parse(); match args.command { - Some(Commands::Create) => { - let log_file = &args.log_file; - let log_level = Level::from_str(&args.log_level).unwrap(); - - let file = std::fs::OpenOptions::new() - .truncate(true) - .read(true) - .create(true) - .write(true) - .open(log_file) - .expect("Cannot write to the log file."); - - let root = slog::Logger::root( - Mutex::new(slog_json::Json::default(file).filter_level(log_level)).map(slog::Fuse), - o!("version" => env!("CARGO_PKG_VERSION")), - ); - - let _guard = set_global_logger(root); - slog_stdlog::init().unwrap(); - run_with_cli(args)?; + Some(Commands::Create { create_args }) => { + utils::setup_db_log(&create_args.log_file, &create_args.log_level); + run_with_cli(create_args, &args.api_sock_path)?; } - Some(Commands::Update) => { - run_api_client(args)?; + Some(Commands::Update { update_args }) => { + run_api_client(update_args, &args.api_sock_path)?; } _ => { panic!("Invalid command provided for dbs-cli."); diff --git a/src/parser/args.rs b/src/parser/args.rs index 3aee9de..05f644d 100644 --- a/src/parser/args.rs +++ b/src/parser/args.rs @@ -12,18 +12,6 @@ pub struct DBSArgs { #[clap(subcommand)] pub command: Option, - #[clap(flatten)] - pub create_args: CreateArgs, - - #[clap(flatten)] - pub boot_args: BootArgs, - - #[clap(long, value_parser, default_value = "dbs-cli.log", display_order = 1)] - pub log_file: String, - - #[clap(long, value_parser, default_value = "Info", display_order = 1)] - pub log_level: String, - #[clap( long, value_parser, @@ -32,20 +20,25 @@ pub struct DBSArgs { display_order = 2 )] pub api_sock_path: String, - - #[clap(flatten)] - pub update_args: UpdateArgs, } +#[allow(clippy::large_enum_variant)] #[derive(Subcommand, Debug, Clone)] pub enum Commands { /// Create Dragonball Instance - Create, + Create { + // create args are for setting up Dragonball CPU/ + #[clap(flatten)] + create_args: CreateArgs, + }, /// Connect to Dragonball Api Server and update the Dragonball VM (Must create a api socket when creating the Dragonball VM) - Update, + Update { + #[clap(flatten)] + update_args: UpdateArgs, + }, } -/// CPU related configurations +/// CPU topology related configurations #[derive(Args, Debug, Serialize, Deserialize, Clone)] pub struct CpuTopologyArgs { #[clap( @@ -120,68 +113,12 @@ pub struct RootfsArgs { #[derive(Args, Debug, Deserialize, Serialize, Clone)] pub struct CreateArgs { /// features of cpu - #[clap( - short = 'C', - long, - value_parser, - default_value_t = 1, - help = "The number of vcpu to start", - display_order = 1 - )] - pub vcpu: u8, - #[clap( - long, - value_parser, - default_value_t = 1, - help = "The max number of vpu can be added", - display_order = 1 - )] - pub max_vcpu: u8, - #[clap( - long, - value_parser, - default_value = "on", - help = "The cpu power management", - display_order = 1 - )] - pub cpu_pm: String, - #[clap( - long, - value_parser, - default_value_t = 0, - help = "vpmu support level", - display_order = 1 - )] - pub vpmu_feature: u8, #[clap(flatten)] - pub cpu_topology: CpuTopologyArgs, + pub cpu: CpuArgs, /// features of mem - #[clap( - long, - value_parser, - default_value = "shmem", - help = "Memory type that can be either hugetlbfs or shmem, default is shmem", - display_order = 2 - )] - pub mem_type: String, - #[clap( - long, - value_parser, - default_value = "", - help = "Memory file path", - display_order = 2 - )] - pub mem_file_path: String, - #[clap( - short, - long, - value_parser, - default_value_t = 128, - help = "The memory size in Mib", - display_order = 2 - )] - pub mem_size: usize, + #[clap(flatten)] + pub mem: MemArgs, // The serial path used to communicate with VM #[clap( @@ -237,12 +174,14 @@ The type of it is an array of BlockDeviceConfigInfo, e.g. display_order = 2 )] pub fs: String, -} -/// Config boot source including rootfs file path -#[derive(Args, Debug, Deserialize, Serialize, Clone)] -#[clap(arg_required_else_help = true)] -pub struct BootArgs { + // feature for log + #[clap(long, value_parser, default_value = "dbs-cli.log", display_order = 1)] + pub log_file: String, + + #[clap(long, value_parser, default_value = "Info", display_order = 1)] + pub log_level: String, + #[clap( short, long, @@ -277,6 +216,74 @@ pub struct BootArgs { pub rootfs_args: RootfsArgs, } +#[derive(Args, Debug, Serialize, Deserialize, Clone)] +pub struct CpuArgs { + #[clap( + short = 'C', + long, + value_parser, + default_value_t = 1, + help = "The number of vcpu to start", + display_order = 1 + )] + pub vcpu: u8, + #[clap( + long, + value_parser, + default_value_t = 1, + help = "The max number of vpu can be added", + display_order = 1 + )] + pub max_vcpu: u8, + #[clap( + long, + value_parser, + default_value = "on", + help = "The cpu power management", + display_order = 1 + )] + pub cpu_pm: String, + #[clap( + long, + value_parser, + default_value_t = 0, + help = "vpmu support level", + display_order = 1 + )] + pub vpmu_feature: u8, + #[clap(flatten)] + pub cpu_topology: CpuTopologyArgs, +} + +#[derive(Args, Debug, Serialize, Deserialize, Clone)] +pub struct MemArgs { + #[clap( + long, + value_parser, + default_value = "shmem", + help = "Memory type that can be either hugetlbfs or shmem, default is shmem", + display_order = 2 + )] + pub mem_type: String, + #[clap( + long, + value_parser, + default_value = "", + help = "Memory file path", + display_order = 2 + )] + pub mem_file_path: String, + #[clap( + short, + long, + value_parser, + default_value_t = 128, + help = "The memory size in Mib", + display_order = 2 + )] + pub mem_size: usize, +} + #[derive(Args, Debug, Serialize, Deserialize, Clone)] pub struct UpdateArgs { #[clap( diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d7d2ff6..83e5a3f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10,20 +10,18 @@ use std::{ }; use anyhow::Result; - -pub use args::Commands; -pub use args::DBSArgs; use crossbeam_channel::unbounded; use dragonball::{api::v1::VmmService, Vmm}; use crate::api_server::ApiServer; use crate::cli_instance::CliInstance; +use crate::parser::args::CreateArgs; pub mod args; const KVM_DEVICE: &str = "/dev/kvm"; -pub fn run_with_cli(args: DBSArgs) -> Result { +pub fn run_with_cli(create_args: CreateArgs, api_sock_path: &String) -> Result { let mut cli_instance = CliInstance::new("dbs-cli"); let kvm = OpenOptions::new().read(true).write(true).open(KVM_DEVICE)?; @@ -61,7 +59,7 @@ pub fn run_with_cli(args: DBSArgs) -> Result { ); // clone the arguments for other thread to use - let clone_args = args.clone(); + let clone_args = create_args.clone(); thread::Builder::new() .name("set_cfg".to_owned()) .spawn(move || { @@ -71,15 +69,18 @@ pub fn run_with_cli(args: DBSArgs) -> Result { }) .unwrap(); - if !args.api_sock_path.is_empty() { + if !api_sock_path.is_empty() { + let clone_api_sock_path = api_sock_path.to_string().clone(); thread::Builder::new() .name("api_server".to_owned()) .spawn(move || { api_server - .run_api_server(&args.api_sock_path) + .run_api_server(clone_api_sock_path) .expect("Failed to api server."); }) .unwrap(); + } else { + println!("Warning: api server is not created because --api-sock-path is not provided when creating VM. Update command is not supported."); } Ok(Vmm::run_vmm_event_loop( diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..b8e4f72 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,27 @@ +use slog::Drain; +use slog::*; +use slog_scope::set_global_logger; + +use std::str::FromStr; +use std::sync::Mutex; + +pub fn setup_db_log(log_file_path: &String, log_level: &str) { + let log_level = Level::from_str(log_level).unwrap(); + + let file = std::fs::OpenOptions::new() + .truncate(true) + .read(true) + .create(true) + .write(true) + .open(log_file_path) + .expect("Cannot write to the log file."); + + let root = slog::Logger::root( + Mutex::new(slog_json::Json::default(file).filter_level(log_level)).map(slog::Fuse), + o!("version" => env!("CARGO_PKG_VERSION")), + ); + + let guard = set_global_logger(root); + guard.cancel_reset(); + slog_stdlog::init().unwrap(); +}