1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
//! Metrics handling library based on the `prometheus-client` crate.
//!
//! # Overview
//!
//! - The crate supports defining common metric types ([`Counter`]s, [`Gauge`]s and [`Histogram`]s).
//!   A single metric is represented by an instance of these types; it can be reported using methods
//!   like [`Counter::inc()`], [`Gauge::set()`] or [`Histogram::observe()`].
//! - Metrics can be grouped into a [`Family`]. Essentially, a `Family` is a map in which metrics
//!   are values keyed by a set of labels. See [`EncodeLabelValue`] and [`EncodeLabelSet`] derive macros
//!   for more info on labels.
//! - To define metrics, a group of logically related metrics is grouped into a struct
//!   and the [`Metrics`](trait@Metrics) trait is [derived](macro@Metrics) for it. This resolves
//!   full metric names and records additional metadata, such as help (from doc comments), unit of measurement
//!   and [`Buckets`] for histograms.
//! - Metric groups are registered in a [`Registry`], which then allows to [encode](Registry::encode())
//!   metric data in the OpenMetrics text format. Registration can be automated using the [`register`]
//!   attribute, but it can be manual as well.
//! - In order to allow for metrics computed during scraping, you can use [`Collector`].
//!
//! # Examples
//!
//! ## Defining metrics
//!
//! ```
//! use vise::*;
//! use std::{fmt, time::Duration};
//!
//! /// Metrics defined by the library or application. A single app / lib can define
//! /// multiple metric structs.
//! #[derive(Debug, Clone, Metrics)]
//! #[metrics(prefix = "my_app")]
//! // ^ Prefix added to all field names to get the final metric name (e.g., `my_app_latencies`).
//! pub(crate) struct MyMetrics {
//!     /// Simple counter. Doc comments for the fields will be reported
//!     /// as Prometheus metric descriptions.
//!     pub counter: Counter,
//!     /// Integer-valued gauge. Unit will be reported to Prometheus and will influence metric name
//!     /// by adding the corresponding suffix to it (in this case, `_bytes`).
//!     #[metrics(unit = Unit::Bytes)]
//!     pub gauge: Gauge<u64>,
//!     /// Group of histograms with the "method" label (see the definition below).
//!     /// Each `Histogram` or `Family` of `Histogram`s must define buckets; in this case,
//!     /// we use default buckets for latencies.
//!     #[metrics(buckets = Buckets::LATENCIES)]
//!     pub latencies: Family<Method, Histogram<Duration>>,
//! }
//!
//! /// Isolated metric label. Note the `label` name specification below.
//! #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EncodeLabelSet, EncodeLabelValue)]
//! #[metrics(label = "method")]
//! pub(crate) struct Method(pub &'static str);
//!
//! // For the isolated metric label to work, you should implement `Display` for it:
//! impl fmt::Display for Method {
//!     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
//!         write!(formatter, "{}", self.0)
//!     }
//! }
//! ```
//!
//! ## Registering metrics automatically
//!
//! Commonly, metrics can be registered by defining a `static`:
//!
//! ```
//! # use vise::{Gauge, Global, Metrics, MetricsCollection, Registry};
//! #[derive(Debug, Clone, Metrics)]
//! pub(crate) struct MyMetrics {
//! #   pub gauge: Gauge<u64>,
//!     // defined in a previous code sample
//! }
//!
//! #[vise::register]
//! pub(crate) static MY_METRICS: Global<MyMetrics> = Global::new();
//!
//! // All registered metrics can be collected in a `Registry`:
//! let registry: Registry = MetricsCollection::default().collect();
//! // Do something with the `registry`, e.g. create an exporter.
//!
//! fn metered_logic() {
//!     // method logic...
//!     MY_METRICS.gauge.set(42);
//! }
//! ```
//!
//! ## Registering metrics manually
//!
//! It is possible to add metrics manually to a [`Registry`]. As a downside, this approach requires
//! boilerplate to register all necessary metrics in an app and potentially libraries
//! that it depends on.
//!
//! ```
//! # use vise::{Gauge, Metrics, Registry};
//! #[derive(Debug, Clone, Metrics)]
//! pub(crate) struct MyMetrics {
//! #   pub gauge: Gauge<u64>,
//!     // defined in a previous code sample
//! }
//!
//! let mut registry = Registry::empty();
//! let my_metrics = MyMetrics::default();
//! registry.register_metrics(&my_metrics);
//! // Do something with the `registry`, e.g. create an exporter.
//!
//! // After registration, metrics can be moved to logic that reports the metrics.
//! // Note that metric types trivially implement `Clone` to allow sharing
//! // them among multiple components.
//! fn metered_logic(metrics: MyMetrics) {
//!     // method logic...
//!     metrics.gauge.set(42);
//! }
//!
//! metered_logic(my_metrics);
//! ```

