canic_core/ops/model/memory/
log.rs1use crate::{
2 Error,
3 interface::ic::timer::{Timer, TimerId},
4 log,
5 log::{Level, Topic},
6 model::memory::log::{LogEntry, StableLog, apply_retention},
7 ops::{
8 model::{OPS_INIT_DELAY, OPS_LOG_RETENTION_INTERVAL},
9 timer::TimerOps,
10 },
11 types::PageRequest,
12};
13use candid::CandidType;
14use serde::Serialize;
15use std::{cell::RefCell, time::Duration};
16
17thread_local! {
18 static RETENTION_TIMER: RefCell<Option<TimerId>> = const { RefCell::new(None) };
19}
20
21const RETENTION_INTERVAL: Duration = OPS_LOG_RETENTION_INTERVAL;
23
24#[derive(CandidType, Clone, Debug, Serialize)]
29pub struct LogEntryDto {
30 pub index: u64,
31 pub created_at: u64,
32 pub crate_name: String,
33 pub level: Level,
34 pub topic: Option<String>,
35 pub message: String,
36}
37
38impl LogEntryDto {
39 fn from_pair(index: usize, entry: LogEntry) -> Self {
40 Self {
41 index: index as u64,
42 created_at: entry.created_at,
43 crate_name: entry.crate_name,
44 level: entry.level,
45 topic: entry.topic,
46 message: entry.message,
47 }
48 }
49}
50
51#[derive(CandidType, Serialize)]
56pub struct LogPageDto {
57 pub entries: Vec<LogEntryDto>,
58 pub total: u64,
59}
60
61pub struct LogOps;
66
67impl LogOps {
68 pub fn start_retention() {
70 RETENTION_TIMER.with_borrow_mut(|slot| {
71 if slot.is_some() {
72 return;
73 }
74
75 let init = TimerOps::set(OPS_INIT_DELAY, "log_retention:init", async {
76 let _ = Self::retain();
77
78 let interval = TimerOps::set_interval(
79 RETENTION_INTERVAL,
80 "log_retention:interval",
81 || async {
82 let _ = Self::retain();
83 },
84 );
85
86 RETENTION_TIMER.with_borrow_mut(|slot| *slot = Some(interval));
87 });
88
89 *slot = Some(init);
90 });
91 }
92
93 pub fn stop_retention() {
95 RETENTION_TIMER.with_borrow_mut(|slot| {
96 if let Some(id) = slot.take() {
97 Timer::clear(id);
98 }
99 });
100 }
101
102 #[must_use]
104 pub fn retain() -> bool {
105 match apply_retention() {
106 Ok(()) => true,
107 Err(err) => {
108 log!(Topic::Memory, Warn, "log retention failed: {err}");
109 false
110 }
111 }
112 }
113
114 pub fn append<T: ToString, M: AsRef<str>>(
116 crate_name: &str,
117 topic: Option<T>,
118 level: Level,
119 message: M,
120 ) -> Result<u64, Error> {
121 StableLog::append(crate_name, topic, level, message)
122 }
123
124 #[must_use]
128 pub fn page(
129 crate_name: Option<String>,
130 topic: Option<String>,
131 min_level: Option<Level>,
132 request: PageRequest,
133 ) -> LogPageDto {
134 let request = request.clamped();
135
136 let (raw_entries, total) = StableLog::entries_page_filtered(
137 crate_name.as_deref(),
138 topic.as_deref(),
139 min_level,
140 request,
141 );
142
143 let entries = raw_entries
144 .into_iter()
145 .map(|(i, entry)| LogEntryDto::from_pair(i, entry))
146 .collect();
147
148 LogPageDto { entries, total }
149 }
150}