1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
#![doc = include_str!("../README.md")]
use tokio::sync::OnceCell;
#[cfg(test)]
#[macro_use]
pub mod test_util;
pub mod common;
pub mod config;
pub mod grpc;
pub mod postgres;
pub mod resources;
pub use crate::config::Config;
pub use clap::Parser;
/// struct holding cli configuration options
#[derive(Parser, Debug, Clone)]
pub struct Cli {
/// Indicates if we should initialize the database. If not found, defaults to false
#[arg(long)]
pub init_psql: Option<bool>,
/// Indicates if we should rebuild the database. If not found, defaults to false
#[arg(long)]
pub rebuild_psql: Option<bool>,
}
impl Copy for Cli {}
/// Initialized log4rs handle
pub static LOG_HANDLE: OnceCell<Option<log4rs::Handle>> = OnceCell::const_new();
pub(crate) async fn get_log_handle() -> Option<log4rs::Handle> {
LOG_HANDLE
.get_or_init(|| async move {
// Set up basic logger to make sure we can write to stdout
let stdout = log4rs::append::console::ConsoleAppender::builder()
.encoder(Box::new(log4rs::encode::pattern::PatternEncoder::new(
"{d(%Y-%m-%d %H:%M:%S)} | {I} | {h({l}):5.5} | {f}:{L} | {m}{n}",
)))
.build();
let file = log4rs::append::file::FileAppender::builder()
.encoder(Box::new(log4rs::encode::json::JsonEncoder::new()))
.build("logs/all.log")
.unwrap();
match log4rs::config::Config::builder()
.appender(log4rs::config::Appender::builder().build("stdout", Box::new(stdout)))
.appender(log4rs::config::Appender::builder().build("file", Box::new(file)))
.logger(
log4rs::config::Logger::builder()
.appender("file")
.build("backend", log::LevelFilter::Debug),
)
.logger(
log4rs::config::Logger::builder()
.appender("file")
.build("app", log::LevelFilter::Debug),
)
.logger(
log4rs::config::Logger::builder()
.appender("file")
.build("test", log::LevelFilter::Debug),
)
.build(
log4rs::config::Root::builder()
.appender("stdout")
.build(log::LevelFilter::Debug),
) {
Ok(config) => log4rs::init_config(config).ok(),
Err(_) => None,
}
})
.await
.to_owned()
}
/// Initialize a log4rs logger with provided configuration file path
pub async fn load_logger_config_from_file(config_file: &str) -> Result<(), String> {
let log_handle = get_log_handle()
.await
.ok_or("(load_logger_config_from_file) Could not get the log handle.")?;
match log4rs::config::load_config_file(config_file, Default::default()) {
Ok(config) => {
log_handle.set_config(config);
Ok(())
}
Err(e) => Err(format!(
"(logger) Could not parse log config file [{}]: {}.",
config_file, e,
)),
}
}
/// Tokio signal handler that will wait for a user to press CTRL+C.
/// This signal handler can be used in our [`tonic::transport::Server`] method `serve_with_shutdown`.
///
/// # Examples
///
/// ## tonic
/// ```
/// use svc_storage::shutdown_signal;
/// pub async fn server() {
/// let (_, health_service) = tonic_health::server::health_reporter();
/// tonic::transport::Server::builder()
/// .add_service(health_service)
/// .serve_with_shutdown("0.0.0.0:50051".parse().unwrap(), shutdown_signal("grpc", None));
/// }
/// ```
///
/// ## using a shutdown signal channel
/// ```
/// use svc_storage::shutdown_signal;
/// pub async fn server() {
/// let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
/// let (_, health_service) = tonic_health::server::health_reporter();
/// tokio::spawn(async move {
/// tonic::transport::Server::builder()
/// .add_service(health_service)
/// .serve_with_shutdown("0.0.0.0:50051".parse().unwrap(), shutdown_signal("grpc", Some(shutdown_rx)))
/// .await;
/// });
///
/// // Send server the shutdown request
/// shutdown_tx.send(()).expect("Could not stop server.");
/// }
/// ```
pub async fn shutdown_signal(
server: &str,
shutdown_rx: Option<tokio::sync::oneshot::Receiver<()>>,
) {
match shutdown_rx {
Some(receiver) => receiver
.await
.expect("(shutdown_signal) expect tokio signal oneshot Receiver"),
None => tokio::signal::ctrl_c()
.await
.expect("(shutdown_signal) expect tokio signal ctrl-c"),
}
log::warn!("(shutdown_signal) server shutdown for [{}].", server);
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_load_logger_config_from_file() {
get_log_handle().await;
ut_info!("(test_config_from_env) Start.");
let result = load_logger_config_from_file("/usr/src/app/log4rs.yaml").await;
ut_debug!("(test_config_from_env) {:?}", result);
assert!(result.is_ok());
// This message should be written to file
ut_error!("(test_config_from_env) Testing log config from file. This should be written to the tests.log file.");
ut_info!("(test_config_from_env) Success.");
}
}