rustfs_obs/entry/
unified.rs

1// Copyright 2024 RustFS Team
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
15use crate::{AuditLogEntry, BaseLogEntry, LogKind, LogRecord, SerializableLevel};
16use chrono::{DateTime, Utc};
17use serde::{Deserialize, Serialize};
18use tracing_core::Level;
19
20/// Server log entry with structured fields
21/// ServerLogEntry is used to log structured log entries from the server
22///
23/// The `ServerLogEntry` structure contains the following fields:
24/// - `base` - the base log entry
25/// - `level` - the log level
26/// - `source` - the source of the log entry
27/// - `user_id` - the user ID
28/// - `fields` - the structured fields of the log entry
29///
30/// The `ServerLogEntry` structure contains the following methods:
31/// - `new` - create a new `ServerLogEntry` with specified level and source
32/// - `with_base` - set the base log entry
33/// - `user_id` - set the user ID
34/// - `fields` - set the fields
35/// - `add_field` - add a field
36///
37/// # Example
38/// ```
39/// use rustfs_obs::ServerLogEntry;
40/// use tracing_core::Level;
41///
42/// let entry = ServerLogEntry::new(Level::INFO, "test_module".to_string())
43///    .user_id(Some("user-456".to_string()))
44///   .add_field("operation".to_string(), "login".to_string());
45/// ```
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
47pub struct ServerLogEntry {
48    #[serde(flatten)]
49    pub base: BaseLogEntry,
50
51    pub level: SerializableLevel,
52    pub source: String,
53
54    #[serde(rename = "userId", skip_serializing_if = "Option::is_none")]
55    pub user_id: Option<String>,
56
57    #[serde(skip_serializing_if = "Vec::is_empty", default)]
58    pub fields: Vec<(String, String)>,
59}
60
61impl ServerLogEntry {
62    /// Create a new ServerLogEntry with specified level and source
63    pub fn new(level: Level, source: String) -> Self {
64        ServerLogEntry {
65            base: BaseLogEntry::new(),
66            level: SerializableLevel(level),
67            source,
68            user_id: None,
69            fields: Vec::new(),
70        }
71    }
72
73    /// Set the base log entry
74    pub fn with_base(mut self, base: BaseLogEntry) -> Self {
75        self.base = base;
76        self
77    }
78
79    /// Set the user ID
80    pub fn user_id(mut self, user_id: Option<String>) -> Self {
81        self.user_id = user_id;
82        self
83    }
84
85    /// Set fields
86    pub fn fields(mut self, fields: Vec<(String, String)>) -> Self {
87        self.fields = fields;
88        self
89    }
90
91    /// Add a field
92    pub fn add_field(mut self, key: String, value: String) -> Self {
93        self.fields.push((key, value));
94        self
95    }
96}
97
98impl LogRecord for ServerLogEntry {
99    fn to_json(&self) -> String {
100        serde_json::to_string(self).unwrap_or_else(|_| String::from("{}"))
101    }
102
103    fn get_timestamp(&self) -> DateTime<Utc> {
104        self.base.timestamp
105    }
106}
107
108/// Console log entry structure
109/// ConsoleLogEntry is used to log console log entries
110/// The `ConsoleLogEntry` structure contains the following fields:
111/// - `base` - the base log entry
112/// - `level` - the log level
113/// - `console_msg` - the console message
114/// - `node_name` - the node name
115/// - `err` - the error message
116///
117/// The `ConsoleLogEntry` structure contains the following methods:
118/// - `new` - create a new `ConsoleLogEntry`
119/// - `new_with_console_msg` - create a new `ConsoleLogEntry` with console message and node name
120/// - `with_base` - set the base log entry
121/// - `set_level` - set the log level
122/// - `set_node_name` - set the node name
123/// - `set_console_msg` - set the console message
124/// - `set_err` - set the error message
125///
126/// # Example
127/// ```
128/// use rustfs_obs::ConsoleLogEntry;
129///
130/// let entry = ConsoleLogEntry::new_with_console_msg("Test message".to_string(), "node-123".to_string());
131/// ```
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct ConsoleLogEntry {
134    #[serde(flatten)]
135    pub base: BaseLogEntry,
136
137    pub level: LogKind,
138    pub console_msg: String,
139    pub node_name: String,
140
141    #[serde(skip)]
142    pub err: Option<String>,
143}
144
145impl ConsoleLogEntry {
146    /// Create a new ConsoleLogEntry
147    pub fn new() -> Self {
148        ConsoleLogEntry {
149            base: BaseLogEntry::new(),
150            level: LogKind::Info,
151            console_msg: String::new(),
152            node_name: String::new(),
153            err: None,
154        }
155    }
156
157    /// Create a new ConsoleLogEntry with console message and node name
158    pub fn new_with_console_msg(console_msg: String, node_name: String) -> Self {
159        ConsoleLogEntry {
160            base: BaseLogEntry::new(),
161            level: LogKind::Info,
162            console_msg,
163            node_name,
164            err: None,
165        }
166    }
167
168    /// Set the base log entry
169    pub fn with_base(mut self, base: BaseLogEntry) -> Self {
170        self.base = base;
171        self
172    }
173
174    /// Set the log level
175    pub fn set_level(mut self, level: LogKind) -> Self {
176        self.level = level;
177        self
178    }
179
180    /// Set the node name
181    pub fn set_node_name(mut self, node_name: String) -> Self {
182        self.node_name = node_name;
183        self
184    }
185
186    /// Set the console message
187    pub fn set_console_msg(mut self, console_msg: String) -> Self {
188        self.console_msg = console_msg;
189        self
190    }
191
192    /// Set the error message
193    pub fn set_err(mut self, err: Option<String>) -> Self {
194        self.err = err;
195        self
196    }
197}
198
199impl Default for ConsoleLogEntry {
200    fn default() -> Self {
201        Self::new()
202    }
203}
204
205impl LogRecord for ConsoleLogEntry {
206    fn to_json(&self) -> String {
207        serde_json::to_string(self).unwrap_or_else(|_| String::from("{}"))
208    }
209
210    fn get_timestamp(&self) -> DateTime<Utc> {
211        self.base.timestamp
212    }
213}
214
215/// Unified log entry type
216/// UnifiedLogEntry is used to log different types of log entries
217///
218/// The `UnifiedLogEntry` enum contains the following variants:
219/// - `Server` - a server log entry
220/// - `Audit` - an audit log entry
221/// - `Console` - a console log entry
222///
223/// The `UnifiedLogEntry` enum contains the following methods:
224/// - `to_json` - convert the log entry to JSON
225/// - `get_timestamp` - get the timestamp of the log entry
226///
227/// # Example
228/// ```
229/// use rustfs_obs::{UnifiedLogEntry, ServerLogEntry};
230/// use tracing_core::Level;
231///
232/// let server_entry = ServerLogEntry::new(Level::INFO, "test_module".to_string());
233/// let unified = UnifiedLogEntry::Server(server_entry);
234/// ```
235#[derive(Debug, Clone, Serialize, Deserialize)]
236#[serde(tag = "type")]
237pub enum UnifiedLogEntry {
238    #[serde(rename = "server")]
239    Server(ServerLogEntry),
240
241    #[serde(rename = "audit")]
242    Audit(Box<AuditLogEntry>),
243
244    #[serde(rename = "console")]
245    Console(ConsoleLogEntry),
246}
247
248impl LogRecord for UnifiedLogEntry {
249    fn to_json(&self) -> String {
250        match self {
251            UnifiedLogEntry::Server(entry) => entry.to_json(),
252            UnifiedLogEntry::Audit(entry) => entry.to_json(),
253            UnifiedLogEntry::Console(entry) => entry.to_json(),
254        }
255    }
256
257    fn get_timestamp(&self) -> DateTime<Utc> {
258        match self {
259            UnifiedLogEntry::Server(entry) => entry.get_timestamp(),
260            UnifiedLogEntry::Audit(entry) => entry.get_timestamp(),
261            UnifiedLogEntry::Console(entry) => entry.get_timestamp(),
262        }
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269
270    #[test]
271    fn test_base_log_entry() {
272        let base = BaseLogEntry::new()
273            .request_id(Some("req-123".to_string()))
274            .message(Some("Test message".to_string()));
275
276        assert_eq!(base.request_id, Some("req-123".to_string()));
277        assert_eq!(base.message, Some("Test message".to_string()));
278    }
279
280    #[test]
281    fn test_server_log_entry() {
282        let entry = ServerLogEntry::new(Level::INFO, "test_module".to_string())
283            .user_id(Some("user-456".to_string()))
284            .add_field("operation".to_string(), "login".to_string());
285
286        assert_eq!(entry.level.0, Level::INFO);
287        assert_eq!(entry.source, "test_module");
288        assert_eq!(entry.user_id, Some("user-456".to_string()));
289        assert_eq!(entry.fields.len(), 1);
290        assert_eq!(entry.fields[0], ("operation".to_string(), "login".to_string()));
291    }
292
293    #[test]
294    fn test_unified_log_entry_json() {
295        let server_entry = ServerLogEntry::new(Level::INFO, "test_source".to_string());
296        let unified = UnifiedLogEntry::Server(server_entry);
297
298        let json = unified.to_json();
299        assert!(json.contains("test_source"));
300    }
301}