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 let _ = TimerOps::set_guarded_interval(
60 &RETENTION_TIMER,
61 OPS_INIT_DELAY,
62 "log_retention:init",
63 || async {
64 let _ = Self::retain();
65 },
66 RETENTION_INTERVAL,
67 "log_retention:interval",
68 || async {
69 let _ = Self::retain();
70 },
71 );
72 }
73
74 pub fn stop_retention() {
76 let _ = TimerOps::clear_guarded(&RETENTION_TIMER);
77 }
78
79 #[must_use]
82 pub fn retain() -> bool {
83 match apply_retention() {
84 Ok(()) => true,
85 Err(err) => {
86 log!(Topic::Memory, Warn, "log retention failed: {err}");
87 false
88 }
89 }
90 }
91
92 pub fn append<T, M>(
94 crate_name: &str,
95 topic: Option<T>,
96 level: Level,
97 message: M,
98 ) -> Result<u64, Error>
99 where
100 T: ToString,
101 M: AsRef<str>,
102 {
103 StableLog::append(crate_name, topic, level, message)
104 }
105
106 #[must_use]
110 pub fn page(
111 crate_name: Option<String>,
112 topic: Option<String>,
113 min_level: Option<Level>,
114 request: PageRequest,
115 ) -> Page<LogEntryDto> {
116 let request = request.clamped();
117
118 let (raw_entries, total) = StableLog::entries_page_filtered(
119 crate_name.as_deref(),
120 topic.as_deref(),
121 min_level,
122 request,
123 );
124
125 let entries = raw_entries
126 .into_iter()
127 .map(|(i, entry)| LogEntryDto::from_indexed_entry(i, entry))
128 .collect();
129
130 Page { entries, total }
131 }
132}