use std::collections::BTreeMap;
use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
Debug,
Info,
Notice,
Warning,
Error,
Critical,
Alert,
Emergency,
}
impl fmt::Display for LogLevel {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}", format!("{self:?}").to_lowercase())
}
}
impl FromStr for LogLevel {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value.to_ascii_lowercase().as_str() {
"debug" => Ok(Self::Debug),
"info" => Ok(Self::Info),
"notice" => Ok(Self::Notice),
"warning" | "warn" => Ok(Self::Warning),
"error" => Ok(Self::Error),
"critical" => Ok(Self::Critical),
"alert" => Ok(Self::Alert),
"emergency" => Ok(Self::Emergency),
_ => Err(format!("unknown log level: {value}")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", content = "value", rename_all = "lowercase")]
pub enum LogPayload {
Text(String),
Binary(Vec<u8>),
}
impl LogPayload {
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::Text(value) => value.as_bytes(),
Self::Binary(value) => value,
}
}
#[must_use]
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text(value) => Some(value),
Self::Binary(_) => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LogRecord {
pub timestamp: u64,
pub level: LogLevel,
pub channel: String,
pub payload: LogPayload,
pub fields: BTreeMap<String, String>,
}
impl LogRecord {
#[must_use]
pub fn text(
timestamp: u64,
level: LogLevel,
channel: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
timestamp,
level,
channel: channel.into(),
payload: LogPayload::Text(message.into()),
fields: BTreeMap::new(),
}
}
#[must_use]
pub fn binary(
timestamp: u64,
level: LogLevel,
channel: impl Into<String>,
payload: Vec<u8>,
) -> Self {
Self {
timestamp,
level,
channel: channel.into(),
payload: LogPayload::Binary(payload),
fields: BTreeMap::new(),
}
}
#[must_use]
pub fn with_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.fields.insert(key.into(), value.into());
self
}
#[must_use]
pub fn message(&self) -> Option<&str> {
self.payload.as_text()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StoredRecord {
pub id: u64,
pub graph_id: u32,
pub record: LogRecord,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Query {
pub channel: Option<String>,
pub min_level: Option<LogLevel>,
pub from_timestamp: Option<u64>,
pub to_timestamp: Option<u64>,
pub contains: Option<String>,
pub fields: BTreeMap<String, String>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageFormat {
Text,
Binary,
}
impl fmt::Display for StorageFormat {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Text => formatter.write_str("text"),
Self::Binary => formatter.write_str("binary"),
}
}
}
impl FromStr for StorageFormat {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value.to_ascii_lowercase().as_str() {
"text" | "lino" => Ok(Self::Text),
"binary" | "bin" | "llvb" => Ok(Self::Binary),
_ => Err(format!("unknown storage format: {value}")),
}
}
}