refactor output system (#74)

This commit is contained in:
Clément DOUIN 2021-04-09 00:15:16 +02:00
parent ebf1b854be
commit f26051685c
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
9 changed files with 325 additions and 92 deletions

View file

@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Wiki entry for Gmail users [#58]
- Info logs for copy/move/delete cmd + silent mode [#74]
### Changed
- Refactor output system + log levels [#74]
## [0.2.3] - 2021-04-08
### Added

65
Cargo.lock generated
View file

@ -30,6 +30,17 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.0.1"
@ -194,6 +205,19 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "env_logger"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "error-chain"
version = "0.12.4"
@ -271,14 +295,25 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
name = "hermit-abi"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
dependencies = [
"libc",
]
[[package]]
name = "himalaya"
version = "0.2.3"
dependencies = [
"clap",
"env_logger",
"error-chain",
"imap",
"lettre",
"log",
"mailparse",
"native-tls",
"rfc2047-decoder",
@ -325,6 +360,12 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyperx"
version = "1.3.0"
@ -475,11 +516,11 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.11"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 0.1.10",
"cfg-if 1.0.0",
]
[[package]]
@ -1033,6 +1074,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.1.15"
@ -1197,6 +1247,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View file

@ -7,9 +7,11 @@ edition = "2018"
[dependencies]
clap = {version = "2.33.3", default-features = false, features = ["suggestions"]}
env_logger = "0.8.3"
error-chain = "0.12.4"
imap = "2.4.0"
lettre = "0.10.0-beta.3"
log = "0.4.14"
mailparse = "0.13.1"
native-tls = "0.2"
rfc2047-decoder = "0.1.2"

View file

@ -87,17 +87,18 @@ soywod <clement.douin@posteo.net>
📫 Minimalist CLI email client
USAGE:
himalaya [FLAGS] [OPTIONS] [SUBCOMMAND]
himalaya [OPTIONS] [SUBCOMMAND]
FLAGS:
-h, --help Prints help information
-s, --silent Disables any output
-V, --version Prints version information
OPTIONS:
-a, --account <STRING> Selects a specific account
-l, --log <LEVEL> Defines the logs level [default: info] [possible values: error, warn, info, debug,
trace]
-m, --mailbox <MAILBOX> Selects a specific mailbox [default: INBOX]
-o, --output <STRING> Defines the output format [default: plain] [possible values: plain, json]
-o, --output <FMT> Defines the output format [default: plain] [possible values: plain, json]
SUBCOMMANDS:
attachments Downloads all message attachments

View file

@ -7,6 +7,8 @@ mod config {
}
mod output {
pub(crate) mod cli;
pub(crate) mod fmt;
pub(crate) mod log;
pub(crate) mod utils;
}
mod imap {
@ -28,6 +30,7 @@ mod mbox {
use clap;
use error_chain::error_chain;
use log::{debug, error};
use std::env;
use crate::{
@ -36,7 +39,11 @@ use crate::{
imap::cli::{imap_matches, imap_subcmds},
mbox::cli::{mbox_matches, mbox_source_arg, mbox_subcmds},
msg::cli::{msg_matches, msg_subcmds},
output::cli::output_args,
output::{
cli::output_args,
fmt::OutputFmt,
log::{init as init_logger, LogLevel},
},
};
error_chain! {
@ -45,6 +52,7 @@ error_chain! {
ImapCli(crate::imap::cli::Error, crate::imap::cli::ErrorKind);
MboxCli(crate::mbox::cli::Error, crate::mbox::cli::ErrorKind);
MsgCli(crate::msg::cli::Error, crate::msg::cli::ErrorKind);
OutputLog(crate::output::log::Error, crate::output::log::ErrorKind);
}
}
@ -62,6 +70,14 @@ fn run() -> Result<()> {
.subcommands(msg_subcmds())
.get_matches();
let output_fmt: OutputFmt = matches.value_of("output").unwrap().into();
let log_level: LogLevel = matches.value_of("log").unwrap().into();
init_logger(&output_fmt, &log_level)?;
debug!("Logger initialized");
debug!("Output format: {}", &output_fmt);
debug!("Log level: {}", &log_level);
loop {
if mbox_matches(&matches)? {
break;
@ -88,8 +104,8 @@ fn main() {
match errs.next() {
None => (),
Some(err) => {
eprintln!("{}", err);
errs.for_each(|err| eprintln!("{}", err));
error!("{}", err);
errs.for_each(|err| error!("{}", err));
}
}
}

View file

@ -1,5 +1,6 @@
use clap::{self, App, Arg, ArgMatches, SubCommand};
use error_chain::error_chain;
use log::{debug, error, info};
use std::{fs, ops::Deref};
use crate::{
@ -9,7 +10,6 @@ use crate::{
input,
mbox::cli::mbox_target_arg,
msg::model::{Attachments, Msg, Msgs, ReadableMsg},
output::utils::{print, Info},
smtp,
};
@ -19,7 +19,6 @@ error_chain! {
Imap(crate::imap::model::Error, crate::imap::model::ErrorKind);
Input(crate::input::Error, crate::input::ErrorKind);
MsgModel(crate::msg::model::Error, crate::msg::model::ErrorKind);
OutputUtils(crate::output::utils::Error, crate::output::utils::ErrorKind);
Smtp(crate::smtp::Error, crate::smtp::ErrorKind);
}
}
@ -162,25 +161,33 @@ pub fn msg_subcmds<'a>() -> Vec<App<'a, 'a>> {
pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
let config = Config::new_from_file()?;
let account = config.find_account_by_name(matches.value_of("account"))?;
let output_fmt = matches.value_of("output").unwrap();
let silent = matches.is_present("silent");
let mbox = matches.value_of("mailbox").unwrap();
if let Some(matches) = matches.subcommand_matches("list") {
debug!("Subcommand matched: list");
let mut imap_conn = ImapConnector::new(&account)?;
let page_size: usize = matches.value_of("page-size").unwrap().parse().unwrap();
debug!("Page size: {}", &page_size);
let page: usize = matches.value_of("page").unwrap().parse().unwrap();
debug!("Page: {}", &page);
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
let msgs = Msgs::from(&msgs);
print(&output_fmt, &silent, msgs)?;
info!("{}", msgs);
imap_conn.logout();
return Ok(());
}
if let Some(matches) = matches.subcommand_matches("search") {
debug!("Subcommand matched: search");
let mut imap_conn = ImapConnector::new(&account)?;
let page_size: usize = matches.value_of("page-size").unwrap().parse().unwrap();
debug!("Page size: {}", &page_size);
let page: usize = matches.value_of("page").unwrap().parse().unwrap();
debug!("Page: {}", &page);
let query = matches
.values_of("query")
.unwrap_or_default()
@ -205,62 +212,61 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
})
.1
.join(" ");
debug!("Query: {}", &query);
let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?;
let msgs = Msgs::from(&msgs);
print(&output_fmt, &silent, msgs)?;
info!("{}", msgs);
imap_conn.logout();
return Ok(());
}
if let Some(matches) = matches.subcommand_matches("read") {
debug!("Subcommand matched: read");
let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let mime = format!("text/{}", matches.value_of("mime-type").unwrap());
debug!("MIME: {}", &mime);
let msg = imap_conn.read_msg(&mbox, &uid)?;
let msg = ReadableMsg::from_bytes(&mime, &msg)?;
print(&output_fmt, &silent, msg)?;
info!("{}", msg);
imap_conn.logout();
return Ok(());
}
if let Some(matches) = matches.subcommand_matches("attachments") {
debug!("Subcommand matched: attachments");
let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let msg = imap_conn.read_msg(&mbox, &uid)?;
let attachments = Attachments::from_bytes(&msg)?;
match output_fmt {
"plain" => {
println!(
debug!(
"{} attachment(s) found for message {}",
attachments.0.len(),
uid
);
attachments.0.iter().for_each(|attachment| {
let filepath = config.downloads_filepath(&account, &attachment.filename);
println!("Downloading {}", &attachment.filename);
debug!("Downloading {}", &attachment.filename);
fs::write(filepath, &attachment.raw).unwrap()
});
info!("{} attachment(s) successfully downloaded", &uid);
println!("Done!");
}
"json" => {
attachments.0.iter().for_each(|attachment| {
let filepath = config.downloads_filepath(&account, &attachment.filename);
fs::write(filepath, &attachment.raw).unwrap()
});
print!("{{}}");
}
_ => (),
}
imap_conn.logout();
return Ok(());
}
if let Some(matches) = matches.subcommand_matches("write") {
debug!("Subcommand matched: write");
let mut imap_conn = ImapConnector::new(&account)?;
let attachments = matches
.values_of("attachments")
@ -276,17 +282,17 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
match input::post_edit_choice() {
Ok(choice) => match choice {
input::Choice::Send => {
println!("Sending");
debug!("Sending message");
let msg = msg.to_sendable_msg()?;
smtp::send(&account, &msg)?;
imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?;
println!("Done!");
info!("Message successfully sent");
break;
}
input::Choice::Draft => {
println!("Saving to draft…");
debug!("Saving to draft…");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
println!("Done!");
info!("Message successfully saved to Drafts");
break;
}
input::Choice::Edit => {
@ -295,7 +301,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
}
input::Choice::Quit => break,
},
Err(err) => eprintln!("{}", err),
Err(err) => error!("{}", err),
}
}
imap_conn.logout();
@ -303,30 +309,44 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
}
if let Some(matches) = matches.subcommand_matches("template") {
debug!("Subcommand matched: template");
if let Some(_) = matches.subcommand_matches("new") {
debug!("Subcommand matched: new");
let tpl = Msg::build_new_tpl(&config, &account)?;
print(&output_fmt, &silent, &tpl)?;
info!("{}", tpl);
}
if let Some(matches) = matches.subcommand_matches("reply") {
debug!("Subcommand matched: reply");
let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
let tpl = if matches.is_present("reply-all") {
msg.build_reply_all_tpl(&config, &account)?
} else {
msg.build_reply_tpl(&config, &account)?
};
print(&output_fmt, &silent, &tpl)?;
info!("{}", tpl);
imap_conn.logout();
}
if let Some(matches) = matches.subcommand_matches("forward") {
debug!("Subcommand matched: forward");
let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
let tpl = msg.build_forward_tpl(&config, &account)?;
print(&output_fmt, &silent, &tpl)?;
info!("{}", tpl);
imap_conn.logout();
}
@ -334,6 +354,8 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
}
if let Some(matches) = matches.subcommand_matches("reply") {
debug!("Subcommand matched: reply");
let mut imap_conn = ImapConnector::new(&account)?;
let attachments = matches
.values_of("attachments")
@ -341,6 +363,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
.map(String::from)
.collect::<Vec<_>>();
let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
let tpl = if matches.is_present("reply-all") {
@ -357,17 +380,17 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
match input::post_edit_choice() {
Ok(choice) => match choice {
input::Choice::Send => {
println!("Sending");
debug!("Sending message");
smtp::send(&account, &msg.to_sendable_msg()?)?;
imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?;
imap_conn.add_flags(mbox, uid, "\\Answered")?;
println!("Done!");
info!("Message successfully sent");
break;
}
input::Choice::Draft => {
println!("Saving to draft");
debug!("Saving draft message");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
println!("Done!");
info!("Message successfully saved to Drafts");
break;
}
input::Choice::Edit => {
@ -376,7 +399,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
}
input::Choice::Quit => break,
},
Err(err) => eprintln!("{}", err),
Err(err) => error!("{}", err),
}
}
@ -385,6 +408,8 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
}
if let Some(matches) = matches.subcommand_matches("forward") {
debug!("Subcommand matched: forward");
let mut imap_conn = ImapConnector::new(&account)?;
let attachments = matches
.values_of("attachments")
@ -392,6 +417,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
.map(String::from)
.collect::<Vec<_>>();
let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
let tpl = msg.build_forward_tpl(&config, &account)?;
@ -403,16 +429,16 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
match input::post_edit_choice() {
Ok(choice) => match choice {
input::Choice::Send => {
println!("Sending");
debug!("Sending message");
smtp::send(&account, &msg.to_sendable_msg()?)?;
imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?;
println!("Done!");
info!("Message successfully sent");
break;
}
input::Choice::Draft => {
println!("Saving to draft");
debug!("Saving draft message");
imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?;
println!("Done!");
info!("Message successfully saved to Drafts");
break;
}
input::Choice::Edit => {
@ -421,7 +447,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
}
input::Choice::Quit => break,
},
Err(err) => eprintln!("{}", err),
Err(err) => error!("{}", err),
}
}
@ -430,63 +456,67 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
}
if let Some(matches) = matches.subcommand_matches("copy") {
debug!("Subcommand matched: copy");
let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let target = matches.value_of("target").unwrap();
debug!("Target: {}", &target);
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
let mut flags = msg.flags.deref().to_vec();
flags.push(Flag::Seen);
imap_conn.append_msg(target, &msg.raw, &flags)?;
imap_conn.logout();
print(
&output_fmt,
&silent,
Info(format!(
info!(
"Message {} successfully copied to folder `{}`",
&uid, &target
)),
)?;
);
imap_conn.logout();
return Ok(());
}
if let Some(matches) = matches.subcommand_matches("move") {
debug!("Subcommand matched: move");
let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap();
debug!("UID: {}", &uid);
let target = matches.value_of("target").unwrap();
debug!("Target: {}", &target);
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
let mut flags = msg.flags.deref().to_vec();
flags.push(Flag::Seen);
imap_conn.append_msg(target, &msg.raw, msg.flags.deref())?;
imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?;
imap_conn.logout();
print(
&output_fmt,
&silent,
Info(format!(
info!(
"Message {} successfully moved to folder `{}`",
&uid, &target
)),
)?;
);
imap_conn.logout();
return Ok(());
}
if let Some(matches) = matches.subcommand_matches("delete") {
debug!("Subcommand matched: delete");
let mut imap_conn = ImapConnector::new(&account)?;
let uid = matches.value_of("uid").unwrap();
imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?;
imap_conn.logout();
debug!("UID: {}", &uid);
print(
&output_fmt,
&silent,
Info(format!("Message {} successfully deleted", &uid)),
)?;
imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?;
info!("Message {} successfully deleted", &uid);
imap_conn.logout();
return Ok(());
}
if let Some(matches) = matches.subcommand_matches("send") {
debug!("Subcommand matched: send");
let mut imap_conn = ImapConnector::new(&account)?;
let msg = matches.value_of("message").unwrap();
let msg = Msg::from(msg.to_string());
@ -498,6 +528,8 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
}
if let Some(matches) = matches.subcommand_matches("save") {
debug!("Subcommand matched: save");
let mut imap_conn = ImapConnector::new(&account)?;
let msg = matches.value_of("message").unwrap();
let msg = Msg::from(msg.to_string());
@ -506,11 +538,13 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
return Ok(());
}
// Default case: list first page messages
debug!("Default subcommand matched: list");
let mut imap_conn = ImapConnector::new(&account)?;
let msgs = imap_conn.list_msgs(&mbox, &10, &0)?;
let msgs = Msgs::from(&msgs);
print(&output_fmt, &silent, msgs)?;
info!("{}", &msgs);
imap_conn.logout();
Ok(())
}

View file

@ -6,12 +6,15 @@ pub fn output_args<'a>() -> Vec<Arg<'a, 'a>> {
.long("output")
.short("o")
.help("Defines the output format")
.value_name("STRING")
.value_name("FMT")
.possible_values(&["plain", "json"])
.default_value("plain"),
Arg::with_name("silent")
.long("silent")
.short("s")
.help("Disables any output"),
Arg::with_name("log")
.long("log")
.short("l")
.help("Defines the logs level")
.value_name("LEVEL")
.possible_values(&["error", "warn", "info", "debug", "trace"])
.default_value("info"),
]
}

28
src/output/fmt.rs Normal file
View file

@ -0,0 +1,28 @@
use std::fmt;
pub enum OutputFmt {
Json,
Plain,
}
impl From<&str> for OutputFmt {
fn from(s: &str) -> Self {
match s {
"json" => Self::Json,
"plain" | _ => Self::Plain,
}
}
}
impl fmt::Display for OutputFmt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
OutputFmt::Json => "JSON",
OutputFmt::Plain => "PLAIN",
},
)
}
}

