1use std::collections::BTreeMap;
2use std::fmt;
3use std::str::FromStr;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum LogLevel {
11 Debug,
13 Info,
15 Notice,
17 Warning,
19 Error,
21 Critical,
23 Alert,
25 Emergency,
27}
28
29impl fmt::Display for LogLevel {
30 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
31 write!(formatter, "{}", format!("{self:?}").to_lowercase())
32 }
33}
34
35impl FromStr for LogLevel {
36 type Err = String;
37
38 fn from_str(value: &str) -> Result<Self, Self::Err> {
39 match value.to_ascii_lowercase().as_str() {
40 "debug" => Ok(Self::Debug),
41 "info" => Ok(Self::Info),
42 "notice" => Ok(Self::Notice),
43 "warning" | "warn" => Ok(Self::Warning),
44 "error" => Ok(Self::Error),
45 "critical" => Ok(Self::Critical),
46 "alert" => Ok(Self::Alert),
47 "emergency" => Ok(Self::Emergency),
48 _ => Err(format!("unknown log level: {value}")),
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(tag = "kind", content = "value", rename_all = "lowercase")]
56pub enum LogPayload {
57 Text(String),
59 Binary(Vec<u8>),
61}
62
63impl LogPayload {
64 #[must_use]
66 pub fn as_bytes(&self) -> &[u8] {
67 match self {
68 Self::Text(value) => value.as_bytes(),
69 Self::Binary(value) => value,
70 }
71 }
72
73 #[must_use]
75 pub fn as_text(&self) -> Option<&str> {
76 match self {
77 Self::Text(value) => Some(value),
78 Self::Binary(_) => None,
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85pub struct LogRecord {
86 pub timestamp: u64,
88 pub level: LogLevel,
90 pub channel: String,
92 pub payload: LogPayload,
94 pub fields: BTreeMap<String, String>,
96}
97
98impl LogRecord {
99 #[must_use]
101 pub fn text(
102 timestamp: u64,
103 level: LogLevel,
104 channel: impl Into<String>,
105 message: impl Into<String>,
106 ) -> Self {
107 Self {
108 timestamp,
109 level,
110 channel: channel.into(),
111 payload: LogPayload::Text(message.into()),
112 fields: BTreeMap::new(),
113 }
114 }
115
116 #[must_use]
118 pub fn binary(
119 timestamp: u64,
120 level: LogLevel,
121 channel: impl Into<String>,
122 payload: Vec<u8>,
123 ) -> Self {
124 Self {
125 timestamp,
126 level,
127 channel: channel.into(),
128 payload: LogPayload::Binary(payload),
129 fields: BTreeMap::new(),
130 }
131 }
132
133 #[must_use]
135 pub fn with_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
136 self.fields.insert(key.into(), value.into());
137 self
138 }
139
140 #[must_use]
142 pub fn message(&self) -> Option<&str> {
143 self.payload.as_text()
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149pub struct StoredRecord {
150 pub id: u64,
152 pub graph_id: u32,
154 pub record: LogRecord,
156}
157
158#[derive(Debug, Clone, Default, PartialEq, Eq)]
160pub struct Query {
161 pub channel: Option<String>,
163 pub min_level: Option<LogLevel>,
165 pub from_timestamp: Option<u64>,
167 pub to_timestamp: Option<u64>,
169 pub contains: Option<String>,
171 pub fields: BTreeMap<String, String>,
173 pub limit: Option<usize>,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum StorageFormat {
180 Text,
182 Binary,
184}
185
186impl fmt::Display for StorageFormat {
187 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
188 match self {
189 Self::Text => formatter.write_str("text"),
190 Self::Binary => formatter.write_str("binary"),
191 }
192 }
193}
194
195impl FromStr for StorageFormat {
196 type Err = String;
197
198 fn from_str(value: &str) -> Result<Self, Self::Err> {
199 match value.to_ascii_lowercase().as_str() {
200 "text" | "lino" => Ok(Self::Text),
201 "binary" | "bin" | "llvb" => Ok(Self::Binary),
202 _ => Err(format!("unknown storage format: {value}")),
203 }
204 }
205}