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
#![doc = include_str!("../README.md")]
use tokio::sync::OnceCell;
#[cfg(test)]
#[macro_use]
pub mod test_util;
pub mod config;
pub mod grpc;
pub use crate::config::Config;
// --------------------------------------------------
// START REST SECTION
// This section should be removed if there is no REST interface
// --------------------------------------------------
pub use clap::Parser;
/// rest implementation module
pub mod rest;
/// struct holding cli configuration options
#[derive(Parser, Debug, Clone)]
pub struct Cli {
/// Target file to write the OpenAPI Spec
#[arg(long)]
pub openapi: Option<String>,
}
// --------------------------------------------------
// END REST SECTION
// --------------------------------------------------
/// 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();
match log4rs::config::Config::builder()
.appender(log4rs::config::Appender::builder().build("stdout", Box::new(stdout)))
.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 [`axum::Server`] method `with_graceful_shutdown`
/// and in our [`tonic::transport::Server`] method `serve_with_shutdown`.
///
/// # Examples
///
/// ## axum
/// ```
/// use svc_assets::shutdown_signal;
/// pub async fn server() {
/// let app = axum::Router::new();
/// axum::Server::bind(&"0.0.0.0:8000".parse().unwrap())
/// .serve(app.into_make_service())
/// .with_graceful_shutdown(shutdown_signal("rest", None));
/// }
/// ```
///
/// ## tonic
/// ```
/// use svc_assets::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_assets::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.");
}
}