referrerpolicy=no-referrer-when-downgrade

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}