opentelemetry_application_insights/
lib.rs

1//! An [Azure Application Insights] exporter implementation for [OpenTelemetry Rust].
2//!
3//! [Azure Application Insights]: https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview
4//! [OpenTelemetry Rust]: https://github.com/open-telemetry/opentelemetry-rust
5//!
6//! **Disclaimer**: This is not an official Microsoft product.
7//!
8//! # Usage
9//!
10//! ## Trace
11//!
12//! This requires the **trace** (enabled by default) and **opentelemetry-http/reqwest** features.
13//!
14//! ```no_run
15//! use opentelemetry::{global, trace::Tracer};
16//! use opentelemetry_sdk::trace::SdkTracerProvider;
17//!
18//! fn main() {
19//!     let connection_string = std::env::var("APPLICATIONINSIGHTS_CONNECTION_STRING").unwrap();
20//!     let exporter = opentelemetry_application_insights::Exporter::new_from_connection_string(
21//!         connection_string,
22//!         reqwest::blocking::Client::new(),
23//!     )
24//!     .expect("valid connection string");
25//!     let tracer_provider = SdkTracerProvider::builder()
26//!         .with_batch_exporter(exporter)
27//!         .build();
28//!     global::set_tracer_provider(tracer_provider.clone());
29//!
30//!     let tracer = global::tracer("example");
31//!     tracer.in_span("main", |_cx| {});
32//!
33//!     tracer_provider.shutdown().unwrap();
34//! }
35//! ```
36//!
37//! ## Logs
38//!
39//! This requires the **logs** (enabled by default) and **opentelemetry-http/reqwest** features.
40//!
41//! ```no_run
42//! use log::{Level, info};
43//! use opentelemetry_appender_log::OpenTelemetryLogBridge;
44//! use opentelemetry_sdk::logs::SdkLoggerProvider;
45//!
46//! fn main() {
47//!     // Setup exporter
48//!     let connection_string = std::env::var("APPLICATIONINSIGHTS_CONNECTION_STRING").unwrap();
49//!     let exporter = opentelemetry_application_insights::Exporter::new_from_connection_string(
50//!         connection_string,
51//!         reqwest::blocking::Client::new(),
52//!     )
53//!     .expect("valid connection string");
54//!     let logger_provider = SdkLoggerProvider::builder()
55//!         .with_batch_exporter(exporter)
56//!         .build();
57//!     let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider);
58//!     log::set_boxed_logger(Box::new(otel_log_appender)).unwrap();
59//!     log::set_max_level(Level::Info.to_level_filter());
60//!
61//!     // Log
62//!     let fruit = "apple";
63//!     let price = 2.99;
64//!     info!("{fruit} costs {price}");
65//!
66//!     // Export remaining logs before exiting
67//!     logger_provider.shutdown().unwrap();
68//! }
69//! ```
70//!
71//! ## Metrics
72//!
73//! This requires the **metrics** (enabled by default) and **opentelemetry-http/reqwest** features.
74//!
75//! ```no_run
76//! use opentelemetry::global;
77//! use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
78//! use std::time::Duration;
79//!
80//! fn main() {
81//!     // Setup exporter
82//!     let connection_string = std::env::var("APPLICATIONINSIGHTS_CONNECTION_STRING").unwrap();
83//!     let exporter = opentelemetry_application_insights::Exporter::new_from_connection_string(
84//!         connection_string,
85//!         reqwest::blocking::Client::new(),
86//!     )
87//!     .expect("valid connection string");
88//!     let reader = PeriodicReader::builder(exporter).build();
89//!     let meter_provider = SdkMeterProvider::builder().with_reader(reader).build();
90//!     global::set_meter_provider(meter_provider.clone());
91//!
92//!     // Record value
93//!     let meter = global::meter("example");
94//!     let histogram = meter.f64_histogram("pi").build();
95//!     histogram.record(3.14, &[]);
96//!
97//!     // Simulate work, during which metrics will periodically be reported.
98//!     std::thread::sleep(Duration::from_secs(300));
99//!
100//!     meter_provider.shutdown().unwrap();
101//! }
102//! ```
103//!
104#![cfg_attr(
105    feature = "live-metrics",
106    doc = r#"
107## Live Metrics
108
109This requires the **live-metrics** feature and the experimental async runtime span processor API behind the **opentelemetry_sdk/experimental_trace_batch_span_processor_with_async_runtime** feature.
110
111Enable live metrics collection: <https://learn.microsoft.com/en-us/azure/azure-monitor/app/live-stream>.
112
113Metrics are based on traces. See attribute mapping below for how traces are mapped to requests,
114dependencies and exceptions and how they are deemed "successful" or not.
115
116To configure role, instance, and machine name provide `service.name`, `service.instance.id`, and
117`host.name` resource attributes respectively in the resource.
118
119Sample telemetry is not supported, yet.
120
121```no_run
122use opentelemetry::{global, trace::Tracer};
123use opentelemetry_sdk::trace::SdkTracerProvider;
124
125#[tokio::main]
126async fn main() {
127    let connection_string = std::env::var("APPLICATIONINSIGHTS_CONNECTION_STRING").unwrap();
128    let exporter = opentelemetry_application_insights::Exporter::new_from_connection_string(
129        connection_string,
130        reqwest::blocking::Client::new(),
131    )
132    .expect("valid connection string");
133    let tracer_provider = SdkTracerProvider::builder()
134        .with_span_processor(opentelemetry_sdk::trace::span_processor_with_async_runtime::BatchSpanProcessor::builder(exporter.clone(), opentelemetry_sdk::runtime::Tokio).build())
135        .with_span_processor(opentelemetry_application_insights::LiveMetricsSpanProcessor::new(exporter, opentelemetry_sdk::runtime::Tokio))
136        .build();
137    global::set_tracer_provider(tracer_provider.clone());
138
139    // ... send traces ...
140
141    tracer_provider.shutdown().unwrap();
142}
143```
144"#
145)]
146//!
147//! ## Async runtimes and HTTP clients
148//!
149//! In order to support different async runtimes, the exporter requires you to specify an HTTP
150//! client that works with your chosen runtime. The [`opentelemetry-http`] crate comes with support
151//! for:
152//!
153//! - [`reqwest`]: enable the **opentelemetry-http/reqwest** feature and configure the exporter
154//!   with either `with_client(reqwest::Client::new())` or
155//!   `with_client(reqwest::blocking::Client::new())`.
156//! - and more...
157//!
158//! [`opentelemetry-http`]: https://crates.io/crates/opentelemetry-http
159//! [`reqwest`]: https://crates.io/crates/reqwest
160//! [`tokio`]: https://crates.io/crates/tokio
161//!
162//! Alternatively you can bring any other HTTP client by implementing the `HttpClient` trait.
163//!
164//! Map async/sync clients with the appropriate builder methods:
165//!
166//! - Sync clients with `{SdkTracerProvider,SdkLoggerProvider}.with_batch_exporter`/`PeriodicReader::builder`. If you're already in an
167//! async context when creating the client, you might need to create it using
168//! `std::thread::spawn(reqwest::blocking::Client::new).join().unwrap()`.
169//! - Async clients with the corresponding experimental async APIs. _Or_ with the pipeline API and
170//! `build_batch`/`install_batch`.
171//!
172//! # Attribute mapping
173//!
174//! OpenTelemetry and Application Insights are using different terminology. This crate tries its
175//! best to map OpenTelemetry fields to their correct Application Insights pendant.
176//!
177//! - [OpenTelemetry specification: Span](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md#span)
178//! - [Application Insights data model](https://docs.microsoft.com/en-us/azure/azure-monitor/app/data-model)
179//!
180//! ## Resource
181//!
182//! Resource and instrumentation library attributes map to the following fields for spans, events
183//! and metrics (the mapping tries to follow the OpenTelemetry semantic conventions for [resource]).
184//!
185//! [resource]: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/resource/semantic_conventions
186//!
187//! | OpenTelemetry attribute key                    | Application Insights field                               |
188//! | ---------------------------------------------- | -------------------------------------------------------- |
189//! | `service.namespace` + `service.name`           | Context: Cloud role (`ai.cloud.role`)                    |
190//! | `k8s.deployment.name`                          | Context: Cloud role (`ai.cloud.role`)                    |
191//! | `k8s.replicaset.name`                          | Context: Cloud role (`ai.cloud.role`)                    |
192//! | `k8s.statefulset.name`                         | Context: Cloud role (`ai.cloud.role`)                    |
193//! | `k8s.job.name`                                 | Context: Cloud role (`ai.cloud.role`)                    |
194//! | `k8s.cronjob.name`                             | Context: Cloud role (`ai.cloud.role`)                    |
195//! | `k8s.daemonset.name`                           | Context: Cloud role (`ai.cloud.role`)                    |
196//! | `k8s.pod.name`                                 | Context: Cloud role instance (`ai.cloud.roleInstance`)   |
197//! | `service.instance.id`                          | Context: Cloud role instance (`ai.cloud.roleInstance`)   |
198//! | `device.id`                                    | Context: Device id (`ai.device.id`)                      |
199//! | `device.model.name`                            | Context: Device model (`ai.device.model`)                |
200//! | `service.version`                              | Context: Application version (`ai.application.ver`)      |
201//! | `telemetry.sdk.name` + `telemetry.sdk.version` | Context: Internal SDK version (`ai.internal.sdkVersion`) |
202//! | `ai.*`                                         | Context: AppInsights Tag (`ai.*`)                        |
203//!
204//! If `service.name` is the default (i.e. starts with "unknown_service:"), the Kubernetes based
205//! values take precedence.
206//!
207//! ## Spans
208//!
209//! The OpenTelemetry SpanKind determines the Application Insights telemetry type:
210//!
211//! | OpenTelemetry SpanKind           | Application Insights telemetry type |
212//! | -------------------------------- | ----------------------------------- |
213//! | `CLIENT`, `PRODUCER`, `INTERNAL` | [Dependency]                        |
214//! | `SERVER`, `CONSUMER`             | [Request]                           |
215//!
216//! The Span's status determines the Success field of a Dependency or Request. Success is `false` if
217//! the status `Error`; otherwise `true`.
218//!
219//! The following of the Span's attributes map to special fields in Application Insights (the
220//! mapping tries to follow the OpenTelemetry semantic conventions for [trace]).
221//!
222//! Note: for `INTERNAL` Spans the Dependency Type is always `"InProc"`.
223//!
224//! [trace]: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/trace/semantic_conventions
225//! [Dependency]: https://learn.microsoft.com/en-us/azure/azure-monitor/app/data-model-dependency-telemetry
226//! [Request]: https://learn.microsoft.com/en-us/azure/azure-monitor/app/data-model-request-telemetry
227//!
228//! | OpenTelemetry attribute key                                                | Application Insights field                               |
229//! | -------------------------------------------------------------------------- | -------------------------------------------------------- |
230//! | `user.id`                                                                  | Context: Authenticated user id (`ai.user.authUserId`)    |
231//! | `SpanKind::Server` + `http.request.method` + `http.route`                  | Context: Operation Name (`ai.operation.name`)            |
232//! | `ai.*`                                                                     | Context: AppInsights Tag (`ai.*`)                        |
233//! | `url.full`                                                                 | Dependency Data                                          |
234//! | `db.query.text`                                                            | Dependency Data                                          |
235//! | `http.request.header.host`                                                 | Dependency Target                                        |
236//! | `server.address` + `server.port`                                           | Dependency Target                                        |
237//! | `network.peer.address` + `network.peer.port`                               | Dependency Target                                        |
238//! | `db.namespace`                                                             | Dependency Target                                        |
239//! | `http.response.status_code`                                                | Dependency Result code                                   |
240//! | `db.system.name`                                                           | Dependency Type                                          |
241//! | `messaging.system`                                                         | Dependency Type                                          |
242//! | `rpc.system`                                                               | Dependency Type                                          |
243//! | `"HTTP"` if any `http.` attribute exists                                   | Dependency Type                                          |
244//! | `"DB"` if any `db.` attribute exists                                       | Dependency Type                                          |
245//! | `url.full`                                                                 | Request Url                                              |
246//! | `url.scheme` + `http.request.header.host` + `url.path` + `url.query`       | Request Url                                              |
247//! | `url.scheme` + `server.address` + `server.port` + `url.path` + `url.query` | Request Url                                              |
248//! | `client.address`                                                           | Request Source                                           |
249//! | `network.peer.address`                                                     | Request Source                                           |
250//! | `http.response.status_code`                                                | Request Response code                                    |
251//!
252//! All other attributes are directly converted to custom properties.
253//!
254//! For Requests the attributes `http.request.method` and `http.route` override the Name.
255//!
256//! ### Deprecated attributes
257//!
258//! The following deprecated attributes also work:
259//!
260//! | Attribute                   | Deprecated attribute                       |
261//! | --------------------------- | ------------------------------------------ |
262//! | `user.id`                   | `enduser.id`                               |
263//! | `db.namespace`              | `db.name`                                  |
264//! | `db.query.text`             | `db.statement`                             |
265//! | `db.system.name`            | `db.system`                                |
266//! | `http.request.method`       | `http.method`                              |
267//! | `http.request.header.host`  | `http.host`                                |
268//! | `http.response.status_code` | `http.status_code`                         |
269//! | `url.full`                  | `http.url`                                 |
270//! | `url.scheme`                | `http.scheme`                              |
271//! | `url.path` + `url.query`    | `http.target`                              |
272//! | `client.address`            | `http.client_ip`                           |
273//! | `network.peer.address`      | `server.socket.address` (for client spans) |
274//! | `network.peer.address`      | `net.sock.peer.addr`    (for client spans) |
275//! | `network.peer.address`      | `net.peer.ip`           (for client spans) |
276//! | `network.peer.port`         | `server.socket.port`    (for client spans) |
277//! | `network.peer.port`         | `net.sock.peer.port`    (for client spans) |
278//! | `network.peer.address`      | `client.socket.address` (for server spans) |
279//! | `network.peer.address`      | `net.sock.peer.addr`    (for server spans) |
280//! | `network.peer.address`      | `net.peer.ip`           (for server spans) |
281//! | `server.address`            | `net.peer.name`         (for client spans) |
282//! | `server.port`               | `net.peer.port`         (for client spans) |
283//! | `server.address`            | `net.host.name`         (for server spans) |
284//! | `server.port`               | `net.host.port`         (for server spans) |
285//!
286//! ## Events
287//!
288//! Events are converted into [Exception] telemetry if the event name equals `"exception"` (see
289//! OpenTelemetry semantic conventions for [exceptions]) with the following mapping:
290//!
291//! | OpenTelemetry attribute key | Application Insights field |
292//! | --------------------------- | -------------------------- |
293//! | `exception.type`            | Exception type             |
294//! | `exception.message`         | Exception message          |
295//! | `exception.stacktrace`      | Exception call stack       |
296//!
297//! Events are converted into [Event] telemetry if the event name equals `"ai.custom"` with the
298//! following mapping:
299//!
300//! | OpenTelemetry attribute key | Application Insights field |
301//! | --------------------------- | -------------------------- |
302//! | `ai.customEvent.name`       | Event name                 |
303//!
304//! All other events are converted into [Trace] telemetry with the following mapping:
305//!
306//! | OpenTelemetry attribute key  | Application Insights field |
307//! | ---------------------------- | -------------------------- |
308//! | `level` ([`tracing::Level`]) | Severity level             |
309//!
310//! All other attributes are directly converted to custom properties.
311//!
312//! [exceptions]: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/exceptions.md
313//! [Exception]: https://learn.microsoft.com/en-us/azure/azure-monitor/app/data-model-exception-telemetry
314//! [Event]: https://learn.microsoft.com/en-us/azure/azure-monitor/app/data-model-event-telemetry
315//! [Trace]: https://learn.microsoft.com/en-us/azure/azure-monitor/app/data-model-trace-telemetry
316//! [`tracing::Level`]: https://docs.rs/tracing/0.1.37/tracing/struct.Level.html
317//!
318//! ## Logs
319//!
320//! Logs are reported similar to events:
321//!
322//! - If they contain an `exception.type` or `exception.message` attribute, they're converted to
323//!   [Exception] telemetry with the same attribute mapping as events.
324//! - Otherwise they're converted to [Trace] telemetry.
325//!
326//! ## Metrics
327//!
328//! Metrics get reported to Application Insights as Metric Data. The [`Aggregation`] determines how
329//! the data is represented.
330//!
331//! | Aggregator           | Data representation                                                  |
332//! | -------------------- | -------------------------------------------------------------------- |
333//! | Histogram            | aggregation with sum, count, min, and max (buckets are not exported) |
334//! | ExponentialHistogram | aggregation with sum, count, min, and max (buckets are not exported) |
335//! | Gauge                | one measurement                                                      |
336//! | Sum                  | aggregation with only a value                                        |
337//!
338//! [`Aggregation`]: https://docs.rs/opentelemetry/0.20.0/opentelemetry/sdk/metrics/data/trait.Aggregation.html
339#![doc(html_root_url = "https://docs.rs/opentelemetry-application-insights/0.43.0")]
340#![allow(clippy::needless_doctest_main)]
341#![deny(missing_docs, unreachable_pub, missing_debug_implementations)]
342#![cfg_attr(docsrs, feature(doc_cfg))]
343#![cfg_attr(test, deny(warnings))]
344#![cfg_attr(test, allow(deprecated))]
345
346mod connection_string;
347mod convert;
348#[cfg(feature = "logs")]
349mod logs;
350#[cfg(feature = "metrics")]
351mod metrics;
352mod models;
353#[cfg(feature = "live-metrics")]
354mod quick_pulse;
355#[cfg(doctest)]
356mod readme_test;
357mod tags;
358#[cfg(feature = "trace")]
359mod trace;
360mod uploader;
361#[cfg(feature = "live-metrics")]
362mod uploader_quick_pulse;
363
364#[cfg(feature = "live-metrics")]
365use connection_string::DEFAULT_LIVE_ENDPOINT;
366use connection_string::{ConnectionString, DEFAULT_BREEZE_ENDPOINT};
367pub use models::context_tag_keys::attrs;
368pub use opentelemetry_http::HttpClient;
369use opentelemetry_sdk::error::OTelSdkError;
370use opentelemetry_sdk::ExportError;
371#[cfg(any(feature = "trace", feature = "logs"))]
372use opentelemetry_sdk::Resource;
373#[cfg(feature = "live-metrics")]
374pub use quick_pulse::LiveMetricsSpanProcessor;
375use std::{
376    convert::TryInto,
377    error::Error as StdError,
378    fmt::Debug,
379    sync::{Arc, Mutex},
380    time::Duration,
381};
382#[cfg(feature = "live-metrics")]
383use uploader_quick_pulse::PostOrPing;
384
385/// Application Insights span exporter
386#[derive(Clone)]
387pub struct Exporter<C> {
388    client: Arc<C>,
389    track_endpoint: Arc<http::Uri>,
390    #[cfg(feature = "live-metrics")]
391    live_post_endpoint: http::Uri,
392    #[cfg(feature = "live-metrics")]
393    live_ping_endpoint: http::Uri,
394    instrumentation_key: String,
395    retry_notify: Option<Arc<Mutex<dyn FnMut(&Error, Duration) + Send + 'static>>>,
396    #[cfg(feature = "trace")]
397    sample_rate: f64,
398    #[cfg(any(feature = "trace", feature = "logs"))]
399    resource: Resource,
400    #[cfg(any(feature = "trace", feature = "logs"))]
401    resource_attributes_in_events_and_logs: bool,
402}
403
404impl<C: Debug> Debug for Exporter<C> {
405    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
406        let mut debug = f.debug_struct("Exporter");
407        debug
408            .field("client", &self.client)
409            .field("track_endpoint", &self.track_endpoint)
410            .field("instrumentation_key", &self.instrumentation_key);
411        #[cfg(feature = "trace")]
412        debug.field("sample_rate", &self.sample_rate);
413        #[cfg(any(feature = "trace", feature = "logs"))]
414        debug.field("resource", &self.resource).field(
415            "resource_attributes_in_events_and_logs",
416            &self.resource_attributes_in_events_and_logs,
417        );
418        #[cfg(feature = "live-metrics")]
419        debug
420            .field("live_post_endpoint", &self.live_post_endpoint)
421            .field("live_ping_endpoint", &self.live_ping_endpoint);
422        debug.finish()
423    }
424}
425
426impl<C> Exporter<C> {
427    /// Create a new exporter.
428    #[deprecated(since = "0.27.0", note = "use new_from_connection_string() instead")]
429    pub fn new(instrumentation_key: String, client: C) -> Self {
430        Self {
431            client: Arc::new(client),
432            track_endpoint: Arc::new(append_v2_track(DEFAULT_BREEZE_ENDPOINT)),
433            #[cfg(feature = "live-metrics")]
434            live_post_endpoint: append_quick_pulse(
435                DEFAULT_LIVE_ENDPOINT,
436                PostOrPing::Post,
437                &instrumentation_key,
438            ),
439            #[cfg(feature = "live-metrics")]
440            live_ping_endpoint: append_quick_pulse(
441                DEFAULT_LIVE_ENDPOINT,
442                PostOrPing::Ping,
443                &instrumentation_key,
444            ),
445            instrumentation_key,
446            retry_notify: None,
447            #[cfg(feature = "trace")]
448            sample_rate: 100.0,
449            #[cfg(any(feature = "trace", feature = "logs"))]
450            resource: Resource::builder_empty().build(),
451            #[cfg(any(feature = "trace", feature = "logs"))]
452            resource_attributes_in_events_and_logs: false,
453        }
454    }
455
456    /// Create a new exporter.
457    ///
458    /// Reads connection string from `APPLICATIONINSIGHTS_CONNECTION_STRING` environment variable.
459    pub fn new_from_env(client: C) -> Result<Self, Box<dyn StdError + Send + Sync + 'static>> {
460        let connection_string = std::env::var("APPLICATIONINSIGHTS_CONNECTION_STRING")?;
461        Self::new_from_connection_string(connection_string, client)
462    }
463
464    /// Create a new exporter.
465    pub fn new_from_connection_string(
466        connection_string: impl AsRef<str>,
467        client: C,
468    ) -> Result<Self, Box<dyn StdError + Send + Sync + 'static>> {
469        let connection_string: ConnectionString = connection_string.as_ref().parse()?;
470        Ok(Self {
471            client: Arc::new(client),
472            track_endpoint: Arc::new(append_v2_track(&connection_string.ingestion_endpoint)),
473            #[cfg(feature = "live-metrics")]
474            live_post_endpoint: append_quick_pulse(
475                &connection_string.live_endpoint,
476                PostOrPing::Post,
477                &connection_string.instrumentation_key,
478            ),
479            #[cfg(feature = "live-metrics")]
480            live_ping_endpoint: append_quick_pulse(
481                &connection_string.live_endpoint,
482                PostOrPing::Ping,
483                &connection_string.instrumentation_key,
484            ),
485            instrumentation_key: connection_string.instrumentation_key,
486            retry_notify: None,
487            #[cfg(feature = "trace")]
488            sample_rate: 100.0,
489            #[cfg(any(feature = "trace", feature = "logs"))]
490            resource: Resource::builder_empty().build(),
491            #[cfg(any(feature = "trace", feature = "logs"))]
492            resource_attributes_in_events_and_logs: false,
493        })
494    }
495
496    /// Set a retry notification function that is called when a request to upload telemetry to
497    /// Application Insights failed and will be retried.
498    pub fn with_retry_notify<N>(mut self, retry_notify: N) -> Self
499    where
500        N: FnMut(&Error, Duration) + Send + 'static,
501    {
502        self.retry_notify = Some(Arc::new(Mutex::new(retry_notify)));
503        self
504    }
505
506    /// Set endpoint used to ingest telemetry. This should consist of scheme and authrity. The
507    /// exporter will call `/v2/track` on the specified endpoint.
508    ///
509    /// Default: <https://dc.services.visualstudio.com>
510    #[deprecated(since = "0.27.0", note = "use new_from_connection_string() instead")]
511    pub fn with_endpoint(
512        mut self,
513        endpoint: &str,
514    ) -> Result<Self, Box<dyn StdError + Send + Sync + 'static>> {
515        self.track_endpoint = Arc::new(append_v2_track(endpoint));
516        Ok(self)
517    }
518
519    /// Set sample rate, which is passed through to Application Insights. It should be a value
520    /// between 0 and 1 and match the rate given to the sampler.
521    ///
522    /// Default: 1.0
523    #[cfg(feature = "trace")]
524    #[cfg_attr(docsrs, doc(cfg(feature = "trace")))]
525    pub fn with_sample_rate(mut self, sample_rate: f64) -> Self {
526        // Application Insights expects the sample rate as a percentage.
527        self.sample_rate = sample_rate * 100.0;
528        self
529    }
530
531    /// Set whether resource attributes should be included in events.
532    ///
533    /// This affects both trace events and logs.
534    ///
535    /// Default: false.
536    #[cfg(any(feature = "trace", feature = "logs"))]
537    #[cfg_attr(docsrs, doc(cfg(any(feature = "trace", feature = "logs"))))]
538    pub fn with_resource_attributes_in_events_and_logs(
539        mut self,
540        resource_attributes_in_events_and_logs: bool,
541    ) -> Self {
542        self.resource_attributes_in_events_and_logs = resource_attributes_in_events_and_logs;
543        self
544    }
545}
546
547fn append_v2_track(uri: impl ToString) -> http::Uri {
548    append_path(uri, "v2/track").expect("appending /v2/track should always work")
549}
550
551#[cfg(feature = "live-metrics")]
552fn append_quick_pulse(
553    uri: impl ToString,
554    post_or_ping: PostOrPing,
555    instrumentation_key: &str,
556) -> http::Uri {
557    let path = format!(
558        "QuickPulseService.svc/{}?ikey={}",
559        post_or_ping, instrumentation_key,
560    );
561    append_path(uri, &path).unwrap_or_else(|_| panic!("appending {} should always work", path))
562}
563
564fn append_path(
565    uri: impl ToString,
566    path: impl AsRef<str>,
567) -> Result<http::Uri, http::uri::InvalidUri> {
568    let mut curr = uri.to_string();
569    if !curr.ends_with('/') {
570        curr.push('/');
571    }
572    curr.push_str(path.as_ref());
573    curr.try_into()
574}
575
576/// Errors that occurred during span export.
577#[derive(thiserror::Error, Debug)]
578#[non_exhaustive]
579pub enum Error {
580    /// Application Insights telemetry data failed to serialize to JSON. Telemetry reporting failed
581    /// because of this.
582    ///
583    /// Note: This is an error in this crate. If you spot this, please open an issue.
584    #[error("serializing upload request failed with {0}")]
585    UploadSerializeRequest(serde_json::Error),
586
587    /// Application Insights telemetry data failed serialize or compress. Telemetry reporting failed
588    /// because of this.
589    ///
590    /// Note: This is an error in this crate. If you spot this, please open an issue.
591    #[error("compressing upload request failed with {0}")]
592    UploadCompressRequest(std::io::Error),
593
594    /// Application Insights telemetry response failed to deserialize from JSON.
595    ///
596    /// Telemetry reporting may have worked. But since we could not look into the response, we
597    /// can't be sure.
598    ///
599    /// Note: This is an error in this crate. If you spot this, please open an issue.
600    #[error("deserializing upload response failed with {0}")]
601    UploadDeserializeResponse(serde_json::Error),
602
603    /// Could not complete the HTTP request to Application Insights to send telemetry data.
604    /// Telemetry reporting failed because of this.
605    #[error("sending upload request failed with {0}")]
606    UploadConnection(Box<dyn StdError + Send + Sync + 'static>),
607
608    /// Application Insights returned at least one error for the reported telemetry data.
609    #[error("upload failed with {0}")]
610    Upload(String),
611
612    /// Failed to process span for live metrics.
613    #[cfg(feature = "live-metrics")]
614    #[cfg_attr(docsrs, doc(cfg(feature = "live-metrics")))]
615    #[error("process span for live metrics failed with {0}")]
616    QuickPulseProcessSpan(opentelemetry_sdk::runtime::TrySendError),
617
618    /// Failed to stop live metrics.
619    #[cfg(feature = "live-metrics")]
620    #[cfg_attr(docsrs, doc(cfg(feature = "live-metrics")))]
621    #[error("stop live metrics failed with {0}")]
622    QuickPulseShutdown(opentelemetry_sdk::runtime::TrySendError),
623}
624
625impl ExportError for Error {
626    fn exporter_name(&self) -> &'static str {
627        "application-insights"
628    }
629}
630
631impl From<Error> for OTelSdkError {
632    fn from(value: Error) -> Self {
633        OTelSdkError::InternalFailure(value.to_string())
634    }
635}