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, Ok(port) => port as u16,
135 };
136
137 #[cfg(feature = "realtime")]
138 let realtime_port = match config.get_int("realtime_port") {
139 Err(_) => 9100, 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(), 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}