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>> {
216 let page = pagination.page as u64;
217 let per_page = pagination.per_page as u64;
218 let db_ptr = match self.id {
219 Id::Contract { .. } => self.db.open_sub_db(CONTRACT_SUB_DB)?,
220 Id::Agent { .. } => self.db.open_sub_db(AGENT_SUB_DB)?,
221 };
222 let txn = self.db.begin_ro_txn()?;
223 let meta_key = StorageKey::system_key(self.id, BASE_KEY_LOGS, SUB_KEY_META);
224
225 let meta = match txn.read(&db_ptr, &meta_key)? {
227 Some(bytes) => postcard::from_bytes(bytes)?,
228 None => BufferMeta {
229 start: 0,
230 end: 0,
231 last_flush_start: 0,
232 last_flush_count: 0,
233 },
234 };
235
236 let total_count = meta.end - meta.start;
238 let total_pages = if total_count == 0 {
240 0
241 } else {
242 (total_count + per_page - 1) / per_page
243 };
244 let total_elements = (total_pages * per_page) as usize;
245
246 let page_start = meta.start + page.saturating_sub(1) * per_page;
248 if page_start >= meta.end {
250 return Ok(PaginatedElements {
251 elements: Vec::new(),
252 total_elements,
253 pagination,
254 });
255 }
256 let page_end = std::cmp::min(meta.start + page * per_page, meta.end);
257
258 let mut logs = Vec::new();
260 for i in page_start..page_end {
261 let physical_index = i % MAX_LOG_BUFFER_SIZE;
263 let key = StorageKey::system_key(self.id, BASE_KEY_LOGS, physical_index);
264 if let Some(bytes) = txn.read(&db_ptr, &key)? {
265 let log_line: LogLine = postcard::from_bytes(bytes)?;
266 logs.push(log_line);
267 }
268 }
269 Ok(PaginatedElements {
270 elements: logs,
271 total_elements,
272 pagination,
273 })
274 }
275}
276
277pub fn print_log_line(line: LogLine) {
281 let msg = line.msg;
282 match line.level {
283 LogLevel::Trace => trace!("{msg}"),
284 LogLevel::Debug => debug!("{msg}"),
285 LogLevel::Info => info!("{msg}"),
286 LogLevel::Warn => warn!("{msg}"),
287 LogLevel::Error => error!("{msg}"),
288 }
289}