Skip to main content

timber_rust/config/
config.rs

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