Skip to content
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

Fix tag extraction from resource attributes #68

Merged
merged 2 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

- Change how the tags Could role, Cloud role instance, Application version and Internal SDK version are extracted:

- Spans no longer extract them from span attributes. They still extract them from resource attributes. And they newly extract them also from instrumentation library attributes.
- Events newly extract them from resource and instrumentation library attributes.

In addition metrics no longer extract the tag Authenticated user id from the enduser.id attribute.

## [0.30.0] - 2024-03-08

- Upgrade `opentelemetry` and `opentelemetry_sdk` to `v0.22`.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ rand = "0.8.5"
regex = "1.5.5"
reqwest = { version = "0.11", default-features = false, features = ["blocking"] }
test-case = "3.0.0"
tokio = { version = "1.17.0", features = ["rt", "macros", "process", "time"] }
tokio = { version = "1.17.0", features = ["rt-multi-thread", "macros", "process", "time"] }
version-sync = { version = "0.9.4", default-features = false, features = ["html_root_url_updated", "contains_regex"] }

[badges]
Expand Down
24 changes: 17 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,21 @@ async fn main() {
//! - [OpenTelemetry specification: Span](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/api.md#span)
//! - [Application Insights data model](https://docs.microsoft.com/en-us/azure/azure-monitor/app/data-model)
//!
//! ## Resource
//!
//! Resource and instrumentation library attributes map to the following fields for spans, events
//! and metrics (the mapping tries to follow the OpenTelemetry semantic conventions for [resource]).
//!
//! [resource]: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/resource/semantic_conventions
//!
//! | OpenTelemetry attribute key | Application Insights field |
//! | ---------------------------------------------- | -------------------------------------------------------- |
//! | `service.namespace` + `service.name` | Context: Cloud role (`ai.cloud.role`) |
//! | `service.instance.id` | Context: Cloud role instance (`ai.cloud.roleInstance`) |
//! | `service.version` | Context: Application version (`ai.application.ver`) |
//! | `telemetry.sdk.name` + `telemetry.sdk.version` | Context: Internal SDK version (`ai.internal.sdkVersion`) |
//! | `ai.*` | Context: AppInsights Tag (`ai.*`) |
//!
//! ## Spans
//!
//! The OpenTelemetry SpanKind determines the Application Insights telemetry type:
Expand All @@ -173,22 +188,17 @@ async fn main() {
//! the status `Error`; otherwise `true`.
//!
//! The following of the Span's attributes map to special fields in Application Insights (the
//! mapping tries to follow the OpenTelemetry semantic conventions for [trace] and [resource]).
//! mapping tries to follow the OpenTelemetry semantic conventions for [trace]).
//!
//! Note: for `INTERNAL` Spans the Dependency Type is always `"InProc"`.
//!
//! [trace]: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/trace/semantic_conventions
//! [resource]: https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/resource/semantic_conventions
//! [Dependency]: https://learn.microsoft.com/en-us/azure/azure-monitor/app/data-model-dependency-telemetry
//! [Request]: https://learn.microsoft.com/en-us/azure/azure-monitor/app/data-model-request-telemetry
//!
//! | OpenTelemetry attribute key | Application Insights field |
//! | -------------------------------------------------------------------------- | ----------------------------------------------------- |
//! | `service.version` | Context: Application version (`ai.application.ver`) |
//! | -------------------------------------------------------------------------- | -------------------------------------------------------- |
//! | `enduser.id` | Context: Authenticated user id (`ai.user.authUserId`) |
//! | `service.namespace` + `service.name` | Context: Cloud role (`ai.cloud.role`) |
//! | `service.instance.id` | Context: Cloud role instance (`ai.cloud.roleInstance`) |
//! | `telemetry.sdk.name` + `telemetry.sdk.version` | Context: Internal SDK version (`ai.internal.sdkVersion`) |
//! | `SpanKind::Server` + `http.request.method` + `http.route` | Context: Operation Name (`ai.operation.name`) |
//! | `ai.*` | Context: AppInsights Tag (`ai.*`) |
//! | `url.full` | Dependency Data |
Expand Down
4 changes: 2 additions & 2 deletions src/quick_pulse.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
models::{context_tag_keys, QuickPulseEnvelope, QuickPulseMetric},
tags::get_tags_from_attrs,
tags::get_tags_for_resource,
trace::{get_duration, is_remote_dependency_success, is_request_success, EVENT_NAME_EXCEPTION},
uploader_quick_pulse::{self, PostOrPing},
Error,
Expand Down Expand Up @@ -170,7 +170,7 @@ impl<C: HttpClient + 'static> QuickPulseSender<C> {
instrumentation_key: String,
resource: Resource,
) -> Self {
let mut tags = get_tags_from_attrs(resource.iter());
let mut tags = get_tags_for_resource(&resource);
let machine_name = resource
.get(Key::from_static_str(semcov::resource::HOST_NAME))
.map(|v| v.as_str().into_owned())
Expand Down
92 changes: 51 additions & 41 deletions src/tags.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use crate::models::context_tag_keys::{self as tags, Tags, TAG_KEY_LOOKUP};
#[cfg(feature = "metrics")]
use opentelemetry::InstrumentationLibrary;
use opentelemetry::{
trace::{SpanId, SpanKind},
Key, Value,
InstrumentationLibrary, Key, Value,
};
use opentelemetry_sdk::export::trace::SpanData;
#[cfg(feature = "metrics")]
use opentelemetry_sdk::{AttributeSet, Resource};
use opentelemetry_sdk::AttributeSet;
use opentelemetry_sdk::{export::trace::SpanData, Resource};
use opentelemetry_semantic_conventions as semcov;
use std::collections::HashMap;

pub(crate) fn get_tags_for_span(span: &SpanData) -> Tags {
let mut tags = get_tags_from_attrs(
span.resource
.iter()
.chain(span.attributes.iter().map(|kv| (&kv.key, &kv.value))),
let mut tags = Tags::new();
build_tags_from_resource_attrs(&mut tags, &span.resource, &span.instrumentation_lib);

let attrs_map = build_tags_from_attrs(
&mut tags,
span.attributes.iter().map(|kv| (&kv.key, &kv.value)),
);

// Set the operation id and operation parent id.
Expand All @@ -24,21 +24,21 @@ pub(crate) fn get_tags_for_span(span: &SpanData) -> Tags {
tags.insert(tags::OPERATION_PARENT_ID, span.parent_span_id.to_string());
}

if let Some(user_id) = attrs_map.get(semcov::trace::ENDUSER_ID) {
// Using authenticated user id here to be safe. Or would ai.user.id (anonymous user id)
// fit better?
tags.insert(tags::USER_AUTH_USER_ID, user_id.as_str().into_owned());
}

// Ensure the name of the operation is `METHOD /the/route/path`.
if span.span_kind == SpanKind::Server || span.span_kind == SpanKind::Consumer {
let mut method: Option<&Value> = None;
let mut route: Option<&Value> = None;
for kv in &span.attributes {
#[allow(deprecated)]
if kv.key.as_str() == semcov::trace::HTTP_REQUEST_METHOD
|| kv.key.as_str() == semcov::trace::HTTP_METHOD
{
method = Some(&kv.value);
} else if kv.key.as_str() == semcov::trace::HTTP_ROUTE {
route = Some(&kv.value);
}
}

let method = attrs_map
.get(semcov::trace::HTTP_REQUEST_METHOD)
.or_else(|| {
#[allow(deprecated)]
attrs_map.get(semcov::trace::HTTP_METHOD)
});
let route = attrs_map.get(semcov::trace::HTTP_ROUTE);
if let (Some(method), Some(route)) = (method, route) {
tags.insert(
tags::OPERATION_NAME,
Expand All @@ -52,6 +52,8 @@ pub(crate) fn get_tags_for_span(span: &SpanData) -> Tags {

pub(crate) fn get_tags_for_event(span: &SpanData) -> Tags {
let mut tags = Tags::new();
build_tags_from_resource_attrs(&mut tags, &span.resource, &span.instrumentation_lib);

tags.insert(tags::OPERATION_ID, span.span_context.trace_id().to_string());
tags.insert(
tags::OPERATION_PARENT_ID,
Expand All @@ -66,23 +68,23 @@ pub(crate) fn get_tags_for_metric(
scope: &InstrumentationLibrary,
attrs: &AttributeSet,
) -> Tags {
get_tags_from_attrs(
resource.iter().chain(
scope
.attributes
.iter()
.map(|kv| (&kv.key, &kv.value))
.chain(attrs.iter()),
),
)
let mut tags = Tags::new();
build_tags_from_resource_attrs(&mut tags, resource, scope);
build_tags_from_attrs(&mut tags, attrs.iter());
tags
}

#[cfg(feature = "live-metrics")]
pub(crate) fn get_tags_for_resource(resource: &Resource) -> Tags {
let mut tags = Tags::new();
build_tags_from_resource_attrs(&mut tags, resource, &Default::default());
tags
}

pub(crate) fn get_tags_from_attrs<'a, T>(attrs: T) -> Tags
fn build_tags_from_attrs<'a, T>(tags: &mut Tags, attrs: T) -> HashMap<&'a str, &'a Value>
where
T: IntoIterator<Item = (&'a Key, &'a Value)>,
{
let mut tags = Tags::new();

let mut attrs_map: HashMap<_, _> = HashMap::new();
for (k, v) in attrs.into_iter() {
// First, allow the user to explicitly express tags with attributes that start with `ai.`
Expand All @@ -99,11 +101,21 @@ where
attrs_map.insert(k, v);
}

if let Some(user_id) = attrs_map.get(semcov::trace::ENDUSER_ID) {
// Using authenticated user id here to be safe. Or would ai.user.id (anonymous user id)
// fit better?
tags.insert(tags::USER_AUTH_USER_ID, user_id.as_str().into_owned());
}
attrs_map
}

fn build_tags_from_resource_attrs(
tags: &mut Tags,
resource: &Resource,
instrumentation_lib: &InstrumentationLibrary,
) {
let attrs = resource.iter().chain(
instrumentation_lib
.attributes
.iter()
.map(|kv| (&kv.key, &kv.value)),
);
let attrs_map = build_tags_from_attrs(tags, attrs);

if let Some(service_name) = attrs_map.get(semcov::resource::SERVICE_NAME) {
let mut cloud_role = service_name.as_str().into_owned();
Expand Down Expand Up @@ -139,6 +151,4 @@ where
format!("{}:{}", sdk_name.as_str(), sdk_version),
);
}

tags
}
2 changes: 2 additions & 0 deletions tests/snapshots/http_requests__live_metrics.snap
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ content-encoding: gzip
"name": "Microsoft.ApplicationInsights.Exception",
"sampleRate": 100.0,
"tags": {
"ai.cloud.role": "unknown_service",
"ai.internal.sdkVersion": "opentelemetry:0.22.1",
"ai.operation.id": "STRIPPED",
"ai.operation.parentId": "STRIPPED"
},
Expand Down
3 changes: 3 additions & 0 deletions tests/snapshots/http_requests__traces_simple.snap
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ content-encoding: gzip
"name": "Microsoft.ApplicationInsights.Message",
"sampleRate": 100.0,
"tags": {
"ai.cloud.role": "test.server",
"ai.operation.id": "STRIPPED",
"ai.operation.parentId": "STRIPPED"
},
Expand All @@ -88,6 +89,7 @@ content-encoding: gzip
"name": "Microsoft.ApplicationInsights.Event",
"sampleRate": 100.0,
"tags": {
"ai.cloud.role": "test.server",
"ai.operation.id": "STRIPPED",
"ai.operation.parentId": "STRIPPED"
},
Expand All @@ -110,6 +112,7 @@ content-encoding: gzip
"name": "Microsoft.ApplicationInsights.Exception",
"sampleRate": 100.0,
"tags": {
"ai.cloud.role": "test.server",
"ai.operation.id": "STRIPPED",
"ai.operation.parentId": "STRIPPED"
},
Expand Down
Loading