// Documentation settings.
#![doc(html_root_url = "https://docs.rs/vise/0.1.0")]
// Linter settings.
#![warn(missing_debug_implementations, missing_docs, bare_trait_objects)]
#![warn(clippy::all, clippy::pedantic)]
#![allow(clippy::must_use_candidate, clippy::module_name_repetitions)]

pub use prometheus_client::{metrics::counter::Counter, registry::Unit};

/// Derives the [`EncodeLabelValue`] trait for a type, which encodes a metric label value.
///
/// The type for which the trait is derived must not have generics (lifetimes, type params or const params).
/// The macro can be configured using `#[metrics()]` attributes.
///
/// # Container attributes
///
/// ## `format`
///
/// **Type:** string
///
/// **Default value:** `{}`
///
/// Specifies the format for the value as used in the `format!` macro etc. when encoding it to
/// a label value. For example, `{}` means using [`Display`](std::fmt::Display).
///
/// [`EncodeLabelValue`]: trait@prometheus_client::encoding::EncodeLabelValue
///
/// ## `rename_all`
///
/// **Type:** string; one of `lowercase`, `UPPERCASE`, `camelCase`, `snake_case`, `SCREAMING_SNAKE_CASE`,
/// `kebab-case`, `SCREAMING-KEBAB-CASE`
///
/// Specifies how enum variant names should be transformed into label values. This attribute
/// can only be placed on enums in which all variants don't have fields (aka C-style enums).
/// Mutually exclusive with the `format` attribute.
///
/// Caveats:
///
/// - `rename_all` assumes that original variant names are in `PascalCase` (i.e., follow Rust naming conventions).
/// - `rename_all` requires original variant names to consist of ASCII chars.
/// - Each letter of capitalized acronyms (e.g., "HTTP" in `HTTPServer`) is treated as a separate word.
///   E.g., `rename_all = "snake_case"` will rename `HTTPServer` to `h_t_t_p_server`.
///   Note that [it is recommended][clippy-acronyms] to not capitalize acronyms (i.e., use `HttpServer`).
/// - No spacing is inserted before numbers or other non-letter chars. E.g., `rename_all = "snake_case"`
///   will rename `Status500` to `status500`, not to `status_500`.
///
/// # Variant attributes
///
/// ## `name`
///
/// **Type:** string
///
/// Specifies the name override for a particular enum variant when used with the `rename_all` attribute
/// described above.
///
/// [clippy-acronyms]: https://rust-lang.github.io/rust-clippy/master/index.html#/upper_case_acronyms
///
/// # Examples
///
/// ## Default format
///
/// Label value using the default `Display` formatting; note that `Display` itself is derived.
///
/// ```
/// use derive_more::Display;
/// use vise::EncodeLabelValue;
///
/// #[derive(Debug, Display, EncodeLabelValue)]
/// struct Method(&'static str);
/// ```
///
/// ## Custom format
///
/// Label value using `Hex` formatting with `0` padding and `0x` prepended.
///
/// ```
/// # use derive_more::LowerHex;
/// # use vise::EncodeLabelValue;
/// #[derive(Debug, LowerHex, EncodeLabelValue)]
/// #[metrics(format = "0x{:02x}")]
/// struct ResponseType(u8);
/// ```
///
/// ## Using `rename_all` on C-style enum
///
/// ```
/// # use derive_more::LowerHex;
/// # use vise::EncodeLabelValue;
/// #[derive(Debug, EncodeLabelValue)]
/// #[metrics(rename_all = "snake_case")]
/// enum Database {
///     Postgres, // renamed to "postgres"
///     MySql, // renamed to "my_sql"
///     #[metrics(name = "rocksdb")] // explicitly overrides the name
///     RocksDB,
/// }
/// ```
pub use vise_macros::EncodeLabelValue;