86
src/output/log.rs Normal file
View file

@ -0,0 +1,86 @@
use error_chain::error_chain;
use log::{Level, LevelFilter, Metadata, Record};
use std::fmt;
use super::fmt::OutputFmt;
error_chain! {}
// Log level struct
pub struct LogLevel(pub LevelFilter);
impl From<&str> for LogLevel {
fn from(s: &str) -> Self {
match s {
"error" => Self(LevelFilter::Error),
"warn" => Self(LevelFilter::Warn),
"debug" => Self(LevelFilter::Debug),
"trace" => Self(LevelFilter::Trace),
"info" | _ => Self(LevelFilter::Info),
}
}
}
impl fmt::Display for LogLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.to_string())
}
}
// Plain logger
struct PlainLogger;
impl log::Log for PlainLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= Level::Trace
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
if let Level::Error = record.level() {
eprintln!("{}", record.args());
} else {
println!("{}", record.args());
}
}
}
fn flush(&self) {}
}
// JSON logger
struct JsonLogger;
impl log::Log for JsonLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
[Level::Error, Level::Info].contains(&metadata.level())
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
if let Level::Error = record.level() {
eprintln!("{}", record.args());
} else {
// Should be safe enough to `.unwrap()` since it's
// formatted by Himalaya itself
print!("{}", serde_json::to_string(record.args()).unwrap());
}
}
}
fn flush(&self) {}
}
// Init
pub fn init(fmt: &OutputFmt, level: &LogLevel) -> Result<()> {
log::set_boxed_logger(match fmt {
&OutputFmt::Json => Box::new(JsonLogger),
&OutputFmt::Plain => Box::new(PlainLogger),
})
.map(|()| log::set_max_level(level.0))
.chain_err(|| "Could not init logger")
}