Skip to main content

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}