feature_probe_server/
base.rs

1use feature_probe_server_sdk::Url;
2use log::warn;
3use serde::Deserialize;
4use std::{fmt::Display, time::Duration};
5use thiserror::Error;
6use tracing_core::{Event, Subscriber};
7use tracing_log::NormalizeEvent;
8use tracing_subscriber::fmt::{
9    format::{self, FormatEvent, FormatFields},
10    time::{FormatTime, SystemTime},
11    FmtContext,
12};
13use tracing_subscriber::registry::LookupSpan;
14
15#[derive(Debug, Error, Deserialize, PartialEq, Eq)]
16pub enum FPServerError {
17    #[error("not found {0}")]
18    NotFound(String),
19    #[error("user base64 decode error")]
20    UserDecodeError,
21    #[error("config error: {0}")]
22    ConfigError(String),
23    #[error("not ready error: {0}")]
24    NotReady(String),
25    #[error("json error: {0}")]
26    JsonError(String),
27}
28
29#[derive(Debug, Clone)]
30pub struct ServerConfig {
31    pub toggles_url: Url,
32    pub events_url: Url,
33    pub keys_url: Option<Url>,
34    pub analysis_url: Option<Url>,
35    pub refresh_interval: Duration,
36    pub server_sdk_key: Option<String>,
37    pub client_sdk_key: Option<String>,
38    pub server_port: u16,
39    #[cfg(feature = "realtime")]
40    pub realtime_port: u16,
41    #[cfg(feature = "realtime")]
42    pub realtime_path: String,
43}
44
45impl ServerConfig {
46    pub fn try_parse(config: config::Config) -> Result<ServerConfig, FPServerError> {
47        let toggles_url = match config.get_string("toggles_url") {
48            Err(_) => {
49                return Err(FPServerError::ConfigError(
50                    "NOT SET FP_TOGGLES_URL".to_owned(),
51                ))
52            }
53            Ok(url) => match Url::parse(&url) {
54                Err(e) => {
55                    return Err(FPServerError::ConfigError(format!(
56                        "INVALID FP_TOGGLES_URL: {}",
57                        e,
58                    )))
59                }
60                Ok(u) => u,
61            },
62        };
63
64        let events_url = match config.get_string("events_url") {
65            Err(_) => {
66                return Err(FPServerError::ConfigError(
67                    "NOT SET FP_EVENTS_URL".to_owned(),
68                ))
69            }
70            Ok(url) => match Url::parse(&url) {
71                Err(e) => {
72                    return Err(FPServerError::ConfigError(format!(
73                        "INVALID FP_EVENTS_URL: {}",
74                        e,
75                    )))
76                }
77                Ok(u) => u,
78            },
79        };
80        let client_sdk_key = config.get_string("client_sdk_key").ok();
81        let server_sdk_key = config.get_string("server_sdk_key").ok();
82
83        let keys_url = match config.get_string("keys_url") {
84            Ok(url) => match Url::parse(&url) {
85                Err(e) => {
86                    return Err(FPServerError::ConfigError(format!(
87                        "INVALID FP_KEYS_URL: {}",
88                        e,
89                    )))
90                }
91                Ok(u) => Some(u),
92            },
93            Err(_) => {
94                if client_sdk_key.is_none() {
95                    return Err(FPServerError::ConfigError(
96                        "NOT SET FP_CLIENT_SDK_KEY".to_owned(),
97                    ));
98                }
99                if server_sdk_key.is_none() {
100                    return Err(FPServerError::ConfigError(
101                        "NOT SET FP_SERVER_SDK_KEY".to_owned(),
102                    ));
103                }
104                None
105            }
106        };
107
108        let analysis_url = match config.get_string("analysis_url") {
109            Ok(url) => match Url::parse(&url) {
110                Err(e) => {
111                    return Err(FPServerError::ConfigError(format!(
112                        "INVALID FP_ANALYSIS_URL: {}",
113                        e,
114                    )))
115                }
116                Ok(u) => Some(u),
117            },
118            Err(_) => {
119                warn!("NOT SET FP_ANALYSIS_URL");
120                None
121            }
122        };
123
124        let refresh_interval = match config.get_int("refresh_seconds") {
125            Err(_) => {
126                return Err(FPServerError::ConfigError(
127                    "NOT SET FP_REFRESH_SECONDS".to_owned(),
128                ))
129            }
130            Ok(interval) => Duration::from_secs(interval as u64),
131        };
132        let server_port = match config.get_int("server_port") {
133            Err(_) => 9000, // default port
134            Ok(port) => port as u16,
135        };
136
137        #[cfg(feature = "realtime")]
138        let realtime_port = match config.get_int("realtime_port") {
139            Err(_) => 9100, // default port
140            Ok(port) => port as u16,
141        };
142
143        #[cfg(feature = "realtime")]
144        let realtime_path = match config.get_string("realtime_path") {
145            Err(_) => "/server/realtime".to_owned(), // default port
146            Ok(path) => path,
147        };
148
149        Ok(ServerConfig {
150            toggles_url,
151            events_url,
152            analysis_url,
153            keys_url,
154            refresh_interval,
155            client_sdk_key,
156            server_sdk_key,
157            server_port,
158            #[cfg(feature = "realtime")]
159            realtime_port,
160            #[cfg(feature = "realtime")]
161            realtime_path,
162        })
163    }
164}
165
166impl Display for ServerConfig {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        let keys_url = match &self.keys_url {
169            None => "None".to_owned(),
170            Some(s) => s.to_string(),
171        };
172        let analysis_url = match &self.analysis_url {
173            None => "None".to_owned(),
174            Some(s) => s.to_string(),
175        };
176        write!(f, "server_port {}, toggles_url {}, events_url {}, analysis_url {}, keys_url {}, refresh_interval {:?}, client_sdk_key {:?}, server_sdk_key {:?}",
177            self.server_port,
178            self.toggles_url,
179            self.events_url,
180            analysis_url,
181            keys_url,
182            self.refresh_interval,
183            self.client_sdk_key,
184            self.server_sdk_key,
185        )
186    }
187}
188
189#[derive(Debug)]
190pub struct LogFormatter<T = SystemTime> {
191    timer: T,
192}
193
194impl<T2> LogFormatter<T2>
195where
196    T2: FormatTime,
197{
198    #[allow(dead_code)]
199    pub fn with_timer(timer: T2) -> Self {
200        LogFormatter { timer }
201    }
202}
203
204impl<C, N, T> FormatEvent<C, N> for LogFormatter<T>
205where
206    C: Subscriber + for<'a> LookupSpan<'a>,
207    N: for<'a> FormatFields<'a> + 'static,
208    T: FormatTime,
209{
210    fn format_event(
211        &self,
212        ctx: &FmtContext<'_, C, N>,
213        mut writer: format::Writer<'_>,
214        event: &Event<'_>,
215    ) -> std::fmt::Result {
216        let normalized = event.normalized_metadata();
217        let meta = normalized.as_ref().unwrap_or_else(|| event.metadata());
218        write!(writer, "[{}][", meta.level())?;
219        if self.timer.format_time(&mut writer).is_err() {
220            write!(writer, "<unknown time>")?;
221        }
222        write!(writer, "]")?;
223        if let Some(m) = meta.module_path() {
224            write!(writer, "[{}", m)?;
225        }
226        if let Some(l) = meta.line() {
227            write!(writer, ":{}", l)?;
228        }
229        write!(writer, "]")?;
230
231        ctx.field_format().format_fields(writer.by_ref(), event)?;
232        writeln!(writer)
233    }
234}