rokkett_logger/
protocol.rs

1mod encoding;
2
3use serde::{Deserialize, Serialize};
4use std::fmt::Display;
5
6/// All requests are wrapped in this struct.
7#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
8pub struct HeaderPackage {
9	pub bytes: usize,
10}
11
12impl Into<Vec<u8>> for HeaderPackage {
13	fn into(self) -> Vec<u8> {
14		match encoding::serialize(&self) {
15			Ok(bytes) => {
16				let mut padded = vec![0u8; 8];
17				padded[..bytes.len()].copy_from_slice(&bytes);
18				padded
19			},
20			Err(err) => {
21				eprintln!("could not serialize {self:?}\nerror = {err}");
22				vec![]
23			}
24		}
25	}
26}
27
28impl HeaderPackage {
29	pub fn try_parse(bytes: &Vec<u8>) -> Option<Self> {
30		encoding::deserialize(bytes).ok()
31	}
32}
33
34/// All requests are wrapped in this struct.
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
36pub struct RequestPackage {
37	/// A command indicates the requested action by the sender (client) to which the receiver (server) will react to.
38	pub command: Command,
39}
40
41impl Into<Vec<u8>> for RequestPackage {
42	fn into(self) -> Vec<u8> {
43		match encoding::serialize(&self) {
44			Ok(bytes) => bytes,
45			Err(err) => {
46				eprintln!("could not serialize {self:?}\nerror = {err}");
47				vec![]
48			}
49		}
50	}
51}
52
53impl From<Vec<u8>> for RequestPackage {
54	fn from(bytes: Vec<u8>) -> Self {
55		match encoding::deserialize::<RequestPackage>(&bytes) {
56			Ok(value) => value,
57			Err(err) => {
58				eprintln!("could not deserialize bytes to RequestPackage\nerror = {err}");
59				RequestPackage { command: Command::NoOp }
60			}
61		}
62	}
63}
64
65/// All requests are wrapped in this struct.
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
67pub struct ResponsePackage {
68	/// When responding to a request, this attribute indicates the success or failure of the command.
69	pub status: Status,
70}
71
72impl Into<Vec<u8>> for ResponsePackage {
73	fn into(self) -> Vec<u8> {
74		match encoding::serialize(&self) {
75			Ok(bytes) => bytes,
76			Err(err) => {
77				eprintln!("could not serialize {self:?}\nerror = {err}");
78				vec![]
79			}
80		}
81	}
82}
83
84impl From<Vec<u8>> for ResponsePackage {
85	fn from(bytes: Vec<u8>) -> Self {
86		match encoding::deserialize::<ResponsePackage>(&bytes) {
87			Ok(value) => value,
88			Err(err) => {
89				eprintln!("could not deserialize bytes to ResponsePackage\nerror = {err}");
90				ResponsePackage {
91					status: Status::ParseErr,
92				}
93			}
94		}
95	}
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
99pub enum Status {
100	/// The command requested by the sender (client) was executed successfully.
101	Ok,
102
103	/// The request was received and parsed successfully, but no action was be taken.
104	/// Usually this means that no command was issued by the sender (client).
105	NoCmd,
106
107	/// Unclassifiable error including a error message.
108	Err(String),
109
110	/// The payload could not be parsed correctly by the receiver (server).
111	ParseErr,
112
113	/// The received command requires authentication or authorization, which was not provided or invalid.
114	AuthErr,
115
116	/// Indicates that the sender (client) has sent too many requests.
117	RateLimit,
118
119	/// The payload was too large and thus not parsed.
120	TooLarge,
121
122	/// The receiver (server) has blocked the request and will most likely close the connection.
123	/// This will happen if the sender (client) has sent too many requests to which the receiver (server) has reponded with an error.
124	Blocked,
125}
126
127impl Display for Status {
128	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129		write!(f, "{self:?}")
130	}
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
134pub enum Command {
135	/// No operation.
136	NoOp,
137
138	/// Authenticate and authorize the sender (client).
139	Login(AuthToken),
140
141	/// Process a log message.
142	Log(LogMessage),
143}
144
145/// Used to authenticate and authorize a sender (client).
146pub type AuthToken = String;
147
148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
149pub struct LogMessage {
150	/// The name of the service which produced the log message.
151	pub service: String,
152
153	/// The log target inside the application.
154	pub target: String,
155
156	/// The log level of the log message.
157	pub level: String,
158
159	/// Timestamp in form of a RFC 3339 or ISO 8601 datetime string.
160	pub timestamp: String,
161
162	/// The source of the log message. Usually in the format of <source file>:<line number>.
163	pub source: String,
164
165	/// The actual log message.
166	pub message: String,
167}
168
169impl LogMessage {
170	/// Create log message from log record.
171	///
172	/// Requires 'client' feature (enabled by default).
173	#[cfg(all(feature = "chrono", feature = "log"))]
174	pub fn from_log_record(record: &log::Record, service: &str) -> Self {
175		LogMessage {
176			service: service.to_owned(),
177			target: record.target().to_string(),
178			level: record.level().to_string(),
179			timestamp: chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
180			source: format!("{}:{}", record.file().unwrap_or("-"), record.line().unwrap_or(0)),
181			message: record.args().to_string(),
182		}
183	}
184}
185
186impl Display for LogMessage {
187	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188		write!(
189			f,
190			"{ts} {lvl} [{srv}] ({trg}@{src}) {msg}",
191			ts = self.timestamp,
192			srv = self.service,
193			lvl = self.level,
194			trg = self.target,
195			src = self.source,
196			msg = self.message
197		)
198	}
199}
200
201#[cfg(test)]
202mod tests {
203	use super::*;
204
205	#[test]
206	#[cfg(all(feature = "chrono", feature = "log"))]
207	fn test_from_log_record_log_message() {
208		let service = "service";
209		let target = "target";
210		let level = "DEBUG";
211		let source_file = file!();
212		let source_line = line!();
213		let source = format!("{source_file}:{source_line}");
214
215		let record = log::Record::builder()
216			.target(target)
217			.level(log::Level::Debug)
218			.file(Some(source_file))
219			.line(Some(source_line))
220			.args(format_args!("foo bar baz"))
221			.build();
222
223		let actual = LogMessage::from_log_record(&record, service);
224
225		assert_eq!(service, actual.service);
226		assert_eq!(target, actual.target);
227		assert_eq!(level, actual.level);
228		assert_eq!(source, actual.source);
229		assert_eq!("foo bar baz", actual.message);
230	}
231
232	#[test]
233	fn test_display_log_message() {
234		let actual: String = format!(
235			"{}",
236			LogMessage {
237				service: "service".to_string(),
238				target: "target".to_string(),
239				level: "level".to_string(),
240				timestamp: "timestamp".to_string(),
241				source: "source".to_string(),
242				message: "message".to_string(),
243			}
244		);
245
246		assert!(actual.is_ascii());
247	}
248
249	#[test]
250	fn test_serde_request_package() {
251		let expected = RequestPackage {
252			command: Command::Log(LogMessage {
253				service: "service".to_string(),
254				target: "target".to_string(),
255				level: "level".to_string(),
256				timestamp: "timestamp".to_string(),
257				source: "source".to_string(),
258				message: "message".to_string(),
259			}),
260		};
261		let encoded: Vec<u8> = expected.clone().into();
262		let actual: RequestPackage = encoded.into();
263		assert_eq!(expected, actual);
264	}
265
266	#[test]
267	fn test_serde_response_package() {
268		let expected = ResponsePackage {
269			status: Status::Err("unknown exception".to_string()),
270		};
271		let encoded: Vec<u8> = expected.clone().into();
272		let actual: ResponsePackage = encoded.into();
273		assert_eq!(expected, actual);
274	}
275}