Skip to content
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
199 changes: 199 additions & 0 deletions libdd-common/src/azure_app_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,53 @@ impl AzureMetadata {
pub fn get_function_runtime_version(&self) -> &str {
get_value_or_unknown!(self.function_runtime_version)
}

/// Returns Azure App Services tags as an iterator of (tag_name, tag_value) tuples.
/// These tags are specific to Azure App Services.
pub fn get_app_service_tags(&self) -> impl ExactSizeIterator<Item = (&'static str, &str)> {
[
(
"aas.environment.extension_version",
self.get_extension_version(),
),
("aas.environment.instance_id", self.get_instance_id()),
("aas.environment.instance_name", self.get_instance_name()),
("aas.environment.os", self.get_operating_system()),
("aas.resource.group", self.get_resource_group()),
("aas.resource.id", self.get_resource_id()),
("aas.site.kind", self.get_site_kind()),
("aas.site.name", self.get_site_name()),
("aas.site.type", self.get_site_type()),
("aas.subscription.id", self.get_subscription_id()),
]
.into_iter()
}

/// Returns Azure Functions tags as an iterator of (tag_name, tag_value) tuples.
/// These tags are specific to Azure Functions.
pub fn get_function_tags(&self) -> impl ExactSizeIterator<Item = (&'static str, &str)> {
[
("aas.environment.instance_id", self.get_instance_id()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to filter here if tags are empty ? I assume this would be filtered either way afterwards by Tag::new(name, value).ok()

("aas.environment.instance_name", self.get_instance_name()),
("aas.environment.os", self.get_operating_system()),
("aas.environment.runtime", self.get_runtime()),
(
"aas.environment.runtime_version",
self.get_runtime_version(),
),
(
"aas.environment.function_runtime",
self.get_function_runtime_version(),
),
("aas.resource.group", self.get_resource_group()),
("aas.resource.id", self.get_resource_id()),
("aas.site.kind", self.get_site_kind()),
("aas.site.name", self.get_site_name()),
("aas.site.type", self.get_site_type()),
("aas.subscription.id", self.get_subscription_id()),
]
.into_iter()
}
}

pub static AAS_METADATA: LazyLock<Option<AzureMetadata>> =
Expand Down Expand Up @@ -712,6 +759,158 @@ mod tests {
assert_eq!(expected_runtime_version, metadata.get_runtime_version());
}

#[test]
fn test_get_app_service_tags() {
let expected_site_name = "my_site_name";
let expected_resource_group = "my_resource_group";
let expected_site_version = "v42";
let expected_operating_system = "FreeBSD";
let expected_instance_name = "my_instance_name";
let expected_instance_id = "my_instance_id";
let expected_subscription_id = "sub-123";
let expected_resource_id = "/subscriptions/sub-123/resourcegroups/my_resource_group/providers/microsoft.web/sites/my_site_name";

let mocked_env = MockEnv::new(&[
(WEBSITE_SITE_NAME, expected_site_name),
(WEBSITE_RESOURCE_GROUP, expected_resource_group),
(SITE_EXTENSION_VERSION, expected_site_version),
(WEBSITE_OS, expected_operating_system),
(INSTANCE_NAME, expected_instance_name),
(INSTANCE_ID, expected_instance_id),
(SERVICE_CONTEXT, "1"),
(
WEBSITE_OWNER_NAME,
&format!("{}+rg-webspace", expected_subscription_id),
),
]);

let metadata = AzureMetadata::new(mocked_env).unwrap();

// Collect tags into a HashMap for easy lookup
let tags: std::collections::HashMap<&str, &str> = metadata.get_app_service_tags().collect();

// Verify all 10 App Service tags are present
assert_eq!(tags.len(), 10);
assert_eq!(tags.get("aas.resource.id"), Some(&expected_resource_id));
assert_eq!(
tags.get("aas.environment.extension_version"),
Some(&expected_site_version)
);
assert_eq!(
tags.get("aas.environment.instance_id"),
Some(&expected_instance_id)
);
assert_eq!(
tags.get("aas.environment.instance_name"),
Some(&expected_instance_name)
);
assert_eq!(
tags.get("aas.environment.os"),
Some(&expected_operating_system)
);
assert_eq!(
tags.get("aas.resource.group"),
Some(&expected_resource_group)
);
assert_eq!(tags.get("aas.site.name"), Some(&expected_site_name));
assert_eq!(tags.get("aas.site.kind"), Some(&"app"));
assert_eq!(tags.get("aas.site.type"), Some(&"app"));
assert_eq!(
tags.get("aas.subscription.id"),
Some(&expected_subscription_id)
);

// Verify runtime tags are NOT present
assert_eq!(tags.get("aas.environment.runtime"), None);
assert_eq!(tags.get("aas.environment.runtime_version"), None);
assert_eq!(tags.get("aas.environment.function_runtime"), None);

// Verify it's an ExactSizeIterator
let iter = metadata.get_app_service_tags();
assert_eq!(iter.len(), 10);
}

#[test]
fn test_get_function_tags() {
let expected_site_name = "my_site_name";
let expected_resource_group = "my_resource_group";
let expected_operating_system = "FreeBSD";
let expected_instance_name = "my_instance_name";
let expected_instance_id = "my_instance_id";
let expected_function_extension_version = "~4";
let expected_runtime = "node";
let expected_runtime_version = "18";
let expected_subscription_id = "sub-123";
let expected_resource_id = "/subscriptions/sub-123/resourcegroups/my_resource_group/providers/microsoft.web/sites/my_site_name";

let mocked_env = MockEnv::new(&[
(WEBSITE_SITE_NAME, expected_site_name),
(WEBSITE_RESOURCE_GROUP, expected_resource_group),
(WEBSITE_OS, expected_operating_system),
(INSTANCE_NAME, expected_instance_name),
(INSTANCE_ID, expected_instance_id),
(SERVICE_CONTEXT, "1"),
(
FUNCTIONS_EXTENSION_VERSION,
expected_function_extension_version,
),
(FUNCTIONS_WORKER_RUNTIME, expected_runtime),
(FUNCTIONS_WORKER_RUNTIME_VERSION, expected_runtime_version),
(
WEBSITE_OWNER_NAME,
&format!("{}+rg-webspace", expected_subscription_id),
),
]);

let metadata = AzureMetadata::new(mocked_env).unwrap();

// Collect tags into a HashMap for easy lookup
let tags: std::collections::HashMap<&str, &str> = metadata.get_function_tags().collect();

// Verify all 12 Function tags are present
assert_eq!(tags.len(), 12);
assert_eq!(tags.get("aas.resource.id"), Some(&expected_resource_id));
assert_eq!(
tags.get("aas.environment.instance_id"),
Some(&expected_instance_id)
);
assert_eq!(
tags.get("aas.environment.instance_name"),
Some(&expected_instance_name)
);
assert_eq!(
tags.get("aas.environment.os"),
Some(&expected_operating_system)
);
assert_eq!(tags.get("aas.environment.runtime"), Some(&expected_runtime));
assert_eq!(
tags.get("aas.environment.runtime_version"),
Some(&expected_runtime_version)
);
assert_eq!(
tags.get("aas.environment.function_runtime"),
Some(&expected_function_extension_version)
);
assert_eq!(
tags.get("aas.resource.group"),
Some(&expected_resource_group)
);
assert_eq!(tags.get("aas.site.name"), Some(&expected_site_name));
assert_eq!(tags.get("aas.site.kind"), Some(&"functionapp"));
assert_eq!(tags.get("aas.site.type"), Some(&"function"));
assert_eq!(
tags.get("aas.subscription.id"),
Some(&expected_subscription_id)
);

// Verify extension_version tag is NOT present
assert_eq!(tags.get("aas.environment.extension_version"), None);

// Verify it's an ExactSizeIterator
let iter = metadata.get_function_tags();
assert_eq!(iter.len(), 12);
}

#[test]
fn test_get_trimmed_env_var_empty_string() {
env::remove_var("TEST_VAR_NONE");
Expand Down
28 changes: 3 additions & 25 deletions libdd-profiling/src/exporter/profile_exporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,31 +110,9 @@ impl ProfileExporter {

// Add Azure App Services tags if available
if let Some(aas) = &*azure_app_services::AAS_METADATA {
let aas_tags = [
("aas.resource.id", aas.get_resource_id()),
(
"aas.environment.extension_version",
aas.get_extension_version(),
),
("aas.environment.instance_id", aas.get_instance_id()),
("aas.environment.instance_name", aas.get_instance_name()),
("aas.environment.os", aas.get_operating_system()),
("aas.resource.group", aas.get_resource_group()),
("aas.site.name", aas.get_site_name()),
("aas.site.kind", aas.get_site_kind()),
("aas.site.type", aas.get_site_type()),
("aas.subscription.id", aas.get_subscription_id()),
];

// Avoid infallible allocation paths when adding the Azure tags.
// This is an upper bound since Tag::new can fail and we'll skip invalid tags.
tags.try_reserve(aas_tags.len())?;

tags.extend(
aas_tags
.into_iter()
.filter_map(|(name, value)| Tag::new(name, value).ok()),
);
let aas_tags_iter = aas.get_app_service_tags();
tags.try_reserve(aas_tags_iter.len())?;
tags.extend(aas_tags_iter.filter_map(|(name, value)| Tag::new(name, value).ok()));
}

// Precompute the base tags string (includes configured tags + Azure App Services tags)
Expand Down
34 changes: 5 additions & 29 deletions libdd-trace-utils/src/trace_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,35 +555,11 @@ pub fn enrich_span_with_azure_function_metadata(span: &mut pb::Span) {
}

if let Some(aas_metadata) = &*azure_app_services::AAS_METADATA_FUNCTION {
let aas_tags = [
("aas.resource.id", aas_metadata.get_resource_id()),
(
"aas.environment.instance_id",
aas_metadata.get_instance_id(),
),
(
"aas.environment.instance_name",
aas_metadata.get_instance_name(),
),
("aas.subscription.id", aas_metadata.get_subscription_id()),
("aas.environment.os", aas_metadata.get_operating_system()),
("aas.environment.runtime", aas_metadata.get_runtime()),
(
"aas.environment.runtime_version",
aas_metadata.get_runtime_version(),
),
(
"aas.environment.function_runtime",
aas_metadata.get_function_runtime_version(),
),
("aas.resource.group", aas_metadata.get_resource_group()),
("aas.site.name", aas_metadata.get_site_name()),
("aas.site.kind", aas_metadata.get_site_kind()),
("aas.site.type", aas_metadata.get_site_type()),
];
aas_tags.into_iter().for_each(|(name, value)| {
span.meta.insert(name.to_string(), value.to_string());
});
span.meta.extend(
aas_metadata
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gives Azure Functions a span attribute extension_version: unknown which should only apply to Azure App Services

Span with this change:
Image

Span without this change:
Image

It turns out I can't test dev versions of libdatadog in dd-trace-dotnet, which is what Azure App Services Windows uses for profiling 😅, but looking at the code I also don't think AAS is supposed to get those functions runtime tags. AFAIK the DD_AAS_DOTNET_EXTENSION_VERSION env only gets set by the AAS extension in .NET and similarly the runtime env vars only get set in Azure Functions

So these environment variables are supposed to be mutually exclusive, and this would cause tags with unknown values to be added to environments where those env vars don't exist.

libdatadog is used in Azure Functions for traces, but not profiles, so this affects traces in Azure Functions
libdatadog is used in .NET AAS Windows for profiles, but not traces, so this affects profiles in AAS Windows

cc @duncanpharvey, correct me if I'm wrong!

Copy link
Copy Markdown
Contributor

@kathiehuang kathiehuang Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Azure App Service: aas.environment.extension_version
Azure Functions: aas.environment.function_runtime, aas.environment.runtime_version

.get_function_tags()
.map(|(name, value)| (name.to_string(), value.to_string())),
);
}
}

Expand Down
Loading