timber_rust/config/
config.rs

1use crate::config::entry::Entry;
2use crate::service::{FeatureDisabledError, FvnBuildHasher, ServiceError};
3use crate::{LogManager, LoggerFactory};
4use std::collections::HashMap;
5use std::fs::{File, OpenOptions};
6use std::io::BufWriter;
7use std::path::Path;
8
9pub struct Config {
10    entries: HashMap<String, Entry, FvnBuildHasher>,
11}
12
13impl Config {
14    /// Builds the [`LogManager`] by borrowing the configuration.
15    ///
16    /// This method is ideal consumes the [`Config`] object. Normally the configuration
17    /// object will be loaded from a file or elsewhere in order to build the [`LogManager`]
18    /// and then discarded. Note that [`Config`] implements [`Clone`] in case a persistent
19    /// copy is needed.
20    ///
21    /// ### Behavior
22    /// - Takes **ownership** of `self`.
23    /// - **Moves** internal data (like strings and configs) into the loggers.
24    ///
25    /// ### Errors
26    /// Returns [`ServiceError`] if:
27    /// - A file cannot be opened with "write + append" permissions.
28    /// - A channel requires a feature that was not compiled in.
29    /// - Other nondescript errors.
30    pub fn build(self) -> Result<LogManager, ServiceError> {
31        let mut manager = LogManager::new();
32        for (channel, entry) in self.entries {
33            let logger = match entry {
34                Entry::Silent {} => LoggerFactory::silent(),
35                Entry::StdOut {
36                    concurrency,
37                    max_retries,
38                    worker_count,
39                } => {
40                    let mut factory = LoggerFactory::cout();
41                    if let Some(max_retries) = max_retries {
42                        factory = factory.max_retries(max_retries);
43                    }
44                    if let Some(worker_count) = worker_count {
45                        factory = factory.max_retries(worker_count);
46                    }
47                    factory.build(concurrency)
48                }
49                Entry::StdErr {
50                    concurrency,
51                    max_retries,
52                    worker_count,
53                } => {
54                    let mut factory = LoggerFactory::cout();
55                    if let Some(max_retries) = max_retries {
56                        factory = factory.max_retries(max_retries);
57                    }
58                    if let Some(worker_count) = worker_count {
59                        factory = factory.max_retries(worker_count);
60                    }
61                    factory.build(concurrency)
62                }
63                Entry::File {
64                    path,
65                    concurrency,
66                    max_retries,
67                    worker_count,
68                } => {
69                    // Create the directory structure if it doesn't exist
70                    if let Some(parent) = Path::new(path.as_str()).parent() {
71                        std::fs::create_dir_all(parent).map_err(|e| ServiceError::Io(e))?;
72                    }
73                    // Build the File with Append and Create flags
74                    let file: File = OpenOptions::new()
75                        .append(true) // High-level: ensures all writes go to the end
76                        .create(true) // High-level: creates the file if missing
77                        .write(true) // Required to enable writing
78                        .open(path)
79                        .map_err(|e| ServiceError::Io(e))?;
80                    // Build the logger
81                    let mut factory = LoggerFactory::io().file(file);
82                    if let Some(max_retries) = max_retries {
83                        factory = factory.max_retries(max_retries);
84                    }
85                    if let Some(worker_count) = worker_count {
86                        factory = factory.max_retries(worker_count);
87                    }
88                    factory.build(concurrency)
89                }
90                Entry::BufferedFile {
91                    path,
92                    concurrency,
93                    max_retries,
94                    worker_count,
95                } => {
96                    // Create the directory structure if it doesn't exist
97                    if let Some(parent) = Path::new(path.as_str()).parent() {
98                        std::fs::create_dir_all(parent).map_err(|e| ServiceError::Io(e))?;
99                    }
100                    // Build the File with Append and Create flags
101                    let file: File = OpenOptions::new()
102                        .append(true) // High-level: ensures all writes go to the end
103                        .create(true) // High-level: creates the file if missing
104                        .write(true) // Required to enable writing
105                        .open(path)
106                        .map_err(|e| ServiceError::Io(e))?;
107                    // Build the logger
108                    let mut factory = LoggerFactory::io().buffered_file(BufWriter::new(file));
109                    if let Some(max_retries) = max_retries {
110                        factory = factory.max_retries(max_retries);
111                    }
112                    if let Some(worker_count) = worker_count {
113                        factory = factory.max_retries(worker_count);
114                    }
115                    factory.build(concurrency)
116                }
117                Entry::String {
118                    concurrency,
119                    max_retries,
120                    worker_count,
121                    capacity,
122                } => {
123                    // Build the logger
124                    let mut factory = LoggerFactory::fmt().string();
125                    if let Some(max_retries) = max_retries {
126                        factory = factory.max_retries(max_retries);
127                    }
128                    if let Some(worker_count) = worker_count {
129                        factory = factory.max_retries(worker_count);
130                    }
131                    if let Some(capacity) = capacity {
132                        factory = factory.max_retries(capacity);
133                    }
134                    factory.build(concurrency)
135                }
136                Entry::Vector {
137                    concurrency,
138                    max_retries,
139                    worker_count,
140                    capacity,
141                } => {
142                    let mut factory = LoggerFactory::vector();
143                    if let Some(max_retries) = max_retries {
144                        factory = factory.max_retries(max_retries);
145                    }
146                    if let Some(worker_count) = worker_count {
147                        factory = factory.max_retries(worker_count);
148                    }
149                    if let Some(capacity) = capacity {
150                        factory = factory.max_retries(capacity);
151                    }
152                    factory.build(concurrency)
153                }
154                #[cfg(feature = "loki")]
155                Entry::Loki { .. } => {
156                    let config = entry.build_loki_config().expect("Corrupted memory");
157                    LoggerFactory::loki().config(config).build()
158                }
159                #[cfg(feature = "aws")]
160                Entry::CloudWatchConfig { .. } => {
161                    let config = entry.build_cloudwatch_config().expect("Corrupted memory");
162                    LoggerFactory::cloudwatch().config(config).build()
163                }
164                #[cfg(feature = "aws")]
165                Entry::CloudWatchEnv { log_group } => {
166                    LoggerFactory::cloudwatch().env(log_group).build()
167                }
168                #[cfg(feature = "awscout")]
169                Entry::CloudWatchCout {
170                    concurrency,
171                    max_retries,
172                    worker_count,
173                } => {
174                    let mut factory = LoggerFactory::cout();
175                    if let Some(max_retries) = max_retries {
176                        factory = factory.max_retries(max_retries);
177                    }
178                    if let Some(worker_count) = worker_count {
179                        factory = factory.max_retries(worker_count);
180                    }
181                    factory.build(concurrency)
182                }
183                Entry::DisabledFeature { feature } => {
184                    return Err(ServiceError::FeatureDisabled(FeatureDisabledError::new(
185                        feature,
186                    )));
187                }
188            };
189
190            // Register logger
191            manager.set_logger(channel, logger);
192        }
193
194        Ok(manager)
195    }
196
197    /// Returns a reference to the [configuration entry][`Entry`] for the specified channel.
198    ///
199    /// Accepts any type that can be referenced as a string (e.g., `&str` or `String`).
200    pub fn get_entry<Q>(&self, channel: &Q) -> Option<&Entry>
201    where
202        Q: AsRef<str> + ?Sized,
203    {
204        self.entries.get(channel.as_ref())
205    }
206
207    /// Returns a mutable reference to the [configuration entry][`Entry`] for the specified channel.
208    pub fn get_entry_mut<Q>(&mut self, channel: &Q) -> Option<&mut Entry>
209    where
210        Q: AsRef<str> + ?Sized,
211    {
212        self.entries.get_mut(channel.as_ref())
213    }
214
215    /// Removes a channel from the configuration and returns its [entry][`Entry`], if it existed.
216    pub fn remove_entry<Q>(&mut self, channel: &Q) -> Option<Entry>
217    where
218        Q: AsRef<str> + ?Sized,
219    {
220        self.entries.remove(channel.as_ref())
221    }
222
223    /// Inserts a raw [configuration entry][`Entry`] for a specific channel.
224    /// If the channel already exists, the old configuration is overwritten.
225    pub fn insert_entry<S>(&mut self, channel: S, entry: Entry)
226    where
227        S: Into<String>,
228    {
229        self.entries.insert(channel.into(), entry);
230    }
231}