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
#![doc = include_str!("../README.md")]

use tokio::sync::OnceCell;

#[cfg(test)]
#[macro_use]
pub mod test_util;

pub mod amqp;
pub mod config;
pub mod grpc;
pub mod region;

pub use crate::config::Config;

/// 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 [`tonic::transport::Server`] method `serve_with_shutdown`.
///
/// # Examples
///
/// ## tonic
/// ```
/// use svc_compliance::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_compliance::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.");
    }
}