use std::{path::PathBuf, thread};
use abscissa_core::{
application::{self, AppCell},
component::Component,
config::{self, CfgCell},
terminal::component::Terminal,
terminal::ColorChoice,
Application, Configurable, FrameworkError, FrameworkErrorKind, StandardPaths,
};
use ibc_relayer::{
config::{Config, TracingServerConfig},
util::debug_section::DebugSection,
};
use crate::{
commands::CliCmd,
components::{JsonTracing, PrettyTracing},
entry::EntryPoint,
tracing_handle::{spawn_reload_handler, ReloadHandle},
};
pub static APPLICATION: AppCell<CliApp> = AppCell::new();
pub fn app_reader() -> &'static CliApp {
&APPLICATION
}
pub fn app_config() -> config::Reader<Config> {
APPLICATION.config.read()
}
#[derive(Debug)]
pub struct CliApp {
config: CfgCell<Config>,
state: application::State<Self>,
json_output: bool,
debug_sections: Vec<DebugSection>,
config_path: Option<PathBuf>,
}
impl Default for CliApp {
fn default() -> Self {
Self {
config: CfgCell::default(),
state: application::State::default(),
json_output: false,
debug_sections: Vec::default(),
config_path: None,
}
}
}
impl CliApp {
pub fn json_output(&self) -> bool {
self.json_output
}
pub fn debug_sections(&self) -> &[DebugSection] {
&self.debug_sections
}
pub fn debug_enabled(&self, section: DebugSection) -> bool {
self.debug_sections.contains(§ion)
}
pub fn config_path(&self) -> Option<&PathBuf> {
self.config_path.as_ref()
}
}
impl Application for CliApp {
type Cmd = EntryPoint;
type Cfg = Config;
type Paths = StandardPaths;
fn config(&self) -> config::Reader<Config> {
self.config.read()
}
fn state(&self) -> &application::State<Self> {
&self.state
}
fn register_components(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> {
let framework_components = self.framework_components(command)?;
let mut app_components = self.state.components_mut();
app_components.register(framework_components)
}
fn after_config(&mut self, config: Self::Cfg) -> Result<(), FrameworkError> {
use ibc_relayer::config::Diagnostic;
let mut components = self.state.components_mut();
components.after_config(&config)?;
if let Err(diagnostic) = config.validate_config() {
match diagnostic {
Diagnostic::Warning(e) => {
tracing::warn!("relayer may be misconfigured: {}", e);
}
Diagnostic::Error(e) => {
return Err(FrameworkErrorKind::ConfigError.context(e).into());
}
}
};
tracing::info!("running Hermes v{}", clap::crate_version!());
self.config.set_once(config);
Ok(())
}
fn framework_components(
&mut self,
command: &Self::Cmd,
) -> Result<Vec<Box<dyn Component<Self>>>, FrameworkError> {
let terminal = Terminal::new(self.term_colors(command));
let config_path = command.config_path();
self.config_path.clone_from(&config_path);
let config = config_path
.map(|path| self.load_config(&path))
.transpose()
.map_err(|err| {
let path = self.config_path.clone().unwrap_or_default();
eprintln!(
"The Hermes configuration file at path '{}' is invalid, reason: {}",
path.to_string_lossy(),
err
);
eprintln!(
"Please see the example configuration for detailed information about the \
supported configuration options: \
https://github.com/informalsystems/hermes/blob/master/config.toml"
);
std::process::exit(1);
})
.expect("invalid config")
.unwrap_or_default();
self.json_output = command.json;
self.debug_sections = command.debug.iter().copied().map(Into::into).collect();
let enable_console = self.debug_enabled(DebugSection::Profiling);
let enable_json = self.debug_enabled(DebugSection::ProfilingJson);
ibc_relayer::util::profiling::enable(enable_console, enable_json);
let is_start_cmd = command
.command
.as_ref()
.is_some_and(|cmd| matches!(cmd, CliCmd::Start(_)));
if command.json {
let tracing = JsonTracing::new(config.global, &self.debug_sections)?;
Ok(vec![Box::new(terminal), Box::new(tracing)])
} else {
let (tracing, reload_handle) =
PrettyTracing::new_with_reload_handle(config.global, &self.debug_sections)?;
if is_start_cmd {
spawn_tracing_reload_server(reload_handle, config.tracing_server.clone());
}
Ok(vec![Box::new(terminal), Box::new(tracing)])
}
}
fn term_colors(&self, _command: &Self::Cmd) -> ColorChoice {
ColorChoice::Never
}
}
fn spawn_tracing_reload_server<S: 'static>(
reload_handle: ReloadHandle<S>,
config: TracingServerConfig,
) {
thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let result = rt.block_on(spawn_reload_handler(reload_handle, config));
if let Err(e) = result {
eprintln!("ERROR: failed to spawn tracing reload handler: {e}");
}
});
}