Skip to content
Open
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
6 changes: 6 additions & 0 deletions src/OpenTelemetry.Instrumentation.AWS/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
* Add the `aws.s3.bucket` and `aws.s3.key` attributes to S3 spans.
([#4029](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4029))

* Add instrumentation scope version and schema URL to metrics and traces.
([#4063](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4063))

* Pass AWS attribute values to created meters as tags.
([#4063](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4063))

## 1.15.0

Released 2026-Jan-21
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,51 @@
using System.Collections.Concurrent;
using Amazon.Runtime.Telemetry;
using Amazon.Runtime.Telemetry.Metrics;
using OpenTelemetry.AWS;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Instrumentation.AWS.Implementation.Metrics;

internal sealed class AWSMeterProvider : MeterProvider
internal sealed class AWSMeterProvider(SemanticConventionVersion version) : MeterProvider
{
private static readonly ConcurrentDictionary<string, AWSMeter> MetersDictionary = new();
private static readonly string MeterVersion = GetMeterVersion();

private readonly ConcurrentDictionary<string, AWSMeter> meters = new();
private readonly string telemetrySchemaUrl = AWSSemanticConventions.GetTelemetrySchemaUrl(version);

public override Meter GetMeter(string scope, Attributes? attributes = null)
{
// Passing attributes to the Meter is currently not possible due to version limitations
// in the dependencies. Since none of the SDK operations utilize attributes at this level,
// so we will omit the attributes for now.
// This will be revisited after the release of OpenTelemetry.Extensions.AWS which will
// update OpenTelemetry core component version(s) to `1.9.0` and allow passing tags to
// the meter constructor.

if (MetersDictionary.TryGetValue(scope, out var meter))
if (!this.meters.TryGetValue(scope, out var meter))
{
return meter;
#if NET
meter = this.meters.GetOrAdd(scope, static (name, state) => CreateMeter(name, state), (this.telemetrySchemaUrl, attributes?.AllAttributes));
#else
meter = this.meters.GetOrAdd(scope, (name) => CreateMeter(name, (this.telemetrySchemaUrl, attributes?.AllAttributes)));
#endif
}

var awsMeter = MetersDictionary.GetOrAdd(
scope,
new AWSMeter(new System.Diagnostics.Metrics.Meter(scope)));
return meter;

static AWSMeter CreateMeter(string name, (string SchemaUrl, IEnumerable<KeyValuePair<string, object?>>? Attributes) state)
{
var options = new System.Diagnostics.Metrics.MeterOptions(name)
{
TelemetrySchemaUrl = state.SchemaUrl,
Version = MeterVersion,
};

if (state.Attributes is { } attributes)
{
options.Tags = attributes;
}

return awsMeter;
return new AWSMeter(new System.Diagnostics.Metrics.Meter(options));
}
}

private static string GetMeterVersion()
{
var assembly = typeof(AWSMeterProvider).Assembly;
return assembly.GetPackageVersion();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,46 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using Amazon.Runtime.Telemetry.Tracing;
using OpenTelemetry.AWS;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Instrumentation.AWS.Implementation.Tracing;

internal sealed class AWSTracerProvider : TracerProvider
internal sealed class AWSTracerProvider(SemanticConventionVersion version) : TracerProvider
{
private static readonly ConcurrentDictionary<string, AWSTracer> TracersDictionary = new();
private static readonly string ActivitySourceVersion = GetActivitySourceVersion();

private readonly ConcurrentDictionary<string, AWSTracer> tracers = new();
private readonly string telemetrySchemaUrl = AWSSemanticConventions.GetTelemetrySchemaUrl(version);

public override Tracer GetTracer(string scope)
{
if (TracersDictionary.TryGetValue(scope, out var awsTracer))
if (!this.tracers.TryGetValue(scope, out var awsTracer))
{
return awsTracer;
#if NET
awsTracer = this.tracers.GetOrAdd(scope, static (name, schemaUrl) => CreateTracer(name, schemaUrl), this.telemetrySchemaUrl);
#else
awsTracer = this.tracers.GetOrAdd(scope, (name) => CreateTracer(name, this.telemetrySchemaUrl));
#endif
}

awsTracer = TracersDictionary.GetOrAdd(
scope,
new AWSTracer(new ActivitySource(scope)));

return awsTracer;

static AWSTracer CreateTracer(string name, string telemetrySchemaUrl)
{
var options = new ActivitySourceOptions(name)
{
TelemetrySchemaUrl = telemetrySchemaUrl,
Version = ActivitySourceVersion,
};

return new AWSTracer(new ActivitySource(options));
}
}

private static string GetActivitySourceVersion()
{
var assembly = typeof(AWSTracerProvider).Assembly;
return assembly.GetPackageVersion();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Amazon;
using Amazon.Runtime.Telemetry;
using OpenTelemetry.AWS;
using OpenTelemetry.Instrumentation.AWS.Implementation.Metrics;
using OpenTelemetry.Internal;

Expand All @@ -23,7 +24,10 @@ public static MeterProviderBuilder AddAWSInstrumentation(
{
Guard.ThrowIfNull(builder);

AWSConfigs.TelemetryProvider.RegisterMeterProvider(new AWSMeterProvider());
// This isn't currently configurable, so use the default
var semanticConventionVersion = AWSSemanticConventions.DefaultSemanticConventionVersion;

AWSConfigs.TelemetryProvider.RegisterMeterProvider(new AWSMeterProvider(semanticConventionVersion));
builder.AddMeter($"{TelemetryConstants.TelemetryScopePrefix}.*");

return builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static TracerProviderBuilder AddAWSInstrumentation(

_ = new AWSClientsInstrumentation(awsClientOptions);

AWSConfigs.TelemetryProvider.RegisterTracerProvider(new AWSTracerProvider());
AWSConfigs.TelemetryProvider.RegisterTracerProvider(new AWSTracerProvider(awsClientOptions.SemanticConventionVersion));
builder.AddSource($"{TelemetryConstants.TelemetryScopePrefix}.*");

return builder;
Expand Down
18 changes: 16 additions & 2 deletions src/OpenTelemetry.Instrumentation.AWSLambda/AWSLambdaWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static class AWSLambdaWrapper
{
internal const string ActivitySourceName = "OpenTelemetry.Instrumentation.AWSLambda";

private static readonly ActivitySource AWSLambdaActivitySource = new(ActivitySourceName, typeof(AWSLambdaWrapper).Assembly.GetPackageVersion());
private static readonly Lazy<ActivitySource> AWSLambdaActivitySource = new(CreateActivitySource);

private static bool isColdStart = true;

Expand Down Expand Up @@ -172,7 +172,7 @@ public static Task<TResult> TraceAsync<TInput, TResult>(

// We assume that functionTags and httpTags have no intersection.
var activityName = AWSLambdaUtils.GetFunctionName(context) ?? "AWS Lambda Invoke";
var activity = AWSLambdaActivitySource.StartActivity(activityName, ActivityKind.Server, parentContext, functionTags.Concat(httpTags)!, links);
var activity = AWSLambdaActivitySource.Value.StartActivity(activityName, ActivityKind.Server, parentContext, functionTags.Concat(httpTags)!, links);

return activity;
}
Expand Down Expand Up @@ -255,4 +255,18 @@ private static async Task<TResult> TraceInternalAsync<TInput, TResult>(
OnFunctionStop(activity, tracerProvider);
}
}

private static ActivitySource CreateActivitySource()
{
var assembly = typeof(AWSLambdaWrapper).Assembly;
var version = assembly.GetPackageVersion();

var activitySourceOptions = new ActivitySourceOptions(ActivitySourceName)
{
TelemetrySchemaUrl = AWSSemanticConventions.SchemaUrl,
Version = version,
};

return new(activitySourceOptions);
}
}
3 changes: 3 additions & 0 deletions src/OpenTelemetry.Instrumentation.AWSLambda/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Add instrumentation scope version and schema URL to traces.
([#4063](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4063))

## 1.15.0

Released 2026-Jan-21
Expand Down
15 changes: 15 additions & 0 deletions src/Shared/AWS/AWSSemanticConventions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ internal partial class AWSSemanticConventions
/// <inheritdoc cref="TagBuilderImpl"/>
public TagBuilderImpl TagBuilder { get; }

public string SchemaUrl { get; }

/// <summary>
/// Returns attribute names whose values must be reported as a <c>string[]</c>
/// (string array) rather than a plain <c>string</c>, as required by the
Expand All @@ -98,6 +100,7 @@ public AWSSemanticConventions(SemanticConventionVersion semanticConventionVersio
this.AttributeBuilder = new(this);
this.ParameterMappingBuilder = new(this);
this.TagBuilder = new(this);
this.SchemaUrl = GetTelemetrySchemaUrl(this.semanticConventionVersion);
}

/// <summary>
Expand Down Expand Up @@ -451,6 +454,18 @@ public TagBuilderImpl(AWSSemanticConventions semanticConventions)
#endregion
}

internal static string GetTelemetrySchemaUrl(SemanticConventionVersion semanticConventionVersion)
{
var versionString = semanticConventionVersion switch
{
SemanticConventionVersion.Latest or SemanticConventionVersion.V1_29_0 => "1.29.0",
SemanticConventionVersion.V1_28_0 => "1.28.0",
_ => throw new InvalidEnumArgumentException(nameof(semanticConventionVersion), (int)semanticConventionVersion, typeof(SemanticConventionVersion)),
};

return $"https://opentelemetry.io/schemas/{versionString}";
}

private AttributeBuilderImpl Add(AttributeBuilderImpl attributes, Func<AWSSemanticConventionsBase, string> attributeNameFunc, Func<AWSSemanticConventionsBase, string> valueFunc) =>
this.Add(attributes, attributeNameFunc, valueFunc(this.GetSemanticConventionVersion()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,35 @@ namespace OpenTelemetry.Instrumentation.AWS.Tests;
/// </summary>
public sealed class AWSClientInstrumentationOptionsTests
{
public static TheoryData<SemanticConventionVersion> SemanticConventionVersions()
{
var testCases = new TheoryData<SemanticConventionVersion>();

#if NET
var values = Enum.GetValues<SemanticConventionVersion>();
#else
var values = Enum.GetValues(typeof(SemanticConventionVersion)).OfType<SemanticConventionVersion>();
#endif

foreach (var value in values)
{
testCases.Add(value);
}

return testCases;
}

[Theory]
[MemberData(nameof(SemanticConventionVersions))]
public async Task CanUseSemanticConvention(SemanticConventionVersion semanticVersion)
{
// Act - Verify that when new versions are added to the enum, no exceptions are thrown
var exception = await Record.ExceptionAsync(async () => await this.GetActivityTagsAsync(semanticVersion));

// Assert
Assert.Null(exception);
}

[Fact]
public async Task CanUseSemanticConvention_V1_28_0()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,9 @@ private void ValidateAWSActivity(Activity aws_activity, Activity parent)
{
Assert.Equal(parent.SpanId, aws_activity.ParentSpanId);
Assert.Equal(ActivityKind.Client, aws_activity.Kind);
Assert.NotNull(aws_activity.Source.Version);
Assert.NotEmpty(aws_activity.Source.Version);
Assert.StartsWith("https://opentelemetry.io/schemas/", aws_activity.Source.TelemetrySchemaUrl);
}

private void ValidateDynamoActivityTags(Activity ddb_activity)
Expand Down
Loading
Loading