/// Derives the [`EncodeLabelSet`] trait for a type, which encodes a set of metric labels.
///
/// The type for which the trait is derived must not have generics (lifetimes, type params or const params).
/// The macro can be configured using `#[metrics()]` attributes.
///
/// # Container attributes
///
/// ## `label`
///
/// **Type:** string
///
/// If specified, the type will be treated as a single label with the given name. This covers
/// the common case in which a label set consists of a single label. In this case, the type
/// also needs to implement [`EncodeLabelValue`].
///
/// If this attribute is not specified (which is the default), a container must be a `struct`
/// with named fields. A label with the matching name will be created for each field.
///
/// # Field attributes
///
/// ## `skip`
///
/// **Type:** path to a function with `fn(&FieldType) -> bool` signature
///
/// This attribute works similarly to `skip_serializing_if` in `serde` – if the function it points
/// to returns `true` for the field value, the field will not be encoded as a label.
///
/// `Option` fields are skipped by default if they are `None` (i.e., they use `skip = Option::is_none`).
///
/// [`EncodeLabelSet`]: trait@prometheus_client::encoding::EncodeLabelSet
///
/// ## `unit`
///
/// **Type:** expression evaluating to [`Unit`]
///
/// Specifies unit of measurement for a label. The unit will be added to the label name as a suffix
/// (e.g., `timeout_seconds` if placed on a field named `timeout`). This is mostly useful for [`Info`] metrics.
///
/// # Examples
///
/// ## Set with a single label
///
/// ```
/// use derive_more::Display;
/// use vise::{EncodeLabelSet, EncodeLabelValue};
///
/// #[derive(Debug, Display, Clone, PartialEq, Eq, Hash)]
/// #[derive(EncodeLabelValue, EncodeLabelSet)]
/// #[metrics(label = "method")]
/// struct Method(&'static str);
/// ```
///
/// ## Set with multiple labels
///
/// ```
/// # use vise::EncodeLabelSet;
/// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EncodeLabelSet)]
/// struct Labels {
///     /// Label that is skipped when empty.
///     #[metrics(skip = str::is_empty)]
///     name: &'static str,
///     /// Numeric label.
///     num: u8,
/// }
/// ```
///
/// ## Set for info metric
///
/// ```
/// use vise::{EncodeLabelSet, DurationAsSecs, Unit};
/// use std::time::Duration;
///
/// #[derive(Debug, EncodeLabelSet)]
/// struct InfoLabels {
///     /// Simple label.
///     version: &'static str,
///     /// Label with a unit.
///     #[metrics(unit = Unit::Seconds)]
///     request_timeout: DurationAsSecs,
///     /// Another label with a unit.
///     #[metrics(unit = Unit::Bytes)]
///     buffer_capacity: u64,
/// }
///
/// let labels = InfoLabels {
///     version: "0.1.0",
///     request_timeout: Duration::from_millis(100).into(),
///     buffer_capacity: 1_024,
/// };
/// // will be exported as the following labels:
/// // { version="0.1.0", request_timeout_seconds="0.1", buffer_capacity="1024" }
/// ```
pub use vise_macros::EncodeLabelSet;

