canic_core/ops/runtime/
log.rs1use crate::{
2 Error,
3 dto::page::{Page, PageRequest},
4 log,
5 log::{Level, Topic},
6 model::memory::log::{LogEntry, StableLog, apply_retention},
7 ops::{
8 OPS_INIT_DELAY, OPS_LOG_RETENTION_INTERVAL,
9 ic::timer::{TimerId, TimerOps},
10 },
11};
12use candid::CandidType;
13use serde::{Deserialize, Serialize};
14use std::{cell::RefCell, time::Duration};
15
16thread_local! {
17 static RETENTION_TIMER: RefCell<Option<TimerId>> = const { RefCell::new(None) };
18}
19
20const RETENTION_INTERVAL: Duration = OPS_LOG_RETENTION_INTERVAL;
22
23#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
28pub struct LogEntryDto {
29 pub index: u64,
30 pub created_at: u64,
31 pub crate_name: String,
32 pub level: Level,
33 pub topic: Option<String>,
34 pub message: String,
35}
36
37impl LogEntryDto {
38 fn from_indexed_entry(index: usize, entry: LogEntry) -> Self {
39 Self {
40 index: index as u64,
41 created_at: entry.created_at,
42 crate_name: entry.crate_name,
43 level: entry.level,
44 topic: entry.topic,
45 message: entry.message,
46 }
47 }
48}
49
50pub struct LogOps;
55
56impl LogOps {
57 pub fn start_retention() {
59 RETENTION_TIMER.with_borrow_mut(|slot| {
60 if slot.is_some() {
61 return;
62 }
63
64 let init = TimerOps::set(OPS_INIT_DELAY, "log_retention:init", async {
65 let _ = Self::retain();
66
67 let interval = TimerOps::set_interval(
68 RETENTION_INTERVAL,
69 "log_retention:interval",
70 || async {
71 let _ = Self::retain();
72 },
73 );
74
75 RETENTION_TIMER.with_borrow_mut(|slot| *slot = Some(interval));
76 });
77
78 *slot = Some(init);
79 });
80 }
81
82 pub fn stop_retention() {
84 RETENTION_TIMER.with_borrow_mut(|slot| {
85 if let Some(id) = slot.take() {
86 TimerOps::clear(id);
87 }
88 });
89 }
90
91 #[must_use]
94 pub fn retain() -> bool {
95 match apply_retention() {
96 Ok(()) => true,
97 Err(err) => {
98 log!(Topic::Memory, Warn, "log retention failed: {err}");
99 false
100 }
101 }
102 }
103
104 pub fn append<T, M>(
106 crate_name: &str,
107 topic: Option<T>,
108 level: Level,
109 message: M,
110 ) -> Result<u64, Error>
111 where
112 T: ToString,
113 M: AsRef<str>,
114 {
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 ) -> Page<LogEntryDto> {
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_indexed_entry(i, entry))
140 .collect();
141
142 Page { entries, total }
143 }
144}