1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
use crate::{
logger::TransportHandle,
logger_levels::LoggerLevels,
logger_transport::{IntoLoggerTransport, LoggerTransport},
};
use logform::{json, Format, LogInfo};
use std::{collections::HashMap, sync::Arc};
#[derive(Clone)]
pub struct LoggerOptions {
pub levels: Option<LoggerLevels>,
pub format: Option<Arc<dyn Format<Input = LogInfo> + Send + Sync>>,
pub level: Option<String>,
pub transports: Option<Vec<(TransportHandle, LoggerTransport<LogInfo>)>>,
pub channel_capacity: Option<usize>,
pub backpressure_strategy: Option<BackpressureStrategy>,
}
impl LoggerOptions {
/// Creates a new `LoggerOptions` instance with default settings.
pub fn new() -> Self {
Self::default()
}
/// Sets the logging level for the logger.
///
/// # Arguments
///
/// * `level` - A string slice that represents the logging level.
pub fn level<T: Into<String>>(mut self, level: T) -> Self {
self.level = Some(level.into());
self
}
/// Sets the log format for the logger.
///
/// # Arguments
///
/// * `format` - The log format to be used.
pub fn format<F>(mut self, format: F) -> Self
where
F: Format<Input = LogInfo> + Send + Sync + 'static,
{
self.format = Some(Arc::new(format));
self
}
/// Adds a single transport to the existing list of transports.
///
/// This method is **additive** — it appends the provided transport to any
/// previously added transports. Each transport is automatically wrapped in
/// an [`Arc`] and assigned a unique [`TransportHandle`].
///
/// This method accepts either a raw transport or a pre-configured
/// [`LoggerTransport`].
///
/// # Example
/// ```ignore
/// use winston_rs::LoggerOptions;
///
/// // Raw transport
/// let options = LoggerOptions::new()
/// .transport(stdout())
/// .transport(FileTransport::new("app.log"));
///
/// // Pre-configured transport
/// let options = LoggerOptions::new()
/// .transport(
/// LoggerTransport::new(FileTransport::new("app.log"))
/// .with_level("debug")
/// .with_format(json())
/// );
/// ```
///
/// Each call to [`transport`](Self::transport) appends a new transport,
/// allowing multiple outputs (e.g. console + file + network) to be used simultaneously.
pub fn transport(mut self, transport: impl IntoLoggerTransport) -> Self {
self.transports
.get_or_insert_with(Vec::new)
.push((TransportHandle::new(), transport.into_logger_transport()));
self
}
/// Replaces all transports with the provided collection.
///
/// This method is **not additive** — it replaces any previously configured
/// transports. Each transport must already be wrapped in an [`Arc`] and will
/// be assigned a unique [`TransportHandle`].
///
/// # Example
/// ```ignore
/// use winston_rs::LoggerOptions;
/// use std::sync::Arc;
///
/// let transports = vec![
/// Arc::new(stdout()),
/// Arc::new(FileTransport::new("app.log")),
/// ];
/// let options = LoggerOptions::new().transports(transports);
/// ```
///
/// Use this method when you want to **replace** all existing transports
/// instead of appending new ones. Multiple calls to `.transports()` will
/// override the previous collection.
pub fn transports<I>(mut self, transports: I) -> Self
where
I: IntoIterator,
I::Item: IntoLoggerTransport,
{
self.transports = Some(
transports
.into_iter()
.map(|t| (TransportHandle::new(), t.into_logger_transport()))
.collect(),
);
self
}
/// Sets custom logging levels for the logger.
///
/// # Arguments
///
/// * `levels` - A `HashMap` where the key is the level name and the value is its severity.
pub fn levels(mut self, levels: HashMap<String, u8>) -> Self {
self.levels = Some(LoggerLevels::new(levels));
self
}
/// Sets the channel capacity for the logger.
///
/// # Arguments
///
/// * `capacity` - An `usize` that defines the capacity of the channel.
pub fn channel_capacity(mut self, capacity: usize) -> Self {
self.channel_capacity = Some(capacity);
self
}
/// Sets the backpressure strategy for the logger.
///
/// # Arguments
///
/// * `strategy` - The backpressure strategy to apply when the channel is full.
pub fn backpressure_strategy(mut self, strategy: BackpressureStrategy) -> Self {
self.backpressure_strategy = Some(strategy);
self
}
}
impl Default for LoggerOptions {
/// Provides the default configuration for `LoggerOptions`.
///
/// The default configuration includes:
/// - A default set of logging levels.
/// - The logging level set to "info".
/// - No default transports.
/// - The JSON format for log entries.
/// - A channel capacity of 1024.
/// - A backpressure strategy set to `BackpressureStrategy::Block`, meaning the logger will block on overflow until space is available.
fn default() -> Self {
LoggerOptions {
levels: Some(LoggerLevels::default()),
level: Some("info".to_string()),
transports: Some(Vec::new()),
format: Some(Arc::new(json())),
channel_capacity: Some(1024),
backpressure_strategy: Some(BackpressureStrategy::Block),
}
}
}
impl std::fmt::Debug for LoggerOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LoggerOptions")
.field("levels", &self.levels)
.field("level", &self.level)
.field("transports", &self.transports)
.field("channel_capacity", &self.channel_capacity)
.field("backpressure_strategy", &self.backpressure_strategy)
// For the format field, just print a placeholder because it can't be debugged:
.field("format", &"<Format trait object>")
.finish()
}
}
#[derive(Clone, Debug)]
pub enum BackpressureStrategy {
DropOldest,
Block,
DropCurrent,
}