From 88ad0636b04edbdba9e94b223e70782a4a782872 Mon Sep 17 00:00:00 2001 From: Mari Date: Tue, 12 Sep 2023 15:51:21 +0200 Subject: [PATCH 01/15] add otel push exporter (#137) * add otel push exporter * rename examples to make it more understanding * add must use notice as well functions to customize timeout and period * fail compile if no runtime is enabled * update changelog * add feature flag documentation * add documentation --- CHANGELOG.md | 4 +- autometrics/Cargo.toml | 33 ++++ autometrics/src/README.md | 19 +++ autometrics/src/lib.rs | 2 + autometrics/src/otel_push_exporter.rs | 160 ++++++++++++++++++ autometrics/tests/compile_test.rs | 2 +- examples/README.md | 3 +- examples/opentelemetry-push-custom/Cargo.toml | 15 ++ examples/opentelemetry-push-custom/README.md | 106 ++++++++++++ .../otel-collector-config.yml | 19 +++ .../opentelemetry-push-custom/src/main.rs | 49 ++++++ examples/opentelemetry-push/Cargo.toml | 7 +- examples/opentelemetry-push/README.md | 12 +- .../otel-collector-config.yml | 2 + examples/opentelemetry-push/src/main.rs | 31 +--- 15 files changed, 423 insertions(+), 41 deletions(-) create mode 100644 autometrics/src/otel_push_exporter.rs create mode 100644 examples/opentelemetry-push-custom/Cargo.toml create mode 100644 examples/opentelemetry-push-custom/README.md create mode 100644 examples/opentelemetry-push-custom/otel-collector-config.yml create mode 100644 examples/opentelemetry-push-custom/src/main.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f3008f70..618c9c2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- +- New exporter, `otel_push_exporter` is now available in addition to the existing + `prometheus_exporter`. It can be used to push metrics in the OTEL format via + HTTP or gRPC to a OTEL-collector-(compatible) server. ### Changed diff --git a/autometrics/Cargo.toml b/autometrics/Cargo.toml index d81a17ea..408b40a2 100644 --- a/autometrics/Cargo.toml +++ b/autometrics/Cargo.toml @@ -36,6 +36,38 @@ prometheus-exporter = [ "dep:prometheus-client", ] +otel-push-exporter = [ + "opentelemetry_sdk", + "opentelemetry_api", + "opentelemetry-otlp", + "opentelemetry-otlp/tls-roots", +] + +otel-push-exporter-http = [ + "otel-push-exporter", + "opentelemetry-otlp/http-proto" +] + +otel-push-exporter-grpc = [ + "otel-push-exporter", + "opentelemetry-otlp/grpc-tonic" +] + +otel-push-exporter-tokio = [ + "otel-push-exporter", + "opentelemetry_sdk/rt-tokio" +] + +otel-push-exporter-tokio-current-thread = [ + "otel-push-exporter", + "opentelemetry_sdk/rt-tokio-current-thread" +] + +otel-push-exporter-async-std = [ + "otel-push-exporter", + "opentelemetry_sdk/rt-async-std" +] + # Exemplars exemplars-tracing = ["tracing", "tracing-subscriber"] exemplars-tracing-opentelemetry-0_20 = [ @@ -68,6 +100,7 @@ opentelemetry-prometheus = { version = "0.13.0", optional = true } opentelemetry_sdk = { version = "0.20", default-features = false, features = [ "metrics", ], optional = true } +opentelemetry-otlp = { version = "0.13.0", default-features = false, optional = true } prometheus = { version = "0.13", default-features = false, optional = true } # Used for prometheus-client feature diff --git a/autometrics/src/README.md b/autometrics/src/README.md index ffcaa5c9..d163d0ce 100644 --- a/autometrics/src/README.md +++ b/autometrics/src/README.md @@ -125,6 +125,25 @@ pub fn main() { - `prometheus-exporter` - exports a Prometheus metrics collector and exporter. This is compatible with any of the [Metrics backends](#metrics-backends) and uses `prometheus-client` by default if none are explicitly selected +### Pushing metrics + +Easily push collected metrics to a OpenTelemetry collector and compatible software. +Combine one of the transport feature flags together with your runtime feature flag: + +**Transport feature flags**: + +- `otel-push-exporter-http` - metrics sent over HTTP(s) using `hyper` +- `otel-push-exporter-grpc` - metrics sent over gRPC using `tonic` + +**Runtime feature flags**: + +- `otel-push-exporter-tokio` - tokio +- `otel-push-exporter-tokio-current-thread` - tokio with `flavor = "current_thread"` +- `otel-push-exporter-async-std` - async-std + +If you require more customization than these offered feature flags, enable just +`otel-push-exporter` and follow the [example](https://github.com/autometrics-dev/autometrics-rs/tree/main/examples/opentelemetry-push-custom). + ### Metrics backends > If you are exporting metrics yourself rather than using the `prometheus-exporter`, you must ensure that you are using the exact same version of the metrics library as `autometrics` (and it must come from `crates.io` rather than git or another source). If not, the autometrics metrics will not appear in your exported metrics. diff --git a/autometrics/src/lib.rs b/autometrics/src/lib.rs index 1033b682..46261c22 100644 --- a/autometrics/src/lib.rs +++ b/autometrics/src/lib.rs @@ -14,6 +14,8 @@ mod constants; pub mod exemplars; mod labels; pub mod objectives; +#[cfg(feature = "otel-push-exporter")] +pub mod otel_push_exporter; #[cfg(feature = "prometheus-exporter")] pub mod prometheus_exporter; pub mod settings; diff --git a/autometrics/src/otel_push_exporter.rs b/autometrics/src/otel_push_exporter.rs new file mode 100644 index 00000000..a475e7eb --- /dev/null +++ b/autometrics/src/otel_push_exporter.rs @@ -0,0 +1,160 @@ +use opentelemetry_api::metrics::MetricsError; +use opentelemetry_otlp::{ExportConfig, Protocol, WithExportConfig}; +use opentelemetry_otlp::{OtlpMetricPipeline, OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT}; +use opentelemetry_sdk::metrics::MeterProvider; +use opentelemetry_sdk::runtime; +use std::ops::Deref; +use std::time::Duration; + +/// Newtype struct holding a [`MeterProvider`] with a custom `Drop` implementation to automatically clean up itself +#[repr(transparent)] +#[must_use = "Assign this to a unused variable instead: `let _meter = ...` (NOT `let _ = ...`), as else it will be dropped immediately - which will cause it to be shut down"] +pub struct OtelMeterProvider(MeterProvider); + +impl Deref for OtelMeterProvider { + type Target = MeterProvider; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for OtelMeterProvider { + fn drop(&mut self) { + // this will only error if `.shutdown` gets called multiple times + let _ = self.0.shutdown(); + } +} + +/// Initialize the OpenTelemetry push exporter using HTTP transport. +/// +/// # Interval and timeout +/// This function uses the environment variables `OTEL_METRIC_EXPORT_TIMEOUT` and `OTEL_METRIC_EXPORT_INTERVAL` +/// to configure the timeout and interval respectively. If you want to customize those +/// from within code, consider using [`init_http_with_timeout_period`]. +#[cfg(feature = "otel-push-exporter-http")] +pub fn init_http(url: impl Into) -> Result { + runtime() + .with_exporter( + opentelemetry_otlp::new_exporter() + .http() + .with_export_config(ExportConfig { + endpoint: url.into(), + protocol: Protocol::HttpBinary, + ..Default::default() + }), + ) + .build() + .map(OtelMeterProvider) +} + +/// Initialize the OpenTelemetry push exporter using HTTP transport with customized `timeout` and `period`. +#[cfg(feature = "otel-push-exporter-http")] +pub fn init_http_with_timeout_period( + url: impl Into, + timeout: Duration, + period: Duration, +) -> Result { + runtime() + .with_exporter( + opentelemetry_otlp::new_exporter() + .http() + .with_export_config(ExportConfig { + endpoint: url.into(), + protocol: Protocol::HttpBinary, + timeout, + ..Default::default() + }), + ) + .with_period(period) + .build() + .map(OtelMeterProvider) +} + +/// Initialize the OpenTelemetry push exporter using gRPC transport. +/// +/// # Interval and timeout +/// This function uses the environment variables `OTEL_METRIC_EXPORT_TIMEOUT` and `OTEL_METRIC_EXPORT_INTERVAL` +/// to configure the timeout and interval respectively. If you want to customize those +/// from within code, consider using [`init_grpc_with_timeout_period`]. +#[cfg(feature = "otel-push-exporter-grpc")] +pub fn init_grpc(url: impl Into) -> Result { + runtime() + .with_exporter( + opentelemetry_otlp::new_exporter() + .tonic() + .with_export_config(ExportConfig { + endpoint: url.into(), + protocol: Protocol::Grpc, + ..Default::default() + }), + ) + .build() + .map(OtelMeterProvider) +} + +/// Initialize the OpenTelemetry push exporter using gRPC transport with customized `timeout` and `period`. +#[cfg(feature = "otel-push-exporter-grpc")] +pub fn init_grpc_with_timeout_period( + url: impl Into, + timeout: Duration, + period: Duration, +) -> Result { + runtime() + .with_exporter( + opentelemetry_otlp::new_exporter() + .tonic() + .with_export_config(ExportConfig { + endpoint: url.into(), + protocol: Protocol::Grpc, + timeout, + ..Default::default() + }), + ) + .with_period(period) + .build() + .map(OtelMeterProvider) +} + +#[cfg(all( + feature = "otel-push-exporter-tokio", + not(any( + feature = "otel-push-exporter-tokio-current-thread", + feature = "otel-push-exporter-async-std" + )) +))] +fn runtime() -> OtlpMetricPipeline { + return opentelemetry_otlp::new_pipeline().metrics(opentelemetry_sdk::runtime::Tokio); +} + +#[cfg(all( + feature = "otel-push-exporter-tokio-current-thread", + not(any( + feature = "otel-push-exporter-tokio", + feature = "otel-push-exporter-async-std" + )) +))] +fn runtime() -> OtlpMetricPipeline { + return opentelemetry_otlp::new_pipeline() + .metrics(opentelemetry_sdk::runtime::TokioCurrentThread); +} + +#[cfg(all( + feature = "otel-push-exporter-async-std", + not(any( + feature = "otel-push-exporter-tokio", + feature = "otel-push-exporter-tokio-current-thread" + )) +))] +fn runtime() -> OtlpMetricPipeline { + return opentelemetry_otlp::new_pipeline().metrics(opentelemetry_sdk::runtime::AsyncStd); +} + +#[cfg(not(any( + feature = "otel-push-exporter-tokio", + feature = "otel-push-exporter-tokio-current-thread", + feature = "otel-push-exporter-async-std" +)))] +fn runtime() -> ! { + compile_error!("select your runtime (`otel-push-exporter-tokio`, `otel-push-exporter-tokio-current-thread` or `otel-push-exporter-async-std`) for the autometrics push exporter or use the custom push exporter if none fit") +} diff --git a/autometrics/tests/compile_test.rs b/autometrics/tests/compile_test.rs index 7edb0cbe..ec54f77d 100644 --- a/autometrics/tests/compile_test.rs +++ b/autometrics/tests/compile_test.rs @@ -1,5 +1,5 @@ -use std::io; use autometrics::autometrics; +use std::io; // general purpose `Result`, part of the std prelude. // notice both `Ok` and `Err` generic type arguments are explicitly provided diff --git a/examples/README.md b/examples/README.md index 123fb9bc..7dec046f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -13,7 +13,8 @@ cargo run --package example-{name of example} - [axum](./axum) - Use autometrics to instrument HTTP handlers using the `axum` framework - [custom-metrics](./custom-metrics/) - Define your own custom metrics alongside the ones generated by autometrics (using any of the metrics collection crates) - [exemplars-tracing](./exemplars-tracing/) - Use fields from `tracing::Span`s as Prometheus exemplars -- [opentelemetry-push](./opentelemetry-push/) - Push metrics to an OpenTelemetry Collector via the OTLP gRPC protocol +- [opentelemetry-push](./opentelemetry-push/) - Push metrics to an OpenTelemetry Collector via the OTLP HTTP or gRPC protocol using the Autometrics provided interface +- [opentelemetry-push-custom](./opentelemetry-push-custom/) - Push metrics to an OpenTelemetry Collector via the OTLP gRPC protocol using custom options ## Full Example diff --git a/examples/opentelemetry-push-custom/Cargo.toml b/examples/opentelemetry-push-custom/Cargo.toml new file mode 100644 index 00000000..fca59cce --- /dev/null +++ b/examples/opentelemetry-push-custom/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "example-opentelemetry-push-custom" +version = "0.0.0" +publish = false +edition = "2021" + +[dependencies] +autometrics = { path = "../../autometrics", features = ["opentelemetry-0_20"] } +autometrics-example-util = { path = "../util" } +# Note that the version of the opentelemetry crate MUST match +# the version used by autometrics +opentelemetry = { version = "0.20", features = ["metrics", "rt-tokio"] } +opentelemetry-otlp = { version = "0.13", features = ["tonic", "metrics"] } +opentelemetry-semantic-conventions = { version = "0.12.0" } +tokio = { version = "1", features = ["full"] } diff --git a/examples/opentelemetry-push-custom/README.md b/examples/opentelemetry-push-custom/README.md new file mode 100644 index 00000000..8ff8f73b --- /dev/null +++ b/examples/opentelemetry-push-custom/README.md @@ -0,0 +1,106 @@ +# Autometrics + OTLP push controller (custom) + +This example demonstrates how you can push autometrics via OTLP gRPC protocol to the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) or another OTel-compatible solution +without using the Autometrics provided interface. + +## Running the example + +### Start a basic OTEL-Collector + +You can use the [`otel-collector-config.yml`](./otel-collector-config.yml) file to start an otel-collector container that listens on 0.0.0.0:4317 for incoming otlp-gRPC traffic, and exports the metrics it receives to stdout. + +Run the following command in a second terminal to start a container in interactive mode: + +```bash +docker run -it --name otel-col \ + -p 4317:4317 -p 13133:13133 \ + -v $PWD/otel-collector-config.yml:/etc/otelcol/config.yaml \ + otel/opentelemetry-collector:latest +``` + +You should see the collector initialization output, that should end with something like: + +```text +... +2023-06-07T15:56:42.617Z info otlpreceiver@v0.75.0/otlp.go:94 Starting GRPC server {"kind": "receiver", "name": "otlp", "data_type": "metrics", "endpoint": "0.0.0.0:4317"} +2023-06-07T15:56:42.618Z info service/service.go:146 Everything is ready. Begin running and processing data. +``` + +### Execute example code + +Then come back on your primary shell and run this example: + +```shell +cargo run -p example-opentelemetry-push-custom +``` + +### Check the output + +On the stdout of the terminal where you started the opentelemetry-collector container, you should see the metrics generated by autometrics macro being pushed every 10 seconds since example exit, like: + +```text +... +Metric #0 +Descriptor: + -> Name: function.calls + -> Description: Autometrics counter for tracking function calls + -> Unit: + -> DataType: Sum + -> IsMonotonic: true + -> AggregationTemporality: Cumulative +NumberDataPoints #0 +Data point attributes: + -> caller: Str() + -> function: Str(do_stuff) + -> module: Str(example_opentelemetry_push) +StartTimestamp: 2023-06-07 16:01:08.549300623 +0000 UTC +Timestamp: 2023-06-07 16:01:48.551531429 +0000 UTC +Value: 10 +Metric #1 +Descriptor: + -> Name: build_info + -> Description: Autometrics info metric for tracking software version and build details + -> Unit: + -> DataType: Sum + -> IsMonotonic: false + -> AggregationTemporality: Cumulative +NumberDataPoints #0 +Data point attributes: + -> branch: Str() + -> commit: Str() + -> version: Str(0.0.0) +StartTimestamp: 2023-06-07 16:01:08.549300623 +0000 UTC +Timestamp: 2023-06-07 16:01:48.551531429 +0000 UTC +Value: 1.000000 +Metric #2 +Descriptor: + -> Name: function.calls.duration + -> Description: Autometrics histogram for tracking function call duration + -> Unit: + -> DataType: Sum + -> IsMonotonic: false + -> AggregationTemporality: Cumulative +NumberDataPoints #0 +Data point attributes: + -> function: Str(do_stuff) + -> module: Str(example_opentelemetry_push) +StartTimestamp: 2023-06-07 16:01:08.549300623 +0000 UTC +Timestamp: 2023-06-07 16:01:48.551531429 +0000 UTC +Value: 0.000122 + {"kind": "exporter", "data_type": "metrics", "name": "logging"} +... +``` + +### Cleanup + +In the end, to stop the opentelemetry collector container just hit `^C`. + +Then delete the container with + +```bash +docker rm otel-col +``` + +## OpenTelemetry Metrics Push Controller + +The metric push controller is implemented as from this [example](https://github.com/open-telemetry/opentelemetry-rust/blob/f20c9b40547ee20b6ec99414bb21abdd3a54d99b/examples/basic-otlp/src/main.rs#L35-L52) from `opentelemetry-rust` crate. diff --git a/examples/opentelemetry-push-custom/otel-collector-config.yml b/examples/opentelemetry-push-custom/otel-collector-config.yml new file mode 100644 index 00000000..731c5d09 --- /dev/null +++ b/examples/opentelemetry-push-custom/otel-collector-config.yml @@ -0,0 +1,19 @@ +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + +exporters: + logging: + loglevel: debug + +processors: + batch: + +service: + pipelines: + metrics: + receivers: [otlp] + processors: [] + exporters: [logging] diff --git a/examples/opentelemetry-push-custom/src/main.rs b/examples/opentelemetry-push-custom/src/main.rs new file mode 100644 index 00000000..57ba0b28 --- /dev/null +++ b/examples/opentelemetry-push-custom/src/main.rs @@ -0,0 +1,49 @@ +use autometrics::autometrics; +use autometrics_example_util::sleep_random_duration; +use opentelemetry::metrics::MetricsError; +use opentelemetry::sdk::metrics::MeterProvider; +use opentelemetry::{runtime, Context}; +use opentelemetry_otlp::{ExportConfig, WithExportConfig}; +use std::error::Error; +use std::time::Duration; +use tokio::time::sleep; + +fn init_metrics() -> Result { + let export_config = ExportConfig { + endpoint: "http://localhost:4317".to_string(), + ..ExportConfig::default() + }; + let push_interval = Duration::from_secs(1); + opentelemetry_otlp::new_pipeline() + .metrics(runtime::Tokio) + .with_exporter( + opentelemetry_otlp::new_exporter() + .tonic() + .with_export_config(export_config), + ) + .with_period(push_interval) + .build() +} + +#[autometrics] +async fn do_stuff() { + println!("Doing stuff..."); + sleep_random_duration().await; +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let meter_provider = init_metrics()?; + let cx = Context::current(); + + for _ in 0..100 { + do_stuff().await; + } + + println!("Waiting so that we could see metrics going down..."); + sleep(Duration::from_secs(10)).await; + meter_provider.force_flush(&cx)?; + + meter_provider.shutdown()?; + Ok(()) +} diff --git a/examples/opentelemetry-push/Cargo.toml b/examples/opentelemetry-push/Cargo.toml index dd0262f4..b33a36e8 100644 --- a/examples/opentelemetry-push/Cargo.toml +++ b/examples/opentelemetry-push/Cargo.toml @@ -5,11 +5,6 @@ publish = false edition = "2021" [dependencies] -autometrics = { path = "../../autometrics", features = ["opentelemetry-0_20"] } +autometrics = { path = "../../autometrics", features = ["opentelemetry-0_20", "otel-push-exporter-http", "otel-push-exporter-tokio"] } autometrics-example-util = { path = "../util" } -# Note that the version of the opentelemetry crate MUST match -# the version used by autometrics -opentelemetry = { version = "0.20", features = ["metrics", "rt-tokio"] } -opentelemetry-otlp = { version = "0.13", features = ["tonic", "metrics"] } -opentelemetry-semantic-conventions = { version = "0.12.0" } tokio = { version = "1", features = ["full"] } diff --git a/examples/opentelemetry-push/README.md b/examples/opentelemetry-push/README.md index 020afd85..78221358 100644 --- a/examples/opentelemetry-push/README.md +++ b/examples/opentelemetry-push/README.md @@ -1,12 +1,14 @@ # Autometrics + OTLP push controller -This example demonstrates how you can push autometrics via OTLP gRPC protocol to the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) or another OTel-compatible solution. +This example demonstrates how you can push autometrics via OTLP HTTP and GRPC protocol +to the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) or another OTel-compatible solution. It +uses the Autometrics provided wrapper to achieve this. ## Running the example ### Start a basic OTEL-Collector -You can use the [`otel-collector-config.yml`](./otel-collector-config.yml) file to start an otel-collector container that listens on 0.0.0.0:4317 for incoming otlp-gRPC traffic, and exports the metrics it receives to stdout. +You can use the [`otel-collector-config.yml`](./otel-collector-config.yml) file to start an otel-collector container that listens on 0.0.0.0:4317 for incoming otlp-gRPC traffic as well as on 0.0.0.0:4318 for incoming otlp-http traffic, and exports the metrics it receives to stdout. Run the following command in a second terminal to start a container in interactive mode: @@ -14,7 +16,7 @@ Run the following command in a second terminal to start a container in interacti docker run -it --name otel-col \ -p 4317:4317 -p 13133:13133 \ -v $PWD/otel-collector-config.yml:/etc/otelcol/config.yaml \ - otel/opentelemetry-collector:latest + otel/opentelemetry-collector-contrib:latest ``` You should see the collector initialization output, that should end with something like: @@ -99,7 +101,3 @@ Then delete the container with ```bash docker rm otel-col ``` - -## OpenTelemetry Metrics Push Controller - -The metric push controller is implemented as from this [example](https://github.com/open-telemetry/opentelemetry-rust/blob/f20c9b40547ee20b6ec99414bb21abdd3a54d99b/examples/basic-otlp/src/main.rs#L35-L52) from `opentelemetry-rust` crate. diff --git a/examples/opentelemetry-push/otel-collector-config.yml b/examples/opentelemetry-push/otel-collector-config.yml index 731c5d09..1303b66c 100644 --- a/examples/opentelemetry-push/otel-collector-config.yml +++ b/examples/opentelemetry-push/otel-collector-config.yml @@ -1,6 +1,8 @@ receivers: otlp: protocols: + http: + endpoint: 0.0.0.0:4318 grpc: endpoint: 0.0.0.0:4317 diff --git a/examples/opentelemetry-push/src/main.rs b/examples/opentelemetry-push/src/main.rs index cee72c6f..96164847 100644 --- a/examples/opentelemetry-push/src/main.rs +++ b/examples/opentelemetry-push/src/main.rs @@ -1,30 +1,9 @@ -use autometrics::autometrics; +use autometrics::{autometrics, otel_push_exporter}; use autometrics_example_util::sleep_random_duration; -use opentelemetry::metrics::MetricsError; -use opentelemetry::sdk::metrics::MeterProvider; -use opentelemetry::{runtime, Context}; -use opentelemetry_otlp::{ExportConfig, WithExportConfig}; use std::error::Error; use std::time::Duration; use tokio::time::sleep; -fn init_metrics() -> Result { - let export_config = ExportConfig { - endpoint: "http://localhost:4317".to_string(), - ..ExportConfig::default() - }; - let push_interval = Duration::from_secs(1); - opentelemetry_otlp::new_pipeline() - .metrics(runtime::Tokio) - .with_exporter( - opentelemetry_otlp::new_exporter() - .tonic() - .with_export_config(export_config), - ) - .with_period(push_interval) - .build() -} - #[autometrics] async fn do_stuff() { println!("Doing stuff..."); @@ -33,8 +12,10 @@ async fn do_stuff() { #[tokio::main] async fn main() -> Result<(), Box> { - let meter_provider = init_metrics()?; - let cx = Context::current(); + // NOTICE: the variable gets assigned to `_meter_provider` instead of just `_`, as the later case + // would cause it to be dropped immediately and thus shut down. + let _meter_provider = otel_push_exporter::init_http("http://0.0.0.0:4318")?; + // or: otel_push_exporter::init_grpc("http://0.0.0.0:4317"); for _ in 0..100 { do_stuff().await; @@ -42,7 +23,7 @@ async fn main() -> Result<(), Box> { println!("Waiting so that we could see metrics going down..."); sleep(Duration::from_secs(10)).await; - meter_provider.force_flush(&cx)?; + // no need to call `.shutdown` as the returned `OtelMeterProvider` has a `Drop` implementation Ok(()) } From c9bd7e1285ccc721e16083b8cf4b2553786d65bd Mon Sep 17 00:00:00 2001 From: Paul Tagliamonte Date: Tue, 19 Sep 2023 12:03:38 -0400 Subject: [PATCH 02/15] Fix a minor typo (#138) Since this isn't ever used, this doesn't really affect anything, but just for clarity, fix the typo of 'prometheus' in the full-api example. Signed-off-by: Paul R. Tagliamonte --- examples/full-api/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/full-api/src/main.rs b/examples/full-api/src/main.rs index aee68269..f0f7de12 100644 --- a/examples/full-api/src/main.rs +++ b/examples/full-api/src/main.rs @@ -16,7 +16,7 @@ mod util; async fn main() { // Run Prometheus and generate random traffic for the app // (You would not actually do this in production, but it makes it easier to see the example in action) - let _promehtheus = run_prometheus(false); + let _prometheus = run_prometheus(false); tokio::spawn(generate_random_traffic()); // Set up the exporter to collect metrics From 951a41f9b8822df01e958a94b2bbe3c6716b10b7 Mon Sep 17 00:00:00 2001 From: Jean SIMARD Date: Mon, 23 Oct 2023 11:07:35 +0200 Subject: [PATCH 03/15] feat: support for 'tracing-opentelemetry:0.21' (#147) --- .github/workflows/ci.yml | 1 + autometrics/Cargo.toml | 11 ++++++++--- autometrics/build.rs | 4 ++-- autometrics/src/README.md | 2 +- .../src/exemplars/tracing_opentelemetry.rs | 15 +++++++++++++-- autometrics/src/lib.rs | 1 + autometrics/tests/exemplars_test.rs | 5 ++++- .../exemplars-tracing-opentelemetry/Cargo.toml | 4 ++-- 8 files changed, 32 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97bd2921..71d30725 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,7 @@ jobs: - run: cargo test --features=prometheus-exporter,prometheus-0_13 - run: cargo test --features=prometheus-exporter,prometheus-client-0_21,exemplars-tracing - run: cargo test --features=prometheus-exporter,prometheus-client-0_21,exemplars-tracing-opentelemetry-0_20 + - run: cargo test --features=prometheus-exporter,prometheus-client-0_21,exemplars-tracing-opentelemetry-0_21 - run: cargo test --features=prometheus-exporter,opentelemetry-0_20 # Build the crate using the other optional features diff --git a/autometrics/Cargo.toml b/autometrics/Cargo.toml index 408b40a2..db32c9ab 100644 --- a/autometrics/Cargo.toml +++ b/autometrics/Cargo.toml @@ -73,7 +73,12 @@ exemplars-tracing = ["tracing", "tracing-subscriber"] exemplars-tracing-opentelemetry-0_20 = [ "opentelemetry_api/trace", "tracing", - "tracing-opentelemetry", + "dep:tracing-opentelemetry-0-20", +] +exemplars-tracing-opentelemetry-0_21 = [ + "opentelemetry_api/trace", + "tracing", + "dep:tracing-opentelemetry-0-21", ] # Custom objectives @@ -113,7 +118,8 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [ ], optional = true } # Used for exemplars-tracing-opentelemetry feature -tracing-opentelemetry = { version = "0.20", default-features = false, optional = true } +tracing-opentelemetry-0-20 = { package = "tracing-opentelemetry", version = "0.20.0", default-features = false, optional = true } +tracing-opentelemetry-0-21 = { package = "tracing-opentelemetry", version = "0.21.0", default-features = false, optional = true } [dev-dependencies] axum = { version = "0.6", features = ["tokio"] } @@ -124,7 +130,6 @@ opentelemetry-stdout = { version = "0.1", features = ["trace"] } prometheus-client = "0.21" tokio = { version = "1", features = ["full"] } tracing = "0.1" -tracing-opentelemetry = "0.20" tracing-subscriber = "0.3" trybuild = "1.0" uuid = { version = "1", features = ["v4"] } diff --git a/autometrics/build.rs b/autometrics/build.rs index 9917a0be..17ae3d0a 100644 --- a/autometrics/build.rs +++ b/autometrics/build.rs @@ -12,7 +12,7 @@ pub fn main() { #[cfg(feature = "prometheus-client")] println!("cargo:warning=The `prometheus-client` feature is deprecated and will be removed in the next version. Please use `prometheus-client-0_21` instead."); #[cfg(feature = "exemplars-tracing-opentelemetry")] - println!("cargo:warning=The `exemplars-tracing-opentelemetry` feature is deprecated and will be removed in the next version. Please use `exemplars-tracing-opentelemetry-0_20` instead."); + println!("cargo:warning=The `exemplars-tracing-opentelemetry` feature is deprecated and will be removed in the next version. Please use `exemplars-tracing-opentelemetry-0_20` or `exemplars-tracing-opentelemetry-0_21` instead."); cfg_aliases! { // Backends @@ -32,7 +32,7 @@ pub fn main() { // Exemplars exemplars: { any(exemplars_tracing, exemplars_tracing_opentelemetry) }, exemplars_tracing: { feature = "exemplars-tracing" }, - exemplars_tracing_opentelemetry: { any(feature = "exemplars-tracing-opentelemetry-0_20", feature = "exemplars-tracing-opentelemetry") }, + exemplars_tracing_opentelemetry: { any(feature = "exemplars-tracing-opentelemetry-0_20", feature = "exemplars-tracing-opentelemetry-0_21", feature = "exemplars-tracing-opentelemetry") }, // Custom objectives custom_objective_percentile: { feature = "custom-objective-percentile" }, diff --git a/autometrics/src/README.md b/autometrics/src/README.md index d163d0ce..fb72294c 100644 --- a/autometrics/src/README.md +++ b/autometrics/src/README.md @@ -158,7 +158,7 @@ If you require more customization than these offered feature flags, enable just See the [exemplars module docs](https://docs.rs/autometrics/latest/autometrics/exemplars/index.html) for details about these features. Currently only supported with the `prometheus-client` backend. - `exemplars-tracing` - extract arbitrary fields from `tracing::Span`s -- `exemplars-tracing-opentelemetry-0_20` - extract the `trace_id` and `span_id` from the `opentelemetry::Context`, which is attached to `tracing::Span`s by the `tracing-opentelemetry` crate +- `exemplars-tracing-opentelemetry-0_20` or `exemplars-tracing-opentelemetry-0_21` - extract the `trace_id` and `span_id` from the `opentelemetry::Context`, which is attached to `tracing::Span`s by the `tracing-opentelemetry` crate ### Custom objective values diff --git a/autometrics/src/exemplars/tracing_opentelemetry.rs b/autometrics/src/exemplars/tracing_opentelemetry.rs index 07ca1052..9e7b3144 100644 --- a/autometrics/src/exemplars/tracing_opentelemetry.rs +++ b/autometrics/src/exemplars/tracing_opentelemetry.rs @@ -2,11 +2,22 @@ use super::TraceLabels; use opentelemetry_api::trace::TraceContextExt; use std::iter::FromIterator; use tracing::Span; -use tracing_opentelemetry::OpenTelemetrySpanExt; + +#[cfg(all( + not(doc), + all( + feature = "exemplars-tracing-opentelemetry-0_20", + feature = "exemplars-tracing-opentelemetry-0_21" + ) +))] +compile_error!("Only one of the `exemplars-tracing-opentelemetry-0_20` and `exemplars-tracing-opentelemetry-0_21` features can be enabled at a time"); pub fn get_exemplar() -> Option { // Get the OpenTelemetry Context from the tracing span - let context = Span::current().context(); + #[cfg(feature = "exemplars-tracing-opentelemetry-0_20")] + let context = tracing_opentelemetry_0_20::OpenTelemetrySpanExt::context(&Span::current()); + #[cfg(feature = "exemplars-tracing-opentelemetry-0_21")] + let context = tracing_opentelemetry_0_21::OpenTelemetrySpanExt::context(&Span::current()); // Now get the OpenTelemetry "span" from the Context // (it's confusing because the word "span" is used by both tracing and OpenTelemetry diff --git a/autometrics/src/lib.rs b/autometrics/src/lib.rs index 46261c22..12623afa 100644 --- a/autometrics/src/lib.rs +++ b/autometrics/src/lib.rs @@ -10,6 +10,7 @@ mod constants; feature = "exemplars-tracing", feature = "exemplars-tracing-opentelemetry", feature = "exemplars-tracing-opentelemetry-0_20", + feature = "exemplars-tracing-opentelemetry-0_21", ))] pub mod exemplars; mod labels; diff --git a/autometrics/tests/exemplars_test.rs b/autometrics/tests/exemplars_test.rs index 655f7fea..a32e5b18 100644 --- a/autometrics/tests/exemplars_test.rs +++ b/autometrics/tests/exemplars_test.rs @@ -70,7 +70,10 @@ fn tracing_opentelemetry_context() { let tracer = provider.tracer("test"); // This adds the OpenTelemetry Context to every tracing Span - let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer); + #[cfg(feature = "exemplars-tracing-opentelemetry-0_20")] + let otel_layer = tracing_opentelemetry_0_20::layer().with_tracer(tracer); + #[cfg(feature = "exemplars-tracing-opentelemetry-0_21")] + let otel_layer = tracing_opentelemetry_0_21::layer().with_tracer(tracer); let subscriber = Registry::default().with(otel_layer); #[autometrics] diff --git a/examples/exemplars-tracing-opentelemetry/Cargo.toml b/examples/exemplars-tracing-opentelemetry/Cargo.toml index e6292be8..369fd8b2 100644 --- a/examples/exemplars-tracing-opentelemetry/Cargo.toml +++ b/examples/exemplars-tracing-opentelemetry/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" autometrics = { path = "../../autometrics", features = [ "prometheus-client-0_21", "prometheus-exporter", - "exemplars-tracing-opentelemetry-0_20", + "exemplars-tracing-opentelemetry-0_21", ] } autometrics-example-util = { path = "../util" } axum = { version = "0.6", features = ["json"] } @@ -17,5 +17,5 @@ opentelemetry-stdout = { version = "0.1", features = ["trace"] } reqwest = { version = "0.11", features = ["json"] } tokio = { version = "1", features = ["full"] } tracing = "0.1" -tracing-opentelemetry = "0.20" +tracing-opentelemetry = "0.21" tracing-subscriber = { version = "0.3", features = ["env-filter"] } From 138118249cc23190983e8a53f38019770d82bffb Mon Sep 17 00:00:00 2001 From: aumetra Date: Wed, 25 Oct 2023 10:06:13 +0200 Subject: [PATCH 04/15] Update to syn v2 (#145) * Update to syn v2 * Update autometrics/tests/init_to_zero_test.rs Co-authored-by: Mari * Restore old error message * Add `syn` v2 update to changelog --------- Co-authored-by: Mari --- CHANGELOG.md | 2 +- autometrics-macros/Cargo.toml | 2 +- autometrics-macros/src/lib.rs | 12 ++++----- autometrics-macros/src/result_labels.rs | 36 ++++++++++++------------- autometrics/tests/init_to_zero_test.rs | 2 ++ 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 618c9c2d..8c81ea7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- +- Update to `syn` v2 ### Deprecated diff --git a/autometrics-macros/Cargo.toml b/autometrics-macros/Cargo.toml index 8cdfab85..dd5c5df7 100644 --- a/autometrics-macros/Cargo.toml +++ b/autometrics-macros/Cargo.toml @@ -18,4 +18,4 @@ proc-macro = true percent-encoding = "2.2" proc-macro2 = "1" quote = "1" -syn = { version = "1", features = ["full"] } +syn = { version = "2", features = ["full"] } diff --git a/autometrics-macros/src/lib.rs b/autometrics-macros/src/lib.rs index 56089019..ac1486de 100644 --- a/autometrics-macros/src/lib.rs +++ b/autometrics-macros/src/lib.rs @@ -128,9 +128,7 @@ fn instrument_function( for args in &brackets.args { ts.push(match args { - GenericArgument::Type(ty) - if matches!(ty, Type::ImplTrait(_)) => - { + GenericArgument::Type(Type::ImplTrait(_)) => { quote! { _ } } generic_arg => quote! { #generic_arg }, @@ -321,17 +319,17 @@ fn instrument_impl_block(args: &AutometricsArgs, mut item: ItemImpl) -> Result { + ImplItem::Fn(mut method) => { // Skip any methods that have the #[skip_autometrics] attribute if method .attrs .iter() - .any(|attr| attr.path.is_ident("skip_autometrics")) + .any(|attr| attr.path().is_ident("skip_autometrics")) { method .attrs - .retain(|attr| !attr.path.is_ident("skip_autometrics")); - return ImplItem::Method(method); + .retain(|attr| !attr.path().is_ident("skip_autometrics")); + return ImplItem::Fn(method); } let item_fn = ItemFn { diff --git a/autometrics-macros/src/result_labels.rs b/autometrics-macros/src/result_labels.rs index 5f924fa7..3f93babb 100644 --- a/autometrics-macros/src/result_labels.rs +++ b/autometrics-macros/src/result_labels.rs @@ -4,8 +4,8 @@ use proc_macro2::TokenStream; use quote::quote; use syn::{ - punctuated::Punctuated, token::Comma, Attribute, Data, DataEnum, DeriveInput, Error, Ident, - Lit, LitStr, Result, Variant, + punctuated::Punctuated, token::Comma, Attribute, Data, DataEnum, DeriveInput, Error, Expr, + ExprLit, Ident, Lit, LitStr, Result, Variant, }; // These labels must match autometrics::ERROR_KEY and autometrics::OK_KEY, @@ -94,8 +94,7 @@ fn conditional_label_clauses( fn extract_label_attribute(attrs: &[Attribute]) -> Result> { attrs .iter() - .find_map(|att| match att.parse_meta() { - Ok(meta) => match &meta { + .find_map(|att| match &att.meta { syn::Meta::List(list) => { // Ignore attribute if it's not `label(...)` if list.path.segments.len() != 1 || list.path.segments[0].ident != ATTR_LABEL { @@ -103,12 +102,16 @@ fn extract_label_attribute(attrs: &[Attribute]) -> Result> { } // Only lists are allowed - let pair = match list.nested.first() { - Some(syn::NestedMeta::Meta(syn::Meta::NameValue(pair))) => pair, - _ => return Some(Err(Error::new_spanned( - meta, - format!("Only `{ATTR_LABEL}({RESULT_KEY} = \"RES\")` (RES can be {OK_KEY:?} or {ERROR_KEY:?}) is supported"), - ))), + let pair = match att.meta.require_list().and_then(|list| list.parse_args::()) { + Ok(pair) => pair, + Err(..) => return Some( + Err( + Error::new_spanned( + &att.meta, + format!("Only `{ATTR_LABEL}({RESULT_KEY} = \"RES\")` (RES can be {OK_KEY:?} or {ERROR_KEY:?}) is supported"), + ), + ), + ), }; // Inside list, only 'result = ...' are allowed @@ -120,11 +123,11 @@ fn extract_label_attribute(attrs: &[Attribute]) -> Result> { } // Inside 'result = val', 'val' must be a string literal - let lit_str = match pair.lit { - Lit::Str(ref lit_str) => lit_str, + let lit_str = match pair.value { + Expr::Lit(ExprLit { lit: Lit::Str(ref lit_str), .. }) => lit_str, _ => { return Some(Err(Error::new_spanned( - &pair.lit, + &pair.value, format!("Only {OK_KEY:?} or {ERROR_KEY:?}, as string literals, are accepted as result values"), ))); } @@ -153,11 +156,6 @@ fn extract_label_attribute(attrs: &[Attribute]) -> Result> { ))) }, _ => None, - }, - Err(e) => Some(Err(Error::new_spanned( - att, - format!("could not parse the meta attribute: {e}"), - ))), - }) + }) .transpose() } diff --git a/autometrics/tests/init_to_zero_test.rs b/autometrics/tests/init_to_zero_test.rs index 9b51d5f2..f5406b7e 100644 --- a/autometrics/tests/init_to_zero_test.rs +++ b/autometrics/tests/init_to_zero_test.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "prometheus-exporter")] + use autometrics::{autometrics, prometheus_exporter}; #[cfg(debug_assertions)] From 2b43d0a1efdb8d86ba58857562c086ce4bca3528 Mon Sep 17 00:00:00 2001 From: Mari Date: Wed, 25 Oct 2023 10:24:26 +0200 Subject: [PATCH 05/15] add better error message when using `async-trait` (#149) * add better error message when using `async-trait` * change output file * move #121 compile tests into trybuild harness --- autometrics-macros/src/lib.rs | 22 +++++++++++- autometrics/Cargo.toml | 1 + .../result_labels/fail/async_trait_support.rs | 34 +++++++++++++++++++ .../fail/async_trait_support.stderr | 29 ++++++++++++++++ .../pass/generics.rs} | 5 ++- 5 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 autometrics/tests/result_labels/fail/async_trait_support.rs create mode 100644 autometrics/tests/result_labels/fail/async_trait_support.stderr rename autometrics/tests/{compile_test.rs => result_labels/pass/generics.rs} (93%) diff --git a/autometrics-macros/src/lib.rs b/autometrics-macros/src/lib.rs index ac1486de..c90f9115 100644 --- a/autometrics-macros/src/lib.rs +++ b/autometrics-macros/src/lib.rs @@ -1,6 +1,6 @@ use crate::parse::{AutometricsArgs, Item}; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use std::env; use syn::{ @@ -22,6 +22,13 @@ pub fn autometrics( item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let args = parse_macro_input!(args as parse::AutometricsArgs); + + // if the input has yet to be modified by the `async-trait` crate, we will fail parsing in + // in the next line, so lets error early now + if let Err(err) = check_async_trait(&item) { + return err.into_compile_error().into(); + } + let item = parse_macro_input!(item as Item); let result = match item { @@ -37,6 +44,19 @@ pub fn autometrics( output.into() } +fn check_async_trait(input: &proc_macro::TokenStream) -> Result<()> { + let str = input.to_string(); + + if str.contains("#[async_trait]") || str.contains("#[async_trait::async_trait]") { + Err(syn::Error::new( + Span::call_site(), + "#[async_trait] must be defined BEFORE #[autometrics]", + )) + } else { + Ok(()) + } +} + #[proc_macro_derive(ResultLabels, attributes(label))] pub fn result_labels(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as syn::DeriveInput); diff --git a/autometrics/Cargo.toml b/autometrics/Cargo.toml index db32c9ab..ecdf880e 100644 --- a/autometrics/Cargo.toml +++ b/autometrics/Cargo.toml @@ -122,6 +122,7 @@ tracing-opentelemetry-0-20 = { package = "tracing-opentelemetry", version = "0.2 tracing-opentelemetry-0-21 = { package = "tracing-opentelemetry", version = "0.21.0", default-features = false, optional = true } [dev-dependencies] +async-trait = "0.1.74" axum = { version = "0.6", features = ["tokio"] } criterion = "0.5" http = "0.2" diff --git a/autometrics/tests/result_labels/fail/async_trait_support.rs b/autometrics/tests/result_labels/fail/async_trait_support.rs new file mode 100644 index 00000000..95c539ad --- /dev/null +++ b/autometrics/tests/result_labels/fail/async_trait_support.rs @@ -0,0 +1,34 @@ +use async_trait::async_trait; +use autometrics::autometrics; + +// https://github.com/autometrics-dev/autometrics-rs/issues/141 + +#[async_trait] +trait TestTrait { + async fn method() -> bool; + async fn self_method(&self) -> bool; +} + +#[derive(Default)] +struct TestStruct; + +#[autometrics] +#[async_trait] +impl TestTrait for TestStruct { + async fn method() -> bool { + true + } + + async fn self_method(&self) -> bool { + true + } +} + +fn main() { + let ts = TestStruct::default(); + + async move { + ::method().await; + ts.self_method().await; + }; +} diff --git a/autometrics/tests/result_labels/fail/async_trait_support.stderr b/autometrics/tests/result_labels/fail/async_trait_support.stderr new file mode 100644 index 00000000..a24e2f7c --- /dev/null +++ b/autometrics/tests/result_labels/fail/async_trait_support.stderr @@ -0,0 +1,29 @@ +error: #[async_trait] must be defined BEFORE #[autometrics] + --> tests/result_labels/fail/async_trait_support.rs:15:1 + | +15 | #[autometrics] + | ^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `autometrics` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `TestStruct: TestTrait` is not satisfied + --> tests/result_labels/fail/async_trait_support.rs:31:9 + | +31 | ::method().await; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TestTrait` is not implemented for `TestStruct` + +error[E0599]: no method named `self_method` found for struct `TestStruct` in the current scope + --> tests/result_labels/fail/async_trait_support.rs:32:12 + | +13 | struct TestStruct; + | ----------------- method `self_method` not found for this struct +... +32 | ts.self_method().await; + | ^^^^^^^^^^^ method not found in `TestStruct` + | + = help: items from traits can only be used if the trait is implemented and in scope +note: `TestTrait` defines an item `self_method`, perhaps you need to implement it + --> tests/result_labels/fail/async_trait_support.rs:7:1 + | +7 | trait TestTrait { + | ^^^^^^^^^^^^^^^ diff --git a/autometrics/tests/compile_test.rs b/autometrics/tests/result_labels/pass/generics.rs similarity index 93% rename from autometrics/tests/compile_test.rs rename to autometrics/tests/result_labels/pass/generics.rs index ec54f77d..f36e6b3f 100644 --- a/autometrics/tests/compile_test.rs +++ b/autometrics/tests/result_labels/pass/generics.rs @@ -35,9 +35,8 @@ fn issue_121_d() -> Result { } } -#[test] -fn invoke_issue_121() { - // we need to handle all three code generation cases +fn main() { + // we need to handle all four code generation cases issue_121_a().unwrap(); issue_121_b().unwrap(); issue_121_c().unwrap(); From 778ecb4abae54f27e182388a1f757effedc72f3d Mon Sep 17 00:00:00 2001 From: Mari Date: Wed, 25 Oct 2023 11:15:42 +0200 Subject: [PATCH 06/15] update changelog for #149 (#150) * update changelog for #149 * add pr template --- .github/pull_request_template.md | 3 +++ CHANGELOG.md | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..4e8f3164 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,3 @@ +# Checklist + +- [ ] Changelog updated diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c81ea7d..6a168917 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Update to `syn` v2 +- Update to `syn` v2 (#145) +- Error messages when using `#[autometrics]` together with `#[async_trait]` + has been improved (#149) ### Deprecated From fdb6234e1b479f23f90aac5c4a418db2b6fd1cfe Mon Sep 17 00:00:00 2001 From: Benno van den Berg Date: Wed, 25 Oct 2023 11:37:28 +0200 Subject: [PATCH 07/15] Add changelog message for PR #147 (#148) Simplify CHANGELOG.md by removing headers --- CHANGELOG.md | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a168917..8c321533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,36 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Added - - New exporter, `otel_push_exporter` is now available in addition to the existing `prometheus_exporter`. It can be used to push metrics in the OTEL format via HTTP or gRPC to a OTEL-collector-(compatible) server. - -### Changed - - Update to `syn` v2 (#145) -- Error messages when using `#[autometrics]` together with `#[async_trait]` +- Add support for tracing-opentelemetry v0.21 (#147) +- Error messages when using `#[autometrics]` together with `#[async_trait]` has been improved (#149) -### Deprecated - -- - -### Removed - -- - -### Fixed - -- - -### Security - -- - ---- - ## [0.6.0](https://github.com/autometrics-dev/autometrics-rs/releases/tag/v0.5.0) - 2023-08-08 ### Added From 10e9c776c88149358bd2b324ec0683254b868e8f Mon Sep 17 00:00:00 2001 From: Mari Date: Tue, 7 Nov 2023 14:18:23 +0100 Subject: [PATCH 08/15] add missing feature flag to opentelemetry_otlp for otel-push-exporter (#151) * add missing feature flag to opentelemetry_otlp for otel-http-push-exporter * update changelog and rustfmt * pr review --- CHANGELOG.md | 2 ++ autometrics/Cargo.toml | 3 ++- autometrics/src/otel_push_exporter.rs | 3 +-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c321533..d9f82125 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add support for tracing-opentelemetry v0.21 (#147) - Error messages when using `#[autometrics]` together with `#[async_trait]` has been improved (#149) +- Fixed missing feature flag for `opentelemetry-otlp` when autometrics feature + `otel-push-exporter` is enabled ## [0.6.0](https://github.com/autometrics-dev/autometrics-rs/releases/tag/v0.5.0) - 2023-08-08 diff --git a/autometrics/Cargo.toml b/autometrics/Cargo.toml index ecdf880e..e4959691 100644 --- a/autometrics/Cargo.toml +++ b/autometrics/Cargo.toml @@ -40,7 +40,8 @@ otel-push-exporter = [ "opentelemetry_sdk", "opentelemetry_api", "opentelemetry-otlp", - "opentelemetry-otlp/tls-roots", + "opentelemetry-otlp/metrics", + "opentelemetry-otlp/tls-roots" ] otel-push-exporter-http = [ diff --git a/autometrics/src/otel_push_exporter.rs b/autometrics/src/otel_push_exporter.rs index a475e7eb..2b4fb053 100644 --- a/autometrics/src/otel_push_exporter.rs +++ b/autometrics/src/otel_push_exporter.rs @@ -1,8 +1,7 @@ use opentelemetry_api::metrics::MetricsError; +use opentelemetry_otlp::OtlpMetricPipeline; use opentelemetry_otlp::{ExportConfig, Protocol, WithExportConfig}; -use opentelemetry_otlp::{OtlpMetricPipeline, OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT}; use opentelemetry_sdk::metrics::MeterProvider; -use opentelemetry_sdk::runtime; use std::ops::Deref; use std::time::Duration; From b88736360da6f6b69b2e3dd04eb8bd669e8f25d1 Mon Sep 17 00:00:00 2001 From: Mari Date: Thu, 9 Nov 2023 15:19:58 +0100 Subject: [PATCH 09/15] [autometrics 1.0 compliance] add repo url and provider (#152) * add repo url and provider * update changelog * coulda lso add the pr number ngl * fix wrong cardinality * unintended change my bad --- CHANGELOG.md | 9 +++++++++ autometrics-macros/src/lib.rs | 2 ++ autometrics/src/constants.rs | 4 ++++ autometrics/src/labels.rs | 26 +++++++++++++++++++++++++- autometrics/src/tracker/prometheus.rs | 6 +++++- 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9f82125..28595ebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed missing feature flag for `opentelemetry-otlp` when autometrics feature `otel-push-exporter` is enabled +### Autometrics 1.0 compliance + +The [Autometrics specification v1.0.0](https://github.com/autometrics-dev/autometrics-shared/blob/main/specs/autometrics_v1.0.0.md) has been released. +The following changes are made in order to bring the release in compliance with the spec: + +- Added `repository_url` and `repository_provider` as `build_info` labels. They can be + configured either through environment variables or automatically based on your `Cargo.toml` + `package.repository` value (only GitHub, GitLab and BitBucket) (#152) + ## [0.6.0](https://github.com/autometrics-dev/autometrics-rs/releases/tag/v0.5.0) - 2023-08-08 ### Added diff --git a/autometrics-macros/src/lib.rs b/autometrics-macros/src/lib.rs index c90f9115..bc29e6a8 100644 --- a/autometrics-macros/src/lib.rs +++ b/autometrics-macros/src/lib.rs @@ -308,6 +308,8 @@ fn instrument_function( option_env!("AUTOMETRICS_VERSION").or(option_env!("CARGO_PKG_VERSION")).unwrap_or_default(), option_env!("AUTOMETRICS_COMMIT").or(option_env!("VERGEN_GIT_SHA")).unwrap_or_default(), option_env!("AUTOMETRICS_BRANCH").or(option_env!("VERGEN_GIT_BRANCH")).unwrap_or_default(), + option_env!("AUTOMETRICS_REPOSITORY_URL").or(option_env!("CARGO_PKG_REPOSITORY")).unwrap_or_default(), + option_env!("AUTOMETRICS_REPOSITORY_PROVIDER").unwrap_or_default(), )); AutometricsTracker::start(#gauge_labels) }; diff --git a/autometrics/src/constants.rs b/autometrics/src/constants.rs index 77acae68..26f49b2b 100644 --- a/autometrics/src/constants.rs +++ b/autometrics/src/constants.rs @@ -37,3 +37,7 @@ pub const COMMIT_KEY: &str = "commit"; pub const BRANCH_KEY: &str = "branch"; pub const SERVICE_NAME_KEY: &str = "service.name"; pub const SERVICE_NAME_KEY_PROMETHEUS: &str = "service_name"; +pub const REPO_URL_KEY: &str = "repository.url"; +pub const REPO_URL_KEY_PROMETHEUS: &str = "repository_url"; +pub const REPO_PROVIDER_KEY: &str = "repository.provider"; +pub const REPO_PROVIDER_KEY_PROMETHEUS: &str = "repository_provider"; diff --git a/autometrics/src/labels.rs b/autometrics/src/labels.rs index 4cc79f74..f242a9c4 100644 --- a/autometrics/src/labels.rs +++ b/autometrics/src/labels.rs @@ -15,15 +15,23 @@ pub struct BuildInfoLabels { pub(crate) commit: &'static str, pub(crate) version: &'static str, pub(crate) service_name: &'static str, + pub(crate) repo_url: &'static str, + pub(crate) repo_provider: &'static str, } impl BuildInfoLabels { - pub fn new(version: &'static str, commit: &'static str, branch: &'static str) -> Self { + pub fn new(version: &'static str, commit: &'static str, branch: &'static str, repo_url: &'static str, mut repo_provider: &'static str) -> Self { + if repo_provider.is_empty() { + repo_provider = Self::determinate_repo_provider_from_url(repo_url); + } + Self { version, commit, branch, service_name: &get_settings().service_name, + repo_url, + repo_provider } } @@ -33,8 +41,24 @@ impl BuildInfoLabels { (VERSION_KEY, self.version), (BRANCH_KEY, self.branch), (SERVICE_NAME_KEY, self.service_name), + (REPO_URL_KEY, self.repo_url), + (REPO_PROVIDER_KEY, self.repo_provider) ] } + + fn determinate_repo_provider_from_url(url: &'static str) -> &'static str { + let lowered = url.to_lowercase(); + + if lowered.contains("github.com") { + "github" + } else if lowered.contains("gitlab.com") { + "gitlab" + } else if lowered.contains("bitbucket.org") { + "bitbucket" + } else { + "" + } + } } /// These are the labels used for the `function.calls` metric. diff --git a/autometrics/src/tracker/prometheus.rs b/autometrics/src/tracker/prometheus.rs index dd3d796c..87d92181 100644 --- a/autometrics/src/tracker/prometheus.rs +++ b/autometrics/src/tracker/prometheus.rs @@ -73,7 +73,9 @@ static BUILD_INFO: Lazy = Lazy::new(|| { COMMIT_KEY, VERSION_KEY, BRANCH_KEY, - SERVICE_NAME_KEY_PROMETHEUS + SERVICE_NAME_KEY_PROMETHEUS, + REPO_URL_KEY_PROMETHEUS, + REPO_PROVIDER_KEY_PROMETHEUS, ], get_settings().prometheus_registry.clone() ) @@ -143,6 +145,8 @@ impl TrackMetrics for PrometheusTracker { build_info_labels.version, build_info_labels.branch, build_info_labels.service_name, + build_info_labels.repo_url, + build_info_labels.repo_provider ]) .set(1); }); From 4cfd2f3b26224fa82daf4ba68fe36e188f3153ff Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 13 Nov 2023 16:21:00 +0100 Subject: [PATCH 10/15] [autometrics 1.0 compliance] add `autometrics.version` to `build_info` (#154) * add `autometrics.version` to `build_info` * update changelog * use dot notation * use a const to prevent repetition --- CHANGELOG.md | 4 +++- autometrics/src/constants.rs | 5 +++++ autometrics/src/labels.rs | 7 +++++-- autometrics/src/tracker/prometheus.rs | 4 +++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28595ebd..33582289 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,9 +24,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The [Autometrics specification v1.0.0](https://github.com/autometrics-dev/autometrics-shared/blob/main/specs/autometrics_v1.0.0.md) has been released. The following changes are made in order to bring the release in compliance with the spec: -- Added `repository_url` and `repository_provider` as `build_info` labels. They can be +- Added `repository.url` and `repository.provider` as `build_info` labels. They can be configured either through environment variables or automatically based on your `Cargo.toml` `package.repository` value (only GitHub, GitLab and BitBucket) (#152) +- Added `autometrics.version` as `build_info` label, specifying the autometrics spec + version that we are targeting. It is currently hardcoded to `1.0.0` (#154) ## [0.6.0](https://github.com/autometrics-dev/autometrics-rs/releases/tag/v0.5.0) - 2023-08-08 diff --git a/autometrics/src/constants.rs b/autometrics/src/constants.rs index 26f49b2b..ba24d6d1 100644 --- a/autometrics/src/constants.rs +++ b/autometrics/src/constants.rs @@ -1,3 +1,6 @@ +// Version of the autometrics spec we are targeting +pub const AUTOMETRICS_SPEC_TARGET: &str = "1.0.0"; + // Metrics pub const COUNTER_NAME: &str = "function.calls"; pub const HISTOGRAM_NAME: &str = "function.calls.duration"; @@ -41,3 +44,5 @@ pub const REPO_URL_KEY: &str = "repository.url"; pub const REPO_URL_KEY_PROMETHEUS: &str = "repository_url"; pub const REPO_PROVIDER_KEY: &str = "repository.provider"; pub const REPO_PROVIDER_KEY_PROMETHEUS: &str = "repository_provider"; +pub const AUTOMETRICS_VERSION_KEY: &str = "autometrics.version"; +pub const AUTOMETRICS_VERSION_KEY_PROMETHEUS: &str = "autometrics_version"; diff --git a/autometrics/src/labels.rs b/autometrics/src/labels.rs index f242a9c4..89d8925c 100644 --- a/autometrics/src/labels.rs +++ b/autometrics/src/labels.rs @@ -17,6 +17,7 @@ pub struct BuildInfoLabels { pub(crate) service_name: &'static str, pub(crate) repo_url: &'static str, pub(crate) repo_provider: &'static str, + pub(crate) autometrics_version: &'static str, } impl BuildInfoLabels { @@ -31,7 +32,8 @@ impl BuildInfoLabels { branch, service_name: &get_settings().service_name, repo_url, - repo_provider + repo_provider, + autometrics_version: AUTOMETRICS_SPEC_TARGET } } @@ -42,7 +44,8 @@ impl BuildInfoLabels { (BRANCH_KEY, self.branch), (SERVICE_NAME_KEY, self.service_name), (REPO_URL_KEY, self.repo_url), - (REPO_PROVIDER_KEY, self.repo_provider) + (REPO_PROVIDER_KEY, self.repo_provider), + (AUTOMETRICS_VERSION_KEY, self.autometrics_version) ] } diff --git a/autometrics/src/tracker/prometheus.rs b/autometrics/src/tracker/prometheus.rs index 87d92181..30b9bf2c 100644 --- a/autometrics/src/tracker/prometheus.rs +++ b/autometrics/src/tracker/prometheus.rs @@ -76,6 +76,7 @@ static BUILD_INFO: Lazy = Lazy::new(|| { SERVICE_NAME_KEY_PROMETHEUS, REPO_URL_KEY_PROMETHEUS, REPO_PROVIDER_KEY_PROMETHEUS, + AUTOMETRICS_VERSION_KEY_PROMETHEUS, ], get_settings().prometheus_registry.clone() ) @@ -146,7 +147,8 @@ impl TrackMetrics for PrometheusTracker { build_info_labels.branch, build_info_labels.service_name, build_info_labels.repo_url, - build_info_labels.repo_provider + build_info_labels.repo_provider, + AUTOMETRICS_SPEC_TARGET ]) .set(1); }); From 6aaaec649bc8dd258d1464a987723d1713de5dd7 Mon Sep 17 00:00:00 2001 From: Mari Date: Thu, 16 Nov 2023 14:05:18 +0100 Subject: [PATCH 11/15] [autometrics 1.0 compliance] allow setting repo url and provider at runtime (#156) * allow setting repo url and provider at runtime * update changelog --- CHANGELOG.md | 2 ++ autometrics-macros/src/lib.rs | 2 -- autometrics/src/labels.rs | 28 +++-------------- autometrics/src/settings.rs | 44 +++++++++++++++++++++++++++ autometrics/src/tracker/prometheus.rs | 2 +- 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33582289..692e6b8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ The following changes are made in order to bring the release in compliance with `package.repository` value (only GitHub, GitLab and BitBucket) (#152) - Added `autometrics.version` as `build_info` label, specifying the autometrics spec version that we are targeting. It is currently hardcoded to `1.0.0` (#154) +- Environment variables for `repository.url` and `repository.provider` will now be read at + both compile time and runtime (#156) ## [0.6.0](https://github.com/autometrics-dev/autometrics-rs/releases/tag/v0.5.0) - 2023-08-08 diff --git a/autometrics-macros/src/lib.rs b/autometrics-macros/src/lib.rs index bc29e6a8..c90f9115 100644 --- a/autometrics-macros/src/lib.rs +++ b/autometrics-macros/src/lib.rs @@ -308,8 +308,6 @@ fn instrument_function( option_env!("AUTOMETRICS_VERSION").or(option_env!("CARGO_PKG_VERSION")).unwrap_or_default(), option_env!("AUTOMETRICS_COMMIT").or(option_env!("VERGEN_GIT_SHA")).unwrap_or_default(), option_env!("AUTOMETRICS_BRANCH").or(option_env!("VERGEN_GIT_BRANCH")).unwrap_or_default(), - option_env!("AUTOMETRICS_REPOSITORY_URL").or(option_env!("CARGO_PKG_REPOSITORY")).unwrap_or_default(), - option_env!("AUTOMETRICS_REPOSITORY_PROVIDER").unwrap_or_default(), )); AutometricsTracker::start(#gauge_labels) }; diff --git a/autometrics/src/labels.rs b/autometrics/src/labels.rs index 89d8925c..a3cb2e44 100644 --- a/autometrics/src/labels.rs +++ b/autometrics/src/labels.rs @@ -21,19 +21,15 @@ pub struct BuildInfoLabels { } impl BuildInfoLabels { - pub fn new(version: &'static str, commit: &'static str, branch: &'static str, repo_url: &'static str, mut repo_provider: &'static str) -> Self { - if repo_provider.is_empty() { - repo_provider = Self::determinate_repo_provider_from_url(repo_url); - } - + pub fn new(version: &'static str, commit: &'static str, branch: &'static str) -> Self { Self { version, commit, branch, service_name: &get_settings().service_name, - repo_url, - repo_provider, - autometrics_version: AUTOMETRICS_SPEC_TARGET + repo_url: &get_settings().repo_url, + repo_provider: &get_settings().repo_provider, + autometrics_version: AUTOMETRICS_SPEC_TARGET, } } @@ -45,23 +41,9 @@ impl BuildInfoLabels { (SERVICE_NAME_KEY, self.service_name), (REPO_URL_KEY, self.repo_url), (REPO_PROVIDER_KEY, self.repo_provider), - (AUTOMETRICS_VERSION_KEY, self.autometrics_version) + (AUTOMETRICS_VERSION_KEY, self.autometrics_version), ] } - - fn determinate_repo_provider_from_url(url: &'static str) -> &'static str { - let lowered = url.to_lowercase(); - - if lowered.contains("github.com") { - "github" - } else if lowered.contains("gitlab.com") { - "gitlab" - } else if lowered.contains("bitbucket.org") { - "bitbucket" - } else { - "" - } - } } /// These are the labels used for the `function.calls` metric. diff --git a/autometrics/src/settings.rs b/autometrics/src/settings.rs index eb45efa9..e042d055 100644 --- a/autometrics/src/settings.rs +++ b/autometrics/src/settings.rs @@ -34,6 +34,8 @@ pub struct AutometricsSettings { #[cfg(any(prometheus_exporter, prometheus, prometheus_client))] pub(crate) histogram_buckets: Vec, pub(crate) service_name: String, + pub(crate) repo_url: String, + pub(crate) repo_provider: String, #[cfg(any(prometheus, opentelemetry))] pub(crate) prometheus_registry: prometheus::Registry, #[cfg(prometheus_client)] @@ -75,6 +77,8 @@ impl AutometricsSettings { #[derive(Debug, Default)] pub struct AutometricsSettingsBuilder { pub(crate) service_name: Option, + pub(crate) repo_url: Option, + pub(crate) repo_provider: Option, #[cfg(any(prometheus_exporter, prometheus, prometheus_client))] pub(crate) histogram_buckets: Option>, #[cfg(any(prometheus, opentelemetry))] @@ -111,6 +115,16 @@ impl AutometricsSettingsBuilder { self } + pub fn repo_url(mut self, repo_url: impl Into) -> Self { + self.repo_url = Some(repo_url.into()); + self + } + + pub fn repo_provider(mut self, repo_provider: impl Into) -> Self { + self.repo_provider = Some(repo_provider.into()); + self + } + /// Configure the [`prometheus::Registry`] that will be used to collect metrics when using /// either the `prometheus` or `opentelemetry` backends. If none is set, it will use /// the [`prometheus::default_registry`]. @@ -184,6 +198,11 @@ impl AutometricsSettingsBuilder { .unwrap_or_else(|| ::default()), ); + let repo_url = self + .repo_url + .or_else(|| env::var("AUTOMETRICS_REPOSITORY_URL").ok()) + .unwrap_or_else(|| env!("CARGO_PKG_REPOSITORY").to_string()); + AutometricsSettings { #[cfg(any(prometheus_exporter, prometheus, prometheus_client))] histogram_buckets: self @@ -194,6 +213,15 @@ impl AutometricsSettingsBuilder { .or_else(|| env::var("AUTOMETRICS_SERVICE_NAME").ok()) .or_else(|| env::var("OTEL_SERVICE_NAME").ok()) .unwrap_or_else(|| env!("CARGO_PKG_NAME").to_string()), + repo_provider: self + .repo_provider + .or_else(|| env::var("AUTOMETRICS_REPOSITORY_PROVIDER").ok()) + .or_else(|| { + AutometricsSettingsBuilder::determinate_repo_provider_from_url(Some(&repo_url)) + .map(|s| s.to_string()) + }) + .unwrap_or_default(), + repo_url, #[cfg(prometheus_client)] prometheus_client_registry, #[cfg(prometheus_client)] @@ -204,6 +232,22 @@ impl AutometricsSettingsBuilder { .unwrap_or_else(|| prometheus::default_registry().clone()), } } + + fn determinate_repo_provider_from_url(url: Option<&str>) -> Option<&'static str> { + url.and_then(|url| { + let lowered = url.to_lowercase(); + + if lowered.contains("github.com") { + Some("github") + } else if lowered.contains("gitlab.com") { + Some("gitlab") + } else if lowered.contains("bitbucket.org") { + Some("bitbucket") + } else { + None + } + }) + } } #[derive(Debug, Error)] diff --git a/autometrics/src/tracker/prometheus.rs b/autometrics/src/tracker/prometheus.rs index 30b9bf2c..3c7e3564 100644 --- a/autometrics/src/tracker/prometheus.rs +++ b/autometrics/src/tracker/prometheus.rs @@ -148,7 +148,7 @@ impl TrackMetrics for PrometheusTracker { build_info_labels.service_name, build_info_labels.repo_url, build_info_labels.repo_provider, - AUTOMETRICS_SPEC_TARGET + build_info_labels.autometrics_version, ]) .set(1); }); From 1af33f20e44aca644bf42ab2046cfdde93d5c070 Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 27 Nov 2023 11:54:29 +0100 Subject: [PATCH 12/15] update `opentelemetry` to 0.21 and `prometheus-client` to 0.22 (#159, #160) * otel update * update actions too * update trybuild error * update actions * exemplars as well * update otel exporter * update changelog * update prometheus-client to 0.22 (#160) * upgrade prometheus-client * update changelog * add notice in changelog * fix typo --- .github/workflows/ci.yml | 11 +++-- CHANGELOG.md | 14 ++++++ autometrics/Cargo.toml | 43 ++++++++----------- autometrics/build.rs | 12 +++--- autometrics/src/README.md | 4 +- .../src/exemplars/tracing_opentelemetry.rs | 17 ++------ autometrics/src/lib.rs | 3 +- autometrics/src/otel_push_exporter.rs | 14 ++++-- autometrics/src/prometheus_exporter.rs | 4 +- autometrics/src/tracker/opentelemetry.rs | 4 +- autometrics/tests/exemplars_test.rs | 9 ++-- .../fail/async_trait_support.stderr | 10 ++++- autometrics/tests/settings_custom_registry.rs | 2 +- .../Cargo.toml | 4 +- examples/exemplars-tracing/Cargo.toml | 2 +- examples/opentelemetry-push-custom/Cargo.toml | 9 ++-- examples/opentelemetry-push/Cargo.toml | 2 +- 17 files changed, 86 insertions(+), 78 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71d30725..a14e8bf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,17 +21,16 @@ jobs: # Note: GitHub hosted runners using the latest stable version of Rust have Clippy pre-installed. - run: cargo clippy --features=metrics-0_21,prometheus-exporter - run: cargo clippy --features=prometheus-0_13 - - run: cargo clippy --features=prometheus-client-0_21 - - run: cargo clippy --features=opentelemetry-0_20 + - run: cargo clippy --features=prometheus-client-0_22 + - run: cargo clippy --features=opentelemetry-0_21 # Run the tests with each of the different metrics libraries - run: cargo test --features=prometheus-exporter - run: cargo test --features=prometheus-exporter,metrics-0_21 - run: cargo test --features=prometheus-exporter,prometheus-0_13 - - run: cargo test --features=prometheus-exporter,prometheus-client-0_21,exemplars-tracing - - run: cargo test --features=prometheus-exporter,prometheus-client-0_21,exemplars-tracing-opentelemetry-0_20 - - run: cargo test --features=prometheus-exporter,prometheus-client-0_21,exemplars-tracing-opentelemetry-0_21 - - run: cargo test --features=prometheus-exporter,opentelemetry-0_20 + - run: cargo test --features=prometheus-exporter,prometheus-client-0_22,exemplars-tracing + - run: cargo test --features=prometheus-exporter,prometheus-client-0_22,exemplars-tracing-opentelemetry-0_22 + - run: cargo test --features=prometheus-exporter,opentelemetry-0_21 # Build the crate using the other optional features - run: cargo build --features=metrics-0_21,custom-objective-percentile,custom-objective-latency diff --git a/CHANGELOG.md b/CHANGELOG.md index 692e6b8a..ff699b6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Breaking changes + +- `opentelemetry` has been updated to v0.21 (#159) + New feature flags: `opentelemetry-0_21`, `exemplars-tracing-opentelemetry-0_22` + Removed feature flags: `opentelemetry-0_20`, `exemplars-tracing-opentelemetry-0_20`, `exemplars-tracing-opentelemetry-0_21` +- `prometheus-client` has been updated to v0.22 (#160) + New feature flag: `prometheus-client-0_22` + Removed feature flag: `prometheus-client-0_21` + +**If you are using these metrics separately in your application in addition to Autometrics, +ensure that you match the version that Autometrics uses.** + +### General changes + - New exporter, `otel_push_exporter` is now available in addition to the existing `prometheus_exporter`. It can be used to push metrics in the OTEL format via HTTP or gRPC to a OTEL-collector-(compatible) server. diff --git a/autometrics/Cargo.toml b/autometrics/Cargo.toml index e4959691..8e38701e 100644 --- a/autometrics/Cargo.toml +++ b/autometrics/Cargo.toml @@ -15,16 +15,16 @@ readme = "README.md" [features] # Metrics backends metrics-0_21 = ["dep:metrics"] -opentelemetry-0_20 = ["opentelemetry_api/metrics", "dep:prometheus"] +opentelemetry-0_21 = ["opentelemetry/metrics", "dep:prometheus"] prometheus-0_13 = ["dep:prometheus"] -prometheus-client-0_21 = ["dep:prometheus-client"] +prometheus-client-0_22 = ["dep:prometheus-client"] # Deprecated feature flags metrics = ["metrics-0_21"] -opentelemetry = ["opentelemetry-0_20"] +opentelemetry = ["opentelemetry-0_21"] prometheus = ["prometheus-0_13"] -prometheus-client = ["prometheus-client-0_21"] -exemplars-tracing-opentelemetry = ["exemplars-tracing-opentelemetry-0_20"] +prometheus-client = ["prometheus-client-0_22"] +exemplars-tracing-opentelemetry = ["exemplars-tracing-opentelemetry-0_22"] # Misc prometheus-exporter = [ @@ -38,7 +38,7 @@ prometheus-exporter = [ otel-push-exporter = [ "opentelemetry_sdk", - "opentelemetry_api", + "dep:opentelemetry", "opentelemetry-otlp", "opentelemetry-otlp/metrics", "opentelemetry-otlp/tls-roots" @@ -71,15 +71,11 @@ otel-push-exporter-async-std = [ # Exemplars exemplars-tracing = ["tracing", "tracing-subscriber"] -exemplars-tracing-opentelemetry-0_20 = [ - "opentelemetry_api/trace", +exemplars-tracing-opentelemetry-0_22 = [ + "dep:opentelemetry", + "opentelemetry_sdk/trace", "tracing", - "dep:tracing-opentelemetry-0-20", -] -exemplars-tracing-opentelemetry-0_21 = [ - "opentelemetry_api/trace", - "tracing", - "dep:tracing-opentelemetry-0-21", + "dep:tracing-opentelemetry-0-22", ] # Custom objectives @@ -94,7 +90,7 @@ spez = "0.1.2" thiserror = "1" # Used for opentelemetry feature -opentelemetry_api = { version = "0.20", default-features = false, optional = true } +opentelemetry = { version = "0.21", default-features = false, optional = true } # Use for metrics feature metrics = { version = "0.21", default-features = false, optional = true } @@ -102,15 +98,15 @@ metrics = { version = "0.21", default-features = false, optional = true } # Used for prometheus-exporter feature http = { version = "0.2", optional = true } metrics-exporter-prometheus = { version = "0.12", default-features = false, optional = true } -opentelemetry-prometheus = { version = "0.13.0", optional = true } -opentelemetry_sdk = { version = "0.20", default-features = false, features = [ +opentelemetry-prometheus = { version = "0.14", optional = true } +opentelemetry_sdk = { version = "0.21", default-features = false, features = [ "metrics", ], optional = true } -opentelemetry-otlp = { version = "0.13.0", default-features = false, optional = true } +opentelemetry-otlp = { version = "0.14.0", default-features = false, optional = true } prometheus = { version = "0.13", default-features = false, optional = true } # Used for prometheus-client feature -prometheus-client = { version = "0.21.1", optional = true } +prometheus-client = { version = "0.22", optional = true } # Used for exemplars-tracing feature tracing = { version = "0.1", optional = true } @@ -119,17 +115,16 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [ ], optional = true } # Used for exemplars-tracing-opentelemetry feature -tracing-opentelemetry-0-20 = { package = "tracing-opentelemetry", version = "0.20.0", default-features = false, optional = true } -tracing-opentelemetry-0-21 = { package = "tracing-opentelemetry", version = "0.21.0", default-features = false, optional = true } +tracing-opentelemetry-0-22 = { package = "tracing-opentelemetry", version = "0.22", default-features = false, optional = true } [dev-dependencies] async-trait = "0.1.74" axum = { version = "0.6", features = ["tokio"] } criterion = "0.5" http = "0.2" -opentelemetry = "0.20" -opentelemetry-stdout = { version = "0.1", features = ["trace"] } -prometheus-client = "0.21" +opentelemetry = "0.21" +opentelemetry-stdout = { version = "0.2", features = ["trace"] } +prometheus-client = "0.22" tokio = { version = "1", features = ["full"] } tracing = "0.1" tracing-subscriber = "0.3" diff --git a/autometrics/build.rs b/autometrics/build.rs index 17ae3d0a..34f838de 100644 --- a/autometrics/build.rs +++ b/autometrics/build.rs @@ -6,20 +6,20 @@ pub fn main() { #[cfg(feature = "metrics")] println!("cargo:warning=The `metrics` feature is deprecated and will be removed in the next version. Please use `metrics-0_21` instead."); #[cfg(feature = "opentelemetry")] - println!("cargo:warning=The `opentelemetry` feature is deprecated and will be removed in the next version. Please use `opentelemetry-0_20` instead."); + println!("cargo:warning=The `opentelemetry` feature is deprecated and will be removed in the next version. Please use `opentelemetry-0_21` instead."); #[cfg(feature = "prometheus")] println!("cargo:warning=The `prometheus` feature is deprecated and will be removed in the next version. Please use `prometheus-0_13` instead."); #[cfg(feature = "prometheus-client")] - println!("cargo:warning=The `prometheus-client` feature is deprecated and will be removed in the next version. Please use `prometheus-client-0_21` instead."); + println!("cargo:warning=The `prometheus-client` feature is deprecated and will be removed in the next version. Please use `prometheus-client-0_22` instead."); #[cfg(feature = "exemplars-tracing-opentelemetry")] - println!("cargo:warning=The `exemplars-tracing-opentelemetry` feature is deprecated and will be removed in the next version. Please use `exemplars-tracing-opentelemetry-0_20` or `exemplars-tracing-opentelemetry-0_21` instead."); + println!("cargo:warning=The `exemplars-tracing-opentelemetry` feature is deprecated and will be removed in the next version. Please use `exemplars-tracing-opentelemetry-0_22` instead."); cfg_aliases! { // Backends metrics: { any(feature = "metrics", feature = "metrics-0_21") }, - opentelemetry: { any(feature = "opentelemetry", feature = "opentelemetry-0_20") }, + opentelemetry: { any(feature = "opentelemetry", feature = "opentelemetry-0_21") }, prometheus: { any(feature = "prometheus", feature = "prometheus-0_13") }, - prometheus_client_feature: { any(feature = "prometheus-client", feature = "prometheus-client-0_21") }, + prometheus_client_feature: { any(feature = "prometheus-client", feature = "prometheus-client-0_22") }, default_backend: { all( prometheus_exporter, not(any(metrics, opentelemetry, prometheus, prometheus_client_feature)) @@ -32,7 +32,7 @@ pub fn main() { // Exemplars exemplars: { any(exemplars_tracing, exemplars_tracing_opentelemetry) }, exemplars_tracing: { feature = "exemplars-tracing" }, - exemplars_tracing_opentelemetry: { any(feature = "exemplars-tracing-opentelemetry-0_20", feature = "exemplars-tracing-opentelemetry-0_21", feature = "exemplars-tracing-opentelemetry") }, + exemplars_tracing_opentelemetry: { any(feature = "exemplars-tracing-opentelemetry-0_22", feature = "exemplars-tracing-opentelemetry") }, // Custom objectives custom_objective_percentile: { feature = "custom-objective-percentile" }, diff --git a/autometrics/src/README.md b/autometrics/src/README.md index fb72294c..eb487088 100644 --- a/autometrics/src/README.md +++ b/autometrics/src/README.md @@ -148,10 +148,10 @@ If you require more customization than these offered feature flags, enable just > If you are exporting metrics yourself rather than using the `prometheus-exporter`, you must ensure that you are using the exact same version of the metrics library as `autometrics` (and it must come from `crates.io` rather than git or another source). If not, the autometrics metrics will not appear in your exported metrics. -- `opentelemetry-0_20` - use the [opentelemetry](https://crates.io/crates/opentelemetry) crate for producing metrics. +- `opentelemetry-0_21` - use the [opentelemetry](https://crates.io/crates/opentelemetry) crate for producing metrics. - `metrics-0_21` - use the [metrics](https://crates.io/crates/metrics) crate for producing metrics - `prometheus-0_13` - use the [prometheus](https://crates.io/crates/prometheus) crate for producing metrics -- `prometheus-client-0_21` - use the official [prometheus-client](https://crates.io/crates/prometheus-client) crate for producing metrics +- `prometheus-client-0_22` - use the official [prometheus-client](https://crates.io/crates/prometheus-client) crate for producing metrics ### Exemplars (for integrating metrics with traces) diff --git a/autometrics/src/exemplars/tracing_opentelemetry.rs b/autometrics/src/exemplars/tracing_opentelemetry.rs index 9e7b3144..403b00d5 100644 --- a/autometrics/src/exemplars/tracing_opentelemetry.rs +++ b/autometrics/src/exemplars/tracing_opentelemetry.rs @@ -1,23 +1,12 @@ use super::TraceLabels; -use opentelemetry_api::trace::TraceContextExt; +use opentelemetry::trace::TraceContextExt as _; use std::iter::FromIterator; use tracing::Span; - -#[cfg(all( - not(doc), - all( - feature = "exemplars-tracing-opentelemetry-0_20", - feature = "exemplars-tracing-opentelemetry-0_21" - ) -))] -compile_error!("Only one of the `exemplars-tracing-opentelemetry-0_20` and `exemplars-tracing-opentelemetry-0_21` features can be enabled at a time"); +use tracing_opentelemetry_0_22::OpenTelemetrySpanExt; pub fn get_exemplar() -> Option { // Get the OpenTelemetry Context from the tracing span - #[cfg(feature = "exemplars-tracing-opentelemetry-0_20")] - let context = tracing_opentelemetry_0_20::OpenTelemetrySpanExt::context(&Span::current()); - #[cfg(feature = "exemplars-tracing-opentelemetry-0_21")] - let context = tracing_opentelemetry_0_21::OpenTelemetrySpanExt::context(&Span::current()); + let context = OpenTelemetrySpanExt::context(&Span::current()); // Now get the OpenTelemetry "span" from the Context // (it's confusing because the word "span" is used by both tracing and OpenTelemetry diff --git a/autometrics/src/lib.rs b/autometrics/src/lib.rs index 12623afa..344f0611 100644 --- a/autometrics/src/lib.rs +++ b/autometrics/src/lib.rs @@ -9,8 +9,7 @@ mod constants; #[cfg(any( feature = "exemplars-tracing", feature = "exemplars-tracing-opentelemetry", - feature = "exemplars-tracing-opentelemetry-0_20", - feature = "exemplars-tracing-opentelemetry-0_21", + feature = "exemplars-tracing-opentelemetry-0_22", ))] pub mod exemplars; mod labels; diff --git a/autometrics/src/otel_push_exporter.rs b/autometrics/src/otel_push_exporter.rs index 2b4fb053..a55b298c 100644 --- a/autometrics/src/otel_push_exporter.rs +++ b/autometrics/src/otel_push_exporter.rs @@ -1,4 +1,4 @@ -use opentelemetry_api::metrics::MetricsError; +use opentelemetry::metrics::MetricsError; use opentelemetry_otlp::OtlpMetricPipeline; use opentelemetry_otlp::{ExportConfig, Protocol, WithExportConfig}; use opentelemetry_sdk::metrics::MeterProvider; @@ -122,7 +122,8 @@ pub fn init_grpc_with_timeout_period( feature = "otel-push-exporter-async-std" )) ))] -fn runtime() -> OtlpMetricPipeline { +fn runtime( +) -> OtlpMetricPipeline { return opentelemetry_otlp::new_pipeline().metrics(opentelemetry_sdk::runtime::Tokio); } @@ -133,7 +134,10 @@ fn runtime() -> OtlpMetricPipeline { feature = "otel-push-exporter-async-std" )) ))] -fn runtime() -> OtlpMetricPipeline { +fn runtime() -> OtlpMetricPipeline< + opentelemetry_sdk::runtime::TokioCurrentThread, + opentelemetry_otlp::NoExporterConfig, +> { return opentelemetry_otlp::new_pipeline() .metrics(opentelemetry_sdk::runtime::TokioCurrentThread); } @@ -145,7 +149,9 @@ fn runtime() -> OtlpMetricPipeline OtlpMetricPipeline { +fn runtime( +) -> OtlpMetricPipeline +{ return opentelemetry_otlp::new_pipeline().metrics(opentelemetry_sdk::runtime::AsyncStd); } diff --git a/autometrics/src/prometheus_exporter.rs b/autometrics/src/prometheus_exporter.rs index 4da1d51b..17b0159a 100644 --- a/autometrics/src/prometheus_exporter.rs +++ b/autometrics/src/prometheus_exporter.rs @@ -28,7 +28,7 @@ use http::{header::CONTENT_TYPE, Response}; use metrics_exporter_prometheus::{BuildError, PrometheusBuilder, PrometheusHandle}; use once_cell::sync::OnceCell; #[cfg(opentelemetry)] -use opentelemetry_api::metrics::MetricsError; +use opentelemetry::metrics::MetricsError; #[cfg(any(opentelemetry, prometheus))] use prometheus::TextEncoder; use thiserror::Error; @@ -210,7 +210,7 @@ fn initialize_prometheus_exporter() -> Result tests/result_labels/fail/async_trait_support.rs:31:9 + --> tests/result_labels/fail/async_trait_support.rs:31:10 | 31 | ::method().await; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TestTrait` is not implemented for `TestStruct` + | ^^^^^^^^^^ the trait `TestTrait` is not implemented for `TestStruct` + | +help: this trait has no implementations, consider adding one + --> tests/result_labels/fail/async_trait_support.rs:7:1 + | +7 | trait TestTrait { + | ^^^^^^^^^^^^^^^ error[E0599]: no method named `self_method` found for struct `TestStruct` in the current scope --> tests/result_labels/fail/async_trait_support.rs:32:12 diff --git a/autometrics/tests/settings_custom_registry.rs b/autometrics/tests/settings_custom_registry.rs index 6ea44c39..70dc9c9d 100644 --- a/autometrics/tests/settings_custom_registry.rs +++ b/autometrics/tests/settings_custom_registry.rs @@ -94,7 +94,7 @@ fn custom_prometheus_registry() { #[cfg(opentelemetry)] #[test] fn custom_opentelemetry_registry() { - use opentelemetry_api::{global, KeyValue}; + use opentelemetry::{global, KeyValue}; use prometheus::{Registry, TextEncoder}; // OpenTelemetry uses the `prometheus` crate under the hood diff --git a/examples/exemplars-tracing-opentelemetry/Cargo.toml b/examples/exemplars-tracing-opentelemetry/Cargo.toml index 369fd8b2..8ae1aabf 100644 --- a/examples/exemplars-tracing-opentelemetry/Cargo.toml +++ b/examples/exemplars-tracing-opentelemetry/Cargo.toml @@ -6,9 +6,9 @@ edition = "2021" [dependencies] autometrics = { path = "../../autometrics", features = [ - "prometheus-client-0_21", + "prometheus-client-0_22", "prometheus-exporter", - "exemplars-tracing-opentelemetry-0_21", + "exemplars-tracing-opentelemetry-0_22", ] } autometrics-example-util = { path = "../util" } axum = { version = "0.6", features = ["json"] } diff --git a/examples/exemplars-tracing/Cargo.toml b/examples/exemplars-tracing/Cargo.toml index c749d893..6d8dd7be 100644 --- a/examples/exemplars-tracing/Cargo.toml +++ b/examples/exemplars-tracing/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] autometrics = { path = "../../autometrics", features = [ - "prometheus-client-0_21", + "prometheus-client-0_22", "prometheus-exporter", "exemplars-tracing" ] } diff --git a/examples/opentelemetry-push-custom/Cargo.toml b/examples/opentelemetry-push-custom/Cargo.toml index fca59cce..2acd3dae 100644 --- a/examples/opentelemetry-push-custom/Cargo.toml +++ b/examples/opentelemetry-push-custom/Cargo.toml @@ -5,11 +5,12 @@ publish = false edition = "2021" [dependencies] -autometrics = { path = "../../autometrics", features = ["opentelemetry-0_20"] } +autometrics = { path = "../../autometrics", features = ["opentelemetry-0_21"] } autometrics-example-util = { path = "../util" } # Note that the version of the opentelemetry crate MUST match # the version used by autometrics -opentelemetry = { version = "0.20", features = ["metrics", "rt-tokio"] } -opentelemetry-otlp = { version = "0.13", features = ["tonic", "metrics"] } -opentelemetry-semantic-conventions = { version = "0.12.0" } +opentelemetry = { version = "0.21", features = ["metrics"] } +opentelemetry_sdk = { version = "0.21.1", features = ["metrics", "rt-tokio"] } +opentelemetry-otlp = { version = "0.14", features = ["tonic", "metrics"] } +opentelemetry-semantic-conventions = { version = "0.13" } tokio = { version = "1", features = ["full"] } diff --git a/examples/opentelemetry-push/Cargo.toml b/examples/opentelemetry-push/Cargo.toml index b33a36e8..fd2747b7 100644 --- a/examples/opentelemetry-push/Cargo.toml +++ b/examples/opentelemetry-push/Cargo.toml @@ -5,6 +5,6 @@ publish = false edition = "2021" [dependencies] -autometrics = { path = "../../autometrics", features = ["opentelemetry-0_20", "otel-push-exporter-http", "otel-push-exporter-tokio"] } +autometrics = { path = "../../autometrics", features = ["opentelemetry-0_21", "otel-push-exporter-http", "otel-push-exporter-tokio"] } autometrics-example-util = { path = "../util" } tokio = { version = "1", features = ["full"] } From 69e64f851ed7e3668a914a62126eabdfe134ee36 Mon Sep 17 00:00:00 2001 From: Mari Date: Thu, 30 Nov 2023 12:57:43 +0100 Subject: [PATCH 13/15] add full support for `async-trait` (#161) * add full support for async-trait * move async trait support from compile fail to pass * update changelog * update changelog again --- CHANGELOG.md | 3 ++ autometrics-macros/src/lib.rs | 48 ++++++++++++------- .../fail/async_trait_support.stderr | 35 -------------- .../{fail => pass}/async_trait_support.rs | 0 4 files changed, 34 insertions(+), 52 deletions(-) delete mode 100644 autometrics/tests/result_labels/fail/async_trait_support.stderr rename autometrics/tests/result_labels/{fail => pass}/async_trait_support.rs (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff699b6e..2764633e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,9 @@ ensure that you match the version that Autometrics uses.** has been improved (#149) - Fixed missing feature flag for `opentelemetry-otlp` when autometrics feature `otel-push-exporter` is enabled +- Fixed incorrect duration being recorded when using `#[async_trait]` together with `#[autometrics]` (#161) + **Please note that the `#[autometrics]` macro needs to be defined BEFORE `#[async_trait]`.** +- Fixed value of the `result` label being empty when the function is annotated with `#[async_trait]` (#161) ### Autometrics 1.0 compliance diff --git a/autometrics-macros/src/lib.rs b/autometrics-macros/src/lib.rs index c90f9115..fcf87909 100644 --- a/autometrics-macros/src/lib.rs +++ b/autometrics-macros/src/lib.rs @@ -1,8 +1,9 @@ use crate::parse::{AutometricsArgs, Item}; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; -use proc_macro2::{Span, TokenStream}; +use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use std::env; +use std::str::FromStr; use syn::{ parse_macro_input, GenericArgument, ImplItem, ItemFn, ItemImpl, PathArguments, Result, ReturnType, Type, @@ -21,19 +22,14 @@ pub fn autometrics( args: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let args = parse_macro_input!(args as parse::AutometricsArgs); - - // if the input has yet to be modified by the `async-trait` crate, we will fail parsing in - // in the next line, so lets error early now - if let Err(err) = check_async_trait(&item) { - return err.into_compile_error().into(); - } + let args = parse_macro_input!(args as AutometricsArgs); + let (async_trait, item) = check_async_trait(item); let item = parse_macro_input!(item as Item); let result = match item { Item::Function(item) => instrument_function(&args, item, &None), - Item::Impl(item) => instrument_impl_block(&args, item), + Item::Impl(item) => instrument_impl_block(&args, item, async_trait), }; let output = match result { @@ -44,16 +40,21 @@ pub fn autometrics( output.into() } -fn check_async_trait(input: &proc_macro::TokenStream) -> Result<()> { +fn check_async_trait(input: proc_macro::TokenStream) -> (bool, proc_macro::TokenStream) { let str = input.to_string(); if str.contains("#[async_trait]") || str.contains("#[async_trait::async_trait]") { - Err(syn::Error::new( - Span::call_site(), - "#[async_trait] must be defined BEFORE #[autometrics]", - )) + // .unwrap is safe because we only remove tokens from the existing stream, we dont add new ones + ( + true, + proc_macro::TokenStream::from_str( + &str.replace("#[async_trait]", "") + .replace("#[async_trait::async_trait]", ""), + ) + .unwrap(), + ) } else { - Ok(()) + (false, input) } } @@ -331,9 +332,19 @@ fn instrument_function( } /// Add autometrics instrumentation to an entire impl block -fn instrument_impl_block(args: &AutometricsArgs, mut item: ItemImpl) -> Result { +fn instrument_impl_block( + args: &AutometricsArgs, + mut item: ItemImpl, + async_trait: bool, +) -> Result { let struct_name = Some(item.self_ty.to_token_stream().to_string()); + let async_trait = if async_trait { + quote! { #[async_trait::async_trait] } + } else { + quote! {} + }; + // Replace all of the method items in place item.items = item .items @@ -368,7 +379,10 @@ fn instrument_impl_block(args: &AutometricsArgs, mut item: ItemImpl) -> Result tests/result_labels/fail/async_trait_support.rs:15:1 - | -15 | #[autometrics] - | ^^^^^^^^^^^^^^ - | - = note: this error originates in the attribute macro `autometrics` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `TestStruct: TestTrait` is not satisfied - --> tests/result_labels/fail/async_trait_support.rs:31:10 - | -31 | ::method().await; - | ^^^^^^^^^^ the trait `TestTrait` is not implemented for `TestStruct` - | -help: this trait has no implementations, consider adding one - --> tests/result_labels/fail/async_trait_support.rs:7:1 - | -7 | trait TestTrait { - | ^^^^^^^^^^^^^^^ - -error[E0599]: no method named `self_method` found for struct `TestStruct` in the current scope - --> tests/result_labels/fail/async_trait_support.rs:32:12 - | -13 | struct TestStruct; - | ----------------- method `self_method` not found for this struct -... -32 | ts.self_method().await; - | ^^^^^^^^^^^ method not found in `TestStruct` - | - = help: items from traits can only be used if the trait is implemented and in scope -note: `TestTrait` defines an item `self_method`, perhaps you need to implement it - --> tests/result_labels/fail/async_trait_support.rs:7:1 - | -7 | trait TestTrait { - | ^^^^^^^^^^^^^^^ diff --git a/autometrics/tests/result_labels/fail/async_trait_support.rs b/autometrics/tests/result_labels/pass/async_trait_support.rs similarity index 100% rename from autometrics/tests/result_labels/fail/async_trait_support.rs rename to autometrics/tests/result_labels/pass/async_trait_support.rs From 8600582a1aeace6fd8ce90f0b079995ade4cb2d5 Mon Sep 17 00:00:00 2001 From: Mari Date: Fri, 1 Dec 2023 13:33:13 +0100 Subject: [PATCH 14/15] fix `#[async_trait]` attribute not being correctly detected when its re-exported from another crate (#164) * fix detection of `#[async_trait]` attribute * use regex instead * remember which async-trait to readd * update changelog * change comment to be used in `.expect` Co-authored-by: Gerry Agbobada <10496163+gagbo@users.noreply.github.com> --------- Co-authored-by: Gerry Agbobada <10496163+gagbo@users.noreply.github.com> --- CHANGELOG.md | 1 + autometrics-macros/Cargo.toml | 1 + autometrics-macros/src/lib.rs | 46 ++++++++++++++++------------------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2764633e..366a39d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ ensure that you match the version that Autometrics uses.** - Fixed incorrect duration being recorded when using `#[async_trait]` together with `#[autometrics]` (#161) **Please note that the `#[autometrics]` macro needs to be defined BEFORE `#[async_trait]`.** - Fixed value of the `result` label being empty when the function is annotated with `#[async_trait]` (#161) +- Fixed `#[async_trait]` not being correctly detected if its re-exported in another crate (#164) ### Autometrics 1.0 compliance diff --git a/autometrics-macros/Cargo.toml b/autometrics-macros/Cargo.toml index dd5c5df7..d4e3fa9b 100644 --- a/autometrics-macros/Cargo.toml +++ b/autometrics-macros/Cargo.toml @@ -18,4 +18,5 @@ proc-macro = true percent-encoding = "2.2" proc-macro2 = "1" quote = "1" +regex = "1.10.2" syn = { version = "2", features = ["full"] } diff --git a/autometrics-macros/src/lib.rs b/autometrics-macros/src/lib.rs index fcf87909..198bf35b 100644 --- a/autometrics-macros/src/lib.rs +++ b/autometrics-macros/src/lib.rs @@ -2,6 +2,7 @@ use crate::parse::{AutometricsArgs, Item}; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; +use regex::Regex; use std::env; use std::str::FromStr; use syn::{ @@ -29,7 +30,7 @@ pub fn autometrics( let result = match item { Item::Function(item) => instrument_function(&args, item, &None), - Item::Impl(item) => instrument_impl_block(&args, item, async_trait), + Item::Impl(item) => instrument_impl_block(&args, item, &async_trait), }; let output = match result { @@ -40,22 +41,21 @@ pub fn autometrics( output.into() } -fn check_async_trait(input: proc_macro::TokenStream) -> (bool, proc_macro::TokenStream) { - let str = input.to_string(); - - if str.contains("#[async_trait]") || str.contains("#[async_trait::async_trait]") { - // .unwrap is safe because we only remove tokens from the existing stream, we dont add new ones - ( - true, - proc_macro::TokenStream::from_str( - &str.replace("#[async_trait]", "") - .replace("#[async_trait::async_trait]", ""), - ) - .unwrap(), - ) - } else { - (false, input) - } +/// returns a tuple of two containing: +/// - `async_trait` attributes that have to be re-added after our instrumentation magic has been added +/// - `input` but without the `async_trait` attributes +fn check_async_trait(input: proc_macro::TokenStream) -> (String, proc_macro::TokenStream) { + let regex = Regex::new(r#"#\[[^\]]*async_trait\]"#).expect("The regex is hardcoded and thus guaranteed to be successfully parseable"); + + let original = input.to_string(); + + let attributes: Vec<_> = regex.find_iter(&original).map(|m| m.as_str()).collect(); + let replaced = regex.replace_all(&original, ""); + + // .unwrap is safe because we only remove tokens from the existing stream, we dont add new ones + let ts = proc_macro::TokenStream::from_str(replaced.as_ref()).unwrap(); + + (attributes.join("\n"), ts) } #[proc_macro_derive(ResultLabels, attributes(label))] @@ -335,16 +335,10 @@ fn instrument_function( fn instrument_impl_block( args: &AutometricsArgs, mut item: ItemImpl, - async_trait: bool, + attributes_to_re_add: &str, ) -> Result { let struct_name = Some(item.self_ty.to_token_stream().to_string()); - let async_trait = if async_trait { - quote! { #[async_trait::async_trait] } - } else { - quote! {} - }; - // Replace all of the method items in place item.items = item .items @@ -379,8 +373,10 @@ fn instrument_impl_block( }) .collect(); + let ts = TokenStream::from_str(attributes_to_re_add)?; + Ok(quote! { - #async_trait + #ts #item }) } From 1c6ae63ff061d0df17654d7f429df99474fcdfe4 Mon Sep 17 00:00:00 2001 From: Mari Date: Fri, 1 Dec 2023 14:51:38 +0100 Subject: [PATCH 15/15] bump version to v1.0.0 (#165) --- CHANGELOG.md | 6 +++++- Cargo.toml | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 366a39d8..a147dbc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- -## [Unreleased] +## Unreleased + +- + +## [1.0.0] - 2023-12-01 ### Breaking changes diff --git a/Cargo.toml b/Cargo.toml index b62fd92b..b21d5b09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.6.0" +version = "1.0.0" edition = "2021" authors = ["Fiberplane ", "Evan Schwartz <3262610+emschwartz@users.noreply.github.com>"] description = " Easily add metrics to your code that actually help you spot and debug issues in production. Built on Prometheus and OpenTelemetry." @@ -21,4 +21,4 @@ members = [ exclude = ["examples/data", "examples/target"] [workspace.dependencies] -autometrics-macros = { version = "0.6.0", path = "autometrics-macros" } +autometrics-macros = { version = "1.0.0", path = "autometrics-macros" }