1use std::cmp::min;
2
3use borderless::{
4 __private::storage_keys::{StorageKey, BASE_KEY_LOGS},
5 http::{queries::Pagination, PaginatedElements},
6 log::{LogLevel, LogLine},
7 prelude::Id,
8};
9use borderless_kv_store::*;
10use serde::{Deserialize, Serialize};
11
12use crate::log_shim::{debug, error, info, trace, warn};
13use crate::{Result, AGENT_SUB_DB, CONTRACT_SUB_DB};
14
15const SUB_KEY_META: u64 = u64::MAX;
17
18const MAX_LOG_BUFFER_SIZE: u64 = 32 * 1024;
20
21#[derive(Serialize, Deserialize, Default)]
22struct BufferMeta {
23 start: u64,
24 end: u64,
25 last_flush_start: u64,
27 last_flush_count: u64,
29}
30
31pub struct Logger<'a, S: Db> {
35 db: &'a S,
36 id: Id,
37}
38
39impl<'a, S: Db> Logger<'a, S> {
40 pub fn new(db: &'a S, id: impl Into<Id>) -> Self {
41 Self { db, id: id.into() }
42 }
43
44 pub fn flush_lines(
67 &self,
68 lines: &[LogLine],
69 db_ptr: &S::Handle,
70 txn: &mut <S as Db>::RwTx<'_>,
71 ) -> Result<()> {
72 let meta_key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, SUB_KEY_META);
74 let mut meta = match txn.read(db_ptr, &meta_key)? {
75 Some(bytes) => postcard::from_bytes(bytes)?,
76 None => {
77 let meta = BufferMeta::default();
79 let bytes = postcard::to_allocvec(&meta)?;
80 txn.write(db_ptr, &meta_key, &bytes)?;
81 meta
82 }
83 };
84
85 let new_line_count = lines.len() as u64;
86 let current_count = meta.end - meta.start;
87
88 if current_count + new_line_count > MAX_LOG_BUFFER_SIZE {
90 let drop_count = current_count + new_line_count - MAX_LOG_BUFFER_SIZE;
91 meta.start += drop_count;
92 }
93
94 meta.last_flush_start = meta.end;
96 meta.last_flush_count = new_line_count;
97
98 for (i, line) in lines.iter().enumerate() {
100 let index = (meta.end + i as u64) % MAX_LOG_BUFFER_SIZE;
101 let key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, index);
102 let bytes = postcard::to_allocvec(line)?;
103 txn.write(db_ptr, &key, &bytes)?;
104 }
105
106 meta.end += new_line_count;
108 let meta_bytes = postcard::to_allocvec(&meta)?;
109 txn.write(db_ptr, &meta_key, &meta_bytes)?;
110 Ok(())
111 }
112
113 pub fn get_full_log(&self) -> Result<Vec<LogLine>> {
115 self.get_log_lines(0, MAX_LOG_BUFFER_SIZE)
116 }
117
118 pub fn get_log_lines(&self, start_offset: u64, count: u64) -> Result<Vec<LogLine>> {
127 let db_ptr = match self.id {
128 Id::Contract { .. } => self.db.open_sub_db(CONTRACT_SUB_DB)?,
129 Id::Agent { .. } => self.db.open_sub_db(AGENT_SUB_DB)?,
130 };
131 let txn = self.db.begin_ro_txn()?;
132 let meta_key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, SUB_KEY_META);
133 let meta = match txn.read(&db_ptr, &meta_key)? {
135 Some(bytes) => postcard::from_bytes(bytes)?,
136 None => BufferMeta::default(),
137 };
138
139 let total_count = meta.end - meta.start;
140 if start_offset >= total_count {
142 return Ok(Vec::new());
143 }
144 let range_start = meta.start + start_offset;
146 let range_end = min(range_start + count, meta.end);
147
148 let mut logs = Vec::new();
149 for i in range_start..range_end {
151 let index = i % MAX_LOG_BUFFER_SIZE;
153 let key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, index);
154 if let Some(bytes) = txn.read(&db_ptr, &key)? {
155 let log_line: LogLine = postcard::from_bytes(bytes)?;
156 logs.push(log_line);
157 }
158 }
159 Ok(logs)
160 }
161
162 pub fn get_last_log(&self) -> Result<Vec<LogLine>> {
164 let db_ptr = match self.id {
165 Id::Contract { .. } => self.db.open_sub_db(CONTRACT_SUB_DB)?,
166 Id::Agent { .. } => self.db.open_sub_db(AGENT_SUB_DB)?,
167 };
168 let txn = self.db.begin_ro_txn()?;
169 let meta_key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, SUB_KEY_META);
170
171 let meta: BufferMeta = match txn.read(&db_ptr, &meta_key)? {
172 Some(bytes) => postcard::from_bytes(bytes)?,
173 None => return Ok(Vec::new()),
174 };
175
176 let mut logs = Vec::new();
177 let flush_start = meta.last_flush_start;
178 let flush_count = meta.last_flush_count;
179
180 for i in flush_start..(flush_start + flush_count) {
182 let index = i % MAX_LOG_BUFFER_SIZE;
184 let key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, index);
185 if let Some(bytes) = txn.read(&db_ptr, &key)? {
186 let log_line: LogLine = postcard::from_bytes(bytes)?;
187 logs.push(log_line);
188 }
189 }
190 Ok(logs)
191 }
192
193 pub fn total_log_lines(&self) -> Result<u64> {
199 let db_ptr = match self.id {
200 Id::Contract { .. } => self.db.open_sub_db(CONTRACT_SUB_DB)?,
201 Id::Agent { .. } => self.db.open_sub_db(AGENT_SUB_DB)?,
202 };
203 let txn = self.db.begin_ro_txn()?;
204 let meta_key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, SUB_KEY_META);
205 let meta = match txn.read(&db_ptr, &meta_key)? {
207 Some(bytes) => postcard::from_bytes(bytes)?,
208 None => BufferMeta::default(),
209 };
210 Ok(meta.end)
211 }
212
213 pub fn get_logs_paginated(&self, pagination: Pagination) -> Result<PaginatedElements<LogLine>> {
215 let page = pagination.page as u64;
216 let per_page = pagination.per_page as u64;
217 let db_ptr = match self.id {
218 Id::Contract { .. } => self.db.open_sub_db(CONTRACT_SUB_DB)?,
219 Id::Agent { .. } => self.db.open_sub_db(AGENT_SUB_DB)?,
220 };
221 let txn = self.db.begin_ro_txn()?;
222 let meta_key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, SUB_KEY_META);
223
224 let meta = match txn.read(&db_ptr, &meta_key)? {
226 Some(bytes) => postcard::from_bytes(bytes)?,
227 None => BufferMeta {
228 start: 0,
229 end: 0,
230 last_flush_start: 0,
231 last_flush_count: 0,
232 },
233 };
234
235 let total_count = meta.end - meta.start;
237 let total_pages = if total_count == 0 {
239 0
240 } else {
241 (total_count + per_page - 1) / per_page
242 };
243 let total_elements = (total_pages * per_page) as usize;
244
245 let page_start = meta.start + page.saturating_sub(1) * per_page;
247 if page_start >= meta.end {
249 return Ok(PaginatedElements {
250 elements: Vec::new(),
251 total_elements,
252 pagination,
253 });
254 }
255 let page_end = std::cmp::min(meta.start + page * per_page, meta.end);
256
257 let mut logs = Vec::new();
259 for i in page_start..page_end {
260 let physical_index = i % MAX_LOG_BUFFER_SIZE;
262 let key = StorageKey::system_key(&self.id, BASE_KEY_LOGS, physical_index);
263 if let Some(bytes) = txn.read(&db_ptr, &key)? {
264 let log_line: LogLine = postcard::from_bytes(bytes)?;
265 logs.push(log_line);
266 }
267 }
268 Ok(PaginatedElements {
269 elements: logs,
270 total_elements,
271 pagination,
272 })
273 }
274}
275
276pub fn print_log_line(line: LogLine) {
280 let msg = line.msg;
281 match line.level {
282 LogLevel::Trace => trace!("{msg}"),
283 LogLevel::Debug => debug!("{msg}"),
284 LogLevel::Info => info!("{msg}"),
285 LogLevel::Warn => warn!("{msg}"),
286 LogLevel::Error => error!("{msg}"),
287 }
288}