initial commit

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ib131388c1056b6708b730a35011811026a6a6964
This commit is contained in:
raf 2026-02-18 20:13:00 +03:00
commit 033e253259
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
33 changed files with 3126 additions and 0 deletions

137
pscand-core/src/logging.rs Normal file
View file

@ -0,0 +1,137 @@
use chrono::{DateTime, Utc};
use parking_lot::Mutex;
use ringbuf::{
storage::Heap,
traits::*,
wrap::caching::{CachingCons, CachingProd},
SharedRb,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use crate::scanner::MetricValue;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogEntry {
pub timestamp: DateTime<Utc>,
pub scanner: String,
pub metrics: HashMap<String, MetricValue>,
}
impl LogEntry {
pub fn new(scanner: impl Into<String>, metrics: HashMap<String, MetricValue>) -> Self {
Self {
timestamp: Utc::now(),
scanner: scanner.into(),
metrics,
}
}
pub fn to_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|e| format!("{{\"error\":\"{}\"}}", e))
}
pub fn to_journal(&self) -> String {
let metrics_json = serde_json::to_string(&self.metrics).unwrap_or_default();
format!(
"PSCAND_SCANNER={} PSCAND_METRICS={}",
self.scanner, metrics_json
)
}
}
type RbStorage = Heap<LogEntry>;
type SharedRbLog = SharedRb<RbStorage>;
struct RingBufferHandles {
prod: CachingProd<Arc<SharedRbLog>>,
cons: CachingCons<Arc<SharedRbLog>>,
}
pub struct RingBufferLogger {
buffer: Arc<Mutex<RingBufferHandles>>,
file_path: Option<PathBuf>,
journal_enabled: bool,
file_enabled: bool,
}
impl RingBufferLogger {
pub fn new(
capacity: usize,
file_path: Option<PathBuf>,
journal_enabled: bool,
file_enabled: bool,
) -> Self {
let rb = SharedRb::<RbStorage>::new(capacity);
let (prod, cons) = rb.split();
let handles = RingBufferHandles { prod, cons };
Self {
buffer: Arc::new(Mutex::new(handles)),
file_path,
journal_enabled,
file_enabled,
}
}
pub fn push(&self, entry: LogEntry) {
{
let mut handles = self.buffer.lock();
if handles.prod.is_full() {
let _ = handles.cons.try_pop();
}
let _ = handles.prod.try_push(entry.clone());
}
if self.journal_enabled {
self.write_to_journal(&entry);
}
if self.file_enabled {
self.write_to_file(&entry);
}
}
fn write_to_journal(&self, entry: &LogEntry) {
let msg = entry.to_journal();
let _ = std::process::Command::new("logger")
.arg("-t")
.arg("pscand")
.arg("-p")
.arg("info")
.arg(msg)
.spawn();
}
fn write_to_file(&self, entry: &LogEntry) {
if let Some(ref path) = self.file_path {
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
let _ = writeln!(file, "{}", entry.to_json());
}
}
}
pub fn get_recent(&self, count: usize) -> Vec<LogEntry> {
let handles = self.buffer.lock();
handles.cons.iter().rev().take(count).cloned().collect()
}
pub fn flush_to_file(&self, path: &PathBuf) -> std::io::Result<()> {
let entries = self.get_recent(usize::MAX);
let mut file = fs::File::create(path)?;
for entry in entries {
writeln!(file, "{}", entry.to_json())?;
}
Ok(())
}
}
impl Default for RingBufferLogger {
fn default() -> Self {
Self::new(60, None, true, false)
}
}