fuel_telemetry_macros/lib.rs
1use quote::quote;
2
3extern crate proc_macro;
4
5const LOG_FILTER: &str = "RUST_LOG";
6
7// Sets environment variables from the cargo target's perspective
8//
9// These values need to come from the cargo target which can only be found
10// during macro expansion. Calling `env!('CARGO_PKG_NAME')` within
11// `telemetry_layer.rs` will be incorrect as the macro will have already
12// expanded leading to the constant value "fuel-telemetry" for all targets
13fn set_env_vars() -> proc_macro2::TokenStream {
14 quote! {
15 if std::env::var("TELEMETRY_PKG_NAME").is_err() {
16 std::env::set_var("TELEMETRY_PKG_NAME", fuel_telemetry::get_process_name());
17 }
18
19 if std::env::var("TELEMETRY_PKG_VERSION").is_err() {
20 std::env::set_var("TELEMETRY_PKG_VERSION", env!("CARGO_PKG_VERSION"));
21 }
22 }
23}
24
25// Starts the `FileWatcher` and `SystemInfoWatcher` daemon
26//
27// Warning: We need to create the `FileWatcher` and `SystemInfoWatcher`
28// before the `TelemetryLayer` as there is a race condition in the
29// thread runtime of `tracing` and the tokio runtime of `Reqwest`.
30// Swapping order of the two could lead to possible deadlocks.
31//
32// If the watchers fail to start, we silently ignore the errors as
33// telemetry should not impede the program from running.
34fn start_watchers() -> proc_macro2::TokenStream {
35 quote! {
36 // In the following, we need to log all errors but as there is no
37 // `tracing` `Subscriber` running yet, we need to fall back to appending
38 // plain text to the log file instead
39 //
40 // Another thing to note is that as this is the original process, we
41 // only exit on `Fatal` errors, meaning that we have since forked and
42 // have become a child process so can safely fatally exit
43
44 // Set global `TRACE_ID` for all watchers (`ProcessWatcher`,
45 // `FileWatcher` and `SytemInfoWatcher`).
46 fuel_telemetry::telemetry_layer::set_trace_id_env_to_new_uuid();
47
48 // Start the `ProcessWatcher`
49 match fuel_telemetry::process_watcher::ProcessWatcher::new() {
50 Ok(mut process_watcher) => {
51 if let Err(err) = process_watcher.start() {
52 let _ = fuel_telemetry::process_watcher::ProcessWatcher::log_error(&format!("Failed to start `ProcessWatcher`: {:?}", err));
53
54 if err.is_fatal() {
55 std::process::exit(1);
56 }
57 }
58 }
59 Err(err) => {
60 let _ = fuel_telemetry::process_watcher::ProcessWatcher::log_error(&format!("Failed to create `ProcessWatcher`: {:?}", err));
61 // Don't exit as this is the original process and we need to continue
62 }
63 }
64
65 // Start the `FileWatcher`
66 let mut file_watcher = fuel_telemetry::file_watcher::FileWatcher::new();
67 if let Err(err) = file_watcher.start() {
68 let _ = fuel_telemetry::file_watcher::FileWatcher::log_error(&format!("Failed to start `FileWatcher`: {:?}", err));
69
70 if err.is_fatal() {
71 std::process::exit(1);
72 }
73 }
74
75 // Start the `SystemInfoWatcher`
76 let mut systeminfo_watcher = fuel_telemetry::systeminfo_watcher::SystemInfoWatcher::new();
77 if let Err(err) = systeminfo_watcher.start() {
78 let _ = fuel_telemetry::systeminfo_watcher::SystemInfoWatcher::log_error(&format!("Failed to start `SystemInfoWatcher`: {:?}", err));
79
80 if err.is_fatal() {
81 std::process::exit(1);
82 }
83 }
84 }
85}
86
87/// Create a new `TelemetryLayer`.
88///
89/// This `tracing` `Layer` is to be used along with the `tracing` crate, and
90/// composes with other `Layer`s to create a `Subscriber`.
91///
92/// Returns a `TelemetryLayer` and a drop guard. Here, the drop guard will flush
93/// any remaining telemetry to the disk.
94///
95/// Warning: this function does not create a `FileWatcher` and
96/// `SystemInfoWatcher`, and so although telemetry files will be written to
97/// disk, they will not be sent to InfluxDB. If in doubt, prefer using
98/// `new_with_watchers!()` or `new_with_watchers_and_init!()` over `new!()`.
99///
100/// ```text
101/// use fuel_telemetry::TelemetryLayer;
102///
103/// let (telemetry_layer, _guard) = fuel_telemetry::new!()?;
104/// tracing_subscriber::registry().with(telemetry_layer).init();
105///
106/// info_telemetry!("This event will be sent to InfluxBD");
107/// ```
108#[proc_macro]
109pub fn new(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
110 let env_vars = set_env_vars();
111
112 quote! {
113 {
114 #env_vars
115
116 fuel_telemetry::TelemetryLayer::__new().and_then(|(layer, guard)| {
117 use fuel_telemetry::__reexport_EnvFilter;
118 use fuel_telemetry::__reexport_Layer;
119
120 std::env::var_os(#LOG_FILTER)
121 .map_or_else(
122 || Ok(__reexport_EnvFilter::new("info")),
123 |_| __reexport_EnvFilter::try_from_default_env()
124 .map_err(|e| fuel_telemetry::TelemetryError::InvalidEnvFilter(e.to_string()))
125 )
126 .map(|filter| (layer.inner_layer.with_filter(filter), guard))
127 })
128 }
129 }
130 .into()
131}
132
133/// A convenience macro to do `new!()` followed by creating and starting a
134/// `FileWatcher` and `SystemInfoWatcher` within a single step.
135///
136/// Returns a `TelemetryLayer` and a drop guard. Here, the drop guard will flush
137/// any remaining telemetry to the disk.
138///
139/// Use this macro if you are using `fuel-telemetry` along with other `tracing`
140/// `Layer`s within your application, or you have your own `tracing`
141/// `Subscriber`.
142///
143/// Otherwise, if you are using `fuel-telemetry` as your only `tracing`
144/// `Subscriber`, you should instead use `new_with_watchers_and_init!()`.
145///
146/// ```text
147/// use fuel_telemetry::prelude::*;
148///
149/// let (telemetry_layer, _guard) = fuel_telemetry::new_with_watchers!()?;
150/// tracing_subscriber::registry().with(telemetry_layer).init();
151///
152/// info_telemetry!("This event will be sent to InfluxBD");
153/// ```
154#[proc_macro]
155pub fn new_with_watchers(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
156 let start_watchers = start_watchers();
157
158 quote! {
159 {
160 #start_watchers
161 fuel_telemetry::new!()
162 }
163 }
164 .into()
165}
166
167/// A convenience macro to do `new_with_watchers!()` followed by setting the
168/// `TracingLayer` as the global default `Subscriber`.
169///
170/// Returns a `TelemetryLayer` and a drop guard. Here, the drop guard will flush
171/// any remaining telemetry to the disk.
172///
173/// Use this macro if you are using `fuel-telemetry` as your only `tracing`
174/// `Subscriber`.
175///
176/// Otherwise, if you are using `fuel-telemetry` along with other `tracing`
177/// `Layer`s within your application, you should instead use `new_with_watchers!()`.
178///
179/// ```text
180/// use fuel_telemetry::prelude::*;
181///
182/// let (telemetry_layer, _guard) = fuel_telemetry::new_with_watchers_and_init!()?;
183///
184/// info_telemetry!("This event will be sent to InfluxBD");
185/// ```
186#[proc_macro]
187pub fn new_with_watchers_and_init(_input: proc_macro::TokenStream) -> proc_macro::TokenStream {
188 let args: Vec<String> = std::env::args().collect();
189 let mut crate_name = String::new();
190 let mut crate_type = String::new();
191
192 // During macro expansion, we extract the crate name and type from the
193 // compiler's command line arguments, as this is the only time and place
194 // this information is available.
195 for window in args.windows(2) {
196 match window[0].as_str() {
197 "--crate-name" => crate_name = window[1].clone(),
198 "--crate-type" => crate_type = window[1].clone(),
199 _ => {}
200 }
201 }
202
203 // If the crate is a library, and it is not the `fuel_telemetry` crate,
204 // then generate a compiler error so we cannot continue!
205 if crate_type == "lib" && crate_name != "fuel_telemetry" {
206 return quote! {
207 {
208 compile_error!("new_with_watchers_and_init!() cannot be called within a library")
209 }
210 }
211 .into();
212 }
213
214 quote! {
215 {
216 fuel_telemetry::new_with_watchers!().map(|(layer, guard)| {
217 use fuel_telemetry::__reexport_SubscriberInitExt;
218 use fuel_telemetry::__reexport_tracing_subscriber;
219 use fuel_telemetry::__reexport_tracing_subscriber_SubscriberExt;
220
221 __reexport_tracing_subscriber::registry()
222 .with(layer)
223 .init();
224
225 guard
226 })
227 }
228 }
229 .into()
230}