1use std::collections::BTreeMap;
2use std::collections::HashSet;
3use std::io::IsTerminal;
4use std::time::Duration;
5
6use schemars::JsonSchema;
7use schemars::Schema;
8use schemars::SchemaGenerator;
9use serde::Deserialize;
10use serde::Deserializer;
11use serde::de::MapAccess;
12use serde::de::Visitor;
13
14use crate::plugins::telemetry::config::AttributeValue;
15use crate::plugins::telemetry::config::TraceIdFormat;
16use crate::plugins::telemetry::resource::ConfigResource;
17
18#[derive(Deserialize, JsonSchema, Clone, Default, Debug, PartialEq)]
20#[serde(deny_unknown_fields, default)]
21pub(crate) struct Logging {
22 pub(crate) common: LoggingCommon,
24 pub(crate) stdout: StdOut,
26 #[serde(skip)]
27 pub(crate) file: File,
29}
30
31#[derive(Clone, Debug, Deserialize, JsonSchema, Default, PartialEq)]
32#[serde(deny_unknown_fields, default)]
33pub(crate) struct LoggingCommon {
34 pub(crate) service_name: Option<String>,
36 pub(crate) service_namespace: Option<String>,
38 pub(crate) resource: BTreeMap<String, AttributeValue>,
40}
41
42impl ConfigResource for LoggingCommon {
43 fn service_name(&self) -> &Option<String> {
44 &self.service_name
45 }
46
47 fn service_namespace(&self) -> &Option<String> {
48 &self.service_namespace
49 }
50
51 fn resource(&self) -> &BTreeMap<String, AttributeValue> {
52 &self.resource
53 }
54}
55
56#[derive(Deserialize, JsonSchema, Clone, Debug, PartialEq)]
57#[serde(deny_unknown_fields, default)]
58pub(crate) struct StdOut {
59 pub(crate) enabled: bool,
61 pub(crate) format: Format,
63 pub(crate) tty_format: Option<Format>,
65 pub(crate) rate_limit: RateLimit,
67}
68
69impl Default for StdOut {
70 fn default() -> Self {
71 StdOut {
72 enabled: true,
73 format: Format::default(),
74 tty_format: None,
75 rate_limit: RateLimit::default(),
76 }
77 }
78}
79
80#[derive(Deserialize, JsonSchema, Clone, Debug, PartialEq)]
81#[serde(deny_unknown_fields, default)]
82pub(crate) struct RateLimit {
83 pub(crate) enabled: bool,
85 pub(crate) capacity: u32,
87 #[serde(deserialize_with = "humantime_serde::deserialize")]
89 #[schemars(with = "String")]
90 pub(crate) interval: Duration,
91}
92
93impl Default for RateLimit {
94 fn default() -> Self {
95 RateLimit {
96 enabled: false,
97 capacity: 1,
98 interval: Duration::from_secs(1),
99 }
100 }
101}
102
103#[allow(dead_code)]
105#[derive(Deserialize, JsonSchema, Clone, Default, Debug, PartialEq)]
106#[serde(deny_unknown_fields, default)]
107pub(crate) struct File {
108 pub(crate) enabled: bool,
110 pub(crate) path: String,
112 pub(crate) format: Format,
114 pub(crate) rollover: Rollover,
116 pub(crate) rate_limit: Option<RateLimit>,
118}
119
120#[derive(Clone, Debug, Eq, PartialEq)]
122pub(crate) enum Format {
123 Json(JsonFormat),
139
140 Text(TextFormat),
142}
143
144impl JsonSchema for Format {
146 fn schema_name() -> std::borrow::Cow<'static, str> {
147 "logging_format".into()
148 }
149
150 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
151 let types = vec![
152 (
153 "json",
154 JsonFormat::json_schema(generator),
155 "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Json.html",
156 ),
157 (
158 "text",
159 TextFormat::json_schema(generator),
160 "Tracing subscriber https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/format/struct.Full.html",
161 ),
162 ];
163
164 let schemas = types
165 .into_iter()
166 .flat_map(|(name, schema, description)| {
167 [
168 schemars::json_schema!({
169 "type": "object",
170 "description": description,
171 "properties": {
172 name.to_string(): schema,
173 },
174 "required": [name],
175 "additionalProperties": false,
176 }),
177 schemars::json_schema!({
178 "type": "string",
179 "description": description,
180 "enum": [name], }),
182 ]
183 })
184 .collect::<Vec<_>>();
185
186 schemars::json_schema!({
187 "oneOf": schemas,
188 })
189 }
190}
191
192impl<'de> Deserialize<'de> for Format {
193 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
194 where
195 D: Deserializer<'de>,
196 {
197 struct StringOrStruct;
198
199 impl<'de> Visitor<'de> for StringOrStruct {
200 type Value = Format;
201
202 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
203 formatter.write_str("string or enum")
204 }
205
206 fn visit_str<E>(self, value: &str) -> Result<Format, E>
207 where
208 E: serde::de::Error,
209 {
210 match value {
211 "json" => Ok(Format::Json(JsonFormat::default())),
212 "text" => Ok(Format::Text(TextFormat::default())),
213 _ => Err(E::custom(format!("unknown log format: {value}"))),
214 }
215 }
216
217 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
218 where
219 A: MapAccess<'de>,
220 {
221 let key = map.next_key::<String>()?;
222
223 match key.as_deref() {
224 Some("json") => Ok(Format::Json(map.next_value::<JsonFormat>()?)),
225 Some("text") => Ok(Format::Text(map.next_value::<TextFormat>()?)),
226 Some(value) => Err(serde::de::Error::custom(format!(
227 "unknown log format: {value}"
228 ))),
229 _ => Err(serde::de::Error::custom("unknown log format")),
230 }
231 }
232 }
233
234 deserializer.deserialize_any(StringOrStruct)
235 }
236}
237
238impl Default for Format {
239 fn default() -> Self {
240 if std::io::stdout().is_terminal() {
241 Format::Text(TextFormat::default())
242 } else {
243 Format::Json(JsonFormat::default())
244 }
245 }
246}
247
248#[derive(Deserialize, JsonSchema, Clone, Debug, Eq, PartialEq)]
249#[serde(deny_unknown_fields, rename_all = "snake_case", default)]
250pub(crate) struct JsonFormat {
251 pub(crate) display_timestamp: bool,
253 pub(crate) display_target: bool,
255 pub(crate) display_level: bool,
257 pub(crate) display_thread_id: bool,
259 pub(crate) display_thread_name: bool,
261 pub(crate) display_filename: bool,
263 pub(crate) display_line_number: bool,
265 pub(crate) display_current_span: bool,
267 pub(crate) display_span_list: bool,
269 pub(crate) display_resource: bool,
271 pub(crate) display_trace_id: DisplayTraceIdFormat,
273 pub(crate) display_span_id: bool,
275 pub(crate) span_attributes: HashSet<String>,
277}
278
279#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Eq)]
280#[serde(deny_unknown_fields, rename_all = "snake_case", untagged)]
281pub(crate) enum DisplayTraceIdFormat {
282 TraceIdFormat(TraceIdFormat),
303 Bool(bool),
304}
305
306impl Default for DisplayTraceIdFormat {
307 fn default() -> Self {
308 Self::TraceIdFormat(TraceIdFormat::default())
309 }
310}
311
312impl Default for JsonFormat {
313 fn default() -> Self {
314 JsonFormat {
315 display_timestamp: true,
316 display_target: true,
317 display_level: true,
318 display_thread_id: false,
319 display_thread_name: false,
320 display_filename: false,
321 display_line_number: false,
322 display_current_span: false,
323 display_span_list: true,
324 display_resource: true,
325 display_trace_id: DisplayTraceIdFormat::Bool(true),
326 display_span_id: true,
327 span_attributes: HashSet::new(),
328 }
329 }
330}
331
332#[derive(Deserialize, JsonSchema, Clone, Debug, Eq, PartialEq)]
333#[serde(deny_unknown_fields, rename_all = "snake_case", default)]
334pub(crate) struct TextFormat {
335 pub(crate) ansi_escape_codes: bool,
337 pub(crate) display_timestamp: bool,
339 pub(crate) display_target: bool,
341 pub(crate) display_level: bool,
343 pub(crate) display_thread_id: bool,
345 pub(crate) display_thread_name: bool,
347 pub(crate) display_filename: bool,
349 pub(crate) display_line_number: bool,
351 pub(crate) display_service_namespace: bool,
353 pub(crate) display_service_name: bool,
355 pub(crate) display_resource: bool,
357 pub(crate) display_current_span: bool,
359 pub(crate) display_span_list: bool,
361 pub(crate) display_trace_id: DisplayTraceIdFormat,
363 pub(crate) display_span_id: bool,
365}
366
367impl Default for TextFormat {
368 fn default() -> Self {
369 TextFormat {
370 ansi_escape_codes: true,
371 display_timestamp: true,
372 display_target: false,
373 display_level: true,
374 display_thread_id: false,
375 display_thread_name: false,
376 display_filename: false,
377 display_line_number: false,
378 display_service_namespace: false,
379 display_service_name: false,
380 display_resource: false,
381 display_current_span: true,
382 display_span_list: true,
383 display_trace_id: DisplayTraceIdFormat::Bool(false),
384 display_span_id: false,
385 }
386 }
387}
388
389#[allow(dead_code)]
391#[derive(Deserialize, JsonSchema, Clone, Default, Debug, PartialEq)]
392#[serde(deny_unknown_fields, rename_all = "snake_case")]
393pub(crate) enum Rollover {
394 Hourly,
396 Daily,
398 #[default]
399 Never,
401}
402
403#[cfg(test)]
404mod test {
405 use serde_json::json;
406
407 use crate::plugins::telemetry::config_new::logging::Format;
408
409 #[test]
410 fn format_de() {
411 let format = serde_json::from_value::<Format>(json!("text")).unwrap();
412 assert_eq!(format, Format::Text(Default::default()));
413 let format = serde_json::from_value::<Format>(json!("json")).unwrap();
414 assert_eq!(format, Format::Json(Default::default()));
415 let format = serde_json::from_value::<Format>(json!({"text":{}})).unwrap();
416 assert_eq!(format, Format::Text(Default::default()));
417 let format = serde_json::from_value::<Format>(json!({"json":{}})).unwrap();
418 assert_eq!(format, Format::Json(Default::default()));
419 }
420}