use std::cmp::min;
use borderless::{
__private::storage_keys::{StorageKey, BASE_KEY_LOGS},
http::{queries::Pagination, PaginatedElements},
log::{LogLevel, LogLine},
prelude::Id,
};
use borderless_kv_store::*;
use serde::{Deserialize, Serialize};
use crate::log_shim::{debug, error, info, trace, warn};
use crate::{Result, AGENT_SUB_DB, CONTRACT_SUB_DB};
const SUB_KEY_META: u64 = u64::MAX;
const MAX_LOG_BUFFER_SIZE: u64 = 32 * 1024;
#[derive(Serialize, Deserialize, Default)]
struct BufferMeta {
start: u64,
end: u64,
last_flush_start: u64,
last_flush_count: u64,
}
pub struct Logger<'a, S: Db> {
db: &'a S,
id: Id,
}
impl<'a, S: Db> Logger<'a, S> {
pub fn new(db: &'a S, id: impl Into<Id>) -> Self {
Self { db, id: id.into() }
}
pub fn flush_lines(
&self,
lines: &[LogLine],
db_ptr: &S::Handle,
txn: &mut <S as Db>::RwTx<'_>,
) -> Result<()> {
let meta_key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, SUB_KEY_META);
let mut meta = match txn.read(db_ptr, &meta_key)? {
Some(bytes) => postcard::from_bytes(bytes)?,
None => {
let meta = BufferMeta::default();
let bytes = postcard::to_allocvec(&meta)?;
txn.write(db_ptr, &meta_key, &bytes)?;
meta
}
};
let new_line_count = lines.len() as u64;
let current_count = meta.end - meta.start;
if current_count + new_line_count > MAX_LOG_BUFFER_SIZE {
let drop_count = current_count + new_line_count - MAX_LOG_BUFFER_SIZE;
meta.start += drop_count;
}
meta.last_flush_start = meta.end;
meta.last_flush_count = new_line_count;
for (i, line) in lines.iter().enumerate() {
let index = (meta.end + i as u64) % MAX_LOG_BUFFER_SIZE;
let key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, index);
let bytes = postcard::to_allocvec(line)?;
txn.write(db_ptr, &key, &bytes)?;
}
meta.end += new_line_count;
let meta_bytes = postcard::to_allocvec(&meta)?;
txn.write(db_ptr, &meta_key, &meta_bytes)?;
Ok(())
}
pub fn get_full_log(&self) -> Result<Vec<LogLine>> {
self.get_log_lines(0, MAX_LOG_BUFFER_SIZE)
}
pub fn get_log_lines(&self, start_offset: u64, count: u64) -> Result<Vec<LogLine>> {
let db_ptr = match self.id {
Id::Contract { .. } => self.db.open_sub_db(CONTRACT_SUB_DB)?,
Id::Agent { .. } => self.db.open_sub_db(AGENT_SUB_DB)?,
};
let txn = self.db.begin_ro_txn()?;
let meta_key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, SUB_KEY_META);
let meta = match txn.read(&db_ptr, &meta_key)? {
Some(bytes) => postcard::from_bytes(bytes)?,
None => BufferMeta::default(),
};
let total_count = meta.end - meta.start;
if start_offset >= total_count {
return Ok(Vec::new());
}
let range_start = meta.start + start_offset;
let range_end = min(range_start + count, meta.end);
let mut logs = Vec::new();
for i in range_start..range_end {
let index = i % MAX_LOG_BUFFER_SIZE;
let key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, index);
if let Some(bytes) = txn.read(&db_ptr, &key)? {
let log_line: LogLine = postcard::from_bytes(bytes)?;
logs.push(log_line);
}
}
Ok(logs)
}
pub fn get_last_log(&self) -> Result<Vec<LogLine>> {
let db_ptr = match self.id {
Id::Contract { .. } => self.db.open_sub_db(CONTRACT_SUB_DB)?,
Id::Agent { .. } => self.db.open_sub_db(AGENT_SUB_DB)?,
};
let txn = self.db.begin_ro_txn()?;
let meta_key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, SUB_KEY_META);
let meta: BufferMeta = match txn.read(&db_ptr, &meta_key)? {
Some(bytes) => postcard::from_bytes(bytes)?,
None => return Ok(Vec::new()),
};
let mut logs = Vec::new();
let flush_start = meta.last_flush_start;
let flush_count = meta.last_flush_count;
for i in flush_start..(flush_start + flush_count) {
let index = i % MAX_LOG_BUFFER_SIZE;
let key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, index);
if let Some(bytes) = txn.read(&db_ptr, &key)? {
let log_line: LogLine = postcard::from_bytes(bytes)?;
logs.push(log_line);
}
}
Ok(logs)
}
pub fn total_log_lines(&self) -> Result<u64> {
let db_ptr = match self.id {
Id::Contract { .. } => self.db.open_sub_db(CONTRACT_SUB_DB)?,
Id::Agent { .. } => self.db.open_sub_db(AGENT_SUB_DB)?,
};
let txn = self.db.begin_ro_txn()?;
let meta_key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, SUB_KEY_META);
let meta = match txn.read(&db_ptr, &meta_key)? {
Some(bytes) => postcard::from_bytes(bytes)?,
None => BufferMeta::default(),
};
Ok(meta.end)
}
pub fn get_logs_paginated(&self, pagination: Pagination) -> Result<PaginatedElements<LogLine>> {
let page = pagination.page as u64;
let per_page = pagination.per_page as u64;
let db_ptr = match self.id {
Id::Contract { .. } => self.db.open_sub_db(CONTRACT_SUB_DB)?,
Id::Agent { .. } => self.db.open_sub_db(AGENT_SUB_DB)?,
};
let txn = self.db.begin_ro_txn()?;
let meta_key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, SUB_KEY_META);
let meta = match txn.read(&db_ptr, &meta_key)? {
Some(bytes) => postcard::from_bytes(bytes)?,
None => BufferMeta {
start: 0,
end: 0,
last_flush_start: 0,
last_flush_count: 0,
},
};
let total_count = meta.end - meta.start;
let total_pages = if total_count == 0 {
0
} else {
(total_count + per_page - 1) / per_page
};
let total_elements = (total_pages * per_page) as usize;
let page_start = meta.start + page.saturating_sub(1) * per_page;
if page_start >= meta.end {
return Ok(PaginatedElements {
elements: Vec::new(),
total_elements,
pagination,
});
}
let page_end = std::cmp::min(meta.start + page * per_page, meta.end);
let mut logs = Vec::new();
for i in page_start..page_end {
let physical_index = i % MAX_LOG_BUFFER_SIZE;
let key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, physical_index);
if let Some(bytes) = txn.read(&db_ptr, &key)? {
let log_line: LogLine = postcard::from_bytes(bytes)?;
logs.push(log_line);
}
}
Ok(PaginatedElements {
elements: logs,
total_elements,
pagination,
})
}
}
pub fn print_log_line(line: LogLine) {
let msg = line.msg;
match line.level {
LogLevel::Trace => trace!("{msg}"),
LogLevel::Debug => debug!("{msg}"),
LogLevel::Info => info!("{msg}"),
LogLevel::Warn => warn!("{msg}"),
LogLevel::Error => error!("{msg}"),
}
}