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}