-
Notifications
You must be signed in to change notification settings - Fork 321
Add TelemetryPolicy #210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add TelemetryPolicy #210
Changes from 6 commits
2d1d028
a7a2589
d53f233
250c336
4075f13
a6e592d
0a70036
045f78f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| use rustc_version::version; | ||
|
|
||
| fn main() { | ||
| let version = match version() { | ||
| Ok(version) => version.to_string(), | ||
| Err(_) => "unknown".to_string(), | ||
| }; | ||
| println!("cargo:rustc-env=AZSDK_RUSTC_VERSION={}", version); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| use crate::policies::{Policy, TelemetryOptions}; | ||
| use std::sync::Arc; | ||
|
|
||
| /// Options passed clients to customer policies, telemetry, etc. | ||
| #[derive(Clone, Debug, Default)] | ||
| pub struct ClientOptions { | ||
| // TODO: Expose retry options and transport overrides. | ||
| pub per_call_policies: Vec<Arc<dyn Policy>>, | ||
| pub per_retry_policies: Vec<Arc<dyn Policy>>, | ||
|
|
||
| pub telemetry: TelemetryOptions, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,20 @@ | ||
| use crate::policies::{Policy, PolicyResult}; | ||
| use crate::{Context, Request, Response}; | ||
| use crate::policies::{Policy, PolicyResult, TelemetryPolicy}; | ||
| use crate::{ClientOptions, Context, Request, Response}; | ||
| use std::sync::Arc; | ||
|
|
||
| /// Execution pipeline. | ||
| /// | ||
| /// A pipeline follows a precise flow: | ||
| /// | ||
| /// 1. Per call policies are executed. Per call policies can fail and bail out of the pipeline | ||
| /// 1. Client library-specified per-call policies are executed. Per-call policies can fail and bail out of the pipeline | ||
| /// immediately. | ||
| /// 2. Retry policy. It allows to reexecute the following policies. | ||
| /// 3. Per retry policies. Per retry polices are always executed at least once but are reexecuted | ||
| /// 2. User-specified per-call policies are executed. | ||
| /// 3. Telemetry policy. | ||
| /// 4. Retry policy. It allows to re-execute the following policies. | ||
| /// 5. Client library-specified per-retry policies. Per-retry polices are always executed at least once but are re-executed | ||
| /// in case of retries. | ||
| /// 4. Transport policy. Transtport policy is always the last policy and is the policy that | ||
| /// 6. User-specified per-retry policies are executed. | ||
| /// 7. Transport policy. Transport policy is always the last policy and is the policy that | ||
| /// actually constructs the `Response` to be passed up the pipeline. | ||
| /// | ||
| /// A pipeline is immutable. In other words a policy can either succeed and call the following | ||
|
|
@@ -24,18 +27,29 @@ pub struct Pipeline { | |
| } | ||
|
|
||
| impl Pipeline { | ||
| /// Creates a new pipeline given the client library crate name and version, | ||
| /// alone with user-specified and client library-specified policies. | ||
| /// | ||
| /// Crates can simply pass `option_env!("CARGO_PKG_NAME")` and `option_env!("CARGO_PKG_VERSION")` for the | ||
| /// `crate_name` and `crate_version` arguments respectively. | ||
| pub fn new( | ||
| crate_name: Option<&'static str>, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The number of arguments here is getting pretty long. What speaks about
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is temporary. For example, the retry policy needs to be removed in favor of retry options - higher-level abstracts that can pick the right policy (then keep those internal). But I can't use CARGO_* env vars from code in azure_core or they will always end up being "core" and azure_core's version. All our other SDKs pass this information in unless it can be retrieved automatically e.g. in .NET by checking for an assembly-level attribute. It's probably worth using a builder at this point, which is what most of our other SDKs do. But that's something I want to tackle in a different PR. This PR is focused on the TelemetryPolicy specifically.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should also mention that most of the parameters you see here will actually be part of |
||
| crate_version: Option<&'static str>, | ||
| options: &ClientOptions, | ||
| per_call_policies: Vec<Arc<dyn Policy>>, | ||
| retry: Arc<dyn Policy>, | ||
| per_retry_policies: Vec<Arc<dyn Policy>>, | ||
| transport_policy: Arc<dyn Policy>, | ||
| ) -> Self { | ||
| let mut pipeline = | ||
| Vec::with_capacity(per_call_policies.len() + per_retry_policies.len() + 2); | ||
| let mut pipeline: Vec<Arc<dyn Policy>> = | ||
| Vec::with_capacity(options.per_call_policies.len() + per_call_policies.len() + options.per_retry_policies.len() + per_retry_policies.len() + 3); | ||
|
|
||
| pipeline.extend_from_slice(&per_call_policies); | ||
| pipeline.extend_from_slice(&options.per_call_policies); | ||
| pipeline.push(Arc::new(TelemetryPolicy::new(crate_name, crate_version, &options.telemetry))); | ||
| pipeline.push(retry); | ||
| pipeline.extend_from_slice(&per_retry_policies); | ||
| pipeline.extend_from_slice(&options.per_retry_policies); | ||
| pipeline.push(transport_policy); | ||
|
|
||
| Self { pipeline } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| use crate::policies::{Policy, PolicyResult}; | ||
| use crate::{Context, Request, Response}; | ||
|
|
||
| use http::{header::USER_AGENT, HeaderValue}; | ||
| use std::env::consts::{ARCH, OS}; | ||
| use std::sync::Arc; | ||
|
|
||
| #[derive(Clone, Debug, Default)] | ||
| pub struct TelemetryOptions { | ||
| pub application_id: Option<String>, | ||
| } | ||
|
|
||
| #[derive(Clone, Debug)] | ||
| pub struct TelemetryPolicy { | ||
| header: String, | ||
| } | ||
|
|
||
| /// Sets the User-Agent header with useful information in a typical format for Azure SDKs. | ||
| /// | ||
| /// Client libraries should create a `TelemetryPolicy` using `option_env!()` like so: | ||
| /// ``` | ||
| /// use azure_core::policies::{TelemetryOptions, TelemetryPolicy}; | ||
| /// let policy = TelemetryPolicy::new(option_env!("CARGO_PKG_NAME"), option_env!("CARGO_PKG_VERSION"), &TelemetryOptions::default()); | ||
| /// ``` | ||
| impl<'a> TelemetryPolicy { | ||
| pub fn new( | ||
| crate_name: Option<&'a str>, | ||
| crate_version: Option<&'a str>, | ||
| options: &TelemetryOptions, | ||
| ) -> Self { | ||
| Self::new_with_rustc_version( | ||
| crate_name, | ||
| crate_version, | ||
| option_env!("AZSDK_RUSTC_VERSION"), | ||
| options, | ||
| ) | ||
| } | ||
|
|
||
| fn new_with_rustc_version( | ||
| crate_name: Option<&'a str>, | ||
| crate_version: Option<&'a str>, | ||
| rustc_version: Option<&'static str>, | ||
heaths marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| options: &TelemetryOptions, | ||
| ) -> Self { | ||
| const UNKNOWN: &'static str = "unknown"; | ||
| let mut crate_name = crate_name.unwrap_or(UNKNOWN); | ||
| let crate_version = crate_version.unwrap_or(UNKNOWN); | ||
| let rustc_version = rustc_version.unwrap_or(UNKNOWN); | ||
| let platform_info = format!("({}; {}; {})", rustc_version, OS, ARCH,); | ||
|
|
||
| if let Some(name) = crate_name.strip_prefix("azure_") { | ||
| crate_name = name; | ||
| } | ||
|
|
||
| let header = match &options.application_id { | ||
| Some(application_id) => format!( | ||
| "{} azsdk-rust-{}/{} {}", | ||
| application_id, crate_name, crate_version, platform_info | ||
| ), | ||
| None => format!( | ||
| "azsdk-rust-{}/{} {}", | ||
| crate_name, crate_version, platform_info | ||
| ), | ||
| }; | ||
|
|
||
| TelemetryPolicy { header: header } | ||
| } | ||
| } | ||
|
|
||
| #[async_trait::async_trait] | ||
| impl Policy for TelemetryPolicy { | ||
| async fn send( | ||
| &self, | ||
| ctx: &mut Context, | ||
| request: &mut Request, | ||
| next: &[Arc<dyn Policy>], | ||
| ) -> PolicyResult<Response> { | ||
| request | ||
| .headers_mut() | ||
| .insert(USER_AGENT, HeaderValue::from_str(&self.header).unwrap()); | ||
heaths marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| next[0].send(ctx, request, &next[1..]).await | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_without_application_id() { | ||
| let policy = TelemetryPolicy::new_with_rustc_version( | ||
| Some("azure_test"), // Tests that "azure_" is removed. | ||
| Some("1.2.3"), | ||
| Some("4.5.6"), | ||
| &TelemetryOptions::default(), | ||
| ); | ||
| assert_eq!( | ||
| policy.header, | ||
| format!("azsdk-rust-test/1.2.3 (4.5.6; {}; {})", OS, ARCH) | ||
heaths marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_with_application_id() { | ||
| let options = TelemetryOptions { | ||
| application_id: Some("my_app".to_string()), | ||
| }; | ||
| let policy = TelemetryPolicy::new_with_rustc_version( | ||
| Some("test"), | ||
| Some("1.2.3"), | ||
| Some("4.5.6"), | ||
| &options, | ||
| ); | ||
| assert_eq!( | ||
| policy.header, | ||
| format!("my_app azsdk-rust-test/1.2.3 (4.5.6; {}; {})", OS, ARCH) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_missing_env() { | ||
| // Would simulate if option_env!("CARGO_PKG_NAME"), for example, returned None. | ||
| let policy = | ||
| TelemetryPolicy::new_with_rustc_version(None, None, None, &TelemetryOptions::default()); | ||
| assert_eq!( | ||
| policy.header, | ||
| format!("azsdk-rust-unknown/unknown (unknown; {}; {})", OS, ARCH) | ||
| ) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.