From 964e2bd79a2eb3677f881738bd2a0c1977de24a9 Mon Sep 17 00:00:00 2001 From: Prashant Srivastava <50466688+srprash@users.noreply.github.com> Date: Tue, 30 May 2023 09:32:44 -0700 Subject: [PATCH 01/12] [Sampler.AWS] Part-3: Update targets. Add rate limiting and fixed rate samplers. (#1151) --- .../AWSXRayRemoteSampler.cs | 52 +++++ .../AWSXRaySamplerClient.cs | 31 +++ src/OpenTelemetry.Sampler.AWS/Clock.cs | 2 +- .../FallbackSampler.cs | 16 +- .../GetSamplingTargetsRequest.cs | 31 +++ .../GetSamplingTargetsResponse.cs | 43 ++++ src/OpenTelemetry.Sampler.AWS/RateLimiter.cs | 62 ++++++ .../RateLimitingSampler.cs | 38 ++++ src/OpenTelemetry.Sampler.AWS/RulesCache.cs | 60 +++++ .../SamplingRuleApplier.cs | 144 +++++++++++- .../SamplingStatisticsDocument.cs | 50 +++++ .../SamplingTargetDocument.cs | 37 ++++ src/OpenTelemetry.Sampler.AWS/Statistics.cs | 8 +- src/OpenTelemetry.Sampler.AWS/SystemClock.cs | 8 +- .../UnprocessedStatistic.cs | 38 ++++ ...etSamplingRulesResponseOptionalFields.json | 42 ++++ .../Data/GetSamplingTargetsResponse.json | 23 ++ ...SamplingTargetsResponseOptionalFields.json | 16 ++ .../OpenTelemetry.Sampler.AWS.Tests.csproj | 7 +- .../TestAWSXRayRemoteSampler.cs | 76 ++++++- .../TestAWSXRaySamplerClient.cs | 85 ++++++++ .../TestClock.cs | 12 +- .../TestRateLimiter.cs | 163 ++++++++++++++ .../TestRateLimitingSampler.cs | 44 ++++ .../TestRulesCache.cs | 132 ++++++++++- .../TestSamplingRuleApplier.cs | 206 +++++++++++++++++- test/OpenTelemetry.Sampler.AWS.Tests/Utils.cs | 16 ++ 27 files changed, 1405 insertions(+), 37 deletions(-) create mode 100644 src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsRequest.cs create mode 100644 src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsResponse.cs create mode 100644 src/OpenTelemetry.Sampler.AWS/RateLimiter.cs create mode 100644 src/OpenTelemetry.Sampler.AWS/RateLimitingSampler.cs create mode 100644 src/OpenTelemetry.Sampler.AWS/SamplingStatisticsDocument.cs create mode 100644 src/OpenTelemetry.Sampler.AWS/SamplingTargetDocument.cs create mode 100644 src/OpenTelemetry.Sampler.AWS/UnprocessedStatistic.cs create mode 100644 test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingRulesResponseOptionalFields.json create mode 100644 test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingTargetsResponse.json create mode 100644 test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingTargetsResponseOptionalFields.json create mode 100644 test/OpenTelemetry.Sampler.AWS.Tests/TestRateLimiter.cs create mode 100644 test/OpenTelemetry.Sampler.AWS.Tests/TestRateLimitingSampler.cs diff --git a/src/OpenTelemetry.Sampler.AWS/AWSXRayRemoteSampler.cs b/src/OpenTelemetry.Sampler.AWS/AWSXRayRemoteSampler.cs index b688720016..179de0ec3b 100644 --- a/src/OpenTelemetry.Sampler.AWS/AWSXRayRemoteSampler.cs +++ b/src/OpenTelemetry.Sampler.AWS/AWSXRayRemoteSampler.cs @@ -47,12 +47,20 @@ internal AWSXRayRemoteSampler(Resource resource, TimeSpan pollingInterval, strin // upto 5 seconds of jitter for rule polling this.RulePollerJitter = TimeSpan.FromMilliseconds(Random.Next(1, 5000)); + // upto 100 milliseconds of jitter for target polling + this.TargetPollerJitter = TimeSpan.FromMilliseconds(Random.Next(1, 100)); + // execute the first update right away and schedule subsequent update later. this.RulePollerTimer = new Timer(this.GetAndUpdateRules, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan); + + // set up the target poller to go off once after the default interval. We will update the timer later. + this.TargetPollerTimer = new Timer(this.GetAndUpdateTargets, null, DefaultTargetInterval, Timeout.InfiniteTimeSpan); } internal TimeSpan RulePollerJitter { get; set; } + internal TimeSpan TargetPollerJitter { get; set; } + internal Clock Clock { get; set; } internal string ClientId { get; set; } @@ -67,6 +75,8 @@ internal AWSXRayRemoteSampler(Resource resource, TimeSpan pollingInterval, strin internal Timer RulePollerTimer { get; set; } + internal Timer TargetPollerTimer { get; set; } + internal TimeSpan PollingInterval { get; set; } internal Trace.Sampler FallbackSampler { get; set; } @@ -137,4 +147,46 @@ private async void GetAndUpdateRules(object? state) // schedule the next rule poll. this.RulePollerTimer.Change(this.PollingInterval.Add(this.RulePollerJitter), Timeout.InfiniteTimeSpan); } + + private async void GetAndUpdateTargets(object? state) + { + List statistics = this.RulesCache.Snapshot(this.Clock.Now()); + + GetSamplingTargetsRequest request = new GetSamplingTargetsRequest(statistics); + GetSamplingTargetsResponse? response = await this.Client.GetSamplingTargets(request).ConfigureAwait(false); + if (response != null) + { + Dictionary targets = new Dictionary(); + foreach (SamplingTargetDocument target in response.SamplingTargetDocuments) + { + if (target.RuleName != null) + { + targets[target.RuleName] = target; + } + } + + this.RulesCache.UpdateTargets(targets); + + if (response.LastRuleModification > 0) + { + DateTime lastRuleModificationTime = this.Clock.ToDateTime(response.LastRuleModification); + + if (lastRuleModificationTime > this.RulesCache.GetUpdatedAt()) + { + // rules have been updated. fetch the new ones right away. + this.RulePollerTimer.Change(TimeSpan.Zero, Timeout.InfiniteTimeSpan); + } + } + } + + // schedule next target poll + DateTime nextTargetFetchTime = this.RulesCache.NextTargetFetchTime(); + TimeSpan nextTargetFetchInterval = nextTargetFetchTime.Subtract(this.Clock.Now()); + if (nextTargetFetchInterval < TimeSpan.Zero) + { + nextTargetFetchInterval = DefaultTargetInterval; + } + + this.TargetPollerTimer.Change(nextTargetFetchInterval.Add(this.TargetPollerJitter), Timeout.InfiniteTimeSpan); + } } diff --git a/src/OpenTelemetry.Sampler.AWS/AWSXRaySamplerClient.cs b/src/OpenTelemetry.Sampler.AWS/AWSXRaySamplerClient.cs index 1a40673fb5..64b7649e8f 100644 --- a/src/OpenTelemetry.Sampler.AWS/AWSXRaySamplerClient.cs +++ b/src/OpenTelemetry.Sampler.AWS/AWSXRaySamplerClient.cs @@ -26,12 +26,15 @@ namespace OpenTelemetry.Sampler.AWS; internal class AWSXRaySamplerClient : IDisposable { private readonly string getSamplingRulesEndpoint; + private readonly string getSamplingTargetsEndpoint; + private readonly HttpClient httpClient; private readonly string jsonContentType = "application/json"; public AWSXRaySamplerClient(string host) { this.getSamplingRulesEndpoint = host + "/GetSamplingRules"; + this.getSamplingTargetsEndpoint = host + "/SamplingTargets"; this.httpClient = new HttpClient(); } @@ -74,6 +77,34 @@ public async Task> GetSamplingRules() return samplingRules; } + public async Task GetSamplingTargets(GetSamplingTargetsRequest getSamplingTargetsRequest) + { + var content = new StringContent(JsonSerializer.Serialize(getSamplingTargetsRequest), Encoding.UTF8, this.jsonContentType); + + using var request = new HttpRequestMessage(HttpMethod.Post, this.getSamplingTargetsEndpoint) + { + Content = content, + }; + + var responseJson = await this.DoRequestAsync(this.getSamplingTargetsEndpoint, request).ConfigureAwait(false); + + try + { + GetSamplingTargetsResponse? getSamplingTargetsResponse = JsonSerializer + .Deserialize(responseJson); + + return getSamplingTargetsResponse; + } + catch (Exception ex) + { + AWSSamplerEventSource.Log.FailedToDeserializeResponse( + nameof(AWSXRaySamplerClient.GetSamplingTargets), + ex.Message); + } + + return null; + } + public void Dispose() { this.Dispose(true); diff --git a/src/OpenTelemetry.Sampler.AWS/Clock.cs b/src/OpenTelemetry.Sampler.AWS/Clock.cs index 39218fbdf2..1043e4aa36 100644 --- a/src/OpenTelemetry.Sampler.AWS/Clock.cs +++ b/src/OpenTelemetry.Sampler.AWS/Clock.cs @@ -28,7 +28,7 @@ public static Clock GetDefault() public abstract DateTime Now(); - public abstract long NowInSeconds(); + public abstract long NowInMilliSeconds(); public abstract DateTime ToDateTime(double seconds); diff --git a/src/OpenTelemetry.Sampler.AWS/FallbackSampler.cs b/src/OpenTelemetry.Sampler.AWS/FallbackSampler.cs index 3917c407fe..72d756b322 100644 --- a/src/OpenTelemetry.Sampler.AWS/FallbackSampler.cs +++ b/src/OpenTelemetry.Sampler.AWS/FallbackSampler.cs @@ -20,19 +20,25 @@ namespace OpenTelemetry.Sampler.AWS; internal class FallbackSampler : Trace.Sampler { - private static readonly Trace.Sampler AlwaysOn = new AlwaysOnSampler(); - + private readonly Trace.Sampler reservoirSampler; + private readonly Trace.Sampler fixedRateSampler; private readonly Clock clock; public FallbackSampler(Clock clock) { this.clock = clock; + this.reservoirSampler = new ParentBasedSampler(new RateLimitingSampler(1, clock)); + this.fixedRateSampler = new ParentBasedSampler(new TraceIdRatioBasedSampler(0.05)); } public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) { - // For now just do an always on sampler. - // TODO: update to a rate limiting sampler. - return AlwaysOn.ShouldSample(samplingParameters); + SamplingResult result = this.reservoirSampler.ShouldSample(in samplingParameters); + if (result.Decision != SamplingDecision.Drop) + { + return result; + } + + return this.fixedRateSampler.ShouldSample(in samplingParameters); } } diff --git a/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsRequest.cs b/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsRequest.cs new file mode 100644 index 0000000000..f7bafb0921 --- /dev/null +++ b/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsRequest.cs @@ -0,0 +1,31 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace OpenTelemetry.Sampler.AWS; + +internal class GetSamplingTargetsRequest +{ + public GetSamplingTargetsRequest(List documents) + { + this.SamplingStatisticsDocuments = documents; + } + + [JsonPropertyName("SamplingStatisticsDocuments")] + public List SamplingStatisticsDocuments { get; set; } +} diff --git a/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsResponse.cs b/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsResponse.cs new file mode 100644 index 0000000000..1490e5e6d7 --- /dev/null +++ b/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsResponse.cs @@ -0,0 +1,43 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace OpenTelemetry.Sampler.AWS; + +internal class GetSamplingTargetsResponse +{ + public GetSamplingTargetsResponse( + double lastRuleModification, + List samplingTargetDocuments, + List unprocessedStatistics) + { + this.LastRuleModification = lastRuleModification; + this.SamplingTargetDocuments = samplingTargetDocuments; + this.UnprocessedStatistics = unprocessedStatistics; + } + + // This is actually a time in unix seconds. + [JsonPropertyName("LastRuleModification")] + public double LastRuleModification { get; set; } + + [JsonPropertyName("SamplingTargetDocuments")] + public List SamplingTargetDocuments { get; set; } + + [JsonPropertyName("UnprocessedStatistics")] + public List UnprocessedStatistics { get; set; } +} diff --git a/src/OpenTelemetry.Sampler.AWS/RateLimiter.cs b/src/OpenTelemetry.Sampler.AWS/RateLimiter.cs new file mode 100644 index 0000000000..aba7ea134d --- /dev/null +++ b/src/OpenTelemetry.Sampler.AWS/RateLimiter.cs @@ -0,0 +1,62 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Threading; + +namespace OpenTelemetry.Sampler.AWS; +internal sealed class RateLimiter +{ + private readonly Clock clock; + private readonly double creditsPerMillisecond; + private readonly long maxBalance; + private long currentBalance; + + internal RateLimiter(double creditsPerSecond, double maxBalance, Clock clock) + { + this.clock = clock; + this.creditsPerMillisecond = creditsPerSecond / 1.0e3; + this.maxBalance = (long)(maxBalance / this.creditsPerMillisecond); + this.currentBalance = this.clock.NowInMilliSeconds() - this.maxBalance; + } + + public bool TrySpend(double itemCost) + { + long cost = (long)(itemCost / this.creditsPerMillisecond); + long currentMillis; + long currentBalanceMillis; + long availableBalanceAfterWithdrawal; + + do + { + currentBalanceMillis = Interlocked.Read(ref this.currentBalance); + currentMillis = this.clock.NowInMilliSeconds(); + long currentAvailableBalance = currentMillis - currentBalanceMillis; + if (currentAvailableBalance > this.maxBalance) + { + currentAvailableBalance = this.maxBalance; + } + + availableBalanceAfterWithdrawal = currentAvailableBalance - cost; + if (availableBalanceAfterWithdrawal < 0) + { + return false; + } + } + while (Interlocked.CompareExchange(ref this.currentBalance, currentMillis - availableBalanceAfterWithdrawal, currentBalanceMillis) != currentBalanceMillis); + + return true; + } +} diff --git a/src/OpenTelemetry.Sampler.AWS/RateLimitingSampler.cs b/src/OpenTelemetry.Sampler.AWS/RateLimitingSampler.cs new file mode 100644 index 0000000000..2a315787c7 --- /dev/null +++ b/src/OpenTelemetry.Sampler.AWS/RateLimitingSampler.cs @@ -0,0 +1,38 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Sampler.AWS; +internal class RateLimitingSampler : Trace.Sampler +{ + private readonly RateLimiter limiter; + + public RateLimitingSampler(long numPerSecond, Clock clock) + { + this.limiter = new RateLimiter(numPerSecond, numPerSecond, clock); + } + + public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) + { + if (this.limiter.TrySpend(1)) + { + return new SamplingResult(SamplingDecision.RecordAndSample); + } + + return new SamplingResult(SamplingDecision.Drop); + } +} diff --git a/src/OpenTelemetry.Sampler.AWS/RulesCache.cs b/src/OpenTelemetry.Sampler.AWS/RulesCache.cs index 182ab0b013..3e8efce9c3 100644 --- a/src/OpenTelemetry.Sampler.AWS/RulesCache.cs +++ b/src/OpenTelemetry.Sampler.AWS/RulesCache.cs @@ -109,6 +109,66 @@ public SamplingResult ShouldSample(in SamplingParameters samplingParameters) return this.FallbackSampler.ShouldSample(in samplingParameters); } + public List Snapshot(DateTime now) + { + List snapshots = new List(); + foreach (var ruleApplier in this.RuleAppliers) + { + snapshots.Add(ruleApplier.Snapshot(now)); + } + + return snapshots; + } + + public void UpdateTargets(Dictionary targets) + { + List newRuleAppliers = new List(); + foreach (var ruleApplier in this.RuleAppliers) + { + targets.TryGetValue(ruleApplier.RuleName, out SamplingTargetDocument? target); + if (target != null) + { + newRuleAppliers.Add(ruleApplier.WithTarget(target, this.Clock.Now())); + } + else + { + // did not get target for this rule. Will be updated in future target poll. + newRuleAppliers.Add(ruleApplier); + } + } + + this.rwLock.EnterWriteLock(); + try + { + this.RuleAppliers = newRuleAppliers; + } + finally + { + this.rwLock.ExitWriteLock(); + } + } + + public DateTime NextTargetFetchTime() + { + var defaultPollingTime = this.Clock.Now().AddSeconds(AWSXRayRemoteSampler.DefaultTargetInterval.TotalSeconds); + + if (this.RuleAppliers.Count == 0) + { + return defaultPollingTime; + } + + var minPollingTime = this.RuleAppliers + .Select(r => r.NextSnapshotTime) + .Min(); + + if (minPollingTime < this.Clock.Now()) + { + return defaultPollingTime; + } + + return minPollingTime; + } + public void Dispose() { this.Dispose(true); diff --git a/src/OpenTelemetry.Sampler.AWS/SamplingRuleApplier.cs b/src/OpenTelemetry.Sampler.AWS/SamplingRuleApplier.cs index abfba82618..56cc0f0443 100644 --- a/src/OpenTelemetry.Sampler.AWS/SamplingRuleApplier.cs +++ b/src/OpenTelemetry.Sampler.AWS/SamplingRuleApplier.cs @@ -15,8 +15,8 @@ // using System; -using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using OpenTelemetry.Resources; using OpenTelemetry.Trace; @@ -31,6 +31,51 @@ public SamplingRuleApplier(string clientId, Clock clock, SamplingRule rule, Stat this.Rule = rule; this.RuleName = this.Rule.RuleName; this.Statistics = statistics ?? new Statistics(); + + if (rule.ReservoirSize > 0) + { + // Until calling GetSamplingTargets, the default is to borrow 1/s if reservoir size is + // positive. + this.ReservoirSampler = new ParentBasedSampler(new RateLimitingSampler(1, this.Clock)); + this.Borrowing = true; + } + else + { + // No reservoir sampling, we will always use the fixed rate. + this.ReservoirSampler = new AlwaysOffSampler(); + this.Borrowing = false; + } + + this.FixedRateSampler = new ParentBasedSampler(new TraceIdRatioBasedSampler(rule.FixedRate)); + + // We either have no reservoir sampling or borrow until we get a quota so have no end time. + this.ReservoirEndTime = DateTime.MaxValue; + + // We don't have a SamplingTarget so are ready to report a snapshot right away. + this.NextSnapshotTime = this.Clock.Now(); + } + + private SamplingRuleApplier( + string clientId, + SamplingRule rule, + Clock clock, + Trace.Sampler reservoirSampler, + Trace.Sampler fixedRateSampler, + bool borrowing, + Statistics statistics, + DateTime reservoirEndTime, + DateTime nextSnapshotTime) + { + this.ClientId = clientId; + this.Rule = rule; + this.RuleName = rule.RuleName; + this.Clock = clock; + this.ReservoirSampler = reservoirSampler; + this.FixedRateSampler = fixedRateSampler; + this.Borrowing = borrowing; + this.Statistics = statistics; + this.ReservoirEndTime = reservoirEndTime; + this.NextSnapshotTime = nextSnapshotTime; } internal string ClientId { get; set; } @@ -43,6 +88,16 @@ public SamplingRuleApplier(string clientId, Clock clock, SamplingRule rule, Stat internal Statistics Statistics { get; set; } + internal Trace.Sampler ReservoirSampler { get; set; } + + internal Trace.Sampler FixedRateSampler { get; set; } + + internal bool Borrowing { get; set; } + + internal DateTime ReservoirEndTime { get; set; } + + internal DateTime NextSnapshotTime { get; set; } + // check if this rule applier matches the request public bool Matches(SamplingParameters samplingParameters, Resource resource) { @@ -107,12 +162,91 @@ public bool Matches(SamplingParameters samplingParameters, Resource resource) Matcher.WildcardMatch(GetArn(in samplingParameters, resource), this.Rule.ResourceArn); } - [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "method work in progress")] public SamplingResult ShouldSample(in SamplingParameters samplingParameters) { - // for now return drop sampling result. - // TODO: use reservoir and fixed rate sampler - return new SamplingResult(false); + Interlocked.Increment(ref this.Statistics.RequestCount); + bool reservoirExpired = this.Clock.Now() >= this.ReservoirEndTime; + SamplingResult result = !reservoirExpired + ? this.ReservoirSampler.ShouldSample(in samplingParameters) + : new SamplingResult(SamplingDecision.Drop); + + if (result.Decision != SamplingDecision.Drop) + { + if (this.Borrowing) + { + Interlocked.Increment(ref this.Statistics.BorrowCount); + } + + Interlocked.Increment(ref this.Statistics.SampleCount); + + return result; + } + + result = this.FixedRateSampler.ShouldSample(samplingParameters); + if (result.Decision != SamplingDecision.Drop) + { + Interlocked.Increment(ref this.Statistics.SampleCount); + } + + return result; + } + + // take the snapshot and reset the statistics. + public SamplingStatisticsDocument Snapshot(DateTime now) + { + double timestamp = this.Clock.ToDouble(now); + + long matchedRequests = Interlocked.Exchange(ref this.Statistics.RequestCount, 0L); + long sampledRequests = Interlocked.Exchange(ref this.Statistics.SampleCount, 0L); + long borrowedRequests = Interlocked.Exchange(ref this.Statistics.BorrowCount, 0L); + + SamplingStatisticsDocument statiscticsDocument = new SamplingStatisticsDocument( + this.ClientId, + this.RuleName, + matchedRequests, + sampledRequests, + borrowedRequests, + timestamp); + + return statiscticsDocument; + } + + public SamplingRuleApplier WithTarget(SamplingTargetDocument target, DateTime now) + { + Trace.Sampler newFixedRateSampler = target.FixedRate != null + ? new ParentBasedSampler(new TraceIdRatioBasedSampler(target.FixedRate.Value)) + : this.FixedRateSampler; + + Trace.Sampler newReservoirSampler = new AlwaysOffSampler(); + DateTime newReservoirEndTime = DateTime.MaxValue; + if (target.ReservoirQuota != null && target.ReservoirQuotaTTL != null) + { + if (target.ReservoirQuota > 0) + { + newReservoirSampler = new ParentBasedSampler(new RateLimitingSampler(target.ReservoirQuota.Value, this.Clock)); + } + else + { + newReservoirSampler = new AlwaysOffSampler(); + } + + newReservoirEndTime = this.Clock.ToDateTime(target.ReservoirQuotaTTL.Value); + } + + DateTime newNextSnapshotTime = target.Interval != null + ? now.AddSeconds(target.Interval.Value) + : now.Add(AWSXRayRemoteSampler.DefaultTargetInterval); + + return new SamplingRuleApplier( + this.ClientId, + this.Rule, + this.Clock, + newReservoirSampler, + newFixedRateSampler, + false, // no need for borrow + this.Statistics, + newReservoirEndTime, + newNextSnapshotTime); } private static string GetServiceType(Resource resource) diff --git a/src/OpenTelemetry.Sampler.AWS/SamplingStatisticsDocument.cs b/src/OpenTelemetry.Sampler.AWS/SamplingStatisticsDocument.cs new file mode 100644 index 0000000000..f452808e95 --- /dev/null +++ b/src/OpenTelemetry.Sampler.AWS/SamplingStatisticsDocument.cs @@ -0,0 +1,50 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Text.Json.Serialization; + +namespace OpenTelemetry.Sampler.AWS; + +internal class SamplingStatisticsDocument +{ + public SamplingStatisticsDocument(string clientID, string ruleName, long requestCount, long sampledCount, long borrowCount, double timestamp) + { + this.ClientID = clientID; + this.RuleName = ruleName; + this.RequestCount = requestCount; + this.SampledCount = sampledCount; + this.BorrowCount = borrowCount; + this.Timestamp = timestamp; + } + + [JsonPropertyName("ClientID")] + public string ClientID { get; set; } + + [JsonPropertyName("RuleName")] + public string RuleName { get; set; } + + [JsonPropertyName("RequestCount")] + public long RequestCount { get; set; } + + [JsonPropertyName("SampledCount")] + public long SampledCount { get; set; } + + [JsonPropertyName("BorrowCount")] + public long BorrowCount { get; set; } + + [JsonPropertyName("Timestamp")] + public double Timestamp { get; set; } +} diff --git a/src/OpenTelemetry.Sampler.AWS/SamplingTargetDocument.cs b/src/OpenTelemetry.Sampler.AWS/SamplingTargetDocument.cs new file mode 100644 index 0000000000..3fa7091b73 --- /dev/null +++ b/src/OpenTelemetry.Sampler.AWS/SamplingTargetDocument.cs @@ -0,0 +1,37 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Text.Json.Serialization; + +namespace OpenTelemetry.Sampler.AWS; + +internal class SamplingTargetDocument +{ + [JsonPropertyName("FixedRate")] + public double? FixedRate { get; set; } + + [JsonPropertyName("Interval")] + public long? Interval { get; set; } + + [JsonPropertyName("ReservoirQuota")] + public long? ReservoirQuota { get; set; } + + [JsonPropertyName("ReservoirQuotaTTL")] + public double? ReservoirQuotaTTL { get; set; } + + [JsonPropertyName("RuleName")] + public string? RuleName { get; set; } +} diff --git a/src/OpenTelemetry.Sampler.AWS/Statistics.cs b/src/OpenTelemetry.Sampler.AWS/Statistics.cs index 9b790eb0e8..e278aab772 100644 --- a/src/OpenTelemetry.Sampler.AWS/Statistics.cs +++ b/src/OpenTelemetry.Sampler.AWS/Statistics.cs @@ -18,9 +18,7 @@ namespace OpenTelemetry.Sampler.AWS; internal class Statistics { - public int RequestCount { get; internal set; } - - public int BorrowCount { get; internal set; } - - public int SampleCount { get; internal set; } + public long RequestCount; + public long BorrowCount; + public long SampleCount; } diff --git a/src/OpenTelemetry.Sampler.AWS/SystemClock.cs b/src/OpenTelemetry.Sampler.AWS/SystemClock.cs index d2ec6eb090..4f23ae6704 100644 --- a/src/OpenTelemetry.Sampler.AWS/SystemClock.cs +++ b/src/OpenTelemetry.Sampler.AWS/SystemClock.cs @@ -15,7 +15,6 @@ // using System; -using System.Diagnostics; namespace OpenTelemetry.Sampler.AWS; @@ -40,12 +39,9 @@ public override DateTime Now() return DateTime.UtcNow; } - public override long NowInSeconds() + public override long NowInMilliSeconds() { - double ts = Stopwatch.GetTimestamp(); - double s = ts / Stopwatch.Frequency; - - return (long)s; + return (long)this.Now().ToUniversalTime().Subtract(EpochStart).TotalMilliseconds; } public override DateTime ToDateTime(double seconds) diff --git a/src/OpenTelemetry.Sampler.AWS/UnprocessedStatistic.cs b/src/OpenTelemetry.Sampler.AWS/UnprocessedStatistic.cs new file mode 100644 index 0000000000..9b03449b2f --- /dev/null +++ b/src/OpenTelemetry.Sampler.AWS/UnprocessedStatistic.cs @@ -0,0 +1,38 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Text.Json.Serialization; + +namespace OpenTelemetry.Sampler.AWS; + +internal class UnprocessedStatistic +{ + public UnprocessedStatistic(string? errorCode, string? message, string? ruleName) + { + this.ErrorCode = errorCode; + this.Message = message; + this.RuleName = ruleName; + } + + [JsonPropertyName("ErrorCode")] + public string? ErrorCode { get; set; } + + [JsonPropertyName("Message")] + public string? Message { get; set; } + + [JsonPropertyName("RuleName")] + public string? RuleName { get; set; } +} diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingRulesResponseOptionalFields.json b/test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingRulesResponseOptionalFields.json new file mode 100644 index 0000000000..c0b9813c11 --- /dev/null +++ b/test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingRulesResponseOptionalFields.json @@ -0,0 +1,42 @@ +{ + "SamplingRuleRecords": [ + { + "SamplingRule": { + "RuleName": "Test", + "RuleARN": "arn:aws:xray:us-east-1:123456789000:sampling-rule/Test", + "ResourceARN": "*", + "Priority": 1, + "FixedRate": 0.0, + "ReservoirSize": 0, + "ServiceName": "*", + "ServiceType": "*", + "Host": "*", + "HTTPMethod": "*", + "URLPath": "*", + "Version": 1, + "Attributes": { "test": "cat-service" } + }, + "CreatedAt": 1.67799933E9, + "ModifiedAt": 1.67799933E9 + }, + { + "SamplingRule": { + "RuleName": "Default", + "RuleARN": "arn:aws:xray:us-east-1:123456789000:sampling-rule/Default", + "ResourceARN": "*", + "Priority": 10000, + "FixedRate": 0.0, + "ReservoirSize": 1, + "ServiceName": "*", + "ServiceType": "*", + "Host": "*", + "HTTPMethod": "*", + "URLPath": "*", + "Version": 1, + "Attributes": {} + }, + "CreatedAt": 0.0, + "ModifiedAt": 1.681856516E9 + } + ] +} diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingTargetsResponse.json b/test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingTargetsResponse.json new file mode 100644 index 0000000000..0293d57330 --- /dev/null +++ b/test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingTargetsResponse.json @@ -0,0 +1,23 @@ +{ + "SamplingTargetDocuments": [ + { + "RuleName": "rule1", + "FixedRate": 0.1, + "ReservoirQuota": 2, + "ReservoirQuotaTTL": 1530923107.0, + "Interval": 10 + }, + { + "RuleName": "rule3", + "FixedRate": 0.003 + } + ], + "LastRuleModification": 1530920505.0, + "UnprocessedStatistics": [ + { + "RuleName": "rule2", + "ErrorCode": "400", + "Message": "Unknown rule" + } + ] +} diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingTargetsResponseOptionalFields.json b/test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingTargetsResponseOptionalFields.json new file mode 100644 index 0000000000..b7ca420ecd --- /dev/null +++ b/test/OpenTelemetry.Sampler.AWS.Tests/Data/GetSamplingTargetsResponseOptionalFields.json @@ -0,0 +1,16 @@ +{ + "SamplingTargetDocuments": [ + { + "RuleName": "Test", + "FixedRate": 1.0 + } + ], + "LastRuleModification": 1530920505.0, + "UnprocessedStatistics": [ + { + "RuleName": "Default", + "ErrorCode": "400", + "Message": "Unknown rule" + } + ] +} diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/OpenTelemetry.Sampler.AWS.Tests.csproj b/test/OpenTelemetry.Sampler.AWS.Tests/OpenTelemetry.Sampler.AWS.Tests.csproj index bb8099097e..e0fb3a51f5 100644 --- a/test/OpenTelemetry.Sampler.AWS.Tests/OpenTelemetry.Sampler.AWS.Tests.csproj +++ b/test/OpenTelemetry.Sampler.AWS.Tests/OpenTelemetry.Sampler.AWS.Tests.csproj @@ -21,9 +21,10 @@ - - PreserveNewest - + + + + diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs b/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs index 5cff06fa5f..ce59cad264 100644 --- a/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs +++ b/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs @@ -16,8 +16,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; using OpenTelemetry.Resources; using OpenTelemetry.Trace; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using WireMock.Server; using Xunit; namespace OpenTelemetry.Sampler.AWS.Tests; @@ -53,13 +59,71 @@ public void TestSamplerWithDefaults() } [Fact] - public void TestSamplerShouldSample() + public void TestSamplerUpdateAndSample() { - Trace.Sampler sampler = AWSXRayRemoteSampler.Builder(ResourceBuilder.CreateEmpty().Build()).Build(); + // setup mock server + TestClock clock = new TestClock(); + WireMockServer mockServer = WireMockServer.Start(); - // for now the fallback sampler should be making the sampling decision - Assert.Equal( - SamplingDecision.RecordAndSample, - sampler.ShouldSample(Utils.CreateSamplingParametersWithTags(new Dictionary())).Decision); + // create sampler + AWSXRayRemoteSampler sampler = AWSXRayRemoteSampler.Builder(ResourceBuilder.CreateEmpty().Build()) + .SetPollingInterval(TimeSpan.FromMilliseconds(10)) + .SetEndpoint(mockServer.Url) + .SetClock(clock) + .Build(); + + // the sampler will use fallback sampler until rules are fetched. + Assert.Equal(SamplingDecision.RecordAndSample, this.DoSample(sampler, "cat-service")); + Assert.Equal(SamplingDecision.Drop, this.DoSample(sampler, "cat-service")); + + // GetSamplingRules mock response + mockServer + .Given(Request.Create().WithPath("/GetSamplingRules").UsingPost()) + .RespondWith( + Response.Create() + .WithStatusCode(200) + .WithHeader("Content-Type", "application/json") + .WithBody(File.ReadAllText("Data/GetSamplingRulesResponseOptionalFields.json"))); + + // rules will be polled in 10 milliseconds + Thread.Sleep(2000); + + // sampler will drop because rule has 0 reservoir and 0 fixed rate + Assert.Equal(SamplingDecision.Drop, this.DoSample(sampler, "cat-service")); + + // GetSamplingTargets mock response + mockServer + .Given(Request.Create().WithPath("/SamplingTargets").UsingPost()) + .RespondWith( + Response.Create() + .WithStatusCode(200) + .WithHeader("Content-Type", "application/json") + .WithBody(File.ReadAllText("Data/GetSamplingTargetsResponseOptionalFields.json"))); + + // targets will be polled in 10 seconds + Thread.Sleep(13000); + + // sampler will always sampler since target has 100% fixed rate + Assert.Equal(SamplingDecision.RecordAndSample, this.DoSample(sampler, "cat-service")); + Assert.Equal(SamplingDecision.RecordAndSample, this.DoSample(sampler, "cat-service")); + Assert.Equal(SamplingDecision.RecordAndSample, this.DoSample(sampler, "cat-service")); + + mockServer.Stop(); + } + + private SamplingDecision DoSample(Trace.Sampler sampler, string serviceName) + { + var samplingParams = new SamplingParameters( + default, + ActivityTraceId.CreateRandom(), + "myActivityName", + ActivityKind.Server, + new List>() + { + new KeyValuePair("test", serviceName), + }, + null); + + return sampler.ShouldSample(samplingParams).Decision; } } diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRaySamplerClient.cs b/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRaySamplerClient.cs index 2b9863c1f8..fa78d9a108 100644 --- a/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRaySamplerClient.cs +++ b/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRaySamplerClient.cs @@ -109,6 +109,91 @@ public void TestGetSamplingRulesMalformed() Assert.Empty(rules); } + [Fact] + public void TestGetSamplingTargets() + { + TestClock clock = new TestClock(); + + this.CreateResponse("/SamplingTargets", "Data/GetSamplingTargetsResponse.json"); + + var request = new GetSamplingTargetsRequest(new List() + { + new SamplingStatisticsDocument( + "clientId", + "rule1", + 100, + 50, + 10, + clock.ToDouble(clock.Now())), + new SamplingStatisticsDocument( + "clientId", + "rule2", + 200, + 100, + 20, + clock.ToDouble(clock.Now())), + new SamplingStatisticsDocument( + "clientId", + "rule3", + 20, + 10, + 2, + clock.ToDouble(clock.Now())), + }); + + var responseTask = this.client.GetSamplingTargets(request); + responseTask.Wait(); + + GetSamplingTargetsResponse targetsResponse = responseTask.Result; + + Assert.Equal(2, targetsResponse.SamplingTargetDocuments.Count); + Assert.Single(targetsResponse.UnprocessedStatistics); + + Assert.Equal("rule1", targetsResponse.SamplingTargetDocuments[0].RuleName); + Assert.Equal(0.1, targetsResponse.SamplingTargetDocuments[0].FixedRate); + Assert.Equal(2, targetsResponse.SamplingTargetDocuments[0].ReservoirQuota); + Assert.Equal(1530923107.0, targetsResponse.SamplingTargetDocuments[0].ReservoirQuotaTTL); + Assert.Equal(10, targetsResponse.SamplingTargetDocuments[0].Interval); + + Assert.Equal("rule3", targetsResponse.SamplingTargetDocuments[1].RuleName); + Assert.Equal(0.003, targetsResponse.SamplingTargetDocuments[1].FixedRate); + Assert.Null(targetsResponse.SamplingTargetDocuments[1].ReservoirQuota); + Assert.Null(targetsResponse.SamplingTargetDocuments[1].ReservoirQuotaTTL); + Assert.Null(targetsResponse.SamplingTargetDocuments[1].Interval); + + Assert.Equal("rule2", targetsResponse.UnprocessedStatistics[0].RuleName); + Assert.Equal("400", targetsResponse.UnprocessedStatistics[0].ErrorCode); + Assert.Equal("Unknown rule", targetsResponse.UnprocessedStatistics[0].Message); + } + + [Fact] + public void TestGetSamplingTargetsWithMalformed() + { + TestClock clock = new TestClock(); + this.mockServer + .Given(Request.Create().WithPath("/SamplingTargets").UsingPost()) + .RespondWith( + Response.Create().WithStatusCode(200).WithHeader("Content-Type", "application/json").WithBody("notJson")); + + var request = new GetSamplingTargetsRequest(new List() + { + new SamplingStatisticsDocument( + "clientId", + "rule1", + 100, + 50, + 10, + clock.ToDouble(clock.Now())), + }); + + var responseTask = this.client.GetSamplingTargets(request); + responseTask.Wait(); + + GetSamplingTargetsResponse targetsResponse = responseTask.Result; + + Assert.Null(targetsResponse); + } + private void CreateResponse(string endpoint, string filePath) { string mockResponse = File.ReadAllText(filePath); diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/TestClock.cs b/test/OpenTelemetry.Sampler.AWS.Tests/TestClock.cs index a5be1d33df..2de7500de5 100644 --- a/test/OpenTelemetry.Sampler.AWS.Tests/TestClock.cs +++ b/test/OpenTelemetry.Sampler.AWS.Tests/TestClock.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -24,6 +25,7 @@ namespace OpenTelemetry.Sampler.AWS.Tests; internal class TestClock : Clock { + private static readonly DateTime EpochStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private DateTime nowTime; public TestClock() @@ -41,19 +43,21 @@ public override DateTime Now() return this.nowTime; } - public override long NowInSeconds() + public override long NowInMilliSeconds() { - throw new NotImplementedException(); + return (long)this.nowTime.ToUniversalTime().Subtract(EpochStart).TotalMilliseconds; } public override DateTime ToDateTime(double seconds) { - throw new NotImplementedException(); + return EpochStart.AddSeconds(seconds); } public override double ToDouble(DateTime dateTime) { - throw new NotImplementedException(); + TimeSpan current = new TimeSpan(dateTime.ToUniversalTime().Ticks - EpochStart.Ticks); + double timestamp = Math.Round(current.TotalMilliseconds, 0) / 1000.0; + return timestamp; } // Advnaces the clock by a given time span. diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/TestRateLimiter.cs b/test/OpenTelemetry.Sampler.AWS.Tests/TestRateLimiter.cs new file mode 100644 index 0000000000..4ac5fb4aa9 --- /dev/null +++ b/test/OpenTelemetry.Sampler.AWS.Tests/TestRateLimiter.cs @@ -0,0 +1,163 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace OpenTelemetry.Sampler.AWS.Tests; + +/// +/// This class is a .Net port of the original Java implementation. +/// This class was taken from Jaeger java client. +/// https://github.com/jaegertracing/jaeger-client-java/blob/master/jaeger-core/src/test/java/io/jaegertracing/internal/utils/RateLimiterTest.java +/// +public class TestRateLimiter +{ + [Fact] + public void TestRateLimiterWholeNumber() + { + var testClock = new TestClock(); + RateLimiter limiter = new RateLimiter(2.0, 2.0, testClock); + + Assert.True(limiter.TrySpend(1.0)); + Assert.True(limiter.TrySpend(1.0)); + Assert.False(limiter.TrySpend(1.0)); + + // move time 250ms forward, not enough credits to pay for 1.0 item + testClock.Advance(TimeSpan.FromMilliseconds(250)); + Assert.False(limiter.TrySpend(1.0)); + + // move time 500ms forward, now enough credits to pay for 1.0 item + testClock.Advance(TimeSpan.FromMilliseconds(500)); + Assert.True(limiter.TrySpend(1.0)); + Assert.False(limiter.TrySpend(1.0)); + + // move time 5s forward, enough to accumulate credits for 10 messages, but it should still be + // capped at 2 + testClock.Advance(TimeSpan.FromSeconds(5)); + Assert.True(limiter.TrySpend(1.0)); + Assert.True(limiter.TrySpend(1.0)); + Assert.False(limiter.TrySpend(1.0)); + Assert.False(limiter.TrySpend(1.0)); + Assert.False(limiter.TrySpend(1.0)); + } + + [Fact] + public void TestRateLimiterLessThanOne() + { + TestClock clock = new TestClock(); + RateLimiter limiter = new RateLimiter(0.5, 0.5, clock); + + Assert.True(limiter.TrySpend(0.25)); + Assert.True(limiter.TrySpend(0.25)); + Assert.False(limiter.TrySpend(0.25)); + + // move time 250ms forward, not enough credits to pay for 0.25 item + clock.Advance(TimeSpan.FromMilliseconds(250)); + Assert.False(limiter.TrySpend(0.25)); + + // move clock 500ms forward, enough credits for 0.25 item + clock.Advance(TimeSpan.FromMilliseconds(500)); + Assert.True(limiter.TrySpend(0.25)); + + // move time 5s forward, enough to accumulate credits for 2.5 messages, but it should still be + // capped at 0.5 + clock.Advance(TimeSpan.FromSeconds(5)); + Assert.True(limiter.TrySpend(0.25)); + Assert.True(limiter.TrySpend(0.25)); + Assert.False(limiter.TrySpend(0.25)); + Assert.False(limiter.TrySpend(0.25)); + Assert.False(limiter.TrySpend(0.25)); + } + + [Fact] + public void TestRateLimiterMaxBalance() + { + TestClock clock = new TestClock(); + RateLimiter limiter = new RateLimiter(0.1, 1.0, clock); + + clock.Advance(TimeSpan.FromMilliseconds(0.1)); + Assert.True(limiter.TrySpend(1.0)); + Assert.False(limiter.TrySpend(1.0)); + + // move time 20s forward, enough to accumulate credits for 2 messages, but it should still be + // capped at 1 + clock.Advance(TimeSpan.FromSeconds(20)); + + Assert.True(limiter.TrySpend(1.0)); + Assert.False(limiter.TrySpend(1.0)); + } + + [Fact] + public void TestRateLimiterInitial() + { + TestClock clock = new TestClock(); + RateLimiter limiter = new RateLimiter(1000, 100, clock); + + Assert.True(limiter.TrySpend(100)); // consume initial (max) balance + Assert.False(limiter.TrySpend(1)); + + clock.Advance(TimeSpan.FromMilliseconds(49)); // add 49 credits + Assert.False(limiter.TrySpend(50)); + + clock.Advance(TimeSpan.FromMilliseconds(1)); // add 1 credit + Assert.True(limiter.TrySpend(50)); // consume accrued balance + Assert.False(limiter.TrySpend(1)); + + clock.Advance(TimeSpan.FromSeconds(1000)); // add a lot of credits (max out balance) + Assert.True(limiter.TrySpend(1)); // take 1 credit + + clock.Advance(TimeSpan.FromSeconds(1000)); // add a lot of credits (max out balance) + Assert.False(limiter.TrySpend(101)); // can't consume more than max balance + Assert.True(limiter.TrySpend(100)); // consume max balance + Assert.False(limiter.TrySpend(1)); + } + + [Fact] + public async Task TestRateLimiterConcurrencyAsync() + { + int numWorkers = 8; + int creditsPerWorker = 1000; + TestClock clock = new TestClock(); + RateLimiter limiter = new RateLimiter(1, numWorkers * creditsPerWorker, clock); + int count = 0; + List tasks = new List(numWorkers); + + for (int w = 0; w < numWorkers; ++w) + { + Task task = Task.Run(() => + { + for (int i = 0; i < creditsPerWorker * 2; ++i) + { + if (limiter.TrySpend(1)) + { + Interlocked.Increment(ref count); // count allowed operations + } + } + }); + + tasks.Add(task); + } + + await Task.WhenAll(tasks); + + Assert.Equal(numWorkers * creditsPerWorker, count); + Assert.False(limiter.TrySpend(1)); + } +} diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/TestRateLimitingSampler.cs b/test/OpenTelemetry.Sampler.AWS.Tests/TestRateLimitingSampler.cs new file mode 100644 index 0000000000..9335667956 --- /dev/null +++ b/test/OpenTelemetry.Sampler.AWS.Tests/TestRateLimitingSampler.cs @@ -0,0 +1,44 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using OpenTelemetry.Trace; +using Xunit; + +namespace OpenTelemetry.Sampler.AWS.Tests; + +public class TestRateLimitingSampler +{ + [Fact] + public void TestLimitsRate() + { + TestClock clock = new TestClock(); + Trace.Sampler sampler = new RateLimitingSampler(1, clock); + + Assert.Equal(SamplingDecision.RecordAndSample, sampler.ShouldSample(Utils.CreateSamplingParameters()).Decision); + + // balance used up + Assert.Equal(SamplingDecision.Drop, sampler.ShouldSample(Utils.CreateSamplingParameters()).Decision); + + // balance restore after 1 second, not yet + clock.Advance(TimeSpan.FromMilliseconds(100)); + Assert.Equal(SamplingDecision.Drop, sampler.ShouldSample(Utils.CreateSamplingParameters()).Decision); + + // balance restored + clock.Advance(TimeSpan.FromMilliseconds(900)); + Assert.Equal(SamplingDecision.RecordAndSample, sampler.ShouldSample(Utils.CreateSamplingParameters()).Decision); + } +} diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/TestRulesCache.cs b/test/OpenTelemetry.Sampler.AWS.Tests/TestRulesCache.cs index 57391ce932..eafc974c00 100644 --- a/test/OpenTelemetry.Sampler.AWS.Tests/TestRulesCache.cs +++ b/test/OpenTelemetry.Sampler.AWS.Tests/TestRulesCache.cs @@ -97,7 +97,137 @@ public void TestUpdateRulesRemovesOlderRule() Assert.Equal("Default", rulesCache.RuleAppliers[0].RuleName); } - // TODO: Add tests for matching sampling rules once the reservoir and fixed rate samplers are added. + [Fact] + public void TestShouldSampleMatchesExactRule() + { + var clock = new TestClock(); + var rulesCache = new RulesCache(clock, "clientId", ResourceBuilder.CreateEmpty().Build(), new AlwaysOffSampler()) + { + RuleAppliers = new List() + { + { new SamplingRuleApplier("clientId", clock, this.CreateRule("ruleWillMatch", 1, 0.0, 1), new Statistics()) }, // higher priority rule will sample + { new SamplingRuleApplier("clientId", clock, this.CreateRule("ruleWillNotMatch", 0, 0.0, 2), new Statistics()) }, // this rule will not sample + }, + }; + + // the rule will sample by borrowing from reservoir + Assert.Equal(SamplingDecision.RecordAndSample, rulesCache.ShouldSample(default).Decision); + + var statistics = rulesCache.Snapshot(clock.Now()); + Assert.Equal(2, statistics.Count); + Assert.Equal("ruleWillMatch", statistics[0].RuleName); + Assert.Equal(1, statistics[0].RequestCount); + Assert.Equal(1, statistics[0].SampledCount); + Assert.Equal(1, statistics[0].BorrowCount); + Assert.Equal("ruleWillNotMatch", statistics[1].RuleName); + Assert.Equal(0, statistics[1].RequestCount); + Assert.Equal(0, statistics[1].SampledCount); + Assert.Equal(0, statistics[1].BorrowCount); + } + + [Fact] + public void TestFallbackSamplerMatchesWhenNoRules() + { + var clock = new TestClock(); + var rulesCache = new RulesCache(clock, "clientId", ResourceBuilder.CreateEmpty().Build(), new AlwaysOffSampler()) + { + RuleAppliers = new List(), + }; + + // the fallback sampler will not sample + Assert.Equal(SamplingDecision.Drop, rulesCache.ShouldSample(default).Decision); + } + + [Fact] + public void TestUpdateTargets() + { + var clock = new TestClock(); + var rulesCache = new RulesCache(clock, "clientId", ResourceBuilder.CreateEmpty().Build(), new AlwaysOffSampler()) + { + RuleAppliers = new List() + { + { new SamplingRuleApplier("clientId", clock, this.CreateRule("rule1", 1, 0.0, 1), new Statistics()) }, // this rule will sample 1 req/sec + { new SamplingRuleApplier("clientId", clock, this.CreateRule("rule2", 0, 0.0, 2), new Statistics()) }, + }, + }; + + Assert.Equal(SamplingDecision.RecordAndSample, rulesCache.ShouldSample(default).Decision); + Assert.Equal(SamplingDecision.Drop, rulesCache.ShouldSample(default).Decision); + + // update targets + var targetForRule1 = new SamplingTargetDocument() + { + FixedRate = 0.0, + Interval = 0, + ReservoirQuota = 2, + ReservoirQuotaTTL = clock.ToDouble(clock.Now().AddMinutes(5)), + RuleName = "rule1", + }; + var targetForRule2 = new SamplingTargetDocument() + { + FixedRate = 0.0, + Interval = 0, + ReservoirQuota = 0, + ReservoirQuotaTTL = clock.ToDouble(clock.Now().AddMinutes(5)), + RuleName = "rule2", + }; + + var targets = new Dictionary() + { + { "rule1", targetForRule1 }, + { "rule2", targetForRule2 }, + }; + + rulesCache.UpdateTargets(targets); + + // now rule1 will sample 2 req/sec + Assert.Equal(SamplingDecision.RecordAndSample, rulesCache.ShouldSample(default).Decision); + Assert.Equal(SamplingDecision.RecordAndSample, rulesCache.ShouldSample(default).Decision); + Assert.Equal(SamplingDecision.Drop, rulesCache.ShouldSample(default).Decision); + } + + [Fact] + public void TestNextTargetFetchTime() + { + var clock = new TestClock(); + var rulesCache = new RulesCache(clock, "clientId", ResourceBuilder.CreateEmpty().Build(), new AlwaysOffSampler()) + { + RuleAppliers = new List() + { + { new SamplingRuleApplier("clientId", clock, this.CreateRule("rule1", 1, 0.0, 1), new Statistics()) }, + { new SamplingRuleApplier("clientId", clock, this.CreateRule("rule2", 0, 0.0, 2), new Statistics()) }, + }, + }; + + // update targets + var targetForRule1 = new SamplingTargetDocument() + { + FixedRate = 0.0, + Interval = 10, // next target poll after 10s + ReservoirQuota = 2, + ReservoirQuotaTTL = clock.ToDouble(clock.Now().Add(TimeSpan.FromMinutes(5))), + RuleName = "rule1", + }; + var targetForRule2 = new SamplingTargetDocument() + { + FixedRate = 0.0, + Interval = 5, // next target poll after 5s + ReservoirQuota = 0, + ReservoirQuotaTTL = clock.ToDouble(clock.Now().Add(TimeSpan.FromMinutes(5))), + RuleName = "rule2", + }; + + var targets = new Dictionary() + { + { "rule1", targetForRule1 }, + { "rule2", targetForRule2 }, + }; + + rulesCache.UpdateTargets(targets); + + // next target will be fetched after 5s + Assert.Equal(clock.Now().AddSeconds(5), rulesCache.NextTargetFetchTime()); + } private SamplingRule CreateDefaultRule(int reservoirSize, double fixedRate) { diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/TestSamplingRuleApplier.cs b/test/OpenTelemetry.Sampler.AWS.Tests/TestSamplingRuleApplier.cs index b62fc93c41..34ae66df43 100644 --- a/test/OpenTelemetry.Sampler.AWS.Tests/TestSamplingRuleApplier.cs +++ b/test/OpenTelemetry.Sampler.AWS.Tests/TestSamplingRuleApplier.cs @@ -14,7 +14,9 @@ // limitations under the License. // +using System; using System.Collections.Generic; +using OpenTelemetry.Trace; using Xunit; namespace OpenTelemetry.Sampler.AWS.Tests; @@ -217,5 +219,207 @@ public void TestAttributeMatchingWithLessActivityTags() Assert.False(applier.Matches(Utils.CreateSamplingParametersWithTags(activityTags), Utils.CreateResource("myServiceName", "aws_ecs"))); } - // TODO: Add more test cases for ShouldSample once the sampling logic is added. + // fixed rate is 1.0 and reservoir is 0 + [Fact] + public void TestFixedRateAlwaysSample() + { + TestClock clock = new TestClock(); + SamplingRule rule = new SamplingRule( + "rule1", + 1, + 1.0, // fixed rate + 0, // reservoir + "*", + "*", + "*", + "*", + "*", + "*", + 1, + new Dictionary()); + + var applier = new SamplingRuleApplier("clientId", clock, rule, new Statistics()); + + Assert.Equal(SamplingDecision.RecordAndSample, applier.ShouldSample(default).Decision); + + // test if the snapshot was correctly captured + var statistics = applier.Snapshot(clock.Now()); + Assert.Equal("clientId", statistics.ClientID); + Assert.Equal("rule1", statistics.RuleName); + Assert.Equal(clock.ToDouble(clock.Now()), statistics.Timestamp); + Assert.Equal(1, statistics.RequestCount); + Assert.Equal(1, statistics.SampledCount); + Assert.Equal(0, statistics.BorrowCount); + + // reset statistics + statistics = applier.Snapshot(clock.Now()); + Assert.Equal(0, statistics.RequestCount); + Assert.Equal(0, statistics.SampledCount); + Assert.Equal(0, statistics.BorrowCount); + } + + // fixed rate is 0.0 and reservoir is 0 + [Fact] + public void TestFixedRateNeverSample() + { + TestClock clock = new TestClock(); + SamplingRule rule = new SamplingRule( + "rule1", + 1, + 0.0, // fixed rate + 0, // reservoir + "*", + "*", + "*", + "*", + "*", + "*", + 1, + new Dictionary()); + + var applier = new SamplingRuleApplier("clientId", clock, rule, new Statistics()); + + Assert.Equal(SamplingDecision.Drop, applier.ShouldSample(default).Decision); + + // test if the snapshot was correctly captured + var statistics = applier.Snapshot(clock.Now()); + Assert.Equal("clientId", statistics.ClientID); + Assert.Equal("rule1", statistics.RuleName); + Assert.Equal(clock.ToDouble(clock.Now()), statistics.Timestamp); + Assert.Equal(1, statistics.RequestCount); + Assert.Equal(0, statistics.SampledCount); + Assert.Equal(0, statistics.BorrowCount); + } + + [Fact] + public void TestBorrowFromReservoir() + { + TestClock clock = new TestClock(); + SamplingRule rule = new SamplingRule( + "rule1", + 1, + 0.0, // fixed rate + 100, // reservoir + "*", + "*", + "*", + "*", + "*", + "*", + 1, + new Dictionary()); + + var applier = new SamplingRuleApplier("clientId", clock, rule, new Statistics()); + + // sampled by borrowing + Assert.Equal(SamplingDecision.RecordAndSample, applier.ShouldSample(default).Decision); + + // can only borrow 1 req/sec + Assert.Equal(SamplingDecision.Drop, applier.ShouldSample(default).Decision); + + // test if the snapshot was correctly captured + var statistics = applier.Snapshot(clock.Now()); + Assert.Equal("clientId", statistics.ClientID); + Assert.Equal("rule1", statistics.RuleName); + Assert.Equal(clock.ToDouble(clock.Now()), statistics.Timestamp); + Assert.Equal(2, statistics.RequestCount); + Assert.Equal(1, statistics.SampledCount); + Assert.Equal(1, statistics.BorrowCount); + } + + [Fact] + public void TestWithTarget() + { + TestClock clock = new TestClock(); + SamplingRule rule = new SamplingRule( + "rule1", + 1, + 0.0, // fixed rate + 100, // reservoir + "*", + "*", + "*", + "*", + "*", + "*", + 1, + new Dictionary()); + + var applier = new SamplingRuleApplier("clientId", clock, rule, new Statistics()); + + // no target assigned yet. so borrow 1 from reseroir every second + Assert.Equal(SamplingDecision.RecordAndSample, applier.ShouldSample(default).Decision); + Assert.Equal(SamplingDecision.Drop, applier.ShouldSample(default).Decision); + clock.Advance(TimeSpan.FromSeconds(1)); + Assert.Equal(SamplingDecision.RecordAndSample, applier.ShouldSample(default).Decision); + Assert.Equal(SamplingDecision.Drop, applier.ShouldSample(default).Decision); + + // get the target + SamplingTargetDocument target = new SamplingTargetDocument() + { + FixedRate = 0.0, + Interval = 10, + ReservoirQuota = 2, + ReservoirQuotaTTL = clock.ToDouble(clock.Now().Add(TimeSpan.FromSeconds(10))), + RuleName = "rule1", + }; + + applier = applier.WithTarget(target, clock.Now()); + + // 2 req/sec quota + Assert.Equal(SamplingDecision.RecordAndSample, applier.ShouldSample(default).Decision); + Assert.Equal(SamplingDecision.RecordAndSample, applier.ShouldSample(default).Decision); + Assert.Equal(SamplingDecision.Drop, applier.ShouldSample(default).Decision); + } + + [Fact] + public void TestWithTargetWithoutQuota() + { + TestClock clock = new TestClock(); + SamplingRule rule = new SamplingRule( + "rule1", + 1, + 0.0, // fixed rate + 100, // reservoir + "*", + "*", + "*", + "*", + "*", + "*", + 1, + new Dictionary()); + + var applier = new SamplingRuleApplier("clientId", clock, rule, new Statistics()); + + // no target assigned yet. so borrow 1 from reseroir every second + Assert.Equal(SamplingDecision.RecordAndSample, applier.ShouldSample(default).Decision); + Assert.Equal(SamplingDecision.Drop, applier.ShouldSample(default).Decision); + clock.Advance(TimeSpan.FromSeconds(1)); + Assert.Equal(SamplingDecision.RecordAndSample, applier.ShouldSample(default).Decision); + Assert.Equal(SamplingDecision.Drop, applier.ShouldSample(default).Decision); + + var statistics = applier.Snapshot(clock.Now()); + Assert.Equal(4, statistics.RequestCount); + Assert.Equal(2, statistics.SampledCount); + Assert.Equal(2, statistics.BorrowCount); + + // get the target + SamplingTargetDocument target = new SamplingTargetDocument() + { + FixedRate = 1.0, + Interval = 10, + ReservoirQuota = null, + ReservoirQuotaTTL = null, + RuleName = "rule1", + }; + applier = applier.WithTarget(target, clock.Now()); + + // no reservoir, sample using fixed rate (100% sample) + Assert.Equal(SamplingDecision.RecordAndSample, applier.ShouldSample(default).Decision); + statistics = applier.Snapshot(clock.Now()); + Assert.Equal(1, statistics.RequestCount); + Assert.Equal(1, statistics.SampledCount); + Assert.Equal(0, statistics.BorrowCount); + } } diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/Utils.cs b/test/OpenTelemetry.Sampler.AWS.Tests/Utils.cs index d5a1babd46..b415200872 100644 --- a/test/OpenTelemetry.Sampler.AWS.Tests/Utils.cs +++ b/test/OpenTelemetry.Sampler.AWS.Tests/Utils.cs @@ -23,6 +23,22 @@ namespace OpenTelemetry.Sampler.AWS.Tests; internal static class Utils { + internal static SamplingParameters CreateSamplingParameters() + { + return CreateSamplingParametersWithTags(new Dictionary()); + } + + internal static SamplingParameters CreateSamplingParametersWithRootContext() + { + return new SamplingParameters( + default, + ActivityTraceId.CreateRandom(), + "myActivityName", + ActivityKind.Server, + null, + null); + } + internal static SamplingParameters CreateSamplingParametersWithTags(Dictionary tags) { ActivityTraceId traceId = ActivityTraceId.CreateRandom(); From 9fab3ed0b212b092c3b4dfafb748a35516dcbc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Kie=C5=82kowicz?= Date: Wed, 31 May 2023 00:14:54 +0200 Subject: [PATCH 02/12] [ResourceDetectors.AWS] Release as .NET 6 instead of NET Standard 2.0 (#1177) --- .../PublicAPI.Shipped.txt | 0 .../PublicAPI.Unshipped.txt | 0 .../AWSECSResourceDetector.cs | 6 +- .../AWSEKSResourceDetector.cs | 9 ++- .../Http/Handler.cs | 14 ++-- .../ServerCertificateValidationProvider.cs | 55 +++++++------- ...OpenTelemetry.ResourceDetectors.AWS.csproj | 12 +-- ...erverCertificateValidationProviderTests.cs | 76 +++++++++++-------- ...lemetry.ResourceDetectors.AWS.Tests.csproj | 1 + 9 files changed, 92 insertions(+), 81 deletions(-) rename src/OpenTelemetry.ResourceDetectors.AWS/.publicApi/{netstandard2.0 => net6.0}/PublicAPI.Shipped.txt (100%) rename src/OpenTelemetry.ResourceDetectors.AWS/.publicApi/{netstandard2.0 => net6.0}/PublicAPI.Unshipped.txt (100%) diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry.ResourceDetectors.AWS/.publicApi/net6.0/PublicAPI.Shipped.txt similarity index 100% rename from src/OpenTelemetry.ResourceDetectors.AWS/.publicApi/netstandard2.0/PublicAPI.Shipped.txt rename to src/OpenTelemetry.ResourceDetectors.AWS/.publicApi/net6.0/PublicAPI.Shipped.txt diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.ResourceDetectors.AWS/.publicApi/net6.0/PublicAPI.Unshipped.txt similarity index 100% rename from src/OpenTelemetry.ResourceDetectors.AWS/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt rename to src/OpenTelemetry.ResourceDetectors.AWS/.publicApi/net6.0/PublicAPI.Unshipped.txt diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSECSResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSECSResourceDetector.cs index b7de0f6e82..5e59597d3e 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSECSResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSECSResourceDetector.cs @@ -14,6 +14,7 @@ // limitations under the License. // +#if !NETFRAMEWORK using System; using System.Collections.Generic; using System.Net.Http; @@ -195,8 +196,8 @@ internal static List> ExtractMetadataV4ResourceAttr { while (!streamReader.EndOfStream) { - var trimmedLine = streamReader.ReadLine().Trim(); - if (trimmedLine.Length > 64) + var trimmedLine = streamReader.ReadLine()?.Trim(); + if (trimmedLine?.Length > 64) { containerId = trimmedLine.Substring(trimmedLine.Length - 64); return containerId; @@ -212,3 +213,4 @@ internal static bool IsECSProcess() return Environment.GetEnvironmentVariable(AWSECSMetadataURLKey) != null || Environment.GetEnvironmentVariable(AWSECSMetadataURLV4Key) != null; } } +#endif diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs index 4f4faf4e77..81e3d6ba4c 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#if !NETFRAMEWORK + using System; using System.Collections.Generic; using System.Net.Http; @@ -85,7 +87,7 @@ internal static List> ExtractResourceAttributes(str { while (!streamReader.EndOfStream) { - stringBuilder.Append(streamReader.ReadLine().Trim()); + stringBuilder.Append(streamReader.ReadLine()?.Trim()); } } @@ -109,8 +111,8 @@ internal static List> ExtractResourceAttributes(str { while (!streamReader.EndOfStream) { - var trimmedLine = streamReader.ReadLine().Trim(); - if (trimmedLine.Length > 64) + var trimmedLine = streamReader.ReadLine()?.Trim(); + if (trimmedLine?.Length > 64) { return trimmedLine.Substring(trimmedLine.Length - 64); } @@ -165,3 +167,4 @@ private static string GetEKSClusterInfo(string credentials, HttpClientHandler? h return ResourceDetectorUtils.SendOutRequest(AWSClusterInfoUrl, "GET", new KeyValuePair("Authorization", credentials), httpClientHandler).Result; } } +#endif diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/Http/Handler.cs b/src/OpenTelemetry.ResourceDetectors.AWS/Http/Handler.cs index 27b89b2e2b..0b2b1cf262 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/Http/Handler.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/Http/Handler.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#if !NETFRAMEWORK + using System; using System.Net.Http; @@ -25,24 +27,19 @@ internal class Handler { try { - ServerCertificateValidationProvider serverCertificateValidationProvider = + ServerCertificateValidationProvider? serverCertificateValidationProvider = ServerCertificateValidationProvider.FromCertificateFile(certificateFile); - if (!serverCertificateValidationProvider.IsCertificateLoaded ?? false) + if (serverCertificateValidationProvider == null) { AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(Handler), "Failed to Load the certificate file into trusted collection"); return null; } - if (serverCertificateValidationProvider.ValidationCallback == null) - { - return null; - } - var clientHandler = new HttpClientHandler(); clientHandler.ServerCertificateCustomValidationCallback = (sender, x509Certificate2, x509Chain, sslPolicyErrors) => - serverCertificateValidationProvider.ValidationCallback(null, x509Certificate2, x509Chain, sslPolicyErrors); + serverCertificateValidationProvider.ValidationCallback(sender, x509Certificate2, x509Chain, sslPolicyErrors); return clientHandler; } catch (Exception ex) @@ -53,3 +50,4 @@ internal class Handler return null; } } +#endif diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/Http/ServerCertificateValidationProvider.cs b/src/OpenTelemetry.ResourceDetectors.AWS/Http/ServerCertificateValidationProvider.cs index 5f26f4f064..535e67efda 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/Http/ServerCertificateValidationProvider.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/Http/ServerCertificateValidationProvider.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#if !NETFRAMEWORK + using System; using System.IO; using System.Linq; @@ -24,43 +26,30 @@ namespace OpenTelemetry.ResourceDetectors.AWS.Http; internal class ServerCertificateValidationProvider { - private static readonly ServerCertificateValidationProvider InvalidProvider = new(null); - - private readonly X509Certificate2Collection? trustedCertificates; + private readonly X509Certificate2Collection trustedCertificates; - private ServerCertificateValidationProvider(X509Certificate2Collection? trustedCertificates) + private ServerCertificateValidationProvider(X509Certificate2Collection trustedCertificates) { - if (trustedCertificates == null) - { - this.trustedCertificates = null; - this.ValidationCallback = null; - this.IsCertificateLoaded = false; - return; - } - this.trustedCertificates = trustedCertificates; - this.ValidationCallback = (sender, cert, chain, errors) => - this.ValidateCertificate(new X509Certificate2(cert), chain, errors); - this.IsCertificateLoaded = true; + this.ValidationCallback = (_, cert, chain, errors) => + this.ValidateCertificate(cert != null ? new X509Certificate2(cert) : null, chain, errors); } - public bool? IsCertificateLoaded { get; } - - public RemoteCertificateValidationCallback? ValidationCallback { get; } + public RemoteCertificateValidationCallback ValidationCallback { get; } - public static ServerCertificateValidationProvider FromCertificateFile(string certificateFile) + public static ServerCertificateValidationProvider? FromCertificateFile(string certificateFile) { if (!File.Exists(certificateFile)) { AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate File does not exist"); - return InvalidProvider; + return null; } var trustedCertificates = new X509Certificate2Collection(); if (!LoadCertificateToTrustedCollection(trustedCertificates, certificateFile)) { AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to load certificate in trusted collection"); - return InvalidProvider; + return null; } return new ServerCertificateValidationProvider(trustedCertificates); @@ -90,7 +79,7 @@ private static bool HasCommonCertificate(X509Chain chain, X509Certificate2Collec { foreach (var certificate in collection) { - if (Enumerable.SequenceEqual(chainElement.Certificate.GetPublicKey(), certificate.GetPublicKey())) + if (chainElement.Certificate.GetPublicKey().SequenceEqual(certificate.GetPublicKey())) { return true; } @@ -100,7 +89,7 @@ private static bool HasCommonCertificate(X509Chain chain, X509Certificate2Collec return false; } - private bool ValidateCertificate(X509Certificate2 cert, X509Chain chain, SslPolicyErrors errors) + private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPolicyErrors errors) { var isSslPolicyPassed = errors == SslPolicyErrors.None || errors == SslPolicyErrors.RemoteCertificateChainErrors; @@ -117,6 +106,18 @@ private bool ValidateCertificate(X509Certificate2 cert, X509Chain chain, SslPoli } } + if (chain == null) + { + AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate chain is null."); + return false; + } + + if (cert == null) + { + AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate is null."); + return false; + } + chain.ChainPolicy.ExtraStore.AddRange(this.trustedCertificates); chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; @@ -149,12 +150,9 @@ private bool ValidateCertificate(X509Certificate2 cert, X509Chain chain, SslPoli } var trustCertificates = string.Empty; - if (this.trustedCertificates != null) + foreach (var trustCertificate in this.trustedCertificates) { - foreach (var trustCertificate in this.trustedCertificates) - { - trustCertificates += " " + trustCertificate.Subject; - } + trustCertificates += " " + trustCertificate.Subject; } AWSResourcesEventSource.Log.FailedToValidateCertificate( @@ -165,3 +163,4 @@ private bool ValidateCertificate(X509Certificate2 cert, X509Chain chain, SslPoli return isSslPolicyPassed && isValidChain && isTrusted; } } +#endif diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj b/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj index a4ec9726b9..c2278435cf 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj +++ b/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj @@ -2,7 +2,7 @@ - netstandard2.0 + net6.0 $(TargetFrameworks);$(NetFrameworkMinimumSupportedVersion) OpenTelemetry Extensions - AWS Resource Detectors for ElasticBeanstalk, EC2, ECS, EKS. ResourceDetectors.AWS- @@ -12,17 +12,9 @@ - - - - - - - - - + diff --git a/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/ServerCertificateValidationProviderTests.cs b/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/ServerCertificateValidationProviderTests.cs index 7f69902b6c..04da229c89 100644 --- a/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/ServerCertificateValidationProviderTests.cs +++ b/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/ServerCertificateValidationProviderTests.cs @@ -16,8 +16,8 @@ #if !NETFRAMEWORK -using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; +using Moq; using OpenTelemetry.ResourceDetectors.AWS.Http; using Xunit; @@ -25,46 +25,62 @@ namespace OpenTelemetry.ResourceDetectors.AWS.Tests.Http; public class ServerCertificateValidationProviderTests { - private const string INVALIDCRTNAME = "invalidcert"; + private const string InvalidCertificateName = "invalidcert"; [Fact] public void TestValidCertificate() { - // This test fails on Linux in netcoreapp3.1, but passes in net6.0 and net7.0. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - using (CertificateUploader certificateUploader = new CertificateUploader()) - { - certificateUploader.Create(); - - // Loads the certificate to the trusted collection from the file - ServerCertificateValidationProvider serverCertificateValidationProvider = - ServerCertificateValidationProvider.FromCertificateFile(certificateUploader.FilePath); - - // Validates if the certificate loaded into the trusted collection. - Assert.True(serverCertificateValidationProvider.IsCertificateLoaded); - - var certificate = new X509Certificate2(certificateUploader.FilePath); - X509Chain chain = new X509Chain(); - chain.Build(certificate); - - // validates if certificate is valid - Assert.NotNull(serverCertificateValidationProvider); - Assert.NotNull(serverCertificateValidationProvider.ValidationCallback); - Assert.True(serverCertificateValidationProvider.ValidationCallback(null, certificate, chain, System.Net.Security.SslPolicyErrors.None)); - } - } + using CertificateUploader certificateUploader = new CertificateUploader(); + certificateUploader.Create(); + + ServerCertificateValidationProvider serverCertificateValidationProvider = + ServerCertificateValidationProvider.FromCertificateFile(certificateUploader.FilePath); + + Assert.NotNull(serverCertificateValidationProvider); + + var certificate = new X509Certificate2(certificateUploader.FilePath); + X509Chain chain = new X509Chain(); + chain.Build(certificate); + + // validates if certificate is valid + Assert.NotNull(serverCertificateValidationProvider); + Assert.NotNull(serverCertificateValidationProvider.ValidationCallback); + Assert.True(serverCertificateValidationProvider.ValidationCallback(this, certificate, chain, System.Net.Security.SslPolicyErrors.None)); } [Fact] public void TestInValidCertificate() { - // Loads the certificate to the trusted collection from the file ServerCertificateValidationProvider serverCertificateValidationProvider = - ServerCertificateValidationProvider.FromCertificateFile(INVALIDCRTNAME); + ServerCertificateValidationProvider.FromCertificateFile(InvalidCertificateName); + + Assert.Null(serverCertificateValidationProvider); + } + + [Fact] + public void TestTestCallbackWithNullCertificate() + { + using var certificateUploader = new CertificateUploader(); + certificateUploader.Create(); + + ServerCertificateValidationProvider serverCertificateValidationProvider = + ServerCertificateValidationProvider.FromCertificateFile(certificateUploader.FilePath); + + Assert.NotNull(serverCertificateValidationProvider); + Assert.False(serverCertificateValidationProvider.ValidationCallback(this, null, Mock.Of(), default)); + } + + [Fact] + public void TestCallbackWithNullChain() + { + using var certificateUploader = new CertificateUploader(); + certificateUploader.Create(); + + ServerCertificateValidationProvider serverCertificateValidationProvider = + ServerCertificateValidationProvider.FromCertificateFile(certificateUploader.FilePath); - // Validates if the certificate file loaded. - Assert.False(serverCertificateValidationProvider.IsCertificateLoaded); + Assert.NotNull(serverCertificateValidationProvider); + Assert.False(serverCertificateValidationProvider.ValidationCallback(this, Mock.Of(), null, default)); } } diff --git a/test/OpenTelemetry.ResourceDetectors.AWS.Tests/OpenTelemetry.ResourceDetectors.AWS.Tests.csproj b/test/OpenTelemetry.ResourceDetectors.AWS.Tests/OpenTelemetry.ResourceDetectors.AWS.Tests.csproj index 061f4214ac..12b3750bb0 100644 --- a/test/OpenTelemetry.ResourceDetectors.AWS.Tests/OpenTelemetry.ResourceDetectors.AWS.Tests.csproj +++ b/test/OpenTelemetry.ResourceDetectors.AWS.Tests/OpenTelemetry.ResourceDetectors.AWS.Tests.csproj @@ -10,6 +10,7 @@ + all From 81364e6cb3e5e1ec7301ecf0b73b3c465780475f Mon Sep 17 00:00:00 2001 From: Cameron Taggart Date: Wed, 31 May 2023 12:17:15 +1000 Subject: [PATCH 03/12] add support for abstract domain sockets in OpenTelemetry.Exporter.Geneva (#1199) --- .../CHANGELOG.md | 2 ++ .../Internal/ConnectionStringBuilder.cs | 19 ++++++++++++++++++- .../ConnectionStringBuilderTests.cs | 5 +++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index 0647e6f08a..29c213c717 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -5,6 +5,8 @@ * TldLogExporter to export `SpanId` value in `ext_dt_spanId` field instead of `TraceId` value. ([#1184](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1184)) +* Add support for abstract domain sockets. + ([#1199](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1199)) ## 1.5.0-alpha.3 diff --git a/src/OpenTelemetry.Exporter.Geneva/Internal/ConnectionStringBuilder.cs b/src/OpenTelemetry.Exporter.Geneva/Internal/ConnectionStringBuilder.cs index d34d91f337..695f5496d6 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Internal/ConnectionStringBuilder.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Internal/ConnectionStringBuilder.cs @@ -132,12 +132,29 @@ public TransportProtocol Protocol } } + /// + /// Replace first charater of string if it matches with with . + /// + /// String to be updated. + /// Old character to be replaced. + /// New character to be replaced with. + /// Updated string. + internal static string ReplaceFirstChar(string str, char oldChar, char newChar) + { + if (str.Length > 0 && str[0] == oldChar) + { + return $"{newChar}{str.Substring(1)}"; + } + + return str; + } + public string ParseUnixDomainSocketPath() { try { var endpoint = new Uri(this.Endpoint); - return endpoint.AbsolutePath; + return ReplaceFirstChar(endpoint.AbsolutePath, '@', '\0'); } catch (UriFormatException ex) { diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/ConnectionStringBuilderTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/ConnectionStringBuilderTests.cs index 35473e893e..01a7044a0a 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/ConnectionStringBuilderTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/ConnectionStringBuilderTests.cs @@ -104,6 +104,11 @@ public void ConnectionStringBuilder_Endpoint_UnixDomainSocketPath() builder = new ConnectionStringBuilder("EtwSession=OpenTelemetry"); Assert.Throws(() => _ = builder.ParseUnixDomainSocketPath()); + + builder = new ConnectionStringBuilder("Endpoint=unix:@/var/run/default_fluent.socket"); + Assert.Equal("unix:@/var/run/default_fluent.socket", builder.Endpoint); + Assert.Equal(TransportProtocol.Unix, builder.Protocol); + Assert.Equal("\0/var/run/default_fluent.socket", builder.ParseUnixDomainSocketPath()); } [Fact] From eb2b6b020456217fb2765e2b70732cfa3bf3859d Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Wed, 31 May 2023 10:17:46 -0700 Subject: [PATCH 04/12] [Sampler.AWS] Fix unit test (#1213) --- test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs b/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs index ce59cad264..5e9766163e 100644 --- a/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs +++ b/test/OpenTelemetry.Sampler.AWS.Tests/TestAWSXRayRemoteSampler.cs @@ -74,7 +74,6 @@ public void TestSamplerUpdateAndSample() // the sampler will use fallback sampler until rules are fetched. Assert.Equal(SamplingDecision.RecordAndSample, this.DoSample(sampler, "cat-service")); - Assert.Equal(SamplingDecision.Drop, this.DoSample(sampler, "cat-service")); // GetSamplingRules mock response mockServer From 65179351177e457b8b83be3bdd0b0e9a5751ca11 Mon Sep 17 00:00:00 2001 From: Christopher Warrington Date: Fri, 2 Jun 2023 10:14:20 -0700 Subject: [PATCH 05/12] [Geneva.Logs] Refine CustomFields documentation (#1216) --- src/OpenTelemetry.Exporter.Geneva/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/OpenTelemetry.Exporter.Geneva/README.md b/src/OpenTelemetry.Exporter.Geneva/README.md index 0faf04b64a..ebd5b8dce6 100644 --- a/src/OpenTelemetry.Exporter.Geneva/README.md +++ b/src/OpenTelemetry.Exporter.Geneva/README.md @@ -95,6 +95,9 @@ On Windows the connection string has the format `EtwSession={ETW session}`. A list of fields which should be stored as individual table columns. +* If null, all fields will be stored as individual columns. +* If non-null, only those fields named in the list will be stored as individual columns. + #### `PrepopulatedFields` (optional) This is a collection of fields that will be applied to all the Logs and Traces From 0b903448c32eb5bb19dcbfd112a3cbdc30dd5c52 Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:35:52 -0700 Subject: [PATCH 06/12] [Exporter.Geneva] Update OTel SDK prerelease version (#1210) --- build/Common.props | 2 +- .../CHANGELOG.md | 7 +++ .../GenevaExporterHelperExtensions.cs | 20 ++++----- .../Metrics/GenevaMetricExporterExtensions.cs | 17 +++---- .../MsgPackExporter/MsgPackLogExporter.cs | 3 ++ .../TLDExporter/TldLogExporter.cs | 4 ++ .../GenevaLogExporterTests.cs | 44 +++++++------------ .../GenevaTraceExporterTests.cs | 10 ++--- 8 files changed, 53 insertions(+), 54 deletions(-) diff --git a/build/Common.props b/build/Common.props index 07c8eaae50..56d5b202c8 100644 --- a/build/Common.props +++ b/build/Common.props @@ -35,7 +35,7 @@ [3.3.3] [1.1.1,2.0) [1.4.0,2.0) - [1.5.0-alpha.1] + [1.5.0-rc.1] [2.1.58,3.0) [3.16.0,4.0) [1.2.0-beta.435,2.0) diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index 29c213c717..146061ec97 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -2,12 +2,19 @@ ## Unreleased +* Fix an issue with getting sanitized category name in pass-through table name + mapping cases for `TldLogExporter`. + ([#1175](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1175)) + * TldLogExporter to export `SpanId` value in `ext_dt_spanId` field instead of `TraceId` value. ([#1184](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1184)) * Add support for abstract domain sockets. ([#1199](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1199)) +* Update OTel SDK version to `1.5.0-rc.1`. + ([#1210](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1210)) + ## 1.5.0-alpha.3 Released 2023-Apr-19 diff --git a/src/OpenTelemetry.Exporter.Geneva/GenevaExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.Geneva/GenevaExporterHelperExtensions.cs index 50de95692b..266c1b11e4 100644 --- a/src/OpenTelemetry.Exporter.Geneva/GenevaExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.Geneva/GenevaExporterHelperExtensions.cs @@ -15,6 +15,7 @@ // using System; +using System.Diagnostics; using OpenTelemetry.Internal; using OpenTelemetry.Trace; @@ -26,34 +27,31 @@ public static TracerProviderBuilder AddGenevaTraceExporter(this TracerProviderBu { Guard.ThrowIfNull(builder); - if (builder is IDeferredTracerProviderBuilder deferredTracerProviderBuilder) + return builder.AddProcessor(sp => { - return deferredTracerProviderBuilder.Configure((sp, builder) => - { - AddGenevaTraceExporter(builder, sp.GetOptions(), configure); - }); - } + var exporterOptions = sp.GetOptions(); - return AddGenevaTraceExporter(builder, new GenevaExporterOptions(), configure); + return BuildGenevaTraceExporter(exporterOptions, configure); + }); } - private static TracerProviderBuilder AddGenevaTraceExporter(this TracerProviderBuilder builder, GenevaExporterOptions options, Action configure) + private static BaseProcessor BuildGenevaTraceExporter(GenevaExporterOptions options, Action configure) { configure?.Invoke(options); var exporter = new GenevaTraceExporter(options); if (exporter.IsUsingUnixDomainSocket) { var batchOptions = new BatchExportActivityProcessorOptions(); - return builder.AddProcessor(new BatchActivityExportProcessor( + return new BatchActivityExportProcessor( exporter, batchOptions.MaxQueueSize, batchOptions.ScheduledDelayMilliseconds, batchOptions.ExporterTimeoutMilliseconds, - batchOptions.MaxExportBatchSize)); + batchOptions.MaxExportBatchSize); } else { - return builder.AddProcessor(new ReentrantActivityExportProcessor(exporter)); + return new ReentrantActivityExportProcessor(exporter); } } } diff --git a/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterExtensions.cs b/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterExtensions.cs index d2f37a5ee0..67da1c315c 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterExtensions.cs @@ -32,21 +32,18 @@ public static MeterProviderBuilder AddGenevaMetricExporter(this MeterProviderBui { Guard.ThrowIfNull(builder); - if (builder is IDeferredMeterProviderBuilder deferredMeterProviderBuilder) + return builder.AddReader(sp => { - return deferredMeterProviderBuilder.Configure((sp, builder) => - { - AddGenevaMetricExporter(builder, sp.GetOptions(), configure); - }); - } + var exporterOptions = sp.GetOptions(); - return AddGenevaMetricExporter(builder, new GenevaMetricExporterOptions(), configure); + return BuildGenevaMetricExporter(exporterOptions, configure); + }); } - private static MeterProviderBuilder AddGenevaMetricExporter(MeterProviderBuilder builder, GenevaMetricExporterOptions options, Action configure = null) + private static MetricReader BuildGenevaMetricExporter(GenevaMetricExporterOptions options, Action configure = null) { configure?.Invoke(options); - return builder.AddReader(new PeriodicExportingMetricReader(new GenevaMetricExporter(options), options.MetricExportIntervalMilliseconds) - { TemporalityPreference = MetricReaderTemporalityPreference.Delta }); + return new PeriodicExportingMetricReader(new GenevaMetricExporter(options), options.MetricExportIntervalMilliseconds) + { TemporalityPreference = MetricReaderTemporalityPreference.Delta }; } } diff --git a/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackLogExporter.cs b/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackLogExporter.cs index 2c353b8337..cb09ae3862 100644 --- a/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackLogExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackLogExporter.cs @@ -138,6 +138,8 @@ internal bool IsUsingUnixDomainSocket internal int SerializeLogRecord(LogRecord logRecord) { + // `LogRecord.State` and `LogRecord.StateValues` were marked Obsolete in https://github.com/open-telemetry/opentelemetry-dotnet/pull/4334 +#pragma warning disable 0618 IReadOnlyList> listKvp; if (logRecord.State == null) { @@ -150,6 +152,7 @@ internal int SerializeLogRecord(LogRecord logRecord) // Attempt to see if State could be ROL_KVP. listKvp = logRecord.State as IReadOnlyList>; } +#pragma warning restore 0618 var buffer = m_buffer.Value; if (buffer == null) diff --git a/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldLogExporter.cs b/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldLogExporter.cs index 2e1a1d1cf1..2842458d55 100644 --- a/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldLogExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldLogExporter.cs @@ -187,6 +187,9 @@ public void Dispose() internal void SerializeLogRecord(LogRecord logRecord) { IReadOnlyList> listKvp; + + // `LogRecord.State` and `LogRecord.StateValues` were marked Obsolete in https://github.com/open-telemetry/opentelemetry-dotnet/pull/4334 +#pragma warning disable 0618 if (logRecord.State == null) { // When State is null, OTel SDK guarantees StateValues is populated @@ -198,6 +201,7 @@ internal void SerializeLogRecord(LogRecord logRecord) // Attempt to see if State could be ROL_KVP. listKvp = logRecord.State as IReadOnlyList>; } +#pragma warning restore 0618 // Structured log. // 2 scenarios. diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaLogExporterTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaLogExporterTests.cs index aba31bb007..cd3554260e 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaLogExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaLogExporterTests.cs @@ -569,14 +569,9 @@ public void SerializationTestWithILoggerLogMethod(bool includeFormattedMessage) _ = exporter.SerializeLogRecord(logRecordList[0]); object fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); var body = GetField(fluentdData, "body"); - if (includeFormattedMessage) - { - Assert.Equal("Formatted Message", body); - } - else - { - Assert.Null(body); - } + + // Body gets populated as "Formatted Message" regardless of the value of `IncludeFormattedMessage` + Assert.Equal("Formatted Message", body); Assert.Equal("Value1", GetField(fluentdData, "Key1")); Assert.Equal("Value2", GetField(fluentdData, "Key2")); @@ -599,14 +594,9 @@ public void SerializationTestWithILoggerLogMethod(bool includeFormattedMessage) _ = exporter.SerializeLogRecord(logRecordList[0]); fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); body = GetField(fluentdData, "body"); - if (includeFormattedMessage) - { - Assert.Equal("Formatted Message", body); - } - else - { - Assert.Null(body); - } + + // Body gets populated as "Formatted Message" regardless of the value of `IncludeFormattedMessage` + Assert.Equal("Formatted Message", body); // ARRANGE logRecordList.Clear(); @@ -627,8 +617,8 @@ public void SerializationTestWithILoggerLogMethod(bool includeFormattedMessage) fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); body = GetField(fluentdData, "body"); - // Formatter is null, hence body is always null - Assert.Null(body); + // Even though Formatter is null, body is populated with the state + Assert.Equal("somestringasdata", body); // ARRANGE logRecordList.Clear(); @@ -654,15 +644,8 @@ public void SerializationTestWithILoggerLogMethod(bool includeFormattedMessage) body = GetField(fluentdData, "body"); - // Only populate body if FormattedMessage is enabled - if (includeFormattedMessage) - { - Assert.Equal("Example formatted message.", body); - } - else - { - Assert.Null(body); - } + // Body gets populated as "Formatted Message" regardless of the value of `IncludeFormattedMessage` + Assert.Equal("Example formatted message.", body); } finally { @@ -1376,6 +1359,9 @@ private void AssertFluentdForwardModeForLogRecord(GenevaExporterOptions exporter bool isUnstructuredLog = true; IReadOnlyList> stateKeyValuePairList; + + // `LogRecord.State` and `LogRecord.StateValues` were marked Obsolete in https://github.com/open-telemetry/opentelemetry-dotnet/pull/4334 +#pragma warning disable 0618 if (logRecord.State == null) { stateKeyValuePairList = logRecord.StateValues; @@ -1384,6 +1370,7 @@ private void AssertFluentdForwardModeForLogRecord(GenevaExporterOptions exporter { stateKeyValuePairList = logRecord.State as IReadOnlyList>; } +#pragma warning restore 0618 if (stateKeyValuePairList != null) { @@ -1392,10 +1379,13 @@ private void AssertFluentdForwardModeForLogRecord(GenevaExporterOptions exporter if (isUnstructuredLog) { + // `LogRecord.State` and `LogRecord.StateValues` were marked Obsolete in https://github.com/open-telemetry/opentelemetry-dotnet/pull/4334 +#pragma warning disable 0618 if (logRecord.State != null) { Assert.Equal(logRecord.State.ToString(), mapping["body"]); } +#pragma warning restore 0618 else { Assert.Equal(stateKeyValuePairList[0].Value, mapping["body"]); diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs index 0ca2f0b373..cb7c61447a 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs @@ -174,13 +174,13 @@ public void GenevaTraceExporter_Success_Windows() var source = new ActivitySource(sourceName); using (var parent = source.StartActivity("HttpIn", ActivityKind.Server)) { - parent?.SetTag("http.method", "GET"); - parent?.SetTag("http.url", "https://localhost/wiki/Rabbit"); + parent.SetTag("http.method", "GET"); + parent.SetTag("http.url", "https://localhost/wiki/Rabbit"); using (var child = source.StartActivity("HttpOut", ActivityKind.Client)) { - child?.SetTag("http.method", "GET"); - child?.SetTag("http.url", "https://www.wikipedia.org/wiki/Rabbit"); - child?.SetTag("http.status_code", 404); + child.SetTag("http.method", "GET"); + child.SetTag("http.url", "https://www.wikipedia.org/wiki/Rabbit"); + child.SetTag("http.status_code", 404); } parent?.SetTag("http.status_code", 200); From 3f26435208253a0108afb70eabff4839da521d4f Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:55:02 -0700 Subject: [PATCH 07/12] [Exporter.Geneva] Update CHANGELOG for 1.5.0-rc.1 (#1217) --- src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index 146061ec97..693592d177 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.5.0-rc.1 + +Released 2023-Jun-05 + * Fix an issue with getting sanitized category name in pass-through table name mapping cases for `TldLogExporter`. ([#1175](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1175)) From 24545cdf988a95d914a42418e8c0ec5d1caaf4cd Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Mon, 5 Jun 2023 15:34:59 -0700 Subject: [PATCH 08/12] [Instrumentation.Runtime] Update CHANGELOG for 1.4.0 release (#1214) --- src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md index de8698354f..59d0e690a3 100644 --- a/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## 1.4.0 + +Released 2023-Jun-01 + +* Bumped the version to `1.4.0` to keep it in sync with the release versions of + `OpenTelemetry.API`. This makes it more intuitive for the users to figure out + what version of core packages would work with a given version of this package. + ## 1.1.0-rc.2 Released 2023-Feb-27 From ac90598825cf6188e2d9aa29c597eeda21c8ddd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Kie=C5=82kowicz?= Date: Tue, 6 Jun 2023 22:33:02 +0200 Subject: [PATCH 09/12] Fix NU1604 for Examples.InfluxDB (#1221) --- examples/InfluxDB/Examples.InfluxDB/Examples.InfluxDB.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/InfluxDB/Examples.InfluxDB/Examples.InfluxDB.csproj b/examples/InfluxDB/Examples.InfluxDB/Examples.InfluxDB.csproj index acb83f28cf..f81e86dc4c 100644 --- a/examples/InfluxDB/Examples.InfluxDB/Examples.InfluxDB.csproj +++ b/examples/InfluxDB/Examples.InfluxDB/Examples.InfluxDB.csproj @@ -9,7 +9,7 @@ - + From e0d9e1e3170d8c72c1630199c9369f0951dd06ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Kie=C5=82kowicz?= Date: Tue, 6 Jun 2023 22:58:29 +0200 Subject: [PATCH 10/12] Bump OpenTelemetry to 1.5.0 (#1220) --- build/Common.props | 2 +- examples/AspNet/Examples.AspNet.csproj | 2 +- .../process-instrumentation/process-instrumentation.csproj | 2 +- .../runtime-instrumentation/runtime-instrumentation.csproj | 2 +- src/OpenTelemetry.Contrib.Extensions.AWSXRay/CHANGELOG.md | 2 ++ src/OpenTelemetry.Exporter.InfluxDB/CHANGELOG.md | 3 +++ src/OpenTelemetry.Exporter.Instana/CHANGELOG.md | 4 ++-- .../OpenTelemetry.Exporter.OneCollector.csproj | 2 +- src/OpenTelemetry.Exporter.Stackdriver/CHANGELOG.md | 4 ++-- .../OpenTelemetry.Extensions.csproj | 2 +- .../CHANGELOG.md | 3 +++ src/OpenTelemetry.Instrumentation.Cassandra/CHANGELOG.md | 5 ++++- .../CHANGELOG.md | 3 +++ .../CHANGELOG.md | 3 +++ .../CHANGELOG.md | 4 ++-- src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md | 4 ++-- src/OpenTelemetry.Instrumentation.MySqlData/CHANGELOG.md | 2 ++ src/OpenTelemetry.Instrumentation.Owin/CHANGELOG.md | 4 ++-- src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md | 3 +++ src/OpenTelemetry.Instrumentation.Quartz/CHANGELOG.md | 3 +++ src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md | 3 +++ .../CHANGELOG.md | 3 +++ src/OpenTelemetry.Instrumentation.Wcf/CHANGELOG.md | 3 +++ src/OpenTelemetry.ResourceDetectors.Azure/CHANGELOG.md | 6 ++++-- src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md | 3 +++ .../OpenTelemetry.Extensions.Enrichment.Tests.csproj | 4 ++-- 26 files changed, 60 insertions(+), 21 deletions(-) diff --git a/build/Common.props b/build/Common.props index 56d5b202c8..56041e80cc 100644 --- a/build/Common.props +++ b/build/Common.props @@ -34,7 +34,7 @@ [4.2.2,5.0) [3.3.3] [1.1.1,2.0) - [1.4.0,2.0) + [1.5.0,2.0) [1.5.0-rc.1] [2.1.58,3.0) [3.16.0,4.0) diff --git a/examples/AspNet/Examples.AspNet.csproj b/examples/AspNet/Examples.AspNet.csproj index d7d8ac9dc1..a8a47cbb92 100644 --- a/examples/AspNet/Examples.AspNet.csproj +++ b/examples/AspNet/Examples.AspNet.csproj @@ -84,7 +84,7 @@ - + diff --git a/examples/process-instrumentation/process-instrumentation.csproj b/examples/process-instrumentation/process-instrumentation.csproj index 727523e7b5..d464f2f3b0 100644 --- a/examples/process-instrumentation/process-instrumentation.csproj +++ b/examples/process-instrumentation/process-instrumentation.csproj @@ -6,7 +6,7 @@ enable - + diff --git a/examples/runtime-instrumentation/runtime-instrumentation.csproj b/examples/runtime-instrumentation/runtime-instrumentation.csproj index 971a331c3d..89ec4225f9 100644 --- a/examples/runtime-instrumentation/runtime-instrumentation.csproj +++ b/examples/runtime-instrumentation/runtime-instrumentation.csproj @@ -6,7 +6,7 @@ enable - + diff --git a/src/OpenTelemetry.Contrib.Extensions.AWSXRay/CHANGELOG.md b/src/OpenTelemetry.Contrib.Extensions.AWSXRay/CHANGELOG.md index e3cc1d02e0..d478e85f5b 100644 --- a/src/OpenTelemetry.Contrib.Extensions.AWSXRay/CHANGELOG.md +++ b/src/OpenTelemetry.Contrib.Extensions.AWSXRay/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +* Updates to 1.5.0 of OpenTelemetry SDK. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) * Enhancement - AWSXRayIdGenerator - Generate X-Ray IDs with global Random instance instead of recreating with ThreadLocal ([#380](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/380)) diff --git a/src/OpenTelemetry.Exporter.InfluxDB/CHANGELOG.md b/src/OpenTelemetry.Exporter.InfluxDB/CHANGELOG.md index 116da71e91..d77403da39 100644 --- a/src/OpenTelemetry.Exporter.InfluxDB/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.InfluxDB/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Updates to 1.5.0 of OpenTelemetry SDK. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) + ## 1.0.0-alpha.1 Released 2023-May-18 diff --git a/src/OpenTelemetry.Exporter.Instana/CHANGELOG.md b/src/OpenTelemetry.Exporter.Instana/CHANGELOG.md index ecb16daf01..bdac13a24c 100644 --- a/src/OpenTelemetry.Exporter.Instana/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Instana/CHANGELOG.md @@ -2,8 +2,8 @@ ## Unreleased -* Update OTel SDK version to `1.4.0`. - ([#1050](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1050)) +* Update OTel SDK version to `1.5.0`. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) * Drop support for .NET Framework 4.6.1. The lowest supported version is .NET Framework 4.6.2. ([#1050](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1050)) diff --git a/src/OpenTelemetry.Exporter.OneCollector/OpenTelemetry.Exporter.OneCollector.csproj b/src/OpenTelemetry.Exporter.OneCollector/OpenTelemetry.Exporter.OneCollector.csproj index 2990eb9ae4..1e28454478 100644 --- a/src/OpenTelemetry.Exporter.OneCollector/OpenTelemetry.Exporter.OneCollector.csproj +++ b/src/OpenTelemetry.Exporter.OneCollector/OpenTelemetry.Exporter.OneCollector.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/OpenTelemetry.Exporter.Stackdriver/CHANGELOG.md b/src/OpenTelemetry.Exporter.Stackdriver/CHANGELOG.md index 1e420b07aa..24b24882a3 100644 --- a/src/OpenTelemetry.Exporter.Stackdriver/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Stackdriver/CHANGELOG.md @@ -2,8 +2,8 @@ ## Unreleased -* Update OTel SDK version to `1.4.0`. - ([#1038](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1038)) +* Update OTel SDK version to `1.5.0`. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) ## 1.0.0-beta.4 diff --git a/src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj b/src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj index a8f69a0230..f2ad80b879 100644 --- a/src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj +++ b/src/OpenTelemetry.Extensions/OpenTelemetry.Extensions.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md index 96c4445b19..171866a9de 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Update `OpenTelemetry.Api` to `1.5.0`. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) + ## 1.0.0-rc9.8 Released 2023-Feb-27 diff --git a/src/OpenTelemetry.Instrumentation.Cassandra/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Cassandra/CHANGELOG.md index d3add35586..4e21b155b6 100644 --- a/src/OpenTelemetry.Instrumentation.Cassandra/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Cassandra/CHANGELOG.md @@ -2,8 +2,11 @@ ## Unreleased +* Updates to 1.5.0 of OpenTelemetry SDK. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) + ## 1.0.0-beta.1 Released 2023-Mar-30 -- Initial release +* Initial release diff --git a/src/OpenTelemetry.Instrumentation.ElasticsearchClient/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.ElasticsearchClient/CHANGELOG.md index 4bfcb84e5d..2618057616 100644 --- a/src/OpenTelemetry.Instrumentation.ElasticsearchClient/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.ElasticsearchClient/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Updated OTel SDK package version to 1.5.0 + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) + ## 1.0.0-beta.4 Released 2023-Mar-06 diff --git a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md index e6428d823d..35853ee185 100644 --- a/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Updated OTel SDK package version to 1.5.0 + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) + ## 1.0.0-beta.6 Released 2023-Mar-13 diff --git a/src/OpenTelemetry.Instrumentation.EventCounters/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.EventCounters/CHANGELOG.md index 6df60b7a5b..e16597e215 100644 --- a/src/OpenTelemetry.Instrumentation.EventCounters/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.EventCounters/CHANGELOG.md @@ -2,8 +2,8 @@ ## Unreleased -* Update OpenTelemetry.Api to 1.4.0. - ([#1038](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1038)) +* Update OpenTelemetry.Api to 1.5.0. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) ## 1.0.0-alpha.2 diff --git a/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md index 967f858177..d694f46d6a 100644 --- a/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Hangfire/CHANGELOG.md @@ -2,8 +2,8 @@ ## Unreleased -* Update OTel API version to `1.4.0`. - ([#1038](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1038)) +* Update OTel API version to `1.5.0`. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) * Removes `AddHangfireInstrumentation` method with default configure default parameter. ([#1129](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1129)) * Support Hangfire `1.8`. diff --git a/src/OpenTelemetry.Instrumentation.MySqlData/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.MySqlData/CHANGELOG.md index 03de56197c..a71cac01b0 100644 --- a/src/OpenTelemetry.Instrumentation.MySqlData/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.MySqlData/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +* Update OTel API version to `1.5.0`. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) * Removes `AddMySqlDataInstrumentation` method with default configure parameter. ([#930](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/930)) diff --git a/src/OpenTelemetry.Instrumentation.Owin/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Owin/CHANGELOG.md index f9b1a72736..a2d4862520 100644 --- a/src/OpenTelemetry.Instrumentation.Owin/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Owin/CHANGELOG.md @@ -2,8 +2,8 @@ ## Unreleased -* Updated OpenTelemetry SDK to 1.4.0 - ([#1038](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1038)) +* Updated OpenTelemetry SDK to 1.5.0 + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) * Removes `AddOwinInstrumentation` method with default configure parameter. ([#929](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/929)) diff --git a/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md index 490331cba4..65c5ed3f74 100644 --- a/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Process/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Update OpenTelemetry API to 1.5.0 + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) + ## 0.5.0-beta.2 Released 2023-Feb-27 diff --git a/src/OpenTelemetry.Instrumentation.Quartz/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Quartz/CHANGELOG.md index 6efa30b017..b074f674e2 100644 --- a/src/OpenTelemetry.Instrumentation.Quartz/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Quartz/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Update OpenTelemetry.Api to 1.5.0. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) + ## 1.0.0-alpha.2 Released 2023-Feb-27 diff --git a/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md index 59d0e690a3..ae00bc3dbf 100644 --- a/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Update OpenTelemetry API to 1.5.0 + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) + ## 1.4.0 Released 2023-Jun-01 diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md index dd61aafd97..ce15a5ce62 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Update OTel API version to `1.5.0`. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) + ## 1.0.0-rc9.9 Released 2023-May-25 diff --git a/src/OpenTelemetry.Instrumentation.Wcf/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Wcf/CHANGELOG.md index 2a6588a8c4..4ecc7bba6a 100644 --- a/src/OpenTelemetry.Instrumentation.Wcf/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Wcf/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Update OTel SDK version to `1.5.0`. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) + ## 1.0.0-rc.9 Released 2023-Feb-27 diff --git a/src/OpenTelemetry.ResourceDetectors.Azure/CHANGELOG.md b/src/OpenTelemetry.ResourceDetectors.Azure/CHANGELOG.md index d5cad43209..d77fccd1b8 100644 --- a/src/OpenTelemetry.ResourceDetectors.Azure/CHANGELOG.md +++ b/src/OpenTelemetry.ResourceDetectors.Azure/CHANGELOG.md @@ -2,14 +2,16 @@ ## Unreleased +* Updates to 1.5.0 of OpenTelemetry SDK. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) * Added Azure VM resource detector. -([#1182](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1182)) + ([#1182](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1182)) ## 1.0.0-alpha.1 Released 2023-Apr-19 * Add AppService resource detector. -([#989](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/989)) + ([#989](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/989)) For more details, please refer to the [README](README.md). diff --git a/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md b/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md index 09cff2b9b3..071218cb33 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md +++ b/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Updates to 1.5.0 of OpenTelemetry SDK. + ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) + ## 1.0.0-beta.3 Released 2023-Apr-7 diff --git a/test/OpenTelemetry.Extensions.Enrichment.Tests/OpenTelemetry.Extensions.Enrichment.Tests.csproj b/test/OpenTelemetry.Extensions.Enrichment.Tests/OpenTelemetry.Extensions.Enrichment.Tests.csproj index 48538d1d58..02e2134a9b 100644 --- a/test/OpenTelemetry.Extensions.Enrichment.Tests/OpenTelemetry.Extensions.Enrichment.Tests.csproj +++ b/test/OpenTelemetry.Extensions.Enrichment.Tests/OpenTelemetry.Extensions.Enrichment.Tests.csproj @@ -18,8 +18,8 @@ - - + + From 966727e8c9d2bba8b7f1dfb4639a5a7f57813e99 Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Tue, 6 Jun 2023 15:50:41 -0700 Subject: [PATCH 11/12] [Instrumentation.Runtime] Update CHANGELOG for 1.5.0 (#1222) --- src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md index ae00bc3dbf..97ed41a722 100644 --- a/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Runtime/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.5.0 + +Released 2023-Jun-06 + * Update OpenTelemetry API to 1.5.0 ([#1220](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1220)) From 698e70a58f4a8e329933a83425de3e2fc2fb9daa Mon Sep 17 00:00:00 2001 From: Utkarsh Umesan Pillai <66651184+utpilla@users.noreply.github.com> Date: Tue, 6 Jun 2023 18:33:29 -0700 Subject: [PATCH 12/12] [Exporter.Geneva] Add named options support for GenevaTraceExporter and GenevaMetricExporter (#1218) --- .../.publicApi/net462/PublicAPI.Unshipped.txt | 5 ++ .../.publicApi/net6.0/PublicAPI.Unshipped.txt | 7 ++- .../netstandard2.0/PublicAPI.Unshipped.txt | 5 ++ .../CHANGELOG.md | 10 ++++ .../GenevaExporterHelperExtensions.cs | 27 ++++++++++- .../Internal/ServiceProviderExtensions.cs | 46 ------------------- .../Metrics/GenevaMetricExporterExtensions.cs | 31 ++++++++++++- .../GenevaMetricExporterTests.cs | 44 ++++++++++++++++++ .../GenevaTraceExporterTests.cs | 43 +++++++++++++++++ 9 files changed, 168 insertions(+), 50 deletions(-) delete mode 100644 src/OpenTelemetry.Exporter.Geneva/Internal/ServiceProviderExtensions.cs diff --git a/src/OpenTelemetry.Exporter.Geneva/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Geneva/.publicApi/net462/PublicAPI.Unshipped.txt index 1fd2dcd539..db58b7a99f 100644 --- a/src/OpenTelemetry.Exporter.Geneva/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Geneva/.publicApi/net462/PublicAPI.Unshipped.txt @@ -3,3 +3,8 @@ OpenTelemetry.Exporter.Geneva.EventNameExportMode.ExportAsPartAName = 1 -> OpenT OpenTelemetry.Exporter.Geneva.EventNameExportMode.None = 0 -> OpenTelemetry.Exporter.Geneva.EventNameExportMode OpenTelemetry.Exporter.Geneva.GenevaExporterOptions.EventNameExportMode.get -> OpenTelemetry.Exporter.Geneva.EventNameExportMode OpenTelemetry.Exporter.Geneva.GenevaExporterOptions.EventNameExportMode.set -> void +static OpenTelemetry.Exporter.Geneva.GenevaExporterHelperExtensions.AddGenevaTraceExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder +static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +*REMOVED*static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure = null) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Exporter.Geneva/.publicApi/net6.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Geneva/.publicApi/net6.0/PublicAPI.Unshipped.txt index 4ca0ea2c19..db58b7a99f 100644 --- a/src/OpenTelemetry.Exporter.Geneva/.publicApi/net6.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Geneva/.publicApi/net6.0/PublicAPI.Unshipped.txt @@ -2,4 +2,9 @@ OpenTelemetry.Exporter.Geneva.EventNameExportMode OpenTelemetry.Exporter.Geneva.EventNameExportMode.ExportAsPartAName = 1 -> OpenTelemetry.Exporter.Geneva.EventNameExportMode OpenTelemetry.Exporter.Geneva.EventNameExportMode.None = 0 -> OpenTelemetry.Exporter.Geneva.EventNameExportMode OpenTelemetry.Exporter.Geneva.GenevaExporterOptions.EventNameExportMode.get -> OpenTelemetry.Exporter.Geneva.EventNameExportMode -OpenTelemetry.Exporter.Geneva.GenevaExporterOptions.EventNameExportMode.set -> void \ No newline at end of file +OpenTelemetry.Exporter.Geneva.GenevaExporterOptions.EventNameExportMode.set -> void +static OpenTelemetry.Exporter.Geneva.GenevaExporterHelperExtensions.AddGenevaTraceExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder +static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +*REMOVED*static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure = null) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Exporter.Geneva/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Geneva/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 1fd2dcd539..db58b7a99f 100644 --- a/src/OpenTelemetry.Exporter.Geneva/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Geneva/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -3,3 +3,8 @@ OpenTelemetry.Exporter.Geneva.EventNameExportMode.ExportAsPartAName = 1 -> OpenT OpenTelemetry.Exporter.Geneva.EventNameExportMode.None = 0 -> OpenTelemetry.Exporter.Geneva.EventNameExportMode OpenTelemetry.Exporter.Geneva.GenevaExporterOptions.EventNameExportMode.get -> OpenTelemetry.Exporter.Geneva.EventNameExportMode OpenTelemetry.Exporter.Geneva.GenevaExporterOptions.EventNameExportMode.set -> void +static OpenTelemetry.Exporter.Geneva.GenevaExporterHelperExtensions.AddGenevaTraceExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder +static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, string name, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder +*REMOVED*static OpenTelemetry.Exporter.Geneva.GenevaMetricExporterExtensions.AddGenevaMetricExporter(this OpenTelemetry.Metrics.MeterProviderBuilder builder, System.Action configure = null) -> OpenTelemetry.Metrics.MeterProviderBuilder diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index 693592d177..be1bc1cd30 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +* Add named options support for `GenevaTraceExporter` and + `GenevaMetricExporter`. + ([#1218](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1218)) + +* Add a new overload for `AddGenevaMetricExporter` without any parameters to + avoid warning + [RS0026](https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/Microsoft.CodeAnalysis.PublicApiAnalyzers.md#rs0026-do-not-add-multiple-public-overloads-with-optional-parameters). + ([#1218](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1218)) + ## 1.5.0-rc.1 Released 2023-Jun-05 @@ -13,6 +22,7 @@ Released 2023-Jun-05 * TldLogExporter to export `SpanId` value in `ext_dt_spanId` field instead of `TraceId` value. ([#1184](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1184)) + * Add support for abstract domain sockets. ([#1199](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1199)) diff --git a/src/OpenTelemetry.Exporter.Geneva/GenevaExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.Geneva/GenevaExporterHelperExtensions.cs index 266c1b11e4..c050b34c3b 100644 --- a/src/OpenTelemetry.Exporter.Geneva/GenevaExporterHelperExtensions.cs +++ b/src/OpenTelemetry.Exporter.Geneva/GenevaExporterHelperExtensions.cs @@ -16,6 +16,8 @@ using System; using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using OpenTelemetry.Internal; using OpenTelemetry.Trace; @@ -23,13 +25,36 @@ namespace OpenTelemetry.Exporter.Geneva; public static class GenevaExporterHelperExtensions { + /// + /// Adds to the . + /// + /// builder to use. + /// Exporter configuration options. + /// The instance of to chain the calls. public static TracerProviderBuilder AddGenevaTraceExporter(this TracerProviderBuilder builder, Action configure) + => AddGenevaTraceExporter(builder, name: null, configure); + + /// + /// Adds to the . + /// + /// builder to use. + /// /// Name which is used when retrieving options. + /// Exporter configuration options. + /// The instance of to chain the calls. + public static TracerProviderBuilder AddGenevaTraceExporter(this TracerProviderBuilder builder, string name, Action configure) { Guard.ThrowIfNull(builder); + name ??= Options.DefaultName; + + if (configure != null) + { + builder.ConfigureServices(services => services.Configure(name, configure)); + } + return builder.AddProcessor(sp => { - var exporterOptions = sp.GetOptions(); + var exporterOptions = sp.GetRequiredService>().Get(name); return BuildGenevaTraceExporter(exporterOptions, configure); }); diff --git a/src/OpenTelemetry.Exporter.Geneva/Internal/ServiceProviderExtensions.cs b/src/OpenTelemetry.Exporter.Geneva/Internal/ServiceProviderExtensions.cs deleted file mode 100644 index 9175001b9f..0000000000 --- a/src/OpenTelemetry.Exporter.Geneva/Internal/ServiceProviderExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#if NET462_OR_GREATER || NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP3_1_OR_GREATER -using Microsoft.Extensions.Options; -#endif - -namespace System; - -/// -/// Extension methods for OpenTelemetry dependency injection support. -/// -internal static class ServiceProviderExtensions -{ - /// - /// Get options from the supplied . - /// - /// Options type. - /// . - /// Options instance. - public static T GetOptions(this IServiceProvider serviceProvider) - where T : class, new() - { -#if NET462_OR_GREATER || NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP3_1_OR_GREATER - IOptions options = (IOptions)serviceProvider.GetService(typeof(IOptions)); - - // Note: options could be null if user never invoked services.AddOptions(). - return options?.Value ?? new T(); -#else - return new T(); -#endif - } -} diff --git a/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterExtensions.cs b/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterExtensions.cs index 67da1c315c..4933ad7a62 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterExtensions.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Metrics/GenevaMetricExporterExtensions.cs @@ -15,6 +15,8 @@ // using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using OpenTelemetry.Internal; using OpenTelemetry.Metrics; @@ -26,15 +28,40 @@ public static class GenevaMetricExporterExtensions /// Adds to the . /// /// builder to use. + /// The instance of to chain the calls. + public static MeterProviderBuilder AddGenevaMetricExporter(this MeterProviderBuilder builder) + => AddGenevaMetricExporter(builder, name: null, configure: null); + + /// + /// Adds to the . + /// + /// builder to use. + /// Exporter configuration options. + /// The instance of to chain the calls. + public static MeterProviderBuilder AddGenevaMetricExporter(this MeterProviderBuilder builder, Action configure) + => AddGenevaMetricExporter(builder, name: null, configure); + + /// + /// Adds to the . + /// + /// builder to use. + /// /// Name which is used when retrieving options. /// Exporter configuration options. /// The instance of to chain the calls. - public static MeterProviderBuilder AddGenevaMetricExporter(this MeterProviderBuilder builder, Action configure = null) + public static MeterProviderBuilder AddGenevaMetricExporter(this MeterProviderBuilder builder, string name, Action configure) { Guard.ThrowIfNull(builder); + name ??= Options.DefaultName; + + if (configure != null) + { + builder.ConfigureServices(services => services.Configure(name, configure)); + } + return builder.AddReader(sp => { - var exporterOptions = sp.GetOptions(); + var exporterOptions = sp.GetRequiredService>().Get(name); return BuildGenevaMetricExporter(exporterOptions, configure); }); diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaMetricExporterTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaMetricExporterTests.cs index 2054179466..f9b3ead921 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaMetricExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaMetricExporterTests.cs @@ -26,7 +26,9 @@ using System.Runtime.InteropServices; using System.Threading.Tasks; using Kaitai; +using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; using Xunit; using static OpenTelemetry.Exporter.Geneva.Tests.MetricsContract; @@ -873,6 +875,48 @@ public void SuccessfulSerializationWithCustomAccountAndNamespace() } } + [Fact] + public void AddGenevaMetricExporterNamedOptionsSupport() + { + string connectionString; + string connectionStringForNamedOptions; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + connectionString = "Account=OTelMonitoringAccount;Namespace=OTelMetricNamespace"; + connectionStringForNamedOptions = "Account=OTelMonitoringAccount-NamedOptions;Namespace=OTelMetricNamespace-NamedOptions"; + } + else + { + var path = GenerateTempFilePath(); + connectionString = $"Endpoint=unix:{path};Account=OTelMonitoringAccount;Namespace=OTelMetricNamespace"; + connectionStringForNamedOptions = $"Endpoint=unix:{path};Account=OTelMonitoringAccount-NamedOptions;Namespace=OTelMetricNamespace-NamedOptions"; + } + + using var meterProvider = Sdk.CreateMeterProviderBuilder() + .ConfigureServices(services => + { + services.Configure(options => + { + options.ConnectionString = connectionString; + }); + services.Configure("ExporterWithNamedOptions", options => + { + options.ConnectionString = connectionStringForNamedOptions; + }); + }) + .AddGenevaMetricExporter(options => + { + // ConnectionString for the options is already set in `IServiceCollection Configure` calls above + Assert.Equal(connectionString, options.ConnectionString); + }) + .AddGenevaMetricExporter("ExporterWithNamedOptions", options => + { + // ConnectionString for the named options is already set in `IServiceCollection Configure` calls above + Assert.Equal(connectionStringForNamedOptions, options.ConnectionString); + }) + .Build(); + } + private static string GenerateTempFilePath() { while (true) diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs index cb7c61447a..7d9dbde3d0 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs @@ -24,6 +24,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; +using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Trace; using Xunit; @@ -514,6 +515,48 @@ public void TLDTraceExporter_Success_Windows() } } + [Fact] + public void AddGenevaTraceExporterNamedOptionsSupport() + { + string connectionString; + string connectionStringForNamedOptions; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + connectionString = "EtwSession=OpenTelemetry"; + connectionStringForNamedOptions = "EtwSession=OpenTelemetry-NamedOptions"; + } + else + { + var path = GetRandomFilePath(); + connectionString = "Endpoint=unix:" + path; + connectionStringForNamedOptions = "Endpoint=unix:" + path + "NamedOptions"; + } + + using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .ConfigureServices(services => + { + services.Configure(options => + { + options.ConnectionString = connectionString; + }); + services.Configure("ExporterWithNamedOptions", options => + { + options.ConnectionString = connectionStringForNamedOptions; + }); + }) + .AddGenevaTraceExporter(options => + { + // ConnectionString for the options is already set in `IServiceCollection Configure` calls above + Assert.Equal(connectionString, options.ConnectionString); + }) + .AddGenevaTraceExporter("ExporterWithNamedOptions", options => + { + // ConnectionString for the named options is already set in `IServiceCollection Configure` calls above + Assert.Equal(connectionStringForNamedOptions, options.ConnectionString); + }) + .Build(); + } + private static string GetRandomFilePath() { while (true)