canic_core/ops/model/memory/
log.rs1use crate::{
2 Error,
3 cdk::timers::{TimerId, clear_timer, set_timer, set_timer_interval},
4 log,
5 log::{Level, Topic},
6 model::memory::log::{LogEntry, StableLog, apply_retention},
7 ops::model::{OPS_INIT_DELAY, OPS_LOG_RETENTION_INTERVAL},
8};
9use candid::CandidType;
10use serde::Serialize;
11use std::{cell::RefCell, time::Duration};
12
13thread_local! {
14 static RETENTION_TIMER: RefCell<Option<TimerId>> = const { RefCell::new(None) };
15}
16
17const RETENTION_INTERVAL: Duration = OPS_LOG_RETENTION_INTERVAL;
19
20#[derive(CandidType, Clone, Debug, Serialize)]
25pub struct LogEntryDto {
26 pub index: u64,
27 pub created_at: u64,
28 pub crate_name: String,
29 pub level: Level,
30 pub topic: Option<String>,
31 pub message: String,
32}
33
34impl LogEntryDto {
35 fn from_pair(index: usize, entry: LogEntry) -> Self {
36 Self {
37 index: index as u64,
38 created_at: entry.created_at,
39 crate_name: entry.crate_name,
40 level: entry.level,
41 topic: entry.topic,
42 message: entry.message,
43 }
44 }
45}
46
47#[derive(CandidType, Serialize)]
52pub struct LogPageDto {
53 pub entries: Vec<LogEntryDto>,
54 pub total: u64,
55}
56
57pub struct LogOps;
62
63impl LogOps {
64 pub fn start_retention() {
66 RETENTION_TIMER.with_borrow_mut(|slot| {
67 if slot.is_some() {
68 return;
69 }
70
71 let init = set_timer(OPS_INIT_DELAY, async {
72 let _ = Self::retain();
73
74 let interval = set_timer_interval(RETENTION_INTERVAL, || async {
75 let _ = Self::retain();
76 });
77
78 RETENTION_TIMER.with_borrow_mut(|slot| *slot = Some(interval));
79 });
80
81 *slot = Some(init);
82 });
83 }
84
85 pub fn stop_retention() {
87 RETENTION_TIMER.with_borrow_mut(|slot| {
88 if let Some(id) = slot.take() {
89 clear_timer(id);
90 }
91 });
92 }
93
94 #[must_use]
96 pub fn retain() -> bool {
97 match apply_retention() {
98 Ok(()) => true,
99 Err(err) => {
100 log!(Topic::Memory, Warn, "log retention failed: {err}");
101 false
102 }
103 }
104 }
105
106 pub fn append<T: ToString, M: AsRef<str>>(
108 crate_name: &str,
109 topic: Option<T>,
110 level: Level,
111 message: M,
112 ) -> Result<u64, Error> {
113 StableLog::append(crate_name, topic, level, message)
114 }
115
116 #[must_use]
120 pub fn page(
121 crate_name: Option<String>,
122 topic: Option<String>,
123 min_level: Option<Level>,
124 offset: u64,
125 limit: u64,
126 ) -> LogPageDto {
127 let (raw_entries, total) = StableLog::entries_page_filtered(
128 crate_name.as_deref(),
129 topic.as_deref(),
130 min_level,
131 offset,
132 limit,
133 );
134
135 let entries = raw_entries
136 .into_iter()
137 .map(|(i, entry)| LogEntryDto::from_pair(i, entry))
138 .collect();
139
140 LogPageDto { entries, total }
141 }
142}