implements error log reporting (#464)

* implements error log reporting

* changes api post variables, includes warnings
This commit is contained in:
Keagan McClelland
2021-09-13 11:49:12 -06:00
committed by GitHub
parent dc7461746c
commit 1808c085e8
10 changed files with 175 additions and 15 deletions

23
appmgr/Cargo.lock generated
View File

@@ -848,6 +848,7 @@ dependencies = [
"sha2", "sha2",
"simple-logging", "simple-logging",
"sqlx", "sqlx",
"stderrlog",
"tar", "tar",
"thiserror", "thiserror",
"tokio 1.11.0", "tokio 1.11.0",
@@ -2958,6 +2959,19 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stderrlog"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45a53e2eff3e94a019afa6265e8ee04cb05b9d33fe9f5078b14e4e391d155a38"
dependencies = [
"atty",
"chrono",
"log",
"termcolor",
"thread_local",
]
[[package]] [[package]]
name = "stdweb" name = "stdweb"
version = "0.4.20" version = "0.4.20"
@@ -3183,6 +3197,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.44" version = "0.1.44"

View File

@@ -104,6 +104,7 @@ sqlx = { version = "0.5", features = [
"runtime-tokio-rustls", "runtime-tokio-rustls",
"sqlite", "sqlite",
] } ] }
stderrlog = "0.5.1"
tar = "0.4.35" tar = "0.4.35"
thiserror = "1.0.24" thiserror = "1.0.24"
tokio = { version = "1.11.0", features = ["full"] } tokio = { version = "1.11.0", features = ["full"] }

View File

