witchcraft_server_config/runtime/
mod.rs

1// Copyright 2021 Palantir Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! Runtime-reloadable configuration.
15use crate::ConfigError;
16use conjure_runtime_config::ServicesConfig;
17use serde::de::Error;
18use serde::{Deserialize, Deserializer};
19use staged_builder::{staged_builder, Validate};
20use std::collections::HashMap;
21use witchcraft_log::LevelFilter;
22
23mod de;
24
25/// The runtime-reloadable configuration for a Witchcraft server.
26#[derive(Clone, PartialEq, Debug)]
27#[staged_builder]
28pub struct RuntimeConfig {
29    diagnostics: DiagnosticsConfig,
30    health_checks: HealthChecksConfig,
31    #[builder(default)]
32    logging: LoggingConfig,
33    #[builder(default)]
34    service_discovery: ServicesConfig,
35}
36
37impl<'de> Deserialize<'de> for RuntimeConfig {
38    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39    where
40        D: Deserializer<'de>,
41    {
42        let raw = de::RuntimeConfig::deserialize(deserializer)?;
43        let mut builder = RuntimeConfig::builder()
44            .diagnostics(raw.diagnostics)
45            .health_checks(raw.health_checks);
46        if let Some(logging) = raw.logging {
47            builder = builder.logging(logging);
48        }
49        if let Some(service_discovery) = raw.service_discovery {
50            builder = builder.service_discovery(service_discovery);
51        }
52
53        Ok(builder.build())
54    }
55}
56
57impl AsRef<RuntimeConfig> for RuntimeConfig {
58    #[inline]
59    fn as_ref(&self) -> &RuntimeConfig {
60        self
61    }
62}
63
64impl RuntimeConfig {
65    /// Returns the server's diagnostics configuration.
66    ///
67    /// Required.
68    #[inline]
69    pub fn diagnostics(&self) -> &DiagnosticsConfig {
70        &self.diagnostics
71    }
72
73    /// Returns the server's health checks configuration.
74    ///
75    /// Required.
76    #[inline]
77    pub fn health_checks(&self) -> &HealthChecksConfig {
78        &self.health_checks
79    }
80
81    /// Returns the server's logging configuration.
82    #[inline]
83    pub fn logging(&self) -> &LoggingConfig {
84        &self.logging
85    }
86
87    /// Returns the server's service discovery configuration.
88    #[inline]
89    pub fn service_discovery(&self) -> &ServicesConfig {
90        &self.service_discovery
91    }
92}
93
94/// Diagnostics configuration.
95#[derive(Clone, PartialEq, Debug)]
96#[staged_builder]
97pub struct DiagnosticsConfig {
98    #[builder(into)]
99    debug_shared_secret: String,
100    #[builder(default)]
101    jemalloc: JemallocConfig,
102}
103
104impl<'de> Deserialize<'de> for DiagnosticsConfig {
105    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106    where
107        D: Deserializer<'de>,
108    {
109        let raw = de::DiagnosticsConfig::deserialize(deserializer)?;
110        let mut builder = DiagnosticsConfig::builder().debug_shared_secret(raw.debug_shared_secret);
111        if let Some(jemalloc) = raw.jemalloc {
112            builder = builder.jemalloc(jemalloc);
113        }
114
115        Ok(builder.build())
116    }
117}
118
119impl DiagnosticsConfig {
120    /// Returns the shared secret used to authorize requests to the server's debug endpoint.
121    ///
122    /// Required.
123    #[inline]
124    pub fn debug_shared_secret(&self) -> &str {
125        &self.debug_shared_secret
126    }
127
128    /// Returns configuration for jemalloc heap profile diagnostics.
129    #[inline]
130    pub fn jemalloc(&self) -> &JemallocConfig {
131        &self.jemalloc
132    }
133}
134
135/// jemalloc diagnostics configuration.
136#[derive(Clone, PartialEq, Debug)]
137#[staged_builder]
138pub struct JemallocConfig {
139    #[builder(default = false)]
140    prof_active: bool,
141    #[builder(default = 19)]
142    lg_prof_sample: usize,
143}
144
145impl Default for JemallocConfig {
146    #[inline]
147    fn default() -> Self {
148        JemallocConfig::builder().build()
149    }
150}
151
152impl<'de> Deserialize<'de> for JemallocConfig {
153    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
154    where
155        D: Deserializer<'de>,
156    {
157        let raw = de::JemallocConfig::deserialize(deserializer)?;
158        let mut builder = JemallocConfig::builder();
159
160        if let Some(prof_active) = raw.prof_active {
161            builder = builder.prof_active(prof_active);
162        }
163        if let Some(lg_prof_sample) = raw.lg_prof_sample {
164            builder = builder.lg_prof_sample(lg_prof_sample);
165        }
166
167        Ok(builder.build())
168    }
169}
170
171impl JemallocConfig {
172    /// Controls whether jemalloc heap allocation sampling is enabled.
173    ///
174    /// See [jemalloc's documentation](https://jemalloc.net/jemalloc.3.html) for details.
175    ///
176    /// Defaults to false.
177    #[inline]
178    pub fn prof_active(&self) -> bool {
179        self.prof_active
180    }
181
182    /// The base 2 log of the average interval in allocated bytes between allocation samples.
183    ///
184    /// Sampled allocations will be reset whenever this value is changed. See
185    /// [jemalloc's documentation](https://jemalloc.net/jemalloc.3.html) for details.
186    ///
187    /// Defaults to 19 (aka a 512 KiB interval).
188    #[inline]
189    pub fn lg_prof_sample(&self) -> usize {
190        self.lg_prof_sample
191    }
192}
193
194/// Health checks configuration.
195#[derive(Clone, PartialEq, Debug)]
196#[staged_builder]
197pub struct HealthChecksConfig {
198    #[builder(into)]
199    shared_secret: String,
200}
201
202impl<'de> Deserialize<'de> for HealthChecksConfig {
203    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
204    where
205        D: Deserializer<'de>,
206    {
207        let raw = de::HealthChecksConfig::deserialize(deserializer)?;
208        let builder = HealthChecksConfig::builder().shared_secret(raw.shared_secret);
209
210        Ok(builder.build())
211    }
212}
213
214impl HealthChecksConfig {
215    /// Returns the shared secret used to authorize requests to the server's health check endpoint.
216    #[inline]
217    pub fn shared_secret(&self) -> &str {
218        &self.shared_secret
219    }
220}
221
222/// Logging configuration.
223#[derive(Clone, PartialEq, Debug)]
224#[staged_builder]
225#[builder(validate)]
226pub struct LoggingConfig {
227    #[builder(default = LevelFilter::Info)]
228    level: LevelFilter,
229    #[builder(map(key(type = String, into), value(type = LevelFilter)))]
230    loggers: HashMap<String, LevelFilter>,
231    #[builder(default = 0.0005)]
232    trace_rate: f32,
233}
234
235impl Validate for LoggingConfig {
236    type Error = ConfigError;
237
238    fn validate(&self) -> Result<(), Self::Error> {
239        if !(0.0..=1.0).contains(&self.trace_rate()) {
240            return Err(ConfigError(
241                "trace-rate must be between 0 and 1, inclusive".to_string(),
242            ));
243        }
244
245        Ok(())
246    }
247}
248
249impl<'de> Deserialize<'de> for LoggingConfig {
250    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
251    where
252        D: Deserializer<'de>,
253    {
254        let raw = de::LoggingConfig::deserialize(deserializer)?;
255        let mut builder = LoggingConfig::builder();
256        if let Some(level) = raw.level {
257            builder = builder.level(level);
258        }
259        if let Some(loggers) = raw.loggers {
260            builder = builder.loggers(loggers);
261        }
262        if let Some(trace_rate) = raw.trace_rate {
263            builder = builder.trace_rate(trace_rate);
264        }
265
266        builder.build().map_err(Error::custom)
267    }
268}
269
270impl Default for LoggingConfig {
271    #[inline]
272    fn default() -> Self {
273        LoggingConfig::builder().build().unwrap()
274    }
275}
276
277impl LoggingConfig {
278    /// Returns the logging verbosity filter applied globally for all service logs.
279    ///
280    /// Defaults to [`LevelFilter::Info`].
281    #[inline]
282    pub fn level(&self) -> LevelFilter {
283        self.level
284    }
285
286    /// Returns a map of verbosity filter overides applied to specific targets.
287    #[inline]
288    pub fn loggers(&self) -> &HashMap<String, LevelFilter> {
289        &self.loggers
290    }
291
292    /// Returns the rate at which new traces will be sampled between 0 and 1, inclusive.
293    ///
294    /// This only applies to fresh traces - Witchcraft will respect sampling decisions made by upstream services for a
295    /// given request.
296    ///
297    /// Defaults to 0.05%.
298    #[inline]
299    pub fn trace_rate(&self) -> f32 {
300        self.trace_rate
301    }
302}