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(summary) => {
85 let dropped = summary.dropped_total();
86 if dropped > 0 {
87 let before = summary.before;
88 let retained = summary.retained;
89 let dropped_by_age = summary.dropped_by_age;
90 let dropped_by_limit = summary.dropped_by_limit;
91 log!(
92 Topic::Memory,
93 Info,
94 "log retention: dropped={dropped} (age={dropped_by_age}, limit={dropped_by_limit}), before={before}, retained={retained}"
95 );
96 }
97 true
98 }
99 Err(err) => {
100 log!(Topic::Memory, Warn, "log retention failed: {err}");
101 false
102 }
103 }
104 }
105
106 pub fn append<T, M>(
108 crate_name: &str,
109 topic: Option<T>,
110 level: Level,
111 message: M,
112 ) -> Result<u64, Error>
113 where
114 T: ToString,
115 M: AsRef<str>,
116 {
117 StableLog::append(crate_name, topic, level, message)
118 }
119
120 #[must_use]
124 pub fn page(
125 crate_name: Option<String>,
126 topic: Option<String>,
127 min_level: Option<Level>,
128 request: PageRequest,
129 ) -> Page<LogEntryDto> {
130 let request = request.clamped();
131
132 let (raw_entries, total) = StableLog::entries_page_filtered(
133 crate_name.as_deref(),
134 topic.as_deref(),
135 min_level,
136 request,
137 );
138
139 let entries = raw_entries
140 .into_iter()
141 .map(|(i, entry)| LogEntryDto::from_indexed_entry(i, entry))
142 .collect();
143
144 Page { entries, total }
145 }
146}