prometric_derive/
lib.rs

1//! This crate contains the attribute macro for generating Prometheus metrics.
2//! Refer to the [metrics] attribute documentation for more information.
3use proc_macro::TokenStream;
4use syn::{ItemStruct, parse_macro_input};
5
6use crate::expand::MetricsAttr;
7
8mod expand;
9mod utils;
10
11/// This attribute macro instruments all of the struct fields with Prometheus metrics according to
12/// the attributes on the fields. It also generates an ergonomic accessor API for each of the
13/// defined metrics.
14///
15/// # Attributes
16///
17/// - `scope`: Sets the prefix for metric names (required)
18/// - `static`: If enabled, generates a static `LazyLock` with a SCREAMING_SNAKE_CASE name.
19///
20/// # Example
21/// ```rust
22/// use prometric_derive::metrics;
23/// use prometric::{Counter, Gauge, Histogram};
24///
25/// // The `scope` attribute is used to set the prefix for the metric names in this struct.
26/// #[metrics(scope = "app")]
27/// struct AppMetrics {
28///     /// The total number of HTTP requests.
29///     #[metric(rename = "http_requests_total", labels = ["method", "path"])]
30///     http_requests: Counter,
31///
32///     // For histograms, the `buckets` attribute is optional. It will default to [prometheus::DEFAULT_BUCKETS] if not provided.
33///     /// The duration of HTTP requests.
34///     #[metric(labels = ["method", "path"], buckets = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0])]
35///     http_requests_duration: Histogram,
36///
37///     /// This doc comment will be overwritten by the `help` attribute.
38///     #[metric(rename = "current_active_users", labels = ["service"], help = "The current number of active users.")]
39///     current_users: Gauge,
40///
41///     /// The balance of the account, in dollars. Uses a floating point number.
42///     #[metric(rename = "account_balance", labels = ["account_id"])]
43///     account_balance: Gauge<f64>,
44///
45///     /// The total number of errors.
46///     #[metric]
47///     errors: Counter,
48/// }
49///
50/// // Build the metrics struct with static labels, which will initialize and register the metrics with the default registry.
51/// // A custom registry can be used by passing it to the builder using `with_registry`.
52/// let metrics = AppMetrics::builder().with_label("host", "localhost").with_label("port", "8080").build();
53///
54/// // Metric fields each get an accessor method generated, which can be used to interact with the metric.
55/// // The arguments to the accessor method are the labels for the metric.
56/// metrics.http_requests("GET", "/").inc();
57/// metrics.http_requests_duration("GET", "/").observe(1.0);
58/// metrics.current_users("service-1").set(10);
59/// metrics.account_balance("1234567890").set(-12.2);
60/// metrics.errors().inc();
61/// ```
62///
63/// # Sample Output
64/// ```text
65/// # HELP app_account_balance The balance of the account, in dollars. Uses a floating point number.
66/// # TYPE app_account_balance gauge
67/// app_account_balance{account_id="1234567890",host="localhost",port="8080"} -12.2
68///
69/// # HELP app_current_active_users The current number of active users.
70/// # TYPE app_current_active_users gauge
71/// app_current_active_users{host="localhost",port="8080",service="service-1"} 20
72///
73/// # HELP app_errors The total number of errors.
74/// # TYPE app_errors counter
75/// app_errors{host="localhost",port="8080"} 1
76///
77/// # HELP app_http_requests_duration The duration of HTTP requests.
78/// # TYPE app_http_requests_duration histogram
79/// app_http_requests_duration_bucket{host="localhost",method="GET",path="/",port="8080",le="0.005"} 0
80/// app_http_requests_duration_bucket{host="localhost",method="GET",path="/",port="8080",le="0.01"} 0
81/// app_http_requests_duration_bucket{host="localhost",method="GET",path="/",port="8080",le="0.025" } 0
82/// app_http_requests_duration_bucket{host="localhost",method="GET",path="/",port="8080",le="0.05"} 0
83/// app_http_requests_duration_bucket{host="localhost",method="GET",path="/",port="8080",le="0.1"} 0
84/// app_http_requests_duration_bucket{host="localhost",method="GET",path="/",port="8080",le="0.25"} 0
85/// app_http_requests_duration_bucket{host="localhost",method="GET",path="/",port="8080",le="0.5"} 0
86/// app_http_requests_duration_bucket{host="localhost",method="GET",path="/",port="8080",le="1"} 1
87/// app_http_requests_duration_bucket{host="localhost",method="GET",path="/",port="8080",le="2.5"} 1
88/// app_http_requests_duration_bucket{host="localhost",method="GET",path="/",port="8080",le="5"} 1
89/// app_http_requests_duration_bucket{host="localhost",method="GET",path="/",port="8080",le="+Inf"} 1
90/// app_http_requests_duration_sum{host="localhost",method="GET",path="/",port="8080"} 1
91/// app_http_requests_duration_count{host="localhost",method="GET",path="/",port="8080"} 1
92///
93/// # HELP app_http_requests_total The total number of HTTP requests.
94/// # TYPE app_http_requests_total counter
95/// app_http_requests_total{host="localhost",method="GET",path="/",port="8080"} 2
96/// app_http_requests_total{host="localhost",method="POST",path="/",port="8080"} 2
97/// ```
98/// # Static Metrics Example
99///
100/// When the `static` attribute is enabled, a static `LazyLock` is generated with a
101/// SCREAMING_SNAKE_CASE name. The builder methods and `Default` implementation are made private,
102/// ensuring the only way to access the metrics is through the static instance.
103///
104/// If `static` is enabled, `prometheus::default_registry()` is used.
105///
106/// ```rust
107/// use prometric::{Counter, Gauge};
108/// use prometric_derive::metrics;
109///
110/// #[metrics(scope = "app", static)]
111/// struct AppMetrics {
112///     /// The total number of requests.
113///     #[metric(labels = ["method"])]
114///     requests: Counter,
115///
116///     /// The current number of active connections.
117///     #[metric]
118///     active_connections: Gauge,
119/// }
120///
121/// // Use the static directly anywhere
122/// APP_METRICS.requests("GET").inc();
123/// APP_METRICS.active_connections().set(10);
124///
125/// // The following would not compile:
126/// // let metrics = AppMetrics::builder();  // Error: builder() is private
127/// // let metrics = AppMetrics::default();   // Error: Default is not implemented
128/// ```
129///
130/// # Exporting Metrics
131/// An HTTP exporter is provided by [`prometric::exporter::ExporterBuilder`]. Usage:
132///
133/// ```rust
134/// use prometric::exporter::ExporterBuilder;
135///
136/// // Metric definitions...
137///
138/// // Export the metrics on an HTTP endpoint in the background:
139/// ExporterBuilder::new()
140///     // Specify the address to listen on
141///     .with_address("127.0.0.1:9090")
142///     // Set the global namespace for the metrics (usually the name of the application)
143///     .with_namespace("exporter")
144///     // Install the exporter. This will start an HTTP server and serve metrics on the specified
145///     // address.
146///     .install()
147///     .expect("Failed to install exporter");
148/// ```
149///
150/// # Process Metrics Example
151///
152/// When the `process` feature is enabled, the `ProcessCollector` is used to collect metrics about
153/// the current process.
154///
155/// ```rust
156/// # #[cfg(feature = "process")] {
157/// use prometric::process::ProcessCollector;
158/// use prometric_derive::metrics;
159///
160/// let mut collector = ProcessCollector::default();
161/// collector.collect();
162/// # }
163/// ```
164///
165/// #### Output
166/// ```text
167/// # HELP process_collection_duration_seconds The duration of the associated collection routine in seconds.
168/// # TYPE process_collection_duration_seconds gauge
169/// process_collection_duration_seconds 0.016130356
170/// # HELP process_cpu_usage The CPU usage of the process as a percentage.
171/// # TYPE process_cpu_usage gauge
172/// process_cpu_usage 6.2536187171936035
173/// # HELP process_disk_written_bytes_total The total written bytes to disk by the process.
174/// # TYPE process_disk_written_bytes_total gauge
175/// process_disk_written_bytes_total 0
176/// # HELP process_max_fds The maximum number of open file descriptors of the process.
177/// # TYPE process_max_fds gauge
178/// process_max_fds 1048576
179/// # HELP process_open_fds The number of open file descriptors of the process.
180/// # TYPE process_open_fds gauge
181/// process_open_fds 639
182/// # HELP process_resident_memory_bytes The resident memory of the process in bytes. (RSS)
183/// # TYPE process_resident_memory_bytes gauge
184/// process_resident_memory_bytes 4702208
185/// # HELP process_resident_memory_usage The resident memory usage of the process as a percentage of the total memory available.
186/// # TYPE process_resident_memory_usage gauge
187/// process_resident_memory_usage 0.00007072418111501723
188/// # HELP process_start_time_seconds The start time of the process in UNIX seconds.
189/// # TYPE process_start_time_seconds gauge
190/// process_start_time_seconds 1763056609
191/// # HELP process_thread_usage Per-thread CPU usage as a percentage of the process's CPU usage (Linux only).
192/// # TYPE process_thread_usage gauge
193/// process_thread_usage{name="process::tests:",pid="980490"} 0.9259260296821594
194/// process_thread_usage{name="test-thread-1",pid="980491"} 0
195/// process_thread_usage{name="test-thread-2",pid="980492"} 94.44445037841797
196/// # HELP process_threads The number of OS threads used by the process (Linux only).
197/// # TYPE process_threads gauge
198/// process_threads 3
199/// # HELP system_cpu_cores The number of logical CPU cores available in the system.
200/// # TYPE system_cpu_cores gauge
201/// system_cpu_cores 16
202/// # HELP system_cpu_usage System-wide CPU usage percentage.
203/// # TYPE system_cpu_usage gauge
204/// system_cpu_usage 6.7168498039245605
205/// # HELP system_max_cpu_frequency The maximum CPU frequency of all cores in MHz.
206/// # TYPE system_max_cpu_frequency gauge
207/// system_max_cpu_frequency 5339
208/// # HELP system_memory_usage System-wide memory usage percentage.
209/// # TYPE system_memory_usage gauge
210/// system_memory_usage 6.398677876736871
211/// # HELP system_min_cpu_frequency The minimum CPU frequency of all cores in MHz.
212/// # TYPE system_min_cpu_frequency gauge
213/// system_min_cpu_frequency 545
214/// ```
215#[proc_macro_attribute]
216pub fn metrics(attr: TokenStream, item: TokenStream) -> TokenStream {
217    // NOTE: We use `proc_macro_attribute` here because we're actually rewriting the struct. Derive
218    // macros are additive.
219    let mut input = parse_macro_input!(item as ItemStruct);
220
221    let attributes: MetricsAttr = match syn::parse(attr) {
222        Ok(v) => v,
223        Err(e) => {
224            return e.to_compile_error().into();
225        }
226    };
227
228    expand::expand(attributes, &mut input).unwrap_or_else(|err| err.into_compile_error()).into()
229}