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