@@ -13,6 +13,7 @@ use embassy::status::{check_all, synchronize_all};
use embassy::util::daemon; use embassy::util::daemon;
use embassy::{Error, ErrorKind, ResultExt}; use embassy::{Error, ErrorKind, ResultExt};
use futures::{FutureExt, TryFutureExt}; use futures::{FutureExt, TryFutureExt};
use log::LevelFilter;
use patch_db::json_ptr::JsonPointer; use patch_db::json_ptr::JsonPointer;
use reqwest::{Client, Proxy}; use reqwest::{Client, Proxy};
use rpc_toolkit::hyper::{Body, Response, Server, StatusCode}; use rpc_toolkit::hyper::{Body, Response, Server, StatusCode};
@@ -31,8 +32,11 @@ fn err_to_500(e: Error) -> Response<Body> {
.unwrap() .unwrap()
} }
async fn inner_main(cfg_path: Option<&str>) -> Result<Option<Shutdown>, Error> { async fn inner_main(
let rpc_ctx = RpcContext::init(cfg_path).await?; cfg_path: Option<&str>,
log_level: LevelFilter,
) -> Result<Option<Shutdown>, Error> {
let rpc_ctx = RpcContext::init(cfg_path, log_level).await?;
let mut shutdown_recv = rpc_ctx.shutdown.subscribe(); let mut shutdown_recv = rpc_ctx.shutdown.subscribe();
let sig_handler_ctx = rpc_ctx.clone(); let sig_handler_ctx = rpc_ctx.clone();
@@ -231,14 +235,15 @@ fn main() {
) )
.get_matches(); .get_matches();
simple_logging::log_to_stderr(match matches.occurrences_of("verbosity") { // initializes the bootstrap logger, this will be replaced with the EmbassyLogger later
0 => log::LevelFilter::Off, let filter = match matches.occurrences_of("verbosity") {
1 => log::LevelFilter::Error, 0 => log::LevelFilter::Error,
2 => log::LevelFilter::Warn, 1 => log::LevelFilter::Warn,
3 => log::LevelFilter::Info, 2 => log::LevelFilter::Info,
4 => log::LevelFilter::Debug, 3 => log::LevelFilter::Debug,
_ => log::LevelFilter::Trace, _ => log::LevelFilter::Trace,
}); };
simple_logging::log_to_stderr(filter);
let cfg_path = matches.value_of("config"); let cfg_path = matches.value_of("config");
let res = { let res = {
@@ -246,7 +251,7 @@ fn main() {
.enable_all() .enable_all()
.build() .build()
.expect("failed to initialize runtime"); .expect("failed to initialize runtime");
rt.block_on(inner_main(cfg_path)) rt.block_on(inner_main(cfg_path, filter))
}; };
match res { match res {

View File

@@ -7,6 +7,7 @@ use std::sync::atomic::{AtomicU64, AtomicUsize};
use std::sync::Arc; use std::sync::Arc;
use bollard::Docker; use bollard::Docker;
use log::LevelFilter;
use patch_db::json_ptr::JsonPointer; use patch_db::json_ptr::JsonPointer;
use patch_db::{PatchDb, Revision}; use patch_db::{PatchDb, Revision};
use reqwest::Url; use reqwest::Url;
@@ -25,6 +26,7 @@ use crate::manager::ManagerMap;
use crate::net::tor::os_key; use crate::net::tor::os_key;
use crate::net::NetController; use crate::net::NetController;
use crate::shutdown::Shutdown; use crate::shutdown::Shutdown;
use crate::util::logger::EmbassyLogger;
use crate::util::{from_toml_async_reader, AsyncFileExt}; use crate::util::{from_toml_async_reader, AsyncFileExt};
use crate::{Error, ResultExt}; use crate::{Error, ResultExt};
@@ -37,6 +39,7 @@ pub struct RpcContextConfig {
pub revision_cache_size: Option<usize>, pub revision_cache_size: Option<usize>,
pub zfs_pool_name: Option<String>, pub zfs_pool_name: Option<String>,
pub datadir: Option<PathBuf>, pub datadir: Option<PathBuf>,
pub log_server: Option<Url>,
} }
impl RpcContextConfig { impl RpcContextConfig {
pub async fn load<P: AsRef<Path>>(path: Option<P>) -> Result<Self, Error> { pub async fn load<P: AsRef<Path>>(path: Option<P>) -> Result<Self, Error> {
@@ -116,17 +119,33 @@ pub struct RpcContextSeed {
pub metrics_cache: RwLock<Option<crate::system::Metrics>>, pub metrics_cache: RwLock<Option<crate::system::Metrics>>,
pub shutdown: Sender<Option<Shutdown>>, pub shutdown: Sender<Option<Shutdown>>,
pub websocket_count: AtomicUsize, pub websocket_count: AtomicUsize,
pub session_id: AtomicU64, pub logger: EmbassyLogger,
pub log_epoch: Arc<AtomicU64>,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct RpcContext(Arc<RpcContextSeed>); pub struct RpcContext(Arc<RpcContextSeed>);
impl RpcContext { impl RpcContext {
pub async fn init<P: AsRef<Path>>(cfg_path: Option<P>) -> Result<Self, Error> { pub async fn init<P: AsRef<Path>>(
cfg_path: Option<P>,
log_level: LevelFilter,
) -> Result<Self, Error> {
let base = RpcContextConfig::load(cfg_path).await?; let base = RpcContextConfig::load(cfg_path).await?;
let (shutdown, _) = tokio::sync::broadcast::channel(1); let (shutdown, _) = tokio::sync::broadcast::channel(1);
let secret_store = base.secret_store().await?; let secret_store = base.secret_store().await?;
let db = base.db(&secret_store).await?; let db = base.db(&secret_store).await?;
let share = crate::db::DatabaseModel::new()
.server_info()
.share_stats()
.get(&mut db.handle(), true)
.await?;
let log_epoch = Arc::new(AtomicU64::new(rand::random()));
let logger = EmbassyLogger::new(
log_level,
log_epoch.clone(),
base.log_server.clone(),
*share,
);
let docker = Docker::connect_with_unix_defaults()?; let docker = Docker::connect_with_unix_defaults()?;
let net_controller = NetController::init( let net_controller = NetController::init(
([127, 0, 0, 1], 80).into(), ([127, 0, 0, 1], 80).into(),
@@ -151,7 +170,8 @@ impl RpcContext {
metrics_cache: RwLock::new(None), metrics_cache: RwLock::new(None),
shutdown, shutdown,
websocket_count: AtomicUsize::new(0), websocket_count: AtomicUsize::new(0),
session_id: AtomicU64::new(rand::random()), logger,
log_epoch,
}); });
let res = Self(seed); let res = Self(seed);
res.managers res.managers

View File

@@ -44,7 +44,7 @@ async fn ws_handler<
.websocket_count .websocket_count
.fetch_sub(1, std::sync::atomic::Ordering::SeqCst); .fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
if new_count == 0 { if new_count == 0 {
ctx.session_id ctx.log_epoch
.store(rand::random(), std::sync::atomic::Ordering::SeqCst) .store(rand::random(), std::sync::atomic::Ordering::SeqCst)
} }
() ()

View File

@@ -55,6 +55,7 @@ impl Database {
tor: Vec::new(), tor: Vec::new(),
clearnet: Vec::new(), clearnet: Vec::new(),
}, },
share_stats: false,
}, },
package_data: AllPackageData::default(), package_data: AllPackageData::default(),
broken_packages: Vec::new(), broken_packages: Vec::new(),
@@ -82,6 +83,7 @@ pub struct ServerInfo {
unread_notification_count: u64, unread_notification_count: u64,
specs: ServerSpecs, specs: ServerSpecs,
connection_addresses: ConnectionAddresses, connection_addresses: ConnectionAddresses,
share_stats: bool,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]

View File

@@ -68,7 +68,13 @@ pub fn main_api() -> Result<(), RpcError> {
Ok(()) Ok(())
} }
#[command(subcommands(system::logs, system::metrics, shutdown::shutdown, shutdown::restart))] #[command(subcommands(
system::config,
system::logs,
system::metrics,
shutdown::shutdown,
shutdown::restart
))]
pub fn server() -> Result<(), RpcError> { pub fn server() -> Result<(), RpcError> {
Ok(()) Ok(())
} }

View File

@@ -455,6 +455,22 @@ async fn get_disk_info() -> Result<MetricsDisk, Error> {
})? })?
} }
#[command(subcommands(share_stats))]
pub async fn config() -> Result<(), Error> {
Ok(())
}
#[command(display(display_serializable))]
async fn share_stats(#[context] ctx: RpcContext, #[arg] value: bool) -> Result<(), Error> {
crate::db::DatabaseModel::new()
.server_info()
.share_stats()
.put(&mut ctx.db.handle(), &value)
.await?;
ctx.logger.set_sharing(value);
Ok(())
}
#[tokio::test] #[tokio::test]
pub async fn test_get_temp() { pub async fn test_get_temp() {
println!("{}", get_temp().await.unwrap()) println!("{}", get_temp().await.unwrap())

85
appmgr/src/util/logger.rs Normal file
View File

@@ -0,0 +1,85 @@
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use log::{set_boxed_logger, LevelFilter, Metadata, Record};
use reqwest::{Client, Url};
use stderrlog::{StdErrLog, Timestamp};
#[derive(Clone)]
pub struct EmbassyLogger {
log_level: log::LevelFilter,
log_epoch: Arc<AtomicU64>,
logger: StdErrLog,
sharing: Arc<AtomicBool>,
share_dest: Url,
}
impl EmbassyLogger {
pub fn new(
log_level: log::LevelFilter,
log_epoch: Arc<AtomicU64>,
share_dest: Option<Url>,
share_errors: bool,
) -> Self {
let share_dest = match share_dest {
None => Url::parse("https://beta-registry-0-3.start9labs.com/error-logs").unwrap(), // TODO
Some(a) => a,
};
let mut logger = stderrlog::new();
logger
.module(module_path!())
.timestamp(Timestamp::Millisecond);
match log_level {
LevelFilter::Off => logger.quiet(true),
LevelFilter::Error => logger.verbosity(0),
LevelFilter::Warn => logger.verbosity(1),
LevelFilter::Info => logger.verbosity(2),
LevelFilter::Debug => logger.verbosity(3),
LevelFilter::Trace => logger.verbosity(4),
};
let embassy_logger = EmbassyLogger {
log_level,
log_epoch,
logger,
sharing: Arc::new(AtomicBool::new(share_errors)),
share_dest: share_dest,
};
set_boxed_logger(Box::new(embassy_logger.clone())).unwrap();
embassy_logger
}
pub fn set_sharing(&self, sharing: bool) {
self.sharing.store(sharing, Ordering::SeqCst)
}
}
impl log::Log for EmbassyLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
self.logger.enabled(metadata)
}
fn log(&self, record: &Record) {
self.logger.log(record);
if self.sharing.load(Ordering::SeqCst) {
if record.level() <= log::Level::Warn {
let mut body = HashMap::new();
body.insert(
"log-epoch",
format!("{}", self.log_epoch.load(Ordering::SeqCst)),
);
body.insert("log-message", format!("{}", record.args()));
// we don't care about the result and need it to be fast
tokio::spawn(
Client::new()
.post(self.share_dest.clone())
.json(&body)
.send(),
);
}
}
}
fn flush(&self) {}
}
#[tokio::test]
pub async fn order_level() {
assert!(log::Level::Warn > log::Level::Error)
}

View File

@@ -21,6 +21,8 @@ use tokio::task::{JoinError, JoinHandle};
use crate::shutdown::Shutdown; use crate::shutdown::Shutdown;
use crate::{Error, ResultExt as _}; use crate::{Error, ResultExt as _};
pub mod logger;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Never {} pub enum Never {}
impl Never {} impl Never {}