diff --git a/src/OpenTelemetry.Resources.AWS/AWSEC2Detector.cs b/src/OpenTelemetry.Resources.AWS/AWSEC2Detector.cs index aa6fc44aa6..98337cd9b7 100644 --- a/src/OpenTelemetry.Resources.AWS/AWSEC2Detector.cs +++ b/src/OpenTelemetry.Resources.AWS/AWSEC2Detector.cs @@ -72,6 +72,7 @@ internal List> ExtractResourceAttributes(AWSEC2Iden .AddAttributeCloudAccountID(identity?.AccountId) .AddAttributeCloudAvailabilityZone(identity?.AvailabilityZone) .AddAttributeHostID(identity?.InstanceId) + .AddAttributeHostImageId(identity?.ImageId) .AddAttributeHostType(identity?.InstanceType) .AddAttributeCloudRegion(identity?.Region) .Build(); diff --git a/src/OpenTelemetry.Resources.AWS/CHANGELOG.md b/src/OpenTelemetry.Resources.AWS/CHANGELOG.md index a613475360..9adc28ab6f 100644 --- a/src/OpenTelemetry.Resources.AWS/CHANGELOG.md +++ b/src/OpenTelemetry.Resources.AWS/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +* Added `host.image.id` resource attribute to `AWSEC2Detector`, populated from + the EC2 instance identity document's `imageId` (AMI ID). + ([#5110](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/5110)) + ## 1.18.0 Released 2026-Aug-21 diff --git a/src/OpenTelemetry.Resources.AWS/Models/AWSEC2IdentityDocumentModel.cs b/src/OpenTelemetry.Resources.AWS/Models/AWSEC2IdentityDocumentModel.cs index 665318c356..28d5136575 100644 --- a/src/OpenTelemetry.Resources.AWS/Models/AWSEC2IdentityDocumentModel.cs +++ b/src/OpenTelemetry.Resources.AWS/Models/AWSEC2IdentityDocumentModel.cs @@ -5,6 +5,8 @@ namespace OpenTelemetry.Resources.AWS.Models; +// See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html + internal class AWSEC2IdentityDocumentModel { [JsonPropertyName("accountId")] @@ -21,4 +23,7 @@ internal class AWSEC2IdentityDocumentModel [JsonPropertyName("instanceType")] public string? InstanceType { get; set; } + + [JsonPropertyName("imageId")] + public string? ImageId { get; set; } } diff --git a/src/OpenTelemetry.Resources.AWS/README.md b/src/OpenTelemetry.Resources.AWS/README.md index ade9c5565c..105efd89eb 100644 --- a/src/OpenTelemetry.Resources.AWS/README.md +++ b/src/OpenTelemetry.Resources.AWS/README.md @@ -54,7 +54,7 @@ The resource detectors will record the following metadata based on where your application is running: - **AWSEC2Detector**: cloud provider, cloud platform, account id, -cloud availability zone, host id, host type, aws region, host name. +cloud availability zone, host id, host type, host image id, aws region, host name. - **AWSEBSDetector**: cloud provider, cloud platform, service name, service namespace, instance id, service version. - **AWSECSDetector**: cloud provider, cloud platform, cloud resource id, diff --git a/src/Shared/AWS/AWSSemanticConventions.Base.cs b/src/Shared/AWS/AWSSemanticConventions.Base.cs index 9139bd2cc5..78ea24d54f 100644 --- a/src/Shared/AWS/AWSSemanticConventions.Base.cs +++ b/src/Shared/AWS/AWSSemanticConventions.Base.cs @@ -544,6 +544,14 @@ private abstract class AWSSemanticConventionsBase /// public virtual string AttributeHostType => string.Empty; + /// + /// VM image ID or host OS image ID. For Cloud, this value is from the provider. + /// + /// + /// HostAttributes.AttributeHostImageId + /// + public virtual string AttributeHostImageId => string.Empty; + /// /// Name of the host. On Unix systems, it may contain what the hostname command returns, or the fully qualified hostname, or another name specified by the user. /// diff --git a/src/Shared/AWS/AWSSemanticConventions.Legacy.cs b/src/Shared/AWS/AWSSemanticConventions.Legacy.cs index 07c3196763..24a104be58 100644 --- a/src/Shared/AWS/AWSSemanticConventions.Legacy.cs +++ b/src/Shared/AWS/AWSSemanticConventions.Legacy.cs @@ -83,6 +83,7 @@ private abstract class AWSSemanticConventionsLegacy : AWSSemanticConventionsBase public override string AttributeHostID => "host.id"; public override string AttributeHostType => "host.type"; public override string AttributeHostName => "host.name"; + public override string AttributeHostImageId => "host.image.id"; // HTTP Attributes [Obsolete("Replaced by http.response.status_code.")] diff --git a/src/Shared/AWS/AWSSemanticConventions.cs b/src/Shared/AWS/AWSSemanticConventions.cs index 3002d3c176..25b1623610 100644 --- a/src/Shared/AWS/AWSSemanticConventions.cs +++ b/src/Shared/AWS/AWSSemanticConventions.cs @@ -341,6 +341,10 @@ public AttributeBuilderImpl AddAttributeHostType(object? value, bool addIfEmpty public AttributeBuilderImpl AddAttributeHostName(object? value, bool addIfEmpty = false) => this.awsSemanticConventions.Add(this, x => x.AttributeHostName, value, addIfEmpty); + /// + public AttributeBuilderImpl AddAttributeHostImageId(object? value, bool addIfEmpty = false) => + this.awsSemanticConventions.Add(this, x => x.AttributeHostImageId, value, addIfEmpty); + #endregion #region Http diff --git a/test/OpenTelemetry.Resources.AWS.Tests/AWSEC2DetectorTests.cs b/test/OpenTelemetry.Resources.AWS.Tests/AWSEC2DetectorTests.cs index e4aeb0d53c..44ed3a54ad 100644 --- a/test/OpenTelemetry.Resources.AWS.Tests/AWSEC2DetectorTests.cs +++ b/test/OpenTelemetry.Resources.AWS.Tests/AWSEC2DetectorTests.cs @@ -87,6 +87,7 @@ public void TestExtractResourceAttributes(SemanticConventionVersion semanticConv Assert.Equal("Test availability zone", resourceAttributes[ExpectedSemanticConventions.AttributeCloudAvailabilityZone]); Assert.Equal("Test instance id", resourceAttributes[ExpectedSemanticConventions.AttributeHostID]); Assert.Equal("Test instance type", resourceAttributes[ExpectedSemanticConventions.AttributeHostType]); + Assert.Equal("Test image id", resourceAttributes[ExpectedSemanticConventions.AttributeHostImageId]); Assert.Equal("Test aws region", resourceAttributes[ExpectedSemanticConventions.AttributeCloudRegion]); Assert.Equal("Test host name", resourceAttributes[ExpectedSemanticConventions.AttributeHostName]); } @@ -103,6 +104,7 @@ public void TestDeserializeResponse() Assert.Equal("us-east-1a", ec2IdentityDocumentModel.AvailabilityZone); Assert.Equal("i-12345678901234567", ec2IdentityDocumentModel.InstanceId); Assert.Equal("t2.micro", ec2IdentityDocumentModel.InstanceType); + Assert.Equal("ami-12345678901234567", ec2IdentityDocumentModel.ImageId); Assert.Equal("us-east-1", ec2IdentityDocumentModel.Region); } @@ -114,6 +116,7 @@ private static class ExpectedSemanticConventions public const string AttributeCloudAvailabilityZone = "cloud.availability_zone"; public const string AttributeCloudRegion = "cloud.region"; public const string AttributeHostID = "host.id"; + public const string AttributeHostImageId = "host.image.id"; public const string AttributeHostType = "host.type"; public const string AttributeHostName = "host.name"; } diff --git a/test/OpenTelemetry.Resources.AWS.Tests/SampleAWSEC2IdentityDocumentModel.cs b/test/OpenTelemetry.Resources.AWS.Tests/SampleAWSEC2IdentityDocumentModel.cs index e2c2e8ffcd..f7afed1b4e 100644 --- a/test/OpenTelemetry.Resources.AWS.Tests/SampleAWSEC2IdentityDocumentModel.cs +++ b/test/OpenTelemetry.Resources.AWS.Tests/SampleAWSEC2IdentityDocumentModel.cs @@ -14,5 +14,6 @@ public SampleAWSEC2IdentityDocumentModel() this.Region = "Test aws region"; this.InstanceId = "Test instance id"; this.InstanceType = "Test instance type"; + this.ImageId = "Test image id"; } }