use crate::address::Address;
use std::fs::OpenOptions;
use std::fs::{self, File};
use std::io::{prelude::*, SeekFrom};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use tokio::sync::broadcast;
pub struct DatabaseLogger {
file: File,
pub tx: broadcast::Sender<bytes::Bytes>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
Panic,
}
impl From<u8> for LogLevel {
fn from(level: u8) -> Self {
match level {
0 => LogLevel::Error,
1 => LogLevel::Warn,
2 => LogLevel::Info,
3 => LogLevel::Debug,
4 => LogLevel::Trace,
101 => LogLevel::Panic,
_ => LogLevel::Debug,
}
}
}
#[serde_with::skip_serializing_none]
#[derive(serde::Serialize, Copy, Clone)]
pub struct Record<'a> {
pub target: Option<&'a str>,
pub filename: Option<&'a str>,
pub line_number: Option<u32>,
pub message: &'a str,
}
pub trait BacktraceProvider {
fn capture(&self) -> Box<dyn ModuleBacktrace>;
}
impl BacktraceProvider for () {
fn capture(&self) -> Box<dyn ModuleBacktrace> {
Box::new(())
}
}
pub trait ModuleBacktrace {
fn frames(&self) -> Vec<BacktraceFrame<'_>>;
}
impl ModuleBacktrace for () {
fn frames(&self) -> Vec<BacktraceFrame<'_>> {
vec![]
}
}
#[serde_with::skip_serializing_none]
#[serde_with::serde_as]
#[derive(serde::Serialize)]
pub struct BacktraceFrame<'a> {
#[serde_as(as = "Option<DemangleSymbol>")]
pub module_name: Option<&'a str>,
#[serde_as(as = "Option<DemangleSymbol>")]
pub func_name: Option<&'a str>,
}
struct DemangleSymbol;
impl serde_with::SerializeAs<&str> for DemangleSymbol {
fn serialize_as<S>(source: &&str, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if let Ok(sym) = rustc_demangle::try_demangle(source) {
serializer.serialize_str(&sym.to_string())
} else {
serializer.serialize_str(source)
}
}
}
#[serde_with::skip_serializing_none]
#[derive(serde::Serialize)]
#[serde(tag = "level")]
enum LogEvent<'a> {
Error(Record<'a>),
Warn(Record<'a>),
Info(Record<'a>),
Debug(Record<'a>),
Trace(Record<'a>),
Panic {
#[serde(flatten)]
record: Record<'a>,
trace: &'a [BacktraceFrame<'a>],
},
}
impl DatabaseLogger {
pub fn filepath(address: &Address, instance_id: u64) -> PathBuf {
let root = crate::stdb_path("worker_node/database_instances");
root.join(address.to_hex())
.join(instance_id.to_string())
.join("module_logs")
}
pub fn open(root: impl AsRef<Path>) -> Self {
let root = root.as_ref();
fs::create_dir_all(root).unwrap();
let mut filepath = PathBuf::from(root);
filepath.push(&PathBuf::from_str("0.log").unwrap());
let file = OpenOptions::new().create(true).append(true).open(&filepath).unwrap();
let (tx, _) = broadcast::channel(64);
Self { file, tx }
}
pub fn _delete(&mut self) {
self.file.set_len(0).unwrap();
self.file.seek(SeekFrom::End(0)).unwrap();
}
pub fn write(&mut self, level: LogLevel, &record: &Record<'_>, bt: &dyn BacktraceProvider) {
let (trace, frames);
let event = match level {
LogLevel::Error => LogEvent::Error(record),
LogLevel::Warn => LogEvent::Warn(record),
LogLevel::Info => LogEvent::Info(record),
LogLevel::Debug => LogEvent::Debug(record),
LogLevel::Trace => LogEvent::Trace(record),
LogLevel::Panic => {
trace = bt.capture();
frames = trace.frames();
LogEvent::Panic { record, trace: &frames }
}
};
let mut buf = serde_json::to_string(&event).unwrap();
buf.push('\n');
self.file.write_all(buf.as_bytes()).unwrap();
let _ = self.tx.send(buf.into());
}
pub async fn _read_all(root: &Path) -> String {
let filepath = root.join("0.log");
tokio::fs::read_to_string(&filepath).await.unwrap()
}
pub async fn read_latest(root: &Path, num_lines: Option<u32>) -> String {
let filepath = root.join("0.log");
let text = tokio::fs::read_to_string(&filepath).await.expect("reading file");
let Some(num_lines) = num_lines else { return text };
let off_from_end = text
.split_inclusive('\n')
.rev()
.take(num_lines as usize)
.map(|line| line.len())
.sum::<usize>();
text[text.len() - off_from_end..].to_owned()
}
}