/// Derives the [`Metrics`](trait@Metrics) trait for a type.
///
/// This macro must be placed on a struct with named fields and with no generics (lifetimes, type params
/// or const params). Each field will be registered as metric or a family of metrics.
/// The macro can be configured using `#[metrics()]` attributes.
///
/// # Container attributes
///
/// ## `prefix`
///
/// **Type:** string
///
/// Specifies a common prefix for all metrics defined in the type. If specified, the prefix will
/// be prepended together with a `_` separator to a field name to get the metric name. Note that
/// the metric name may be additionally transformed depending on the unit and metric type.
///
/// # Field attributes
///
/// ## `buckets`
///
/// **Type:** expression evaluating to a type implementing `Into<`[`Buckets`]`>`
///
/// Specifies buckets for a [`Histogram`] or a [`Family`] of `Histogram`s. This attribute is mandatory
/// for these metric types and will result in a compile-time error if used on counters / gauges.
///
/// ## `unit`
///
/// **Type:** expression evaluating to [`Unit`]
///
/// Specifies unit of measurement for a metric. Note that specifying a unit influences the metric naming.
///
/// ## `labels`
///
/// **Type:** expression evaluating to an array `[&'static str; _]`
///
/// Specifies label names for a [`LabeledFamily`]. This attribute is mandatory for [`LabeledFamily`]
/// and will result in a compile-time error if used with other metric types. The number of label names
/// must match the number of label values in the `LabeledFamily` (i.e., its type).
///
/// # Examples
///
/// See crate-level docs and other crate docs for the examples of usage.
pub use vise_macros::Metrics;

/// Registers a [`Global`] metrics instance or [`Collector`], so that it will be included
/// into registries instantiated using [`MetricsCollection`].
///
/// This macro must be placed on a static item of a type implementing [`CollectToRegistry`].
///
/// # Examples
///
/// ## Usage with global metrics
///
/// ```
/// use vise::{Gauge, Global, Metrics};
///
/// #[derive(Debug, Metrics)]
/// #[metrics(prefix = "test")]
/// pub(crate) struct TestMetrics {
///     gauge: Gauge,
/// }
///
/// #[vise::register]
/// static TEST_METRICS: Global<TestMetrics> = Global::new();
/// ```
///
/// ## Usage with collectors
///
/// ```
/// use vise::{Collector, Gauge, Global, Metrics};
///
/// #[derive(Debug, Metrics)]
/// #[metrics(prefix = "dynamic")]
/// pub(crate) struct DynamicMetrics {
///     gauge: Gauge,
/// }
///
/// #[vise::register]
/// static TEST_COLLECTOR: Collector<DynamicMetrics> = Collector::new();
/// ```
pub use vise_macros::register;

#[doc(hidden)] // only used by the proc macros
pub mod _reexports {
    pub use linkme;
    pub use prometheus_client::{encoding, metrics::TypedMetric};
}

mod buckets;
mod builder;
mod collector;
pub mod descriptors;
mod format;
mod metrics;
mod registry;
#[cfg(test)]
mod tests;
pub mod traits;
#[doc(hidden)]
pub mod validation;
mod wrappers;

pub use crate::{
    buckets::Buckets,
    builder::{BuildMetric, MetricBuilder},
    collector::{BeforeScrapeError, Collector},
    format::Format,
    metrics::{Global, Metrics},
    registry::{
        CollectToRegistry, MetricsCollection, MetricsVisitor, RegisteredDescriptors, Registry,
        METRICS_REGISTRATIONS,
    },
    wrappers::{
        DurationAsSecs, Family, Gauge, GaugeGuard, Histogram, Info, LabelWithUnit, LabeledFamily,
        LatencyObserver, SetInfoError,
    },
};

#[cfg(doctest)]
doc_comment::doctest!("../README.md");