tracing_gum/lib.rs
1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
16
17#![deny(unused_crate_dependencies)]
18#![deny(missing_docs)]
19#![deny(clippy::dbg_macro)]
20
21//! A wrapper around `tracing` macros, to provide semi automatic
22//! `traceID` annotation without codebase turnover.
23//!
24//! # Usage
25//!
26//! The API follows the [`tracing`
27//! API](https://docs.rs/tracing/latest/tracing/index.html), but the docs contain
28//! more detail than you probably need to know, so here's the quick version.
29//!
30//! Most common usage is of the form:
31//!
32//! ```rs
33//! gum::warn!(
34//! target: LOG_TARGET,
35//! worker_pid = %idle_worker.pid,
36//! ?error,
37//! "failed to send a handshake to the spawned worker",
38//! );
39//! ```
40//!
41//! ### Log levels
42//!
43//! All of the [`tracing` macros](https://docs.rs/tracing/latest/tracing/index.html#macros) are available.
44//! In decreasing order of priority they are:
45//!
46//! - `error!`
47//! - `warn!`
48//! - `info!`
49//! - `debug!`
50//! - `trace!`
51//!
52//! ### `target`
53//!
54//! The `LOG_TARGET` should be defined once per crate, e.g.:
55//!
56//! ```rs
57//! const LOG_TARGET: &str = "parachain::pvf";
58//! ```
59//!
60//! This should be of the form `<target>::<subtarget>`, where the `::<subtarget>` is optional.
61//!
62//! The target and subtarget are used when debugging by specializing the Grafana Loki query to
63//! filter specific subsystem logs. The more specific the query is the better when approaching the
64//! query response limit.
65//!
66//! ### Fields
67//!
68//! Here's the rundown on how fields work:
69//!
70//! - Fields on spans and events are specified using the `syntax field_name = field_value`.
71//! - Local variables may be used as field values without an assignment, similar to struct
72//! initializers.
73//! - The `?` sigil is shorthand that specifies a field should be recorded using its `fmt::Debug`
74//! implementation.
75//! - The `%` sigil operates similarly, but indicates that the value should be recorded using its
76//! `fmt::Display` implementation.
77//!
78//! For full details, again see [the tracing
79//! docs](https://docs.rs/tracing/latest/tracing/index.html#recording-fields).
80//!
81//! ### Viewing traces
82//!
83//! When testing,
84//!
85//! ```rs
86//! sp_tracing::init_for_tests();
87//! ```
88//!
89//! should enable all trace logs.
90//!
91//! Alternatively, you can do:
92//!
93//! ```rs
94//! sp_tracing::try_init_simple();
95//! ```
96//!
97//! On the command line you specify `RUST_LOG` with the desired target and trace level:
98//!
99//! ```sh
100//! RUST_LOG=parachain::pvf=trace cargo test
101//! ```
102//!
103//! On the other hand if you want all `parachain` logs, specify `parachain=trace`, which will also
104//! include logs from `parachain::pvf` and other subtargets.
105
106pub use tracing::{enabled, event, Level};
107
108// jaeger dependency
109
110/// Alias for the 16 byte unique identifier used with jaeger.
111pub(crate) type TraceIdentifier = u128;
112
113/// A helper to convert the hash to the fixed size representation
114/// needed for jaeger.
115#[inline]
116pub fn hash_to_trace_identifier(hash: Hash) -> TraceIdentifier {
117 let mut buf = [0u8; 16];
118 buf.copy_from_slice(&hash.as_ref()[0..16]);
119 // The slice bytes are copied in reading order, so if interpreted
120 // in string form by a human, that means lower indices have higher
121 // values and hence corresponds to BIG endian ordering of the individual
122 // bytes.
123 u128::from_be_bytes(buf) as TraceIdentifier
124}
125
126#[doc(hidden)]
127pub use polkadot_primitives::{CandidateHash, Hash};
128
129pub use gum_proc_macro::{debug, error, info, trace, warn, warn_if_frequent};
130
131#[cfg(test)]
132mod tests;
133
134const FREQ_SMOOTHING_FACTOR: f32 = 0.5;
135
136/// Exponential moving average
137#[derive(Debug, Default)]
138struct EmaBucket {
139 current: f32,
140 count: u32,
141}
142
143impl EmaBucket {
144 fn update(&mut self, value: f32, alpha: f32) {
145 if self.count == 0 {
146 self.current = value;
147 } else {
148 self.current += alpha * (value - self.current);
149 }
150 self.count += 1;
151 }
152}
153
154/// Utility struct to compare the rate of its own calls.
155pub struct Freq {
156 ema: EmaBucket,
157 last: u64,
158}
159
160impl Freq {
161 /// Initiates a new instance
162 pub fn new() -> Self {
163 Self { ema: Default::default(), last: Default::default() }
164 }
165
166 /// Compares the rate of its own calls with the passed one.
167 pub fn is_frequent(&mut self, max_rate: Times) -> bool {
168 self.record();
169
170 // Two attempts is not enough to call something as frequent.
171 if self.ema.count < 3 {
172 return false
173 }
174
175 let rate = 1000.0 / self.ema.current; // Current EMA represents interval in ms
176 rate > max_rate.into()
177 }
178
179 fn record(&mut self) {
180 let now = coarsetime::Clock::now_since_epoch().as_millis() as u64;
181 if self.last > 0 {
182 self.ema.update((now - self.last) as f32, FREQ_SMOOTHING_FACTOR);
183 }
184 self.last = now;
185 }
186}
187
188/// Represents frequency per second, minute, hour and day
189pub enum Times {
190 /// Per second
191 PerSecond(u32),
192 /// Per minute
193 PerMinute(u32),
194 /// Per hour
195 PerHour(u32),
196 /// Per day
197 PerDay(u32),
198}
199
200impl From<Times> for f32 {
201 fn from(value: Times) -> Self {
202 match value {
203 Times::PerSecond(v) => v as f32,
204 Times::PerMinute(v) => v as f32 / 60.0,
205 Times::PerHour(v) => v as f32 / (60.0 * 60.0),
206 Times::PerDay(v) => v as f32 / (60.0 * 60.0 * 24.0),
207 }
208 }
209}