use crate::Result;
use crate::daemon_id::DaemonId;
use crate::log_parse::ParsedLog;
use chrono::{DateTime, Local};
#[derive(Debug, Clone)]
pub struct LogEntry {
pub id: i64,
pub daemon_id: String,
pub timestamp: DateTime<Local>,
pub message: String,
pub level: Option<String>,
pub msg: Option<String>,
pub logger: Option<String>,
pub fields_json: Option<String>,
}
#[derive(Debug, Clone)]
pub enum MessageFilter {
Contains {
pattern: String,
case_sensitive: bool,
},
Regex { pattern: String },
}
#[derive(Debug, Clone)]
pub enum FieldFilter {
LevelMin(String),
FieldEq { key: String, value: String },
LoggerContains(String),
}
pub fn levels_at_or_above(min: &str) -> Vec<&'static str> {
match min {
"error" => vec!["error"],
"warn" => vec!["warn", "error"],
"info" => vec!["info", "warn", "error"],
"debug" => vec!["debug", "info", "warn", "error"],
"trace" => vec!["trace", "debug", "info", "warn", "error"],
_ => vec![],
}
}
impl MessageFilter {
#[allow(dead_code)]
pub fn contains(pattern: impl Into<String>) -> Self {
Self::Contains {
pattern: pattern.into(),
case_sensitive: false,
}
}
#[allow(dead_code)]
pub fn contains_case_sensitive(pattern: impl Into<String>) -> Self {
Self::Contains {
pattern: pattern.into(),
case_sensitive: true,
}
}
#[allow(dead_code)]
pub fn regex(pattern: impl Into<String>) -> Self {
Self::Regex {
pattern: pattern.into(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct LogQuery {
pub daemon_ids: Vec<String>,
pub from: Option<DateTime<Local>>,
pub to: Option<DateTime<Local>>,
pub limit: Option<usize>,
pub order_desc: bool,
pub after_id: Option<i64>,
pub before_id: Option<i64>,
pub message_filters: Vec<MessageFilter>,
pub field_filters: Vec<FieldFilter>,
pub include_structured: bool,
}
pub fn escape_like_pattern(pattern: &str) -> String {
pattern
.replace('\\', "\\\\")
.replace('%', "\\%")
.replace('_', "\\_")
}
#[derive(Debug, Clone, Copy)]
pub struct RetentionPolicy {
pub age: Option<chrono::Duration>,
pub count: Option<u64>,
}
impl RetentionPolicy {
#[allow(dead_code)]
pub fn is_none(&self) -> bool {
self.age.is_none() && self.count.is_none()
}
}
#[derive(Debug, Clone)]
pub struct ArchiveHook {
pub command: String,
pub batch_size: usize,
}
impl ArchiveHook {
pub fn is_enabled(&self) -> bool {
!self.command.trim().is_empty()
}
}
pub trait LogStore: Send + Sync {
fn append(&self, daemon_id: &DaemonId, message: &str) -> Result<()>;
fn append_structured(&self, daemon_id: &DaemonId, parsed: &ParsedLog) -> Result<()> {
self.append(daemon_id, &parsed.message)
}
fn append_structured_batch(&self, daemon_id: &DaemonId, entries: &[ParsedLog]) -> Result<()> {
for entry in entries {
self.append_structured(daemon_id, entry)?;
}
Ok(())
}
#[allow(dead_code)]
fn append_batch(&self, daemon_id: &DaemonId, messages: &[String]) -> Result<()> {
for msg in messages {
self.append(daemon_id, msg)?;
}
Ok(())
}
fn query(&self, opts: &LogQuery) -> Result<Vec<LogEntry>>;
fn tail(&self, daemon_id: &DaemonId, after_id: Option<i64>) -> Result<Vec<LogEntry>>;
fn clear(&self, daemon_ids: &[DaemonId]) -> Result<()>;
fn apply_retention(
&self,
policy: &RetentionPolicy,
excluded_daemon_ids: &[DaemonId],
archive_hook: Option<&ArchiveHook>,
) -> Result<u64> {
let _ = (policy, excluded_daemon_ids, archive_hook);
Ok(0)
}
fn apply_retention_for_daemon(
&self,
daemon_id: &DaemonId,
policy: &RetentionPolicy,
archive_hook: Option<&ArchiveHook>,
) -> Result<u64> {
let _ = (daemon_id, policy, archive_hook);
Ok(0)
}
fn last_id(&self, daemon_id: &DaemonId) -> Result<Option<i64>> {
let entries = self.query(&LogQuery {
daemon_ids: vec![daemon_id.qualified()],
from: None,
to: None,
limit: Some(1),
order_desc: true,
after_id: None,
before_id: None,
message_filters: Vec::new(),
field_filters: Vec::new(),
include_structured: false,
})?;
Ok(entries.first().map(|e| e.id))
}
fn list_daemon_ids(&self) -> Result<Vec<String>>;
fn last_clear_generation(&self, daemon_id: &DaemonId) -> Result<Option<u64>> {
let _ = daemon_id;
Ok(None)
}
fn query_with_generation(
&self,
opts: &LogQuery,
daemon_id: &DaemonId,
) -> Result<(Vec<LogEntry>, Option<u64>)> {
let entries = self.query(opts)?;
let generation = self.last_clear_generation(daemon_id)?;
Ok((entries, generation))
}
}
pub mod sqlite;