diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs index a8448a30b86..31b74d0cbf5 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs @@ -63,7 +63,7 @@ public void ReceiveMessage(BasicDeliverEventArgs ea) // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name var activityName = $"{ea.RoutingKey} receive"; - using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Consumer, parentContext)) + using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Consumer, parentContext.ActivityContext)) { try { diff --git a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs index 8eded341382..d8c93e70691 100644 --- a/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs +++ b/examples/MicroserviceExample/Utils/Messaging/MessageSender.cs @@ -61,7 +61,7 @@ public string SendMessage() if (activity != null) { // Inject the ActivityContext into the message headers to propagate trace context to the receiving service. - TextFormat.Inject(activity.Context, props, this.InjectTraceContextIntoBasicProperties); + TextFormat.Inject(new PropagationContext(activity.Context, activity.Baggage), props, this.InjectTraceContextIntoBasicProperties); // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here. RabbitMqHelper.AddMessagingTags(activity); diff --git a/src/OpenTelemetry.Api/CHANGELOG.md b/src/OpenTelemetry.Api/CHANGELOG.md index 096f827bfff..1d93745268b 100644 --- a/src/OpenTelemetry.Api/CHANGELOG.md +++ b/src/OpenTelemetry.Api/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +* `PropagationContext` is now used instead of `ActivityContext` in the + `ITextFormat` API + ([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048)) +* Added `BaggageFormat` an `ITextFormat` implementation for managing Baggage + propagation via the [W3C + Baggage](https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md) + header + ([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048)) +* Removed `DistributedContext` as it is no longer part of the spec + ([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048))) * Renaming from `ot` to `otel` ([#1046](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1046)) * Added `RuntimeContext` API diff --git a/src/OpenTelemetry.Api/Context/CorrelationContext.cs b/src/OpenTelemetry.Api/Context/CorrelationContext.cs index 693424a1d24..19d0f11c3f5 100644 --- a/src/OpenTelemetry.Api/Context/CorrelationContext.cs +++ b/src/OpenTelemetry.Api/Context/CorrelationContext.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace OpenTelemetry.Context @@ -25,27 +26,33 @@ namespace OpenTelemetry.Context /// public readonly struct CorrelationContext : IEquatable { - private static readonly List EmptyList = new List(); - private readonly List entries; + internal static readonly CorrelationContext Empty = new CorrelationContext(null); + internal static readonly IEnumerable> EmptyBaggage = new KeyValuePair[0]; + private readonly Activity activity; - /// - /// Initializes a new instance of the struct. - /// - /// Entries for correlation context. - internal CorrelationContext(List entries) + internal CorrelationContext(in Activity activity) { - this.entries = entries; + this.activity = activity; } /// - /// Gets empty object of struct. + /// Gets the current . /// - public static CorrelationContext Empty { get; } = new CorrelationContext(EmptyList); + public static CorrelationContext Current + { + get + { + Activity activity = Activity.Current; + return activity == null + ? Empty + : new CorrelationContext(activity); + } + } /// - /// Gets all the in this . + /// Gets the correlation values. /// - public IEnumerable Entries => this.entries; + public IEnumerable> Correlations => this.activity?.Baggage ?? EmptyBaggage; /// /// Compare two entries of for equality. @@ -62,23 +69,62 @@ internal CorrelationContext(List entries) public static bool operator !=(CorrelationContext left, CorrelationContext right) => !(left == right); /// - /// Gets the with the specified name. + /// Retrieves a correlation item. + /// + /// Correlation item key. + /// Retrieved correlation value or if no match was found. + public string GetCorrelation(string key) + => this.activity?.GetBaggageItem(key); + + /// + /// Adds a correlation item. + /// + /// Correlation item key. + /// Correlation item value. + /// The instance for chaining. + public CorrelationContext AddCorrelation(string key, string value) + { + this.activity?.AddBaggage(key, value); + + return this; + } + + /// + /// Adds correlation items. /// - /// Name of the to get. - /// The with the specified name. If not found - null. - public string GetEntryValue(string key) => this.entries.LastOrDefault(x => x.Key == key).Value; + /// Correlation items. + /// The instance for chaining. + public CorrelationContext AddCorrelation(IEnumerable> correlations) + { + if (correlations != null) + { + foreach (KeyValuePair correlation in correlations) + { + this.activity?.AddBaggage(correlation.Key, correlation.Value); + } + } + + return this; + } /// public bool Equals(CorrelationContext other) { - if (this.entries.Count != other.entries.Count) + var thisCorrelations = this.Correlations; + var otherCorrelations = other.Correlations; + + if (thisCorrelations.Count() != otherCorrelations.Count()) { return false; } - foreach (CorrelationContextEntry entry in this.entries) + var thisEnumerator = thisCorrelations.GetEnumerator(); + var otherEnumerator = otherCorrelations.GetEnumerator(); + + while (thisEnumerator.MoveNext() && otherEnumerator.MoveNext()) { - if (other.GetEntryValue(entry.Key) != entry.Value) + if (thisEnumerator.Current.Key != otherEnumerator.Current.Key + || thisEnumerator.Current.Value != otherEnumerator.Current.Value) { return false; } @@ -96,7 +142,7 @@ public override bool Equals(object obj) /// public override int GetHashCode() { - return this.entries.GetHashCode(); + return this.Correlations.GetHashCode(); } } } diff --git a/src/OpenTelemetry.Api/Context/CorrelationContextBuilder.cs b/src/OpenTelemetry.Api/Context/CorrelationContextBuilder.cs deleted file mode 100644 index 0ea5e8a0e42..00000000000 --- a/src/OpenTelemetry.Api/Context/CorrelationContextBuilder.cs +++ /dev/null @@ -1,238 +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. -// - -using System.Collections.Generic; - -namespace OpenTelemetry.Context -{ - /// - /// Correlation context Builder. - /// - public struct CorrelationContextBuilder : System.IEquatable - { - private List entries; - - /// - /// Initializes a new instance of the struct. - /// - /// Flag to allow inheriting the current context entries. - public CorrelationContextBuilder(bool inheritCurrentContext) - { - this.entries = null; - - if (DistributedContext.Carrier is NoopDistributedContextCarrier) - { - return; - } - - if (inheritCurrentContext) - { - this.entries = new List(DistributedContext.Current.CorrelationContext.Entries); - } - } - - /// - /// Initializes a new instance of the struct using some context. - /// - /// Initial context. - public CorrelationContextBuilder(CorrelationContext context) - { - if (DistributedContext.Carrier is NoopDistributedContextCarrier) - { - this.entries = null; - return; - } - - this.entries = new List(context.Entries); - } - - /// - /// Compare two entries of for equality. - /// - /// First Entry to compare. - /// Second Entry to compare. - public static bool operator ==(CorrelationContextBuilder left, CorrelationContextBuilder right) - { - return left.Equals(right); - } - - /// - /// Compare two entries of for equality. - /// - /// First Entry to compare. - /// Second Entry to compare. - public static bool operator !=(CorrelationContextBuilder left, CorrelationContextBuilder right) - { - return !(left == right); - } - - /// - /// Create instance from key and value entry. - /// - /// Entry key. - /// Entry value. - /// Instance of . - public static CorrelationContext CreateContext(string key, string value) => - new CorrelationContextBuilder(inheritCurrentContext: false).Add(key, value).Build(); - - /// - /// Create instance from entry. - /// - /// Entry to add to the context. - /// Instance of . - public static CorrelationContext CreateContext(CorrelationContextEntry entry) => - new CorrelationContextBuilder(inheritCurrentContext: false).Add(entry).Build(); - - /// - /// Create instance from entry. - /// - /// List of entries to add to the context. - /// Instance of . - public static CorrelationContext CreateContext(IEnumerable entries) => - new CorrelationContextBuilder(inheritCurrentContext: false).Add(entries).Build(); - - /// - /// Add Distributed Context entry to the builder. - /// - /// Entry to add to the context. - /// The current instance. - public CorrelationContextBuilder Add(CorrelationContextEntry entry) - { - if (DistributedContext.Carrier is NoopDistributedContextCarrier || entry == default) - { - return this; - } - - if (this.entries == null) - { - this.entries = new List(); - } - else - { - for (int i = 0; i < this.entries.Count; i++) - { - if (this.entries[i].Key == entry.Key) - { - this.entries[i] = entry; - return this; - } - } - } - - this.entries.Add(entry); - return this; - } - - /// - /// Add Distributed Context entry to the builder. - /// - /// Entry key. - /// Entry value. - /// Entry metadata. - /// The current instance. - public CorrelationContextBuilder Add(string key, string value, EntryMetadata metadata) - { - return this.Add(new CorrelationContextEntry(key, value, metadata)); - } - - /// - /// Add Distributed Context entry to the builder. - /// - /// Entry key. - /// Entry value. - /// The current instance. - public CorrelationContextBuilder Add(string key, string value) - { - return this.Add(new CorrelationContextEntry(key, value)); - } - - /// - /// Add Distributed Context entry to the builder. - /// - /// List of entries to add to the context. - /// The current instance. - public CorrelationContextBuilder Add(IEnumerable entries) - { - if (DistributedContext.Carrier is NoopDistributedContextCarrier || entries == null) - { - return this; - } - - foreach (var entry in entries) - { - this.Add(entry); - } - - return this; - } - - /// - /// Remove Distributed Context entry from the context. - /// - /// Entry key. - /// The current instance. - public CorrelationContextBuilder Remove(string key) - { - if (key == null || DistributedContext.Carrier is NoopDistributedContextCarrier || this.entries == null) - { - return this; - } - - int index = this.entries.FindIndex(entry => entry.Key == key); - if (index >= 0) - { - this.entries.RemoveAt(index); - } - - return this; - } - - /// - /// Build a Correlation Context from current builder. - /// - /// instance. - public CorrelationContext Build() - { - if (DistributedContext.Carrier is NoopDistributedContextCarrier || this.entries == null) - { - return CorrelationContext.Empty; - } - - var context = new CorrelationContext(this.entries); - this.entries = null; // empty current builder entries. - return context; - } - - /// - public override bool Equals(object obj) - { - return obj is CorrelationContextBuilder builder && - EqualityComparer>.Default.Equals(this.entries, builder.entries); - } - - /// - public override int GetHashCode() - { - return this.entries.GetHashCode(); - } - - /// - public bool Equals(CorrelationContextBuilder other) - { - return EqualityComparer>.Default.Equals(this.entries, other.entries); - } - } -} diff --git a/src/OpenTelemetry.Api/Context/CorrelationContextEntry.cs b/src/OpenTelemetry.Api/Context/CorrelationContextEntry.cs deleted file mode 100644 index 3c36648fba0..00000000000 --- a/src/OpenTelemetry.Api/Context/CorrelationContextEntry.cs +++ /dev/null @@ -1,125 +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. -// - -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Context -{ - /// - /// Distributed Context entry with the key, value and metadata. - /// - public readonly struct CorrelationContextEntry : System.IEquatable - { - /// - /// Initializes a new instance of the struct with the key and value. - /// - /// Key name for the entry. - /// Value associated with the key name. - public CorrelationContextEntry(string key, string value) - : this(key, value, EntryMetadata.NoPropagationEntry) - { - } - - /// - /// Initializes a new instance of the struct with the key, value, and metadata. - /// - /// Key name for the entry. - /// Value associated with the key name. - /// Entry metadata. - public CorrelationContextEntry(string key, string value, in EntryMetadata metadata) - { - if (key == null) - { - this.Key = string.Empty; - OpenTelemetryApiEventSource.Log.InvalidArgument(nameof(CorrelationContextEntry), nameof(key), "is null"); - } - else - { - this.Key = key; - } - - if (value == null) - { - this.Value = string.Empty; - OpenTelemetryApiEventSource.Log.InvalidArgument(nameof(CorrelationContextEntry), nameof(value), "is null"); - } - else - { - this.Value = value; - } - - this.Metadata = metadata; - } - - /// - /// Gets the tag key. - /// - public string Key { get; } - - /// - /// Gets the tag value. - /// - public string Value { get; } - - /// - /// Gets the metadata associated with this entry. - /// - public EntryMetadata Metadata { get; } - - /// - /// Compare two entries of for equality. - /// - /// First Entry to compare. - /// Second Entry to compare. - public static bool operator ==(CorrelationContextEntry entry1, CorrelationContextEntry entry2) => entry1.Equals(entry2); - - /// - /// Compare two entries of for not equality. - /// - /// First Entry to compare. - /// Second Entry to compare. - public static bool operator !=(CorrelationContextEntry entry1, CorrelationContextEntry entry2) => !entry1.Equals(entry2); - - /// - public override bool Equals(object obj) - { - return obj is CorrelationContextEntry that && this.Key == that.Key && this.Value == that.Value; - } - - /// - public override string ToString() - { - return $"{nameof(CorrelationContextEntry)}{{{nameof(this.Key)}={this.Key}, {nameof(this.Value)}={this.Value}}}"; - } - - /// - public override int GetHashCode() - { - var h = 1; - h *= 1000003; - h ^= this.Key.GetHashCode(); - h *= 1000003; - h ^= this.Value.GetHashCode(); - return h; - } - - /// - public bool Equals(CorrelationContextEntry other) - { - return this.Key == other.Key && this.Value == other.Value; - } - } -} diff --git a/src/OpenTelemetry.Api/Context/DistributedContext.cs b/src/OpenTelemetry.Api/Context/DistributedContext.cs deleted file mode 100644 index a2f95d8b1f4..00000000000 --- a/src/OpenTelemetry.Api/Context/DistributedContext.cs +++ /dev/null @@ -1,117 +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. -// - -using System; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Context -{ - /// - /// Distributed context. - /// - public readonly struct DistributedContext : IEquatable - { - private static DistributedContextCarrier carrier = NoopDistributedContextCarrier.Instance; - private readonly CorrelationContext correlationContext; - - /// - /// Initializes a new instance of the struct. - /// - /// The correlation context. - internal DistributedContext(CorrelationContext correlationContext) - { - this.correlationContext = correlationContext; - } - - /// - /// Gets empty object of struct. - /// - public static DistributedContext Empty { get; } = new DistributedContext(CorrelationContext.Empty); - - /// - /// Gets the current . - /// - public static DistributedContext Current => carrier.Current; - - /// - /// Gets or sets the default carrier instance of the class. - /// SDK will need to override the value to AsyncLocalDistributedContextCarrier.Instance. - /// - public static DistributedContextCarrier Carrier - { - get => carrier; - set - { - if (value is null) - { - OpenTelemetryApiEventSource.Log.InvalidArgument("set_Carrier", nameof(value), "is null"); - } - - carrier = value ?? NoopDistributedContextCarrier.Instance; - } - } - - /// - /// Gets the for the current distributed context. - /// - public CorrelationContext CorrelationContext => this.correlationContext; - - /// - /// Compare two entries of for equality. - /// - /// First Entry to compare. - /// Second Entry to compare. - public static bool operator ==(DistributedContext left, DistributedContext right) - { - return left.Equals(right); - } - - /// - /// Compare two entries of for equality. - /// - /// First Entry to compare. - /// Second Entry to compare. - public static bool operator !=(DistributedContext left, DistributedContext right) - { - return !(left == right); - } - - /// - /// Sets the current . - /// - /// Context to set as current. - /// Scope object. On disposal - original context will be restored. - public static IDisposable SetCurrent(in DistributedContext context) => carrier.SetCurrent(context); - - /// - public bool Equals(DistributedContext other) - { - return this.CorrelationContext.Equals(other.CorrelationContext); - } - - /// - public override bool Equals(object obj) - { - return obj is DistributedContext context && this.Equals(context); - } - - /// - public override int GetHashCode() - { - return this.correlationContext.GetHashCode(); - } - } -} diff --git a/src/OpenTelemetry.Api/Context/DistributedContextBuilder.cs b/src/OpenTelemetry.Api/Context/DistributedContextBuilder.cs deleted file mode 100644 index 20147ab2565..00000000000 --- a/src/OpenTelemetry.Api/Context/DistributedContextBuilder.cs +++ /dev/null @@ -1,90 +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. -// - -using System; -using System.Collections.Generic; - -namespace OpenTelemetry.Context -{ - /// - /// Distributed context Builder. - /// - public struct DistributedContextBuilder - { - private CorrelationContextBuilder correlationContextBuilder; - - /// - /// Initializes a new instance of the struct. - /// - /// Flag to allow inheriting the current context entries. - public DistributedContextBuilder(bool inheritCurrentContext) - { - this.correlationContextBuilder = new CorrelationContextBuilder(false); - - if (DistributedContext.Carrier is NoopDistributedContextCarrier) - { - return; - } - - if (inheritCurrentContext) - { - this.correlationContextBuilder.Add(DistributedContext.Current.CorrelationContext.Entries); - } - } - - /// - /// Create context. - /// - /// The correlation key. - /// The correlation value. - /// A instance. - public static DistributedContext CreateContext(string key, string value) => - new DistributedContext(new CorrelationContextBuilder(inheritCurrentContext: false).Add(key, value).Build()); - - /// - /// Create context. - /// - /// A list of correlations to create the context with. - /// A instance. - public static DistributedContext CreateContext(IEnumerable entries) => - new DistributedContext(new CorrelationContextBuilder(inheritCurrentContext: false).Add(entries).Build()); - - /// - /// Configures correlations to be used with the context. - /// - /// An used to configure correlations. - /// The current instance. - public DistributedContextBuilder Correlations(Action configureCorrelations) - { - configureCorrelations?.Invoke(this.correlationContextBuilder); - return this; - } - - /// - /// Build a Distributed Context from current builder. - /// - /// instance. - public DistributedContext Build() - { - if (DistributedContext.Carrier is NoopDistributedContextCarrier) - { - return DistributedContext.Empty; - } - - return new DistributedContext(this.correlationContextBuilder.Build()); - } - } -} diff --git a/src/OpenTelemetry.Api/Context/DistributedContextCarrier.cs b/src/OpenTelemetry.Api/Context/DistributedContextCarrier.cs deleted file mode 100644 index ab27dce58a0..00000000000 --- a/src/OpenTelemetry.Api/Context/DistributedContextCarrier.cs +++ /dev/null @@ -1,38 +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. -// - -using System; - -namespace OpenTelemetry.Context -{ - /// - /// Abstraction to the carrier of the DistributedContext.Current object. - /// - public abstract class DistributedContextCarrier - { - /// - /// Gets the current . - /// - public abstract DistributedContext Current { get; } - - /// - /// Sets the current . - /// - /// Context to set as current. - /// Scope object. On disposal - original context will be restored. - public abstract IDisposable SetCurrent(in DistributedContext context); - } -} diff --git a/src/OpenTelemetry.Api/Context/NoopDistributedContextCarrier.cs b/src/OpenTelemetry.Api/Context/NoopDistributedContextCarrier.cs deleted file mode 100644 index b5776760344..00000000000 --- a/src/OpenTelemetry.Api/Context/NoopDistributedContextCarrier.cs +++ /dev/null @@ -1,65 +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. -// - -using System; - -namespace OpenTelemetry.Context -{ - /// - /// No-op Distributed context carrier. - /// - public class NoopDistributedContextCarrier : DistributedContextCarrier - { - /// - /// Initializes a new instance of the class. - /// - private NoopDistributedContextCarrier() - { - } - - /// - /// Gets the instance of . - /// - public static NoopDistributedContextCarrier Instance { get; } = new NoopDistributedContextCarrier(); - - /// - /// Gets the current . - /// - public override DistributedContext Current => DistributedContext.Empty; - - /// - /// Sets the current . - /// - /// Context to set as current. - /// Scope object. On disposal - original context will be restored. - public override IDisposable SetCurrent(in DistributedContext context) => EmptyDisposable.Instance; - - private class EmptyDisposable : IDisposable - { - public static EmptyDisposable Instance { get; } = new EmptyDisposable(); - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - } - } - } -} diff --git a/src/OpenTelemetry.Api/Context/Propagation/BaggageFormat.cs b/src/OpenTelemetry.Api/Context/Propagation/BaggageFormat.cs new file mode 100644 index 00000000000..7fa8afd45da --- /dev/null +++ b/src/OpenTelemetry.Api/Context/Propagation/BaggageFormat.cs @@ -0,0 +1,157 @@ +// +// 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.Linq; +using System.Net; +using System.Text; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Context.Propagation +{ + /// + /// W3C baggage: https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md. + /// + public class BaggageFormat : ITextFormat + { + internal const string BaggageHeaderName = "Baggage"; + + private const int MaxBaggageLength = 8192; + private const int MaxBaggageItems = 180; + + /// + public ISet Fields => new HashSet { BaggageHeaderName }; + + /// + public PropagationContext Extract(PropagationContext context, T carrier, Func> getter) + { + if (context.ActivityBaggage != null) + { + // If baggage has already been extracted, perform a noop. + return context; + } + + if (carrier == null) + { + OpenTelemetryApiEventSource.Log.FailedToExtractBaggage(nameof(BaggageFormat), "null carrier"); + return context; + } + + if (getter == null) + { + OpenTelemetryApiEventSource.Log.FailedToExtractBaggage(nameof(BaggageFormat), "null getter"); + return context; + } + + try + { + IEnumerable> baggage = null; + var baggageCollection = getter(carrier, BaggageHeaderName); + if (baggageCollection?.Any() ?? false) + { + TryExtractBaggage(baggageCollection.ToArray(), out baggage); + } + + return new PropagationContext( + context.ActivityContext, + baggage ?? context.ActivityBaggage); + } + catch (Exception ex) + { + OpenTelemetryApiEventSource.Log.BaggageExtractException(nameof(BaggageFormat), ex); + } + + return context; + } + + /// + public void Inject(PropagationContext context, T carrier, Action setter) + { + if (carrier == null) + { + OpenTelemetryApiEventSource.Log.FailedToInjectBaggage(nameof(BaggageFormat), "null carrier"); + return; + } + + if (setter == null) + { + OpenTelemetryApiEventSource.Log.FailedToInjectBaggage(nameof(BaggageFormat), "null setter"); + return; + } + + using IEnumerator> e = context.ActivityBaggage?.GetEnumerator(); + + if (e?.MoveNext() == true) + { + int itemCount = 1; + StringBuilder baggage = new StringBuilder(); + do + { + KeyValuePair item = e.Current; + baggage.Append(WebUtility.UrlEncode(item.Key)).Append('=').Append(WebUtility.UrlEncode(item.Value)).Append(','); + } + while (e.MoveNext() && itemCount++ < MaxBaggageItems && baggage.Length < MaxBaggageLength); + baggage.Remove(baggage.Length - 1, 1); + setter(carrier, BaggageHeaderName, baggage.ToString()); + } + } + + internal static bool TryExtractBaggage(string[] baggageCollection, out IEnumerable> baggage) + { + int baggageLength = -1; + bool done = false; + Dictionary baggageDictionary = null; + + foreach (var item in baggageCollection) + { + if (done) + { + break; + } + + if (string.IsNullOrEmpty(item)) + { + continue; + } + + foreach (var pair in item.Split(',')) + { + baggageLength += pair.Length + 1; // pair and comma + + if (baggageLength >= MaxBaggageLength || baggageDictionary?.Count >= MaxBaggageItems) + { + done = true; + break; + } + + if (NameValueHeaderValue.TryParse(pair, out NameValueHeaderValue baggageItem)) + { + if (baggageDictionary == null) + { + baggageDictionary = new Dictionary(); + } + + baggageDictionary[baggageItem.Name] = baggageItem.Value; + } + } + } + + baggage = baggageDictionary; + return baggageDictionary != null; + } + } +} diff --git a/src/OpenTelemetry.Api/Context/Propagation/BaseHeaderParser.cs b/src/OpenTelemetry.Api/Context/Propagation/BaseHeaderParser.cs new file mode 100644 index 00000000000..d1b0df2ba38 --- /dev/null +++ b/src/OpenTelemetry.Api/Context/Propagation/BaseHeaderParser.cs @@ -0,0 +1,83 @@ +// +// 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. +// + +namespace OpenTelemetry.Context.Propagation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs + internal abstract class BaseHeaderParser : HttpHeaderParser + { + protected BaseHeaderParser(bool supportsMultipleValues) + : base(supportsMultipleValues) + { + } + + public sealed override bool TryParseValue(string value, ref int index, out T parsedValue) + { + parsedValue = default(T); + + // If multiple values are supported (i.e. list of values), then accept an empty string: The header may + // be added multiple times to the request/response message. E.g. + // Accept: text/xml; q=1 + // Accept: + // Accept: text/plain; q=0.2 + if (string.IsNullOrEmpty(value) || (index == value.Length)) + { + return this.SupportsMultipleValues; + } + + var separatorFound = false; + var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, this.SupportsMultipleValues, out separatorFound); + + if (separatorFound && !this.SupportsMultipleValues) + { + return false; // leading separators not allowed if we don't support multiple values. + } + + if (current == value.Length) + { + if (this.SupportsMultipleValues) + { + index = current; + } + + return this.SupportsMultipleValues; + } + + T result; + var length = this.GetParsedValueLength(value, current, out result); + + if (length == 0) + { + return false; + } + + current = current + length; + current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, this.SupportsMultipleValues, out separatorFound); + + // If we support multiple values and we've not reached the end of the string, then we must have a separator. + if ((separatorFound && !this.SupportsMultipleValues) || (!separatorFound && (current < value.Length))) + { + return false; + } + + index = current; + parsedValue = result; + return true; + } + + protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue); + } +} diff --git a/src/OpenTelemetry.Api/Context/Propagation/CompositePropagator.cs b/src/OpenTelemetry.Api/Context/Propagation/CompositePropagator.cs index ef69bca4490..dda775342a6 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/CompositePropagator.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/CompositePropagator.cs @@ -16,8 +16,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; namespace OpenTelemetry.Context.Propagation { @@ -33,42 +31,32 @@ public class CompositePropagator : ITextFormat /// Initializes a new instance of the class. /// /// List of wire context propagator. - public CompositePropagator(List textFormats) + public CompositePropagator(IEnumerable textFormats) { - this.textFormats = textFormats ?? throw new ArgumentNullException(nameof(textFormats)); + this.textFormats = new List(textFormats ?? throw new ArgumentNullException(nameof(textFormats))); } /// public ISet Fields => EmptyFields; /// - public ActivityContext Extract(ActivityContext activityContext, T carrier, Func> getter) + public PropagationContext Extract(PropagationContext context, T carrier, Func> getter) { foreach (var textFormat in this.textFormats) { - activityContext = textFormat.Extract(activityContext, carrier, getter); - if (activityContext.IsValid()) - { - return activityContext; - } + context = textFormat.Extract(context, carrier, getter); } - return activityContext; + return context; } /// - public void Inject(ActivityContext activityContext, T carrier, Action setter) + public void Inject(PropagationContext context, T carrier, Action setter) { foreach (var textFormat in this.textFormats) { - textFormat.Inject(activityContext, carrier, setter); + textFormat.Inject(context, carrier, setter); } } - - /// - public bool IsInjected(T carrier, Func> getter) - { - return this.textFormats.All(textFormat => textFormat.IsInjected(carrier, getter)); - } } } diff --git a/src/OpenTelemetry.Api/Context/Propagation/EntryPropagationFilter.cs b/src/OpenTelemetry.Api/Context/Propagation/EntryPropagationFilter.cs deleted file mode 100644 index d207ed744a4..00000000000 --- a/src/OpenTelemetry.Api/Context/Propagation/EntryPropagationFilter.cs +++ /dev/null @@ -1,89 +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. -// - -using System; - -namespace OpenTelemetry.Context.Propagation -{ - /// - /// Filter defining propagation rules for . - /// - public readonly struct EntryPropagationFilter - { - /// - /// Initializes a new instance of the struct. - /// - /// Operator to apply. - /// String to apply the operator with. - /// Action to execute on entry. - public EntryPropagationFilter(FilterMatchOperator op, string matchString, Action action) - { - this.Operator = op; - this.MatchString = matchString; - this.Action = action; - } - - /// - /// Operator to use in a filter. - /// - public enum FilterMatchOperator - { - /// - /// Equals operator. - /// - Equal, - - /// - /// Not equals operator. - /// - NotEqual, - - /// - /// Operator checking the prefix. - /// - HasPrefix, - } - - internal FilterMatchOperator Operator { get; } - - internal string MatchString { get; } - - internal Action Action { get; } - - /// - /// Check whether matches this filter pattern. - /// - /// Distributed Context entry to check. - /// True if matches this filter, false - otherwise. - public bool IsMatch(CorrelationContextEntry entry) - { - bool result = false; - switch (this.Operator) - { - case FilterMatchOperator.Equal: result = entry.Key.Equals(this.MatchString); break; - case FilterMatchOperator.NotEqual: result = !entry.Key.Equals(this.MatchString); break; - case FilterMatchOperator.HasPrefix: result = entry.Key.StartsWith(this.MatchString, StringComparison.Ordinal); break; - } - - if (result) - { - this.Action(entry); - } - - return result; - } - } -} diff --git a/src/OpenTelemetry.Api/Context/Propagation/GenericHeaderParser.cs b/src/OpenTelemetry.Api/Context/Propagation/GenericHeaderParser.cs new file mode 100644 index 00000000000..6d37a966465 --- /dev/null +++ b/src/OpenTelemetry.Api/Context/Propagation/GenericHeaderParser.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; + +namespace OpenTelemetry.Context.Propagation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs + internal sealed class GenericHeaderParser : BaseHeaderParser + { + private GetParsedValueLengthDelegate getParsedValueLength; + + internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength) + : base(supportsMultipleValues) + { + if (getParsedValueLength == null) + { + throw new ArgumentNullException(nameof(getParsedValueLength)); + } + + this.getParsedValueLength = getParsedValueLength; + } + + internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue); + + protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue) + { + return this.getParsedValueLength(value, startIndex, out parsedValue); + } + } +} diff --git a/src/OpenTelemetry.Api/Context/Propagation/HeaderUtilities.cs b/src/OpenTelemetry.Api/Context/Propagation/HeaderUtilities.cs new file mode 100644 index 00000000000..6c876eeae12 --- /dev/null +++ b/src/OpenTelemetry.Api/Context/Propagation/HeaderUtilities.cs @@ -0,0 +1,54 @@ +// +// 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. +// + +namespace OpenTelemetry.Context.Propagation +{ + // Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs + internal static class HeaderUtilities + { + internal static int GetNextNonEmptyOrWhitespaceIndex( + string input, + int startIndex, + bool skipEmptyValues, + out bool separatorFound) + { + separatorFound = false; + var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex); + + if ((current == input.Length) || (input[current] != ',')) + { + return current; + } + + // If we have a separator, skip the separator and all following whitespaces. If we support + // empty values, continue until the current character is neither a separator nor a whitespace. + separatorFound = true; + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + + if (skipEmptyValues) + { + while ((current < input.Length) && (input[current] == ',')) + { + current++; // skip delimiter. + current = current + HttpRuleParser.GetWhitespaceLength(input, current); + } + } + + return current; + } + } +} diff --git a/src/OpenTelemetry.Api/Context/Propagation/HttpHeaderParser.cs b/src/OpenTelemetry.Api/Context/Propagation/HttpHeaderParser.cs new file mode 100644 index 00000000000..95e5a09c498 --- /dev/null +++ b/src/OpenTelemetry.Api/Context/Propagation/HttpHeaderParser.cs @@ -0,0 +1,40 @@ +// +// 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. +// + +namespace OpenTelemetry.Context.Propagation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs + internal abstract class HttpHeaderParser + { + private bool supportsMultipleValues; + + protected HttpHeaderParser(bool supportsMultipleValues) + { + this.supportsMultipleValues = supportsMultipleValues; + } + + public bool SupportsMultipleValues + { + get { return this.supportsMultipleValues; } + } + + // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index' + // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0 + // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first + // non-whitespace after the separator ','. + public abstract bool TryParseValue(string value, ref int index, out T parsedValue); + } +} diff --git a/src/OpenTelemetry/Context/Propagation/DistributedContextBinarySerializer.cs b/src/OpenTelemetry.Api/Context/Propagation/HttpParseResult.cs similarity index 51% rename from src/OpenTelemetry/Context/Propagation/DistributedContextBinarySerializer.cs rename to src/OpenTelemetry.Api/Context/Propagation/HttpParseResult.cs index e0044af3ff7..3b9831b9e8c 100644 --- a/src/OpenTelemetry/Context/Propagation/DistributedContextBinarySerializer.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/HttpParseResult.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,22 +16,22 @@ namespace OpenTelemetry.Context.Propagation { - internal sealed class DistributedContextBinarySerializer : DistributedContextBinarySerializerBase + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs + internal enum HttpParseResult { - internal DistributedContextBinarySerializer() - { - } + /// + /// Parsed successfully. + /// + Parsed, - /// - public override byte[] ToByteArray(DistributedContext context) - { - return SerializationUtils.SerializeBinary(context); - } + /// + /// Was not parsed. + /// + NotParsed, - /// - public override DistributedContext FromByteArray(byte[] bytes) - { - return SerializationUtils.DeserializeBinary(bytes); - } + /// + /// Invalid format. + /// + InvalidFormat, } } diff --git a/src/OpenTelemetry.Api/Context/Propagation/HttpRuleParser.cs b/src/OpenTelemetry.Api/Context/Propagation/HttpRuleParser.cs new file mode 100644 index 00000000000..3654b87827d --- /dev/null +++ b/src/OpenTelemetry.Api/Context/Propagation/HttpRuleParser.cs @@ -0,0 +1,263 @@ +// +// 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. +// + +namespace OpenTelemetry.Context.Propagation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs + internal static class HttpRuleParser + { + internal const char CR = '\r'; + internal const char LF = '\n'; + internal const char SP = ' '; + internal const char Tab = '\t'; + internal const int MaxInt64Digits = 19; + internal const int MaxInt32Digits = 10; + + private const int MaxNestedCount = 5; + private static readonly bool[] TokenChars = CreateTokenChars(); + + internal static bool IsTokenChar(char character) + { + // Must be between 'space' (32) and 'DEL' (127) + if (character > 127) + { + return false; + } + + return TokenChars[character]; + } + + internal static int GetTokenLength(string input, int startIndex) + { + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + while (current < input.Length) + { + if (!IsTokenChar(input[current])) + { + return current - startIndex; + } + + current++; + } + + return input.Length - startIndex; + } + + internal static int GetWhitespaceLength(string input, int startIndex) + { + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + char c; + while (current < input.Length) + { + c = input[current]; + + if ((c == SP) || (c == Tab)) + { + current++; + continue; + } + + if (c == CR) + { + // If we have a #13 char, it must be followed by #10 and then at least one SP or HT. + if ((current + 2 < input.Length) && (input[current + 1] == LF)) + { + char spaceOrTab = input[current + 2]; + if ((spaceOrTab == SP) || (spaceOrTab == Tab)) + { + current += 3; + continue; + } + } + } + + return current - startIndex; + } + + // All characters between startIndex and the end of the string are LWS characters. + return input.Length - startIndex; + } + + internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length) + { + var nestedCount = 0; + return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length); + } + + // quoted-pair = "\" CHAR + // CHAR = + internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length) + { + length = 0; + + if (input[startIndex] != '\\') + { + return HttpParseResult.NotParsed; + } + + // Quoted-char has 2 characters. Check whether there are 2 chars left ('\' + char) + // If so, check whether the character is in the range 0-127. If not, it's an invalid value. + if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) + { + return HttpParseResult.InvalidFormat; + } + + // We don't care what the char next to '\' is. + length = 2; + return HttpParseResult.Parsed; + } + + private static bool[] CreateTokenChars() + { + // token = 1* + // CTL = + var tokenChars = new bool[128]; // everything is false + + for (int i = 33; i < 127; i++) + { + // skip Space (32) & DEL (127) + tokenChars[i] = true; + } + + // remove separators: these are not valid token characters + tokenChars[(byte)'('] = false; + tokenChars[(byte)')'] = false; + tokenChars[(byte)'<'] = false; + tokenChars[(byte)'>'] = false; + tokenChars[(byte)'@'] = false; + tokenChars[(byte)','] = false; + tokenChars[(byte)';'] = false; + tokenChars[(byte)':'] = false; + tokenChars[(byte)'\\'] = false; + tokenChars[(byte)'"'] = false; + tokenChars[(byte)'/'] = false; + tokenChars[(byte)'['] = false; + tokenChars[(byte)']'] = false; + tokenChars[(byte)'?'] = false; + tokenChars[(byte)'='] = false; + tokenChars[(byte)'{'] = false; + tokenChars[(byte)'}'] = false; + + return tokenChars; + } + + // TEXT = + // LWS = [CRLF] 1*( SP | HT ) + // CTL = + // + // Since we don't really care about the content of a quoted string or comment, we're more tolerant and + // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). + // + // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like + // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested + // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) + // is unusual. + private static HttpParseResult GetExpressionLength( + string input, + int startIndex, + char openChar, + char closeChar, + bool supportsNesting, + ref int nestedCount, + out int length) + { + length = 0; + + if (input[startIndex] != openChar) + { + return HttpParseResult.NotParsed; + } + + var current = startIndex + 1; // Start parsing with the character next to the first open-char + while (current < input.Length) + { + // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. + // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. + var quotedPairLength = 0; + if ((current + 2 < input.Length) && + (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed)) + { + // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, + // but we actually have a quoted-string: e.g. "\u" ('\' followed by a char >127 - quoted-pair only + // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars). + current = current + quotedPairLength; + continue; + } + + // If we support nested expressions and we find an open-char, then parse the nested expressions. + if (supportsNesting && (input[current] == openChar)) + { + nestedCount++; + try + { + // Check if we exceeded the number of nested calls. + if (nestedCount > MaxNestedCount) + { + return HttpParseResult.InvalidFormat; + } + + var nestedLength = 0; + HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength); + + switch (nestedResult) + { + case HttpParseResult.Parsed: + current += nestedLength; // add the length of the nested expression and continue. + break; + + case HttpParseResult.NotParsed: + break; + + case HttpParseResult.InvalidFormat: + // If the nested expression is invalid, we can't continue, so we fail with invalid format. + return HttpParseResult.InvalidFormat; + + default: + break; + } + } + finally + { + nestedCount--; + } + } + + if (input[current] == closeChar) + { + length = current - startIndex + 1; + return HttpParseResult.Parsed; + } + + current++; + } + + // We didn't see the final quote, therefore we have an invalid expression string. + return HttpParseResult.InvalidFormat; + } + } +} diff --git a/src/OpenTelemetry.Api/Context/Propagation/ITextFormat.cs b/src/OpenTelemetry.Api/Context/Propagation/ITextFormat.cs index 135a246412b..f79e5639b3d 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/ITextFormat.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/ITextFormat.cs @@ -13,9 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. // + using System; using System.Collections.Generic; -using System.Diagnostics; namespace OpenTelemetry.Context.Propagation { @@ -36,28 +36,19 @@ public interface ITextFormat /// Injects textual representation of activity context to transmit over the wire. /// /// Type of an object to set context on. Typically HttpRequest or similar. - /// Activity context to transmit over the wire. + /// The default context to transmit over the wire. /// Object to set context on. Instance of this object will be passed to setter. /// Action that will set name and value pair on the object. - void Inject(ActivityContext activityContext, T carrier, Action setter); + void Inject(PropagationContext context, T carrier, Action setter); /// /// Extracts activity context from textual representation. /// /// Type of object to extract context from. Typically HttpRequest or similar. - /// The default activity context to be used if Extract fails. - /// Object to extract context from. Instance of this object will be passed to the getter. - /// Function that will return string value of a key with the specified name. - /// Activity context from it's text representation. - ActivityContext Extract(ActivityContext activityContext, T carrier, Func> getter); - - /// - /// Tests if an activity context has been injected into a carrier. - /// - /// Type of object to extract context from. Typically HttpRequest or similar. + /// The default context to be used if Extract fails. /// Object to extract context from. Instance of this object will be passed to the getter. /// Function that will return string value of a key with the specified name. - /// if the carrier has been injected with an activity context. - bool IsInjected(T carrier, Func> getter); + /// Context from it's text representation. + PropagationContext Extract(PropagationContext context, T carrier, Func> getter); } } diff --git a/src/OpenTelemetry.Api/Context/Propagation/NameValueHeaderValue.cs b/src/OpenTelemetry.Api/Context/Propagation/NameValueHeaderValue.cs new file mode 100644 index 00000000000..8b12553ecf4 --- /dev/null +++ b/src/OpenTelemetry.Api/Context/Propagation/NameValueHeaderValue.cs @@ -0,0 +1,123 @@ +// +// 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. +// + +namespace OpenTelemetry.Context.Propagation +{ + // Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs + internal class NameValueHeaderValue + { + private static readonly HttpHeaderParser SingleValueParser + = new GenericHeaderParser(false, GetNameValueLength); + + private string name; + private string value; + + private NameValueHeaderValue() + { + // Used by the parser to create a new instance of this type. + } + + public string Name + { + get { return this.name; } + } + + public string Value + { + get { return this.value; } + } + + public static bool TryParse(string input, out NameValueHeaderValue parsedValue) + { + var index = 0; + return SingleValueParser.TryParseValue(input, ref index, out parsedValue); + } + + internal static int GetValueLength(string input, int startIndex) + { + if (startIndex >= input.Length) + { + return 0; + } + + var valueLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (valueLength == 0) + { + // A value can either be a token or a quoted string. Check if it is a quoted string. + if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed) + { + // We have an invalid value. Reset the name and return. + return 0; + } + } + + return valueLength; + } + + private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue) + { + parsedValue = null; + + if (string.IsNullOrEmpty(input) || (startIndex >= input.Length)) + { + return 0; + } + + // Parse the name, i.e. in name/value string "=". Caller must remove + // leading whitespaces. + var nameLength = HttpRuleParser.GetTokenLength(input, startIndex); + + if (nameLength == 0) + { + return 0; + } + + var name = input.Substring(startIndex, nameLength); + var current = startIndex + nameLength; + current += HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the separator between name and value + if ((current == input.Length) || (input[current] != '=')) + { + // We only have a name and that's OK. Return. + parsedValue = new NameValueHeaderValue + { + name = name, + }; + current += HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + + current++; // skip delimiter. + current += HttpRuleParser.GetWhitespaceLength(input, current); + + // Parse the value, i.e. in name/value string "=" + int valueLength = GetValueLength(input, current); + + // Value after the '=' may be empty + // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation. + parsedValue = new NameValueHeaderValue + { + name = name, + value = input.Substring(current, valueLength), + }; + current += valueLength; + current += HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces + return current - startIndex; + } + } +} diff --git a/src/OpenTelemetry.Api/Context/Propagation/PropagationContext.cs b/src/OpenTelemetry.Api/Context/Propagation/PropagationContext.cs new file mode 100644 index 00000000000..ecaeea64b93 --- /dev/null +++ b/src/OpenTelemetry.Api/Context/Propagation/PropagationContext.cs @@ -0,0 +1,110 @@ +// +// 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.Diagnostics; +using System.Linq; + +namespace OpenTelemetry.Context.Propagation +{ + /// + /// Stores propagation data. + /// + public readonly struct PropagationContext : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Entries for activity context. + /// Entries for activity baggage. + public PropagationContext(ActivityContext activityContext, IEnumerable> activityBaggage) + { + this.ActivityContext = activityContext; + this.ActivityBaggage = activityBaggage; + } + + /// + /// Gets . + /// + public ActivityContext ActivityContext { get; } + + /// + /// Gets ActivityBaggage. + /// + public IEnumerable> ActivityBaggage { get; } + + /// + /// Compare two entries of for equality. + /// + /// First Entry to compare. + /// Second Entry to compare. + public static bool operator ==(PropagationContext left, PropagationContext right) => left.Equals(right); + + /// + /// Compare two entries of for not equality. + /// + /// First Entry to compare. + /// Second Entry to compare. + public static bool operator !=(PropagationContext left, PropagationContext right) => !(left == right); + + /// + public bool Equals(PropagationContext value) + { + if (this.ActivityContext != value.ActivityContext + || this.ActivityBaggage is null != value.ActivityBaggage is null) + { + return false; + } + + if (this.ActivityBaggage is null) + { + return true; + } + + if (this.ActivityBaggage.Count() != value.ActivityBaggage.Count()) + { + return false; + } + + var thisEnumerator = this.ActivityBaggage.GetEnumerator(); + var valueEnumerator = value.ActivityBaggage.GetEnumerator(); + + while (thisEnumerator.MoveNext() && valueEnumerator.MoveNext()) + { + if (thisEnumerator.Current.Key != valueEnumerator.Current.Key + || thisEnumerator.Current.Value != valueEnumerator.Current.Value) + { + return false; + } + } + + return true; + } + + /// + public override bool Equals(object obj) => (obj is PropagationContext context) && this.Equals(context); + + /// + public override int GetHashCode() + { + var hashCode = 323591981; + hashCode = (hashCode * -1521134295) + this.ActivityContext.GetHashCode(); + hashCode = (hashCode * -1521134295) + EqualityComparer>>.Default.GetHashCode(this.ActivityBaggage); + return hashCode; + } + } +} diff --git a/src/OpenTelemetry.Api/Context/Propagation/TraceContextFormat.cs b/src/OpenTelemetry.Api/Context/Propagation/TraceContextFormat.cs index 5e50250d47f..971b4bb4d1f 100644 --- a/src/OpenTelemetry.Api/Context/Propagation/TraceContextFormat.cs +++ b/src/OpenTelemetry.Api/Context/Propagation/TraceContextFormat.cs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // + using System; using System.Collections.Generic; using System.Diagnostics; @@ -42,49 +43,24 @@ public class TraceContextFormat : ITextFormat public ISet Fields => new HashSet { TraceState, TraceParent }; /// - public bool IsInjected(T carrier, Func> getter) + public PropagationContext Extract(PropagationContext context, T carrier, Func> getter) { - if (carrier == null) - { - OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null carrier"); - return false; - } - - if (getter == null) + if (context.ActivityContext.IsValid()) { - OpenTelemetryApiEventSource.Log.FailedToExtractContext("null getter"); - return false; + // If a valid context has already been extracted, perform a noop. + return context; } - try - { - var traceparentCollection = getter(carrier, TraceParent); - - // There must be a single traceparent - return traceparentCollection != null && traceparentCollection.Count() == 1; - } - catch (Exception ex) - { - OpenTelemetryApiEventSource.Log.ActivityContextExtractException(ex); - } - - // in case of exception indicate to upstream that there is no parseable context from the top - return false; - } - - /// - public ActivityContext Extract(ActivityContext activityContext, T carrier, Func> getter) - { if (carrier == null) { - OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null carrier"); - return activityContext; + OpenTelemetryApiEventSource.Log.FailedToExtractActivityContext(nameof(TraceContextFormat), "null carrier"); + return context; } if (getter == null) { - OpenTelemetryApiEventSource.Log.FailedToExtractContext("null getter"); - return activityContext; + OpenTelemetryApiEventSource.Log.FailedToExtractActivityContext(nameof(TraceContextFormat), "null getter"); + return context; } try @@ -94,7 +70,7 @@ public ActivityContext Extract(ActivityContext activityContext, T carrier, Fu // There must be a single traceparent if (traceparentCollection == null || traceparentCollection.Count() != 1) { - return activityContext; + return context; } var traceparent = traceparentCollection.First(); @@ -102,54 +78,56 @@ public ActivityContext Extract(ActivityContext activityContext, T carrier, Fu if (!traceparentParsed) { - return activityContext; + return context; } - string tracestate = string.Empty; + string tracestate = null; var tracestateCollection = getter(carrier, TraceState); if (tracestateCollection?.Any() ?? false) { TryExtractTracestate(tracestateCollection.ToArray(), out tracestate); } - return new ActivityContext(traceId, spanId, traceoptions, tracestate, isRemote: true); + return new PropagationContext( + new ActivityContext(traceId, spanId, traceoptions, tracestate, isRemote: true), + context.ActivityBaggage); } catch (Exception ex) { - OpenTelemetryApiEventSource.Log.ActivityContextExtractException(ex); + OpenTelemetryApiEventSource.Log.ActivityContextExtractException(nameof(TraceContextFormat), ex); } // in case of exception indicate to upstream that there is no parseable context from the top - return activityContext; + return context; } /// - public void Inject(ActivityContext activityContext, T carrier, Action setter) + public void Inject(PropagationContext context, T carrier, Action setter) { - if (activityContext == default) + if (context.ActivityContext.TraceId == default || context.ActivityContext.SpanId == default) { - OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("Invalid context"); + OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext(nameof(TraceContextFormat), "Invalid context"); return; } if (carrier == null) { - OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null carrier"); + OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext(nameof(TraceContextFormat), "null carrier"); return; } if (setter == null) { - OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null setter"); + OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext(nameof(TraceContextFormat), "null setter"); return; } - var traceparent = string.Concat("00-", activityContext.TraceId.ToHexString(), "-", activityContext.SpanId.ToHexString()); - traceparent = string.Concat(traceparent, (activityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0 ? "-01" : "-00"); + var traceparent = string.Concat("00-", context.ActivityContext.TraceId.ToHexString(), "-", context.ActivityContext.SpanId.ToHexString()); + traceparent = string.Concat(traceparent, (context.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0 ? "-01" : "-00"); setter(carrier, TraceParent, traceparent); - string tracestateStr = activityContext.TraceState; + string tracestateStr = context.ActivityContext.TraceState; if (tracestateStr?.Length > 0) { setter(carrier, TraceState, tracestateStr); diff --git a/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs b/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs index 2bfe05b330e..88739bc4d33 100644 --- a/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs +++ b/src/OpenTelemetry.Api/Internal/OpenTelemetryApiEventSource.cs @@ -28,11 +28,20 @@ internal class OpenTelemetryApiEventSource : EventSource public static OpenTelemetryApiEventSource Log = new OpenTelemetryApiEventSource(); [NonEvent] - public void ActivityContextExtractException(Exception ex) + public void ActivityContextExtractException(string format, Exception ex) { if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) { - this.FailedToExtractActivityContext(ex.ToInvariantString()); + this.FailedToExtractActivityContext(format, ex.ToInvariantString()); + } + } + + [NonEvent] + public void BaggageExtractException(string format, Exception ex) + { + if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) + { + this.FailedToExtractBaggage(format, ex.ToInvariantString()); } } @@ -93,22 +102,28 @@ public void InvalidArgument(string methodName, string argumentName, string issue this.WriteEvent(7, methodName, argumentName, issue); } - [Event(8, Message = "Failed to extract activity context: '{0}'", Level = EventLevel.Warning)] - public void FailedToExtractActivityContext(string exception) + [Event(8, Message = "Failed to extract activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)] + public void FailedToExtractActivityContext(string format, string exception) + { + this.WriteEvent(8, format, exception); + } + + [Event(9, Message = "Failed to inject activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)] + public void FailedToInjectActivityContext(string format, string error) { - this.WriteEvent(8, exception); + this.WriteEvent(9, format, error); } - [Event(9, Message = "Failed to inject activity context: '{0}'", Level = EventLevel.Warning)] - public void FailedToInjectActivityContext(string error) + [Event(10, Message = "Failed to extract baggage in format: '{0}', baggage: '{1}'.", Level = EventLevel.Warning)] + public void FailedToExtractBaggage(string format, string exception) { - this.WriteEvent(9, error); + this.WriteEvent(10, format, exception); } - [Event(10, Message = "Failed to extract span context: '{0}'", Level = EventLevel.Warning)] - public void FailedToExtractContext(string error) + [Event(11, Message = "Failed to inject baggage in format: '{0}', baggage: '{1}'.", Level = EventLevel.Warning)] + public void FailedToInjectBaggage(string format, string error) { - this.WriteEvent(10, error); + this.WriteEvent(11, format, error); } } } diff --git a/src/OpenTelemetry.Api/Metrics/BoundCounterMetric.cs b/src/OpenTelemetry.Api/Metrics/BoundCounterMetric.cs index 2cdbdc84b9f..5e04dffe074 100644 --- a/src/OpenTelemetry.Api/Metrics/BoundCounterMetric.cs +++ b/src/OpenTelemetry.Api/Metrics/BoundCounterMetric.cs @@ -38,6 +38,6 @@ public abstract class BoundCounterMetric /// /// the associated distributed context. /// value by which the bound counter metric should be added. - public abstract void Add(in DistributedContext context, T value); + public abstract void Add(in CorrelationContext context, T value); } } diff --git a/src/OpenTelemetry.Api/Metrics/BoundMeasureMetric.cs b/src/OpenTelemetry.Api/Metrics/BoundMeasureMetric.cs index 37576d05eca..d5126e2e010 100644 --- a/src/OpenTelemetry.Api/Metrics/BoundMeasureMetric.cs +++ b/src/OpenTelemetry.Api/Metrics/BoundMeasureMetric.cs @@ -38,6 +38,6 @@ public abstract class BoundMeasureMetric /// /// the associated distributed context. /// the measurement to be recorded. - public abstract void Record(in DistributedContext context, T value); + public abstract void Record(in CorrelationContext context, T value); } } diff --git a/src/OpenTelemetry.Api/Metrics/CounterMetric.cs b/src/OpenTelemetry.Api/Metrics/CounterMetric.cs index f83565dc7e3..9da617fca8c 100644 --- a/src/OpenTelemetry.Api/Metrics/CounterMetric.cs +++ b/src/OpenTelemetry.Api/Metrics/CounterMetric.cs @@ -49,7 +49,7 @@ public abstract class CounterMetric /// the associated distributed context. /// value by which the counter should be incremented. /// The labelset associated with this value. - public abstract void Add(in DistributedContext context, T value, LabelSet labelset); + public abstract void Add(in CorrelationContext context, T value, LabelSet labelset); /// /// Adds or Increments the counter. @@ -57,7 +57,7 @@ public abstract class CounterMetric /// the associated distributed context. /// value by which the counter should be incremented. /// The labels or dimensions associated with this value. - public abstract void Add(in DistributedContext context, T value, IEnumerable> labels); + public abstract void Add(in CorrelationContext context, T value, IEnumerable> labels); /// /// Gets the bound counter metric with given labelset. diff --git a/src/OpenTelemetry.Api/Metrics/MeasureMetric.cs b/src/OpenTelemetry.Api/Metrics/MeasureMetric.cs index 1e23d1e6619..153b2497d40 100644 --- a/src/OpenTelemetry.Api/Metrics/MeasureMetric.cs +++ b/src/OpenTelemetry.Api/Metrics/MeasureMetric.cs @@ -49,7 +49,7 @@ public abstract class MeasureMetric /// the associated distributed context. /// value to record. /// The labelset associated with this value. - public void Record(in DistributedContext context, T value, LabelSet labelset) => this.Bind(labelset).Record(context, value); + public void Record(in CorrelationContext context, T value, LabelSet labelset) => this.Bind(labelset).Record(context, value); /// /// Records a measure. @@ -57,7 +57,7 @@ public abstract class MeasureMetric /// the associated distributed context. /// value to record. /// The labels or dimensions associated with this value. - public void Record(in DistributedContext context, T value, IEnumerable> labels) => this.Bind(labels).Record(context, value); + public void Record(in CorrelationContext context, T value, IEnumerable> labels) => this.Bind(labels).Record(context, value); /// /// Gets the bound measure metric with given labelset. diff --git a/src/OpenTelemetry.Api/Metrics/NoopBoundCounterMetric.cs b/src/OpenTelemetry.Api/Metrics/NoopBoundCounterMetric.cs index d3e3bc453a9..6acfce58d19 100644 --- a/src/OpenTelemetry.Api/Metrics/NoopBoundCounterMetric.cs +++ b/src/OpenTelemetry.Api/Metrics/NoopBoundCounterMetric.cs @@ -37,7 +37,7 @@ public override void Add(in SpanContext context, T value) } /// - public override void Add(in DistributedContext context, T value) + public override void Add(in CorrelationContext context, T value) { } } diff --git a/src/OpenTelemetry.Api/Metrics/NoopBoundMeasureMetric.cs b/src/OpenTelemetry.Api/Metrics/NoopBoundMeasureMetric.cs index 4d8391a466c..66aef213e5c 100644 --- a/src/OpenTelemetry.Api/Metrics/NoopBoundMeasureMetric.cs +++ b/src/OpenTelemetry.Api/Metrics/NoopBoundMeasureMetric.cs @@ -37,7 +37,7 @@ public override void Record(in SpanContext context, T value) } /// - public override void Record(in DistributedContext context, T value) + public override void Record(in CorrelationContext context, T value) { } } diff --git a/src/OpenTelemetry.Api/Metrics/NoopCounterMetric.cs b/src/OpenTelemetry.Api/Metrics/NoopCounterMetric.cs index f5642fda6c8..de95886dd8e 100644 --- a/src/OpenTelemetry.Api/Metrics/NoopCounterMetric.cs +++ b/src/OpenTelemetry.Api/Metrics/NoopCounterMetric.cs @@ -43,12 +43,12 @@ public override void Add(in SpanContext context, T value, IEnumerable - public override void Add(in DistributedContext context, T value, LabelSet labelset) + public override void Add(in CorrelationContext context, T value, LabelSet labelset) { } /// - public override void Add(in DistributedContext context, T value, IEnumerable> labels) + public override void Add(in CorrelationContext context, T value, IEnumerable> labels) { } diff --git a/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs b/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs index 616388b108e..11e97a8e26a 100644 --- a/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs +++ b/src/OpenTelemetry.Api/Trace/TelemetrySpan.cs @@ -30,6 +30,7 @@ public class TelemetrySpan : IDisposable { internal static readonly TelemetrySpan NoopInstance = new TelemetrySpan(null); internal readonly Activity Activity; + private static readonly IEnumerable> EmptyBaggage = new KeyValuePair[0]; internal TelemetrySpan(Activity activity) { @@ -65,6 +66,11 @@ public bool IsRecording } } + /// + /// Gets the span baggage. + /// + public IEnumerable> Baggage => this.Activity?.Baggage ?? EmptyBaggage; + /// /// Sets the status of the span execution. /// @@ -272,6 +278,31 @@ public void End(DateTimeOffset endTimestamp) this.Activity?.Stop(); } + /// + /// Retrieves a baggage item. + /// + /// Baggage item key. + /// Retrieved baggage value or if no match was found. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string GetBaggageItem(string key) + { + return this.Activity?.GetBaggageItem(key); + } + + /// + /// Adds a baggage item to the . + /// + /// Baggage item key. + /// Baggage item value. + /// The instance for chaining. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TelemetrySpan AddBaggage(string key, string value) + { + this.Activity?.AddBaggage(key, value); + + return this; + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() diff --git a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs index 7538298403d..80b57e3d655 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs @@ -26,18 +26,17 @@ namespace OpenTelemetry.Instrumentation.AspNet public class AspNetInstrumentationOptions { /// - /// Gets or sets for context propagation. + /// Gets or sets for context propagation. Default value: with & . /// - public ITextFormat TextFormat { get; set; } = new TraceContextFormat(); + public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[] + { + new TraceContextFormat(), + new BaggageFormat(), + }); /// /// Gets or sets a hook to exclude calls based on domain or other per-request criterion. /// - internal Predicate RequestFilter { get; set; } = DefaultFilter; - - private static bool DefaultFilter(HttpContext httpContext) - { - return true; - } + internal Predicate RequestFilter { get; set; } } } diff --git a/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md index 952aac4a905..5d66dc264ba 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +* Changed the default propagation to support W3C Baggage + ([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048)) + * The default ITextFormat is now `CompositePropagator(TraceContextFormat, + BaggageFormat)`. Baggage sent via the [W3C + Baggage](https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md) + header will now be parsed and set on incoming Http spans. + ## 0.4.0-beta.2 Released 2020-07-24 diff --git a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs index 90f6dd0dbb7..bab7424db80 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs @@ -49,7 +49,7 @@ public override void OnStartActivity(Activity activity, object payload) return; } - if (this.options.RequestFilter != null && !this.options.RequestFilter(context)) + if (this.options.RequestFilter?.Invoke(context) == false) { AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); activity.IsAllDataRequested = false; @@ -61,17 +61,16 @@ public override void OnStartActivity(Activity activity, object payload) if (!(this.options.TextFormat is TraceContextFormat)) { - // This requires to ignore the current activity and create a new one - // using the context extracted using the format TextFormat supports. var ctx = this.options.TextFormat.Extract(default, request, HttpRequestHeaderValuesGetter); - if (ctx != default) + + if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context) { // Create a new activity with its parent set from the extracted context. // This makes the new activity as a "sibling" of the activity created by - // Asp.Net. + // ASP.NET. Activity newOne = new Activity(ActivityNameByHttpInListener); - newOne.SetParentId(ctx.TraceId, ctx.SpanId, ctx.TraceFlags); - newOne.TraceStateString = ctx.TraceState; + newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags); + newOne.TraceStateString = ctx.ActivityContext.TraceState; // Starting the new activity make it the Activity.Current one. newOne.Start(); @@ -83,6 +82,14 @@ public override void OnStartActivity(Activity activity, object payload) activity.SetCustomProperty("ActivityByHttpInListener", newOne); activity = newOne; } + + if (ctx.ActivityBaggage != null) + { + foreach (var baggageItem in ctx.ActivityBaggage) + { + activity.AddBaggage(baggageItem.Key, baggageItem.Value); + } + } } // see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md @@ -114,6 +121,7 @@ public override void OnStartActivity(Activity activity, object payload) public override void OnStopActivity(Activity activity, object payload) { Activity activityToEnrich = activity; + Activity createdActivity = null; if (!(this.options.TextFormat is TraceContextFormat)) { @@ -125,9 +133,10 @@ public override void OnStopActivity(Activity activity, object payload) if (activity.OperationName.Equals("Microsoft.AspNet.HttpReqIn.Start")) { // This block is hit if Asp.Net did restore Current to its own activity, - // then we need to retrieve the one created by HttpInListener - // and populate tags to it. - activityToEnrich = (Activity)activity.GetCustomProperty("ActivityByHttpInListener"); + // and we need to retrieve the one created by HttpInListener, + // or an additional activity was never created. + createdActivity = (Activity)activity.GetCustomProperty("ActivityByHttpInListener"); + activityToEnrich = createdActivity ?? activity; } } @@ -190,13 +199,12 @@ public override void OnStopActivity(Activity activity, object payload) var activityByAspNet = (Activity)activity.GetCustomProperty("ActivityByAspNet"); Activity.Current = activityByAspNet; } - else if (activity.OperationName.Equals("Microsoft.AspNet.HttpReqIn.Start")) + else if (createdActivity != null) { // This block is hit if Asp.Net did restore Current to its own activity, // then we need to retrieve the one created by HttpInListener // and stop it. - var activityByHttpInListener = (Activity)activity.GetCustomProperty("ActivityByHttpInListener"); - activityByHttpInListener.Stop(); + createdActivity.Stop(); // Restore current back to the one created by Asp.Net Activity.Current = activity; diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationOptions.cs index 687774be972..08722ac7437 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/AspNetCoreInstrumentationOptions.cs @@ -26,18 +26,17 @@ namespace OpenTelemetry.Instrumentation.AspNetCore public class AspNetCoreInstrumentationOptions { /// - /// Gets or sets for context propagation. + /// Gets or sets for context propagation. Default value: with & . /// - public ITextFormat TextFormat { get; set; } = new TraceContextFormat(); + public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[] + { + new TraceContextFormat(), + new BaggageFormat(), + }); /// /// Gets or sets a hook to exclude calls based on domain or other per-request criterion. /// - internal Predicate RequestFilter { get; set; } = DefaultFilter; - - private static bool DefaultFilter(HttpContext httpContext) - { - return true; - } + internal Predicate RequestFilter { get; set; } } } diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md index a368dbda2ba..43e1fbc1af9 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/CHANGELOG.md @@ -2,12 +2,18 @@ ## Unreleased +* Changed the default propagation to support W3C Baggage + ([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048)) + * The default ITextFormat is now `CompositePropagator(TraceContextFormat, + BaggageFormat)`. Baggage sent via the [W3C + Baggage](https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md) + header will now be parsed and set on incoming Http spans. * Introduced support for Grpc.AspNetCore (#803). * Attributes are added to gRPC invocations: `rpc.system`, `rpc.service`, `rpc.method`. These attributes are added to an existing span generated by - the instrumentation. This is unlike the instrumentation for client-side - gRPC calls where one span is created for the gRPC call and a separate span - is created for the underlying HTTP call in the event both gRPC and HTTP + the instrumentation. This is unlike the instrumentation for client-side gRPC + calls where one span is created for the gRPC call and a separate span is + created for the underlying HTTP call in the event both gRPC and HTTP instrumentation are enabled. ## 0.4.0-beta.2 diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs index 524fa426d1a..58c07e5f8a3 100644 --- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs @@ -57,7 +57,7 @@ public override void OnStartActivity(Activity activity, object payload) return; } - if (this.options.RequestFilter != null && !this.options.RequestFilter(context)) + if (this.options.RequestFilter?.Invoke(context) == false) { AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); activity.IsAllDataRequested = false; @@ -67,24 +67,29 @@ public override void OnStartActivity(Activity activity, object payload) var request = context.Request; if (!this.hostingSupportsW3C || !(this.options.TextFormat is TraceContextFormat)) { - // This requires to ignore the current activity and create a new one - // using the context extracted from w3ctraceparent header or - // using the format TextFormat supports. - var ctx = this.options.TextFormat.Extract(default, request, HttpRequestHeaderValuesGetter); - if (ctx != default) + + if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context) { // Create a new activity with its parent set from the extracted context. // This makes the new activity as a "sibling" of the activity created by // Asp.Net Core. Activity newOne = new Activity(ActivityNameByHttpInListener); - newOne.SetParentId(ctx.TraceId, ctx.SpanId, ctx.TraceFlags); - newOne.TraceStateString = ctx.TraceState; + newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags); + newOne.TraceStateString = ctx.ActivityContext.TraceState; // Starting the new activity make it the Activity.Current one. newOne.Start(); activity = newOne; } + + if (ctx.ActivityBaggage != null) + { + foreach (var baggageItem in ctx.ActivityBaggage) + { + activity.AddBaggage(baggageItem.Key, baggageItem.Value); + } + } } activity.SetKind(ActivityKind.Server); diff --git a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md index b7781620eed..df3a092b994 100644 --- a/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.Http/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +* Changed the default propagation to support W3C Baggage + ([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048)) + * The default ITextFormat is now `CompositePropagator(TraceContextFormat, + BaggageFormat)`. Outgoing Http request will now send Baggage using the [W3C + Baggage](https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md) + header. Previously Baggage was sent using the `Correlation-Context` header, + which is now outdated. * Removed `AddHttpInstrumentation` and `AddHttpWebRequestInstrumentation` (.NET Framework) `TracerProviderBuilderExtensions`. `AddHttpClientInstrumentation` will now register `HttpClient` instrumentation on .NET Core and `HttpClient` + diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs index 5e29c2d81a1..a7a5e4ba108 100644 --- a/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.Http/HttpClientInstrumentationOptions.cs @@ -31,9 +31,13 @@ public class HttpClientInstrumentationOptions public bool SetHttpFlavor { get; set; } /// - /// Gets or sets for context propagation. Default value: . + /// Gets or sets for context propagation. Default value: with & . /// - public ITextFormat TextFormat { get; set; } = new TraceContextFormat(); + public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[] + { + new TraceContextFormat(), + new BaggageFormat(), + }); /// /// Gets or sets an optional callback method for filtering requests that are sent through the instrumentation. diff --git a/src/OpenTelemetry.Instrumentation.Http/HttpWebRequestInstrumentationOptions.netfx.cs b/src/OpenTelemetry.Instrumentation.Http/HttpWebRequestInstrumentationOptions.netfx.cs index 1a2799a5b30..1530fac156c 100644 --- a/src/OpenTelemetry.Instrumentation.Http/HttpWebRequestInstrumentationOptions.netfx.cs +++ b/src/OpenTelemetry.Instrumentation.Http/HttpWebRequestInstrumentationOptions.netfx.cs @@ -32,9 +32,13 @@ public class HttpWebRequestInstrumentationOptions public bool SetHttpFlavor { get; set; } /// - /// Gets or sets for context propagation. Default value: . + /// Gets or sets for context propagation. Default value: with & . /// - public ITextFormat TextFormat { get; set; } = new TraceContextFormat(); + public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[] + { + new TraceContextFormat(), + new BaggageFormat(), + }); /// /// Gets or sets an optional callback method for filtering requests that are sent through the instrumentation. diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs index 05c2c5efcf4..c30c1ccd28d 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpHandlerDiagnosticListener.cs @@ -81,7 +81,7 @@ public override void OnStartActivity(Activity activity, object payload) return; } - if (this.options.TextFormat.IsInjected(request, HttpRequestMessageHeaderValuesGetter)) + if (this.options.TextFormat.Extract(default, request, HttpRequestMessageHeaderValuesGetter) != default) { // this request is already instrumented, we should back off activity.IsAllDataRequested = false; @@ -107,7 +107,7 @@ public override void OnStartActivity(Activity activity, object payload) if (!(this.httpClientSupportsW3C && this.options.TextFormat is TraceContextFormat)) { - this.options.TextFormat.Inject(activity.Context, request, HttpRequestMessageHeaderValueSetter); + this.options.TextFormat.Inject(new PropagationContext(activity.Context, activity.Baggage), request, HttpRequestMessageHeaderValueSetter); } } diff --git a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs index 9e910157f82..62e3634f07f 100644 --- a/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs +++ b/src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs @@ -23,6 +23,7 @@ using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Text; +using OpenTelemetry.Context.Propagation; using OpenTelemetry.Trace; namespace OpenTelemetry.Instrumentation.Http.Implementation @@ -44,8 +45,6 @@ internal static class HttpWebRequestActivitySource internal static HttpWebRequestInstrumentationOptions Options = new HttpWebRequestInstrumentationOptions(); - private const string CorrelationContextHeaderName = "Correlation-Context"; - private static readonly Version Version = typeof(HttpWebRequestActivitySource).Assembly.GetName().Version; private static readonly ActivitySource WebRequestActivitySource = new ActivitySource(ActivitySourceName, Version.ToString()); @@ -188,32 +187,11 @@ private static void AddExceptionTags(Exception exception, Activity activity) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void InstrumentRequest(HttpWebRequest request, Activity activity) - { - Options.TextFormat.Inject(activity.Context, request, HttpWebRequestHeaderValuesSetter); - - if (request.Headers.Get(CorrelationContextHeaderName) == null) - { - // we expect baggage to be empty or contain a few items - using IEnumerator> e = activity.Baggage.GetEnumerator(); - - if (e.MoveNext()) - { - StringBuilder baggage = new StringBuilder(); - do - { - KeyValuePair item = e.Current; - baggage.Append(WebUtility.UrlEncode(item.Key)).Append('=').Append(WebUtility.UrlEncode(item.Value)).Append(','); - } - while (e.MoveNext()); - baggage.Remove(baggage.Length - 1, 1); - request.Headers.Add(CorrelationContextHeaderName, baggage.ToString()); - } - } - } + => Options.TextFormat.Inject(new PropagationContext(activity.Context, activity.Baggage), request, HttpWebRequestHeaderValuesSetter); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsRequestInstrumented(HttpWebRequest request) - => Options.TextFormat.IsInjected(request, HttpWebRequestHeaderValuesGetter); + => Options.TextFormat.Extract(default, request, HttpWebRequestHeaderValuesGetter) != default; private static void ProcessRequest(HttpWebRequest request) { diff --git a/src/OpenTelemetry.Shims.OpenTracing/SpanContextShim.cs b/src/OpenTelemetry.Shims.OpenTracing/SpanContextShim.cs index 6215c09ca53..38109683774 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/SpanContextShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/SpanContextShim.cs @@ -22,7 +22,9 @@ namespace OpenTelemetry.Shims.OpenTracing { public sealed class SpanContextShim : ISpanContext { - public SpanContextShim(in Trace.SpanContext spanContext) + private readonly IEnumerable> baggage; + + public SpanContextShim(in Trace.SpanContext spanContext, IEnumerable> baggage = null) { if (!spanContext.IsValid) { @@ -30,6 +32,7 @@ public SpanContextShim(in Trace.SpanContext spanContext) } this.SpanContext = spanContext; + this.baggage = baggage; } public Trace.SpanContext SpanContext { get; private set; } @@ -42,9 +45,6 @@ public SpanContextShim(in Trace.SpanContext spanContext) /// public IEnumerable> GetBaggageItems() - { - // TODO - throw new NotImplementedException(); - } + => this.baggage; } } diff --git a/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs b/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs index b137956977e..6022e9c1cad 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/SpanShim.cs @@ -52,7 +52,7 @@ public SpanShim(TelemetrySpan span) throw new ArgumentException(nameof(this.Span.Context)); } - this.spanContextShim = new SpanContextShim(this.Span.Context); + this.spanContextShim = new SpanContextShim(this.Span.Context, this.Span.Baggage); } public ISpanContext Context => this.spanContextShim; diff --git a/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs b/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs index a75ed3a5f1c..fb232e39803 100644 --- a/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs +++ b/src/OpenTelemetry.Shims.OpenTracing/TracerShim.cs @@ -60,7 +60,7 @@ public TracerShim(Trace.Tracer tracer, ITextFormat textFormat) throw new ArgumentNullException(nameof(carrier)); } - ActivityContext activityContext = default; + PropagationContext propagationContext = default; if ((format == BuiltinFormats.TextMap || format == BuiltinFormats.HttpHeaders) && carrier is ITextMap textMapCarrier) { @@ -71,7 +71,7 @@ public TracerShim(Trace.Tracer tracer, ITextFormat textFormat) carrierMap.Add(entry.Key, new[] { entry.Value }); } - IEnumerable GetCarrierKeyValue(Dictionary> source, string key) + static IEnumerable GetCarrierKeyValue(Dictionary> source, string key) { if (key == null || !source.TryGetValue(key, out var value)) { @@ -81,10 +81,10 @@ IEnumerable GetCarrierKeyValue(Dictionary> s return value; } - activityContext = this.textFormat.Extract(default, carrierMap, GetCarrierKeyValue); + propagationContext = this.textFormat.Extract(propagationContext, carrierMap, GetCarrierKeyValue); } - return !activityContext.IsValid() ? null : new SpanContextShim(new Trace.SpanContext(activityContext)); + return !propagationContext.ActivityContext.IsValid() ? null : new SpanContextShim(new Trace.SpanContext(propagationContext.ActivityContext), propagationContext.ActivityBaggage); } /// @@ -115,7 +115,10 @@ public void Inject( if ((format == BuiltinFormats.TextMap || format == BuiltinFormats.HttpHeaders) && carrier is ITextMap textMapCarrier) { - this.textFormat.Inject(shim.SpanContext, textMapCarrier, (instrumentation, key, value) => instrumentation.Set(key, value)); + this.textFormat.Inject( + new PropagationContext(shim.SpanContext, shim.GetBaggageItems()), + textMapCarrier, + (instrumentation, key, value) => instrumentation.Set(key, value)); } } } diff --git a/src/OpenTelemetry/Context/AsyncLocalDistributedContextCarrier.cs b/src/OpenTelemetry/Context/AsyncLocalDistributedContextCarrier.cs deleted file mode 100644 index 315dd3f73f3..00000000000 --- a/src/OpenTelemetry/Context/AsyncLocalDistributedContextCarrier.cs +++ /dev/null @@ -1,102 +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. -// - -using System; -#if NET452 -using System.Collections; -using System.Reflection; -using System.Runtime.Remoting.Messaging; -#endif -using System.Threading; - -namespace OpenTelemetry.Context -{ - /// - /// Distributed Context carrier using AsyncLocal. - /// - public sealed class AsyncLocalDistributedContextCarrier : DistributedContextCarrier - { -#if NET452 - // A special workaround to suppress context propagation cross AppDomains. - // - // By default the value added to System.Runtime.Remoting.Messaging.CallContext - // will be marshalled/unmarshalled across AppDomain boundary. This will cause - // serious issue if the destination AppDomain doesn't have the corresponding type - // to unmarshal data (which is DistributedContext in this case). - // The worst case is AppDomain crash with ReflectionLoadTypeException. - // - // The workaround is to use a well known type that exists in all AppDomains, and - // put the actual payload (DistributedContext instance) as a non-public field so - // the field is ignored during marshalling. - private const string ContextSlotName = "OpenTelemetry.DistributedContext"; - private static readonly FieldInfo WrapperField = typeof(BitArray).GetField("_syncRoot", BindingFlags.Instance | BindingFlags.NonPublic); -#else - private static AsyncLocal carrier = new AsyncLocal(); -#endif - - private AsyncLocalDistributedContextCarrier() - { - this.OverwriteCurrent(DistributedContext.Empty); - } - - /// - /// Gets the instance of . - /// - public static DistributedContextCarrier Instance { get; } = new AsyncLocalDistributedContextCarrier(); - - /// - /// Gets the current . - /// - public override DistributedContext Current - { - get - { -#if NET452 - var wrapper = CallContext.LogicalGetData(ContextSlotName) as BitArray; - - if (wrapper == null) - { - var context = default(DistributedContext); - this.OverwriteCurrent(context); - return context; - } - - return (DistributedContext)WrapperField.GetValue(wrapper); -#else - return carrier.Value; -#endif - } - } - - /// - /// Sets the current . - /// - /// Context to set as current. - /// Scope object. On disposal - original context will be restored. - public override IDisposable SetCurrent(in DistributedContext context) => new DistributedContextState(in context); - - internal void OverwriteCurrent(in DistributedContext context) - { -#if NET452 - var wrapper = new BitArray(0); - WrapperField.SetValue(wrapper, context); - CallContext.LogicalSetData(ContextSlotName, wrapper); -#else - carrier.Value = context; -#endif - } - } -} diff --git a/src/OpenTelemetry/Context/DistributedContextState.cs b/src/OpenTelemetry/Context/DistributedContextState.cs deleted file mode 100644 index 11a27e0b3e3..00000000000 --- a/src/OpenTelemetry/Context/DistributedContextState.cs +++ /dev/null @@ -1,36 +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. -// - -using System; - -namespace OpenTelemetry.Context -{ - internal struct DistributedContextState : IDisposable - { - private DistributedContext context; - - internal DistributedContextState(in DistributedContext context) - { - this.context = AsyncLocalDistributedContextCarrier.Instance.Current; - ((AsyncLocalDistributedContextCarrier)AsyncLocalDistributedContextCarrier.Instance).OverwriteCurrent(in context); - } - - public void Dispose() - { - ((AsyncLocalDistributedContextCarrier)AsyncLocalDistributedContextCarrier.Instance).OverwriteCurrent(in this.context); - } - } -} diff --git a/src/OpenTelemetry/Context/NoopDistributedContextBinarySerializer.cs b/src/OpenTelemetry/Context/NoopDistributedContextBinarySerializer.cs deleted file mode 100644 index ed98e1f68b8..00000000000 --- a/src/OpenTelemetry/Context/NoopDistributedContextBinarySerializer.cs +++ /dev/null @@ -1,56 +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 !NET452 -using System; -#endif -using OpenTelemetry.Context.Propagation; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Context -{ - public class NoopDistributedContextBinarySerializer : DistributedContextBinarySerializerBase - { - internal static readonly DistributedContextBinarySerializerBase Instance = new NoopDistributedContextBinarySerializer(); -#if NET452 - private static readonly byte[] EmptyByteArray = { }; -#else - private static readonly byte[] EmptyByteArray = Array.Empty(); -#endif - - /// - public override byte[] ToByteArray(DistributedContext context) - { - if (context.CorrelationContext.Entries is null) - { - OpenTelemetrySdkEventSource.Log.FailedToInjectContext("entries are null"); - } - - return EmptyByteArray; - } - - /// - public override DistributedContext FromByteArray(byte[] bytes) - { - if (bytes == null) - { - OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null bytes"); - } - - return DistributedContext.Empty; - } - } -} diff --git a/src/OpenTelemetry/Context/Propagation/B3Format.cs b/src/OpenTelemetry/Context/Propagation/B3Format.cs index 9c2d16ef413..217bbee794a 100644 --- a/src/OpenTelemetry/Context/Propagation/B3Format.cs +++ b/src/OpenTelemetry/Context/Propagation/B3Format.cs @@ -70,95 +70,64 @@ public B3Format(bool singleHeader) public ISet Fields => AllFields; /// - public bool IsInjected(T carrier, Func> getter) + public PropagationContext Extract(PropagationContext context, T carrier, Func> getter) { - if (carrier == null) - { - OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null carrier"); - return false; - } - - if (getter == null) + if (context.ActivityContext.IsValid()) { - OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null getter"); - return false; + // If a valid context has already been extracted, perform a noop. + return context; } - try - { - if (this.singleHeader) - { - var header = getter(carrier, XB3Combined)?.FirstOrDefault(); - return !string.IsNullOrWhiteSpace(header); - } - else - { - var traceIdStr = getter(carrier, XB3TraceId)?.FirstOrDefault(); - var spanIdStr = getter(carrier, XB3SpanId)?.FirstOrDefault(); - - return traceIdStr != null && spanIdStr != null; - } - } - catch (Exception e) - { - OpenTelemetrySdkEventSource.Log.ContextExtractException(e); - return false; - } - } - - /// - public ActivityContext Extract(ActivityContext activityContext, T carrier, Func> getter) - { if (carrier == null) { - OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null carrier"); - return activityContext; + OpenTelemetrySdkEventSource.Log.FailedToExtractActivityContext(nameof(B3Format), "null carrier"); + return context; } if (getter == null) { - OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null getter"); - return activityContext; + OpenTelemetrySdkEventSource.Log.FailedToExtractActivityContext(nameof(B3Format), "null getter"); + return context; } if (this.singleHeader) { - return ExtractFromSingleHeader(activityContext, carrier, getter); + return ExtractFromSingleHeader(context, carrier, getter); } else { - return ExtractFromMultipleHeaders(activityContext, carrier, getter); + return ExtractFromMultipleHeaders(context, carrier, getter); } } /// - public void Inject(ActivityContext activityContext, T carrier, Action setter) + public void Inject(PropagationContext context, T carrier, Action setter) { - if (!activityContext.IsValid()) + if (context.ActivityContext.TraceId == default || context.ActivityContext.SpanId == default) { - OpenTelemetrySdkEventSource.Log.FailedToInjectContext("invalid context"); + OpenTelemetrySdkEventSource.Log.FailedToInjectActivityContext(nameof(B3Format), "invalid context"); return; } if (carrier == null) { - OpenTelemetrySdkEventSource.Log.FailedToInjectContext("null carrier"); + OpenTelemetrySdkEventSource.Log.FailedToInjectActivityContext(nameof(B3Format), "null carrier"); return; } if (setter == null) { - OpenTelemetrySdkEventSource.Log.FailedToInjectContext("null setter"); + OpenTelemetrySdkEventSource.Log.FailedToInjectActivityContext(nameof(B3Format), "null setter"); return; } if (this.singleHeader) { var sb = new StringBuilder(); - sb.Append(activityContext.TraceId.ToHexString()); + sb.Append(context.ActivityContext.TraceId.ToHexString()); sb.Append(XB3CombinedDelimiter); - sb.Append(activityContext.SpanId.ToHexString()); - if ((activityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0) + sb.Append(context.ActivityContext.SpanId.ToHexString()); + if ((context.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0) { sb.Append(XB3CombinedDelimiter); sb.Append(SampledValue); @@ -168,16 +137,16 @@ public void Inject(ActivityContext activityContext, T carrier, Action(ActivityContext activityContext, T carrier, Func> getter) + private static PropagationContext ExtractFromMultipleHeaders(PropagationContext context, T carrier, Func> getter) { try { @@ -195,7 +164,7 @@ private static ActivityContext ExtractFromMultipleHeaders(ActivityContext act } else { - return activityContext; + return context; } ActivitySpanId spanId; @@ -206,7 +175,7 @@ private static ActivityContext ExtractFromMultipleHeaders(ActivityContext act } else { - return activityContext; + return context; } var traceOptions = ActivityTraceFlags.None; @@ -216,35 +185,37 @@ private static ActivityContext ExtractFromMultipleHeaders(ActivityContext act traceOptions |= ActivityTraceFlags.Recorded; } - return new ActivityContext(traceId, spanId, traceOptions, isRemote: true); + return new PropagationContext( + new ActivityContext(traceId, spanId, traceOptions, isRemote: true), + context.ActivityBaggage); } catch (Exception e) { - OpenTelemetrySdkEventSource.Log.ContextExtractException(e); - return activityContext; + OpenTelemetrySdkEventSource.Log.ActivityContextExtractException(nameof(B3Format), e); + return context; } } - private static ActivityContext ExtractFromSingleHeader(ActivityContext activityContext, T carrier, Func> getter) + private static PropagationContext ExtractFromSingleHeader(PropagationContext context, T carrier, Func> getter) { try { var header = getter(carrier, XB3Combined)?.FirstOrDefault(); if (string.IsNullOrWhiteSpace(header)) { - return activityContext; + return context; } var parts = header.Split(XB3CombinedDelimiter); if (parts.Length < 2 || parts.Length > 4) { - return activityContext; + return context; } var traceIdStr = parts[0]; if (string.IsNullOrWhiteSpace(traceIdStr)) { - return activityContext; + return context; } if (traceIdStr.Length == 16) @@ -258,7 +229,7 @@ private static ActivityContext ExtractFromSingleHeader(ActivityContext activi var spanIdStr = parts[1]; if (string.IsNullOrWhiteSpace(spanIdStr)) { - return activityContext; + return context; } var spanId = ActivitySpanId.CreateFromString(spanIdStr.AsSpan()); @@ -274,12 +245,14 @@ private static ActivityContext ExtractFromSingleHeader(ActivityContext activi } } - return new ActivityContext(traceId, spanId, traceOptions, isRemote: true); + return new PropagationContext( + new ActivityContext(traceId, spanId, traceOptions, isRemote: true), + context.ActivityBaggage); } catch (Exception e) { - OpenTelemetrySdkEventSource.Log.ContextExtractException(e); - return activityContext; + OpenTelemetrySdkEventSource.Log.ActivityContextExtractException(nameof(B3Format), e); + return context; } } } diff --git a/src/OpenTelemetry/Context/Propagation/DistributedContextBinarySerializerBase.cs b/src/OpenTelemetry/Context/Propagation/DistributedContextBinarySerializerBase.cs deleted file mode 100644 index 53d24f94baf..00000000000 --- a/src/OpenTelemetry/Context/Propagation/DistributedContextBinarySerializerBase.cs +++ /dev/null @@ -1,38 +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. -// - -namespace OpenTelemetry.Context.Propagation -{ - /// - /// DistributedContextBinarySerializerBase base class. - /// - public abstract class DistributedContextBinarySerializerBase - { - /// - /// Deserializes input to based on the binary format standard. - /// - /// Array of bytes in binary format standard. - /// . - public abstract DistributedContext FromByteArray(byte[] bytes); - - /// - /// Serializes a to the on-the-wire format. - /// - /// . - /// Serialized . - public abstract byte[] ToByteArray(DistributedContext tags); - } -} diff --git a/src/OpenTelemetry/Context/Propagation/SerializationUtils.cs b/src/OpenTelemetry/Context/Propagation/SerializationUtils.cs deleted file mode 100644 index 1e8237c338f..00000000000 --- a/src/OpenTelemetry/Context/Propagation/SerializationUtils.cs +++ /dev/null @@ -1,186 +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. -// -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.Context.Propagation -{ - internal static class SerializationUtils - { - internal const int VersionId = 0; - internal const int TagFieldId = 0; - - // This size limit only applies to the bytes representing tag keys and values. - internal const int TagContextSerializedSizeLimit = 8192; -#if NET452 - private static readonly byte[] InvalidContext = new byte[0]; -#else - private static readonly byte[] InvalidContext = Array.Empty(); -#endif - - // Serializes a DistributedContext to the on-the-wire format. - // Encoded tags are of the form: - internal static byte[] SerializeBinary(DistributedContext dc) - { - // Use a ByteArrayDataOutput to avoid needing to handle IOExceptions. - // ByteArrayDataOutput byteArrayDataOutput = ByteStreams.newDataOutput(); - var byteArrayDataOutput = new MemoryStream(); - - byteArrayDataOutput.WriteByte(VersionId); - var totalChars = 0; // Here chars are equivalent to bytes, since we're using ascii chars. - foreach (var tag in dc.CorrelationContext.Entries) - { - totalChars += tag.Key.Length; - totalChars += tag.Value.Length; - EncodeTag(tag, byteArrayDataOutput); - } - - // for (Iterator i = InternalUtils.getTags(tags); i.hasNext();) { - // Tag tag = i.next(); - // totalChars += tag.getKey().getName().length(); - // totalChars += tag.getValue().asString().length(); - // encodeTag(tag, byteArrayDataOutput); - // } - if (totalChars > TagContextSerializedSizeLimit) - { - OpenTelemetrySdkEventSource.Log.FailedToInjectContext("Size of DistributedContext exceeds the maximum serialized size " - + TagContextSerializedSizeLimit); - return InvalidContext; - } - - return byteArrayDataOutput.ToArray(); - } - - // Deserializes input to DistributedContext based on the binary format standard. - // The encoded tags are of the form: - internal static DistributedContext DeserializeBinary(byte[] bytes) - { - if (bytes.Length == 0) - { - OpenTelemetrySdkEventSource.Log.FailedToExtractContext("Input byte[] can not be empty."); - return DistributedContext.Empty; - } - - try - { - var buffer = new MemoryStream(bytes); - var versionId = buffer.ReadByte(); - if (versionId > VersionId || versionId < 0) - { - OpenTelemetrySdkEventSource.Log.FailedToExtractContext("Wrong Version ID: " + versionId + ". Currently supports version up to: " + VersionId); - return DistributedContext.Empty; - } - - if (TryParseTags(buffer, out var tags)) - { - return DistributedContextBuilder.CreateContext(tags); - } - } - catch (Exception e) - { - OpenTelemetrySdkEventSource.Log.ContextExtractException(e); - } - - return DistributedContext.Empty; - } - - internal static bool TryParseTags(MemoryStream buffer, out List tags) - { - tags = new List(); - var limit = buffer.Length; - var totalChars = 0; // Here chars are equivalent to bytes, since we're using ascii chars. - while (buffer.Position < limit) - { - var type = buffer.ReadByte(); - if (type == TagFieldId) - { - var key = CreateTagKey(DecodeString(buffer)); - var val = CreateTagValue(key, DecodeString(buffer)); - totalChars += key.Length; - totalChars += val.Length; - tags.Add(new CorrelationContextEntry(key, val)); - } - else - { - // Stop parsing at the first unknown field ID, since there is no way to know its length. - // TODO(sebright): Consider storing the rest of the byte array in the TagContext. - break; - } - } - - if (totalChars > TagContextSerializedSizeLimit) - { - OpenTelemetrySdkEventSource.Log.FailedToExtractContext( - "Size of TagContext exceeds the maximum serialized size " - + TagContextSerializedSizeLimit); - - return false; - } - - return true; - } - - // TODO(sebright): Consider exposing a string name validation method to avoid needing to catch an - // IllegalArgumentException here. - private static string CreateTagKey(string name) - { - return name; - } - - // TODO(sebright): Consider exposing a string validation method to avoid needing to catch - // an IllegalArgumentException here. - private static string CreateTagValue(string key, string value) - { - return value; - } - - private static void EncodeTag(CorrelationContextEntry tag, MemoryStream byteArrayDataOutput) - { - byteArrayDataOutput.WriteByte(TagFieldId); - EncodeString(tag.Key, byteArrayDataOutput); - EncodeString(tag.Value, byteArrayDataOutput); - } - - private static void EncodeString(string input, MemoryStream byteArrayDataOutput) - { - PutVarInt(input.Length, byteArrayDataOutput); - var bytes = Encoding.UTF8.GetBytes(input); - byteArrayDataOutput.Write(bytes, 0, bytes.Length); - } - - private static void PutVarInt(int input, MemoryStream byteArrayDataOutput) - { - var output = new byte[VarInt.VarIntSize(input)]; - VarInt.PutVarInt(input, output, 0); - byteArrayDataOutput.Write(output, 0, output.Length); - } - - private static string DecodeString(MemoryStream buffer) - { - var length = VarInt.GetVarInt(buffer); - var builder = new StringBuilder(); - for (var i = 0; i < length; i++) - { - builder.Append((char)buffer.ReadByte()); - } - - return builder.ToString(); - } - } -} diff --git a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs index e89120f8ced..9ef0e721267 100644 --- a/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs +++ b/src/OpenTelemetry/Internal/OpenTelemetrySdkEventSource.cs @@ -38,11 +38,11 @@ public void SpanProcessorException(string evnt, Exception ex) } [NonEvent] - public void ContextExtractException(Exception ex) + public void ActivityContextExtractException(string format, Exception ex) { if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) { - this.FailedToExtractContext(ex.ToInvariantString()); + this.FailedToExtractActivityContext(format, ex.ToInvariantString()); } } @@ -139,16 +139,16 @@ public void InvalidArgument(string methodName, string argumentName, string issue this.WriteEvent(8, methodName, argumentName, issue); } - [Event(9, Message = "Failed to extract span context: '{0}'", Level = EventLevel.Warning)] - public void FailedToExtractContext(string error) + [Event(9, Message = "Failed to extract activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)] + public void FailedToExtractActivityContext(string format, string error) { - this.WriteEvent(9, error); + this.WriteEvent(9, format, error); } - [Event(10, Message = "Failed to inject span context: '{0}'", Level = EventLevel.Warning)] - public void FailedToInjectContext(string error) + [Event(10, Message = "Failed to inject activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)] + public void FailedToInjectActivityContext(string format, string error) { - this.WriteEvent(10, error); + this.WriteEvent(10, format, error); } [Event(11, Message = "Failed to parse tracestate: too many items", Level = EventLevel.Warning)] diff --git a/src/OpenTelemetry/Metrics/DoubleBoundCounterMetricSdk.cs b/src/OpenTelemetry/Metrics/DoubleBoundCounterMetricSdk.cs index b8fa1431fac..57bf8dbb821 100644 --- a/src/OpenTelemetry/Metrics/DoubleBoundCounterMetricSdk.cs +++ b/src/OpenTelemetry/Metrics/DoubleBoundCounterMetricSdk.cs @@ -34,7 +34,7 @@ public override void Add(in SpanContext context, double value) this.sumAggregator.Update(value); } - public override void Add(in DistributedContext context, double value) + public override void Add(in CorrelationContext context, double value) { this.sumAggregator.Update(value); } diff --git a/src/OpenTelemetry/Metrics/DoubleBoundMeasureMetricSdk.cs b/src/OpenTelemetry/Metrics/DoubleBoundMeasureMetricSdk.cs index 7b295031e8c..1c457f0ad78 100644 --- a/src/OpenTelemetry/Metrics/DoubleBoundMeasureMetricSdk.cs +++ b/src/OpenTelemetry/Metrics/DoubleBoundMeasureMetricSdk.cs @@ -29,7 +29,7 @@ public override void Record(in SpanContext context, double value) this.measureAggregator.Update(value); } - public override void Record(in DistributedContext context, double value) + public override void Record(in CorrelationContext context, double value) { this.measureAggregator.Update(value); } diff --git a/src/OpenTelemetry/Metrics/DoubleCounterMetricSdk.cs b/src/OpenTelemetry/Metrics/DoubleCounterMetricSdk.cs index 7f16948d606..9832b874888 100644 --- a/src/OpenTelemetry/Metrics/DoubleCounterMetricSdk.cs +++ b/src/OpenTelemetry/Metrics/DoubleCounterMetricSdk.cs @@ -39,13 +39,13 @@ public override void Add(in SpanContext context, double value, IEnumerable> labels) + public override void Add(in CorrelationContext context, double value, IEnumerable> labels) { // user not using bound instrument. Hence create a short-lived bound instrument. this.Bind(new LabelSetSdk(labels), isShortLived: true).Add(context, value); diff --git a/src/OpenTelemetry/Metrics/Int64BoundCounterMetricSdk.cs b/src/OpenTelemetry/Metrics/Int64BoundCounterMetricSdk.cs index f79b54f838e..86b2d7242d7 100644 --- a/src/OpenTelemetry/Metrics/Int64BoundCounterMetricSdk.cs +++ b/src/OpenTelemetry/Metrics/Int64BoundCounterMetricSdk.cs @@ -34,7 +34,7 @@ public override void Add(in SpanContext context, long value) this.sumAggregator.Update(value); } - public override void Add(in DistributedContext context, long value) + public override void Add(in CorrelationContext context, long value) { this.sumAggregator.Update(value); } diff --git a/src/OpenTelemetry/Metrics/Int64BoundMeasureMetricSdk.cs b/src/OpenTelemetry/Metrics/Int64BoundMeasureMetricSdk.cs index a9e1c6de1a8..77c6c16f537 100644 --- a/src/OpenTelemetry/Metrics/Int64BoundMeasureMetricSdk.cs +++ b/src/OpenTelemetry/Metrics/Int64BoundMeasureMetricSdk.cs @@ -29,7 +29,7 @@ public override void Record(in SpanContext context, long value) this.measureAggregator.Update(value); } - public override void Record(in DistributedContext context, long value) + public override void Record(in CorrelationContext context, long value) { this.measureAggregator.Update(value); } diff --git a/src/OpenTelemetry/Metrics/Int64CounterMetricSdk.cs b/src/OpenTelemetry/Metrics/Int64CounterMetricSdk.cs index aec085a174b..ea8e2dd670b 100644 --- a/src/OpenTelemetry/Metrics/Int64CounterMetricSdk.cs +++ b/src/OpenTelemetry/Metrics/Int64CounterMetricSdk.cs @@ -39,13 +39,13 @@ public override void Add(in SpanContext context, long value, IEnumerable> labels) + public override void Add(in CorrelationContext context, long value, IEnumerable> labels) { // user not using bound instrument. Hence create a short-lived bound instrument. this.Bind(new LabelSetSdk(labels), isShortLived: true).Add(context, value); diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs b/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs index 9bd6a196024..4c70d205191 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs @@ -53,10 +53,8 @@ public void Dispose() [InlineData("https://localhost:443/about_attr_route/10", 2, "about_attr_route/{customerId}", "TraceContext")] [InlineData("http://localhost:1880/api/weatherforecast", 3, "api/{controller}/{id}", "TraceContext")] [InlineData("https://localhost:1843/subroute/10", 4, "subroute/{customerId}", "TraceContext")] - - // TODO: Reenable this tests once filtering mechanism is designed. - // [InlineData("http://localhost/api/value", 0, null, "/api/value")] // Request will be filtered - // [InlineData("http://localhost/api/value", 0, null, "{ThrowException}")] // Filter user code will throw an exception + [InlineData("http://localhost/api/value", 0, null, "TraceContext", "/api/value")] // Request will be filtered + [InlineData("http://localhost/api/value", 0, null, "TraceContext", "{ThrowException}")] // Filter user code will throw an exception [InlineData("http://localhost/api/value/2", 0, null, "CustomContext", "/api/value")] // Request will not be filtered [InlineData("http://localhost/api/value/2", 0, null, "CustomContext", "/api/value", true)] // Request will not be filtered public void AspNetRequestsAreCollectedSuccessfully( @@ -129,10 +127,12 @@ public void AspNetRequestsAreCollectedSuccessfully( var expectedTraceId = ActivityTraceId.CreateRandom(); var expectedSpanId = ActivitySpanId.CreateRandom(); var textFormat = new Mock(); - textFormat.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())).Returns(new ActivityContext( - expectedTraceId, - expectedSpanId, - ActivityTraceFlags.Recorded)); + textFormat.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())).Returns(new PropagationContext( + new ActivityContext( + expectedTraceId, + expectedSpanId, + ActivityTraceFlags.Recorded), + null)); var activity = new Activity(ActivityNameAspNet).AddBaggage("Stuff", "123"); activity.SetParentId(expectedTraceId, expectedSpanId, ActivityTraceFlags.Recorded); @@ -186,7 +186,19 @@ public void AspNetRequestsAreCollectedSuccessfully( if (HttpContext.Current.Request.Path == filter || filter == "{ThrowException}") { - Assert.Equal(0, activityProcessor.Invocations.Count); // Nothing was called because request was filtered. + if (filter == "{ThrowException}") + { + // This behavior is not good. If filter throws, Stop is called without Start. + // Need to do something here, but user can't currently set the filter + // so it wil always noop. When we support user filter, + // treat this as a todo: define exception behavior. + Assert.Equal(2, activityProcessor.Invocations.Count); // Stop & Disposed called. + } + else + { + Assert.Equal(1, activityProcessor.Invocations.Count); // Only disposed was called because request was filtered. + } + return; } diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs index 2e2c03f5391..1d6696a54c1 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs @@ -144,10 +144,11 @@ public async Task CustomTextFormat() var expectedSpanId = ActivitySpanId.CreateRandom(); var textFormat = new Mock(); - textFormat.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())).Returns(new ActivityContext( + textFormat.Setup(m => m.Extract(It.IsAny(), It.IsAny(), It.IsAny>>())).Returns(new PropagationContext( + new ActivityContext( expectedTraceId, expectedSpanId, - ActivityTraceFlags.Recorded)); + ActivityTraceFlags.Recorded), null)); // Arrange using (var testFactory = this.factory diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.netcore31.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.netcore31.cs index fc54c3ff1ec..321bea4bcd9 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.netcore31.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpClientTests.Basic.netcore31.cs @@ -73,22 +73,23 @@ public async Task HttpClientInstrumentationInjectsHeadersAsync() // Ensure that the header value func does not throw if the header key can't be found var mockTextFormat = new Mock(); - var isInjectedHeaderValueGetterThrows = false; - mockTextFormat - .Setup(x => x.IsInjected(It.IsAny(), It.IsAny>>())) - .Callback>>( - (carrier, getter) => - { - try - { - // traceparent doesn't exist - getter(carrier, "traceparent"); - } - catch - { - isInjectedHeaderValueGetterThrows = true; - } - }); + + // var isInjectedHeaderValueGetterThrows = false; + // mockTextFormat + // .Setup(x => x.IsInjected(It.IsAny(), It.IsAny>>())) + // .Callback>>( + // (carrier, getter) => + // { + // try + // { + // // traceparent doesn't exist + // getter(carrier, "traceparent"); + // } + // catch + // { + // isInjectedHeaderValueGetterThrows = true; + // } + // }); using (Sdk.CreateTracerProviderBuilder() .AddHttpClientInstrumentation(o => o.TextFormat = mockTextFormat.Object) @@ -114,19 +115,16 @@ public async Task HttpClientInstrumentationInjectsHeadersAsync() Assert.Equal($"00-{span.Context.TraceId}-{span.Context.SpanId}-01", traceparents.Single()); Assert.Equal("k1=v1,k2=v2", tracestates.Single()); - - mockTextFormat.Verify(x => x.IsInjected(It.IsAny(), It.IsAny>>()), Times.Once); - Assert.False(isInjectedHeaderValueGetterThrows); } [Fact] public async Task HttpClientInstrumentationInjectsHeadersAsync_CustomFormat() { var textFormat = new Mock(); - textFormat.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) - .Callback>((context, message, action) => + textFormat.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) + .Callback>((context, message, action) => { - action(message, "custom_traceparent", $"00/{context.TraceId}/{context.SpanId}/01"); + action(message, "custom_traceparent", $"00/{context.ActivityContext.TraceId}/{context.ActivityContext.SpanId}/01"); action(message, "custom_tracestate", Activity.Current.TraceStateString); }); diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs index 0a61bb86760..346041176f3 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestActivitySourceTests.netfx.cs @@ -435,7 +435,7 @@ public async Task TestTraceStateAndCorrelationContext() var traceparent = startRequest.Headers["traceparent"]; var tracestate = startRequest.Headers["tracestate"]; - var correlationContext = startRequest.Headers["Correlation-Context"]; + var correlationContext = startRequest.Headers["baggage"]; Assert.NotNull(traceparent); Assert.Equal("some=state", tracestate); Assert.Equal("k=v", correlationContext); @@ -742,7 +742,7 @@ public async Task TestInvalidBaggage() Assert.Equal(1, eventRecords.Records.Count(rec => rec.Key == "Stop")); WebRequest thisRequest = (WebRequest)eventRecords.Records.First().Value.GetCustomProperty("HttpWebRequest.Request"); - string[] correlationContext = thisRequest.Headers["Correlation-Context"].Split(','); + string[] correlationContext = thisRequest.Headers["baggage"].Split(','); Assert.Equal(3, correlationContext.Length); Assert.Contains("key=value", correlationContext); diff --git a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.netfx.cs b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.netfx.cs index 5f64525fbc0..5352ad52171 100644 --- a/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.netfx.cs +++ b/test/OpenTelemetry.Instrumentation.Http.Tests/HttpWebRequestTests.Basic.netfx.cs @@ -97,10 +97,10 @@ public async Task HttpWebRequestInstrumentationInjectsHeadersAsync() public async Task HttpWebRequestInstrumentationInjectsHeadersAsync_CustomFormat() { var textFormat = new Mock(); - textFormat.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) - .Callback>((context, message, action) => + textFormat.Setup(m => m.Inject(It.IsAny(), It.IsAny(), It.IsAny>())) + .Callback>((context, message, action) => { - action(message, "custom_traceparent", $"00/{context.TraceId}/{context.SpanId}/01"); + action(message, "custom_traceparent", $"00/{context.ActivityContext.TraceId}/{context.ActivityContext.SpanId}/01"); action(message, "custom_tracestate", Activity.Current.TraceStateString); }); diff --git a/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanContextShimTests.cs b/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanContextShimTests.cs index 8e0890ba2e5..352b2c1226a 100644 --- a/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanContextShimTests.cs +++ b/test/OpenTelemetry.Shims.OpenTracing.Tests/SpanContextShimTests.cs @@ -50,7 +50,8 @@ public void GetSpanId() public void GetBaggage() { var shim = GetSpanContextShim(); - Assert.Throws(() => shim.GetBaggageItems()); + var baggage = shim.GetBaggageItems(); + Assert.Null(baggage); } internal static SpanContextShim GetSpanContextShim() diff --git a/test/OpenTelemetry.Tests/Context/CorrelationContextBuilderTest.cs b/test/OpenTelemetry.Tests/Context/CorrelationContextBuilderTest.cs deleted file mode 100644 index af61d091364..00000000000 --- a/test/OpenTelemetry.Tests/Context/CorrelationContextBuilderTest.cs +++ /dev/null @@ -1,132 +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. -// - -using System.Collections.Generic; -using Xunit; - -namespace OpenTelemetry.Context.Tests -{ - public class CorrelationContextBuilderTest - { - private const string Key1 = "key 1"; - private const string Key2 = "key 2"; - - private const string Value1 = "value 1"; - private const string Value2 = "value 2"; - - private static readonly List List1 = new List(1) - { new CorrelationContextEntry(Key1, Value1) }; - - private static readonly List List2 = new List(2) - { - new CorrelationContextEntry(Key1, Value1), - new CorrelationContextEntry(Key2, Value2), - }; - - public CorrelationContextBuilderTest() - { - DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance; - } - - [Fact] - public void ContextCreation() - { - CorrelationContext dc = CorrelationContextBuilder.CreateContext(null); - Assert.Equal(CorrelationContext.Empty, dc); - - dc = CorrelationContextBuilder.CreateContext(CorrelationContext.Empty.Entries); - Assert.Equal(CorrelationContext.Empty, dc); - - dc = CorrelationContextBuilder.CreateContext(Key1, Value1); - Assert.Equal(CorrelationContextBuilder.CreateContext(List1), dc); - - Assert.Equal(dc, new CorrelationContextBuilder(dc).Build()); - } - - [Fact] - public void AddEntries() - { - Assert.Equal(CorrelationContext.Empty, new CorrelationContextBuilder(inheritCurrentContext: false).Build()); - - Assert.Equal( - CorrelationContextBuilder.CreateContext(List1), new CorrelationContextBuilder(inheritCurrentContext: false) - .Add(Key1, Value1) - .Build()); - - Assert.Equal( - CorrelationContextBuilder.CreateContext(List1), new CorrelationContextBuilder(inheritCurrentContext: false) - .Add(new CorrelationContextEntry(Key1, Value1)) - .Build()); - - Assert.Equal( - CorrelationContextBuilder.CreateContext(List2), new CorrelationContextBuilder(inheritCurrentContext: false) - .Add(Key1, Value1) - .Add(Key2, Value2) - .Build()); - - Assert.Equal( - CorrelationContextBuilder.CreateContext(List2), new CorrelationContextBuilder(inheritCurrentContext: false) - .Add(new CorrelationContextEntry(Key1, Value1)) - .Add(new CorrelationContextEntry(Key2, Value2)) - .Build()); - - Assert.Equal( - CorrelationContextBuilder.CreateContext(List1), new CorrelationContextBuilder(inheritCurrentContext: false) - .Add(List1) - .Build()); - - Assert.Equal( - CorrelationContextBuilder.CreateContext(List2), new CorrelationContextBuilder(inheritCurrentContext: false) - .Add(List2) - .Build()); - } - - [Fact] - public void RemoveEntries() - { - Assert.Equal( - CorrelationContextBuilder.CreateContext(List1), new CorrelationContextBuilder(inheritCurrentContext: false) - .Add(List2) - .Remove(Key2) - .Build()); - - Assert.Equal( - CorrelationContext.Empty, new CorrelationContextBuilder(inheritCurrentContext: false) - .Add(List2) - .Remove(Key2) - .Remove(Key1) - .Build()); - } - - [Fact] - public void EnsureEmptyListAfterBuild() - { - var dcb = new CorrelationContextBuilder(inheritCurrentContext: false); - Assert.Equal(CorrelationContext.Empty, dcb.Build()); - - dcb.Add(List2); - Assert.Equal(CorrelationContextBuilder.CreateContext(List2), dcb.Build()); - Assert.Equal(CorrelationContext.Empty, dcb.Build()); - - var dc = dcb.Add(List1).Build(); - Assert.Equal(dc, dcb.Add(List1).Build()); - - dcb = new CorrelationContextBuilder(dc); - Assert.Equal(dc, dcb.Build()); - Assert.Equal(CorrelationContext.Empty, dcb.Build()); - } - } -} diff --git a/test/OpenTelemetry.Tests/Context/CorrelationContextEntryTest.cs b/test/OpenTelemetry.Tests/Context/CorrelationContextEntryTest.cs deleted file mode 100644 index f84833ab095..00000000000 --- a/test/OpenTelemetry.Tests/Context/CorrelationContextEntryTest.cs +++ /dev/null @@ -1,102 +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. -// -using Xunit; - -namespace OpenTelemetry.Context.Tests -{ - public class CorrelationContextEntryTest - { - [Fact] - public void TestGetKey() - { - Assert.Equal("k", new CorrelationContextEntry("k", "v").Key); - } - - [Fact] - public void TestTagEquals() - { - var tag1 = new CorrelationContextEntry("Key", "foo"); - var tag2 = new CorrelationContextEntry("Key", "foo"); - var tag3 = new CorrelationContextEntry("Key", "bar"); - var tag4 = new CorrelationContextEntry("Key2", "foo"); - Assert.Equal(tag1, tag2); - Assert.NotEqual(tag1, tag3); - Assert.NotEqual(tag1, tag4); - Assert.NotEqual(tag2, tag3); - Assert.NotEqual(tag2, tag4); - Assert.NotEqual(tag3, tag4); - } - - [Fact] - public void TestNullKeyNullValue() - { - var entry = new CorrelationContextEntry(null, null); - Assert.Empty(entry.Key); - Assert.Empty(entry.Value); - } - - [Fact] - public void TestNullKey() - { - var entry = new CorrelationContextEntry(null, "foo"); - Assert.Empty(entry.Key); - Assert.Equal("foo", entry.Value); - } - - [Fact] - public void TestNullValue() - { - var entry = new CorrelationContextEntry("foo", null); - Assert.Equal("foo", entry.Key); - Assert.Empty(entry.Value); - } - - [Fact] - public void TestEquality() - { - var entry1 = new CorrelationContextEntry("key", "value1"); - var entry2 = new CorrelationContextEntry("key", "value1"); - object entry3 = entry2; - var entry4 = new CorrelationContextEntry("key", "value2"); - - Assert.True(entry1 == entry2); - Assert.True(entry1.Equals(entry2)); - Assert.True(entry1.Equals(entry3)); - - Assert.True(entry1 != entry4); - } - - [Fact] - public void TestToString() - { - var entry1 = new CorrelationContextEntry("key1", "value1"); - Assert.Equal("CorrelationContextEntry{Key=key1, Value=value1}", entry1.ToString()); - - var entry2 = new CorrelationContextEntry(null, "value1"); - Assert.Equal("CorrelationContextEntry{Key=, Value=value1}", entry2.ToString()); - } - - [Fact] - public void TestGetHashCode() - { - var entry1 = new CorrelationContextEntry("key1", "value1"); - Assert.NotEqual(0, entry1.GetHashCode()); - - var entry2 = new CorrelationContextEntry(null, "value1"); - Assert.NotEqual(0, entry2.GetHashCode()); - } - } -} diff --git a/test/OpenTelemetry.Tests/Context/CorrelationContextTest.cs b/test/OpenTelemetry.Tests/Context/CorrelationContextTest.cs index c0fa675e2c8..56bcc4430e4 100644 --- a/test/OpenTelemetry.Tests/Context/CorrelationContextTest.cs +++ b/test/OpenTelemetry.Tests/Context/CorrelationContextTest.cs @@ -14,121 +14,135 @@ // limitations under the License. // using System.Collections.Generic; +using System.Diagnostics; using Xunit; namespace OpenTelemetry.Context.Tests { public class CorrelationContextTest { - private const string K1 = "k1"; - private const string K2 = "k2"; + private const string K1 = "Key1"; + private const string K2 = "Key2"; - private const string V1 = "v1"; - private const string V2 = "v2"; - - public CorrelationContextTest() - { - DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance; - } + private const string V1 = "Value1"; + private const string V2 = "Value2"; [Fact] public void EmptyContext() { - var dc = CorrelationContextBuilder.CreateContext(new List()); - Assert.Empty(dc.Entries); - Assert.Equal(CorrelationContext.Empty, dc); + var cc = CorrelationContext.Current; + Assert.Empty(cc.Correlations); + Assert.Equal(CorrelationContext.Empty, cc); + + cc.AddCorrelation(K1, V1); + Assert.Empty(cc.Correlations); + + Assert.Null(cc.GetCorrelation(K1)); } [Fact] public void NonEmptyContext() { - var list = new List(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) }; - var dc = CorrelationContextBuilder.CreateContext(list); - Assert.Equal(list, dc.Entries); - } + using Activity activity = new Activity("TestActivity"); + activity.Start(); - [Fact] - public void AddExtraKey() - { - var list = new List(1) { new CorrelationContextEntry(K1, V1) }; - var dc = CorrelationContextBuilder.CreateContext(list); - Assert.Equal(list, dc.Entries); + var list = new List>(2) + { + new KeyValuePair(K1, V1), + new KeyValuePair(K2, V2), + }; - list.Add(new CorrelationContextEntry(K2, V2)); - var dc1 = CorrelationContextBuilder.CreateContext(list); - Assert.Equal(list, dc1.Entries); - } + var cc = CorrelationContext.Current; - [Fact] - public void AddExistingKey() - { - var list = new List(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K1, V2) }; - var dc = CorrelationContextBuilder.CreateContext(list); - Assert.Equal(new List(1) { new CorrelationContextEntry(K1, V2) }, dc.Entries); - } + cc.AddCorrelation(K1, V1); + cc.AddCorrelation(K2, V2); - [Fact] - public void UseDefaultEntry() - { - Assert.Equal(CorrelationContext.Empty, CorrelationContextBuilder.CreateContext(new List(1) { default })); - Assert.Equal(CorrelationContext.Empty, CorrelationContextBuilder.CreateContext(null)); + Assert.NotEqual(CorrelationContext.Empty, cc); + Assert.Equal(list, cc.Correlations); + + Assert.Equal(V1, cc.GetCorrelation(K1)); + Assert.Null(cc.GetCorrelation(K1.ToLower())); + Assert.Null(cc.GetCorrelation(K1.ToUpper())); + Assert.Null(cc.GetCorrelation("NO_KEY")); } [Fact] - public void RemoveExistingKey() + public void AddExistingKey() { - var list = new List(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) }; - var dc = CorrelationContextBuilder.CreateContext(list); - Assert.Equal(list, dc.Entries); + using Activity activity = new Activity("TestActivity"); + activity.Start(); - list.RemoveAt(0); + var list = new List>(2) + { + new KeyValuePair(K1, V1), + new KeyValuePair(K1, V1), + }; - dc = CorrelationContextBuilder.CreateContext(list); - Assert.Equal(list, dc.Entries); + var cc = CorrelationContext.Current; - list.Clear(); - dc = CorrelationContextBuilder.CreateContext(list); - Assert.Equal(CorrelationContext.Empty, dc); + cc.AddCorrelation(K1, V1); + cc.AddCorrelation(K1, V1); + + Assert.Equal(list, cc.Correlations); } [Fact] public void TestIterator() { - var list = new List(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) }; - var dc = CorrelationContextBuilder.CreateContext(list); + using Activity activity = new Activity("TestActivity"); + activity.Start(); + + var list = new List>(2) + { + new KeyValuePair(K1, V1), + new KeyValuePair(K2, V2), + }; + + var cc = CorrelationContext.Current; + + cc.AddCorrelation(K1, V1); + cc.AddCorrelation(K2, V2); + + var i = cc.Correlations.GetEnumerator(); - var i = dc.Entries.GetEnumerator(); Assert.True(i.MoveNext()); var tag1 = i.Current; Assert.True(i.MoveNext()); var tag2 = i.Current; Assert.False(i.MoveNext()); - Assert.Equal(new List { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) }, new List { tag1, tag2 }); + + Assert.Equal(list, new List> { tag1, tag2 }); } [Fact] public void TestEquals() { - var dc1 = CorrelationContextBuilder.CreateContext(new List(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) }); - var dc2 = CorrelationContextBuilder.CreateContext(new List(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) }); - object odc2 = dc2; - var dc3 = CorrelationContextBuilder.CreateContext(new List(2) { new CorrelationContextEntry(K2, V2), new CorrelationContextEntry(K1, V1) }); - var dc4 = CorrelationContextBuilder.CreateContext(new List(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V1) }); - var dc5 = CorrelationContextBuilder.CreateContext(new List(2) { new CorrelationContextEntry(K1, V2), new CorrelationContextEntry(K2, V1) }); - var dc6 = CorrelationContextBuilder.CreateContext(new List(3) { new CorrelationContextEntry(K1, V2) }); - - Assert.True(dc1.Equals(dc2)); - Assert.True(dc1.Equals(odc2)); - Assert.True(dc1 == dc2); - Assert.True(dc1.Equals(dc3)); - - Assert.False(dc1.Equals(dc4)); - Assert.True(dc1 != dc4); - Assert.False(dc2.Equals(dc4)); - Assert.False(dc3.Equals(dc4)); - Assert.False(dc5.Equals(dc4)); - Assert.False(dc4.Equals(dc5)); - Assert.False(dc5.Equals(dc6)); + var cc1 = CreateCorrelationContext(new KeyValuePair(K1, V1), new KeyValuePair(K2, V2)); + var cc2 = CreateCorrelationContext(new KeyValuePair(K1, V1), new KeyValuePair(K2, V2)); + var cc3 = CreateCorrelationContext(new KeyValuePair(K2, V2), new KeyValuePair(K1, V1)); + var cc4 = CreateCorrelationContext(new KeyValuePair(K1, V1), new KeyValuePair(K2, V1)); + var cc5 = CreateCorrelationContext(new KeyValuePair(K1, V2), new KeyValuePair(K2, V1)); + + Assert.True(cc1.Equals(cc2)); + + Assert.False(cc1.Equals(cc3)); + Assert.False(cc1.Equals(cc4)); + Assert.False(cc2.Equals(cc4)); + Assert.False(cc3.Equals(cc4)); + Assert.False(cc5.Equals(cc4)); + Assert.False(cc4.Equals(cc5)); + } + + private static CorrelationContext CreateCorrelationContext(params KeyValuePair[] correlations) + { + using Activity activity = new Activity("TestActivity"); + activity.Start(); + + var cc = CorrelationContext.Current; + + cc.AddCorrelation(correlations); + + return cc; } } } diff --git a/test/OpenTelemetry.Tests/Context/DistributedContextsScopeTest.cs b/test/OpenTelemetry.Tests/Context/DistributedContextsScopeTest.cs deleted file mode 100644 index 1849506d0f8..00000000000 --- a/test/OpenTelemetry.Tests/Context/DistributedContextsScopeTest.cs +++ /dev/null @@ -1,125 +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. -// -using System.Collections.Generic; -using System.Threading.Tasks; -using Xunit; - -namespace OpenTelemetry.Context.Tests -{ - public class DistributedContextsScopeTest - { - private const string Key1 = "key 1"; - private const string Key2 = "key 2"; - - private const string Value1 = "value 1"; - private const string Value2 = "value 2"; - - [Fact] - public void NoopContextCarrier() - { - DistributedContext.Carrier = NoopDistributedContextCarrier.Instance; - List list = new List(2) - { - new CorrelationContextEntry(Key1, Value1), new CorrelationContextEntry(Key2, Value2), - }; - Assert.Equal(DistributedContext.Empty, DistributedContext.Current); - - using (DistributedContext.SetCurrent(DistributedContextBuilder.CreateContext(Key1, Value1))) - { - Assert.Equal(DistributedContext.Empty, DistributedContext.Current); - using (DistributedContext.SetCurrent(DistributedContextBuilder.CreateContext(list))) - { - Assert.Equal(DistributedContext.Empty, DistributedContext.Current); - } - } - - Assert.Equal(DistributedContext.Empty, DistributedContext.Current); - } - - [Fact] - public async void AsyncContextCarrier() - { - DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance; - List list = new List(2) { new CorrelationContextEntry(Key1, Value1), new CorrelationContextEntry(Key2, Value2), }; - - var dc1 = DistributedContextBuilder.CreateContext(Key1, Value1); - var dc2 = DistributedContextBuilder.CreateContext(list); - - DistributedContext.SetCurrent(DistributedContext.Empty); - Assert.Equal(DistributedContext.Empty, DistributedContext.Current); - - using (DistributedContext.SetCurrent(dc1)) - { - Assert.Equal(dc1, DistributedContext.Current); - using (DistributedContext.SetCurrent(dc2)) - { - Assert.Equal(dc2, DistributedContext.Current); - } - - Assert.Equal(dc1, DistributedContext.Current); - - using (DistributedContext.SetCurrent(dc2)) - { - await Task.Run(() => Assert.Equal(dc2, DistributedContext.Current)); - } - - await Task.Run(() => Assert.Equal(dc1, DistributedContext.Current)); - } - - Assert.Equal(DistributedContext.Empty, DistributedContext.Current); - await Task.Run(() => Assert.Equal(DistributedContext.Empty, DistributedContext.Current)); - } - - [Fact] - public async void TestContextInheritance() - { - DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance; - var list1 = new List(1) { new CorrelationContextEntry(Key1, Value1) }; - var list2 = new List(2) { new CorrelationContextEntry(Key1, Value1), new CorrelationContextEntry(Key2, Value2) }; - - DistributedContext.SetCurrent(DistributedContext.Empty); - await Task.Run(() => Assert.Equal(DistributedContext.Empty, DistributedContext.Current)); - - using (DistributedContext.SetCurrent(DistributedContextBuilder.CreateContext(list1))) - { - await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list1), DistributedContext.Current)); - - using (DistributedContext.SetCurrent(new DistributedContextBuilder(inheritCurrentContext: true).Build())) - { - await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list1), DistributedContext.Current)); - - using (DistributedContext.SetCurrent(new DistributedContextBuilder(inheritCurrentContext: true).Correlations(b => b.Add(Key2, Value2)).Build())) - { - await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list2), DistributedContext.Current)); - using (DistributedContext.SetCurrent(new DistributedContextBuilder(inheritCurrentContext: true).Correlations(b => b.Remove(Key2)).Build())) - { - await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list1), DistributedContext.Current)); - } - } - - await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list1), DistributedContext.Current)); - - using (DistributedContext.SetCurrent(new DistributedContextBuilder(inheritCurrentContext: false).Build())) - { - await Task.Run(() => Assert.Equal(DistributedContext.Empty, DistributedContext.Current)); - } - - await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list1), DistributedContext.Current)); - } - } - } - } -} diff --git a/test/OpenTelemetry.Tests/Context/Propagation/DistributedContextDeserializationTest.cs b/test/OpenTelemetry.Tests/Context/Propagation/DistributedContextDeserializationTest.cs deleted file mode 100644 index 247bc73f80f..00000000000 --- a/test/OpenTelemetry.Tests/Context/Propagation/DistributedContextDeserializationTest.cs +++ /dev/null @@ -1,272 +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. -// - -using System.Collections.Generic; -using System.IO; -using System.Text; -using OpenTelemetry.Internal; -using Xunit; - -namespace OpenTelemetry.Context.Propagation.Tests -{ - public class DistributedContextDeserializationTest - { - private readonly DistributedContextBinarySerializer serializer; - - public DistributedContextDeserializationTest() - { - DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance; - this.serializer = new DistributedContextBinarySerializer(); - } - - [Fact] - public void TestConstants() - { - // Refer to the JavaDoc on SerializationUtils for the definitions on these constants. - Assert.Equal(0, SerializationUtils.VersionId); - Assert.Equal(0, SerializationUtils.TagFieldId); - Assert.Equal(8192, SerializationUtils.TagContextSerializedSizeLimit); - } - - [Fact] - public void TestNoTagsSerialization() - { - var dc = this.serializer.FromByteArray(this.serializer.ToByteArray(DistributedContext.Empty)); - Assert.Empty(dc.CorrelationContext.Entries); - - dc = this.serializer.FromByteArray(new byte[] { SerializationUtils.VersionId }); // One byte that represents Version ID. - Assert.Empty(dc.CorrelationContext.Entries); - } - - [Fact] - public void TestDeserializeEmptyByteArrayThrowException() - { - Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(new byte[0])); - } - - [Fact] - public void TestDeserializeTooLargeByteArrayThrowException() - { - var output = new MemoryStream(); - output.WriteByte(SerializationUtils.VersionId); - - for (var i = 0; i < (SerializationUtils.TagContextSerializedSizeLimit / 8) - 1; i++) - { - // Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8. - string str = i.ToString("0000"); - EncodeTagToOutPut(str, str, output); - } - - // The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte - // more than limit. - EncodeTagToOutPut("last", "last1", output); - - var bytes = output.ToArray(); - Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(bytes)); - } - - // Deserializing this inPut should cause an error, even though it represents a relatively small - // TagContext. - [Fact] - public void TestDeserializeTooLargeByteArrayThrowException_WithDuplicateTagKeys() - { - var output = new MemoryStream(); - output.WriteByte(SerializationUtils.VersionId); - - for (var i = 0; i < (SerializationUtils.TagContextSerializedSizeLimit / 8) - 1; i++) - { - // Each tag will be with format {key : "key_", value : "0123"}, so the length of it is 8. - EncodeTagToOutPut("key_", i.ToString("0000"), output); - } - - // The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte - // more than limit. - EncodeTagToOutPut("key_", "last1", output); - - var bytes = output.ToArray(); - Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(bytes)); - } - - [Fact] - public void TestDeserializeOneTag() - { - var output = new MemoryStream(); - output.WriteByte(SerializationUtils.VersionId); - EncodeTagToOutPut("Key", "Value", output); - - var expected = DistributedContextBuilder.CreateContext("Key", "Value"); - Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray())); - } - - [Fact] - public void TestDeserializeMultipleTags() - { - var output = new MemoryStream(); - output.WriteByte(SerializationUtils.VersionId); - EncodeTagToOutPut("Key1", "Value1", output); - EncodeTagToOutPut("Key2", "Value2", output); - - var expected = DistributedContextBuilder.CreateContext( - new List(2) { new CorrelationContextEntry("Key1", "Value1"), new CorrelationContextEntry("Key2", "Value2") }); - Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray())); - } - - [Fact] - public void TestDeserializeDuplicateKeys() - { - var output = new MemoryStream(); - output.WriteByte(SerializationUtils.VersionId); - EncodeTagToOutPut("Key1", "Value1", output); - EncodeTagToOutPut("Key1", "Value2", output); - - var expected = DistributedContextBuilder.CreateContext("Key1", "Value2"); - Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray())); - } - - [Fact] - public void TestDeserializeNonConsecutiveDuplicateKeys() - { - var output = new MemoryStream(); - output.WriteByte(SerializationUtils.VersionId); - EncodeTagToOutPut("Key1", "Value1", output); - EncodeTagToOutPut("Key2", "Value2", output); - EncodeTagToOutPut("Key3", "Value3", output); - EncodeTagToOutPut("Key1", "Value4", output); - EncodeTagToOutPut("Key2", "Value5", output); - - var expected = DistributedContextBuilder.CreateContext( - new List(3) - { - new CorrelationContextEntry("Key1", "Value4"), - new CorrelationContextEntry("Key2", "Value5"), - new CorrelationContextEntry("Key3", "Value3"), - }); - Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray())); - } - - [Fact] - public void TestDeserializeDuplicateTags() - { - var output = new MemoryStream(); - output.WriteByte(SerializationUtils.VersionId); - EncodeTagToOutPut("Key1", "Value1", output); - EncodeTagToOutPut("Key1", "Value2", output); - - var expected = DistributedContextBuilder.CreateContext("Key1", "Value2"); - Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray())); - } - - [Fact] - public void TestDeserializeNonConsecutiveDuplicateTags() - { - var output = new MemoryStream(); - output.WriteByte(SerializationUtils.VersionId); - EncodeTagToOutPut("Key1", "Value1", output); - EncodeTagToOutPut("Key2", "Value2", output); - EncodeTagToOutPut("Key3", "Value3", output); - EncodeTagToOutPut("Key1", "Value1", output); - EncodeTagToOutPut("Key2", "Value2", output); - - var expected = DistributedContextBuilder.CreateContext( - new List(3) - { - new CorrelationContextEntry("Key1", "Value1"), - new CorrelationContextEntry("Key2", "Value2"), - new CorrelationContextEntry("Key3", "Value3"), - }); - Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray())); - } - - [Fact] - public void StopParsingAtUnknownField() - { - var output = new MemoryStream(); - output.WriteByte(SerializationUtils.VersionId); - EncodeTagToOutPut("Key1", "Value1", output); - EncodeTagToOutPut("Key2", "Value2", output); - - // Write unknown field ID 1. - output.WriteByte(1); - output.Write(new byte[] { 1, 2, 3, 4 }, 0, 4); - - EncodeTagToOutPut("Key3", "Value3", output); - - var expected = DistributedContextBuilder.CreateContext( - new List(2) - { - new CorrelationContextEntry("Key1", "Value1"), - new CorrelationContextEntry("Key2", "Value2"), - }); - Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray())); - } - - [Fact] - public void StopParsingAtUnknownTagAtStart() - { - var output = new MemoryStream(); - output.WriteByte(SerializationUtils.VersionId); - - // Write unknown field ID 1. - output.WriteByte(1); - output.Write(new byte[] { 1, 2, 3, 4 }, 0, 4); - - EncodeTagToOutPut("Key", "Value", output); - Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(output.ToArray())); - } - - [Fact] - public void TestDeserializeWrongFormat() - { - // encoded tags should follow the format ()* - Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(new byte[3])); - } - - [Fact] - public void TestDeserializeWrongVersionId() - { - Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(new byte[] { SerializationUtils.VersionId + 1 })); - } - - [Fact] - public void TestDeserializeNegativeVersionId() - { - Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(new byte[] { 0xff })); - } - - // == - // - // == varint encoded integer - // == tag_key_len bytes comprising tag key name - // == varint encoded integer - // == tag_val_len bytes comprising UTF-8 string - private static void EncodeTagToOutPut(string key, string value, MemoryStream output) - { - output.WriteByte(SerializationUtils.TagFieldId); - EncodeString(key, output); - EncodeString(value, output); - } - - private static void EncodeString(string input, MemoryStream output) - { - var length = input.Length; - var bytes = new byte[VarInt.VarIntSize(length)]; - VarInt.PutVarInt(length, bytes, 0); - output.Write(bytes, 0, bytes.Length); - var inPutBytes = Encoding.UTF8.GetBytes(input); - output.Write(inPutBytes, 0, inPutBytes.Length); - } - } -} diff --git a/test/OpenTelemetry.Tests/Context/Propagation/DistributedContextRoundtripTest.cs b/test/OpenTelemetry.Tests/Context/Propagation/DistributedContextRoundtripTest.cs deleted file mode 100644 index 4661eae3f52..00000000000 --- a/test/OpenTelemetry.Tests/Context/Propagation/DistributedContextRoundtripTest.cs +++ /dev/null @@ -1,82 +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. -// - -using System.Collections.Generic; -using Xunit; - -namespace OpenTelemetry.Context.Propagation.Tests -{ - public class DistributedContextRoundtripTest - { - private const string K1 = "k1"; - private const string K2 = "k2"; - private const string K3 = "k3"; - - private const string V1 = "v1"; - private const string V2 = "v2"; - private const string V3 = "v3"; - - private readonly DistributedContextBinarySerializer serializer; - - public DistributedContextRoundtripTest() - { - DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance; - this.serializer = new DistributedContextBinarySerializer(); - } - - [Fact] - public void TestRoundtripSerialization_NormalTagContext() - { - this.TestRoundtripSerialization(DistributedContext.Empty); - this.TestRoundtripSerialization(DistributedContextBuilder.CreateContext(K1, V1)); - - var expected = DistributedContextBuilder.CreateContext( - new List(3) - { - new CorrelationContextEntry(K1, V1), - new CorrelationContextEntry(K2, V2), - new CorrelationContextEntry(K3, V3), - }); - - this.TestRoundtripSerialization(expected); - this.TestRoundtripSerialization(DistributedContextBuilder.CreateContext(K1, string.Empty)); - } - - [Fact] - public void TestRoundtrip_TagContextWithMaximumSize() - { - var list = new List(); - - for (var i = 0; i < SerializationUtils.TagContextSerializedSizeLimit / 8; i++) - { - // Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8. - // Add 1024 tags, the total size should just be 8192. - - var str = i.ToString("0000"); - list.Add(new CorrelationContextEntry(str, str)); - } - - this.TestRoundtripSerialization(DistributedContextBuilder.CreateContext(list)); - } - - private void TestRoundtripSerialization(DistributedContext expected) - { - var bytes = this.serializer.ToByteArray(expected); - var actual = this.serializer.FromByteArray(bytes); - Assert.Equal(expected, actual); - } - } -} diff --git a/test/OpenTelemetry.Tests/Context/Propagation/DistributedContextSerializationTest.cs b/test/OpenTelemetry.Tests/Context/Propagation/DistributedContextSerializationTest.cs deleted file mode 100644 index 219714509aa..00000000000 --- a/test/OpenTelemetry.Tests/Context/Propagation/DistributedContextSerializationTest.cs +++ /dev/null @@ -1,153 +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. -// - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using OpenTelemetry.Internal; -using Xunit; - -namespace OpenTelemetry.Context.Propagation.Tests -{ - public class DistributedContextSerializationTest - { - private const string K1 = "k1"; - private const string K2 = "k2"; - private const string K3 = "k3"; - private const string K4 = "k4"; - - private const string V1 = "v1"; - private const string V2 = "v2"; - private const string V3 = "v3"; - private const string V4 = "v4"; - - private static readonly CorrelationContextEntry T1 = new CorrelationContextEntry(K1, V1); - private static readonly CorrelationContextEntry T2 = new CorrelationContextEntry(K2, V2); - private static readonly CorrelationContextEntry T3 = new CorrelationContextEntry(K3, V3); - private static readonly CorrelationContextEntry T4 = new CorrelationContextEntry(K4, V4); - - private readonly DistributedContextBinarySerializer serializer; - - public DistributedContextSerializationTest() - { - DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance; - this.serializer = new DistributedContextBinarySerializer(); - } - - [Fact] - public void TestSerializeDefault() - { - this.TestSerialize(); - } - - [Fact] - public void TestSerializeWithOneTag() - { - this.TestSerialize(T1); - } - - [Fact] - public void TestSerializeWithMultipleTags() - { - this.TestSerialize(T1, T2, T3, T4); - } - - [Fact] - public void TestSerializeTooLargeTagContext() - { - var list = new List(); - - for (var i = 0; i < (SerializationUtils.TagContextSerializedSizeLimit / 8) - 1; i++) - { - // Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8. - var str = i.ToString("0000"); - list.Add(new CorrelationContextEntry(str, str)); - } - - // The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte - // more than limit. - list.Add(new CorrelationContextEntry("last", "last1")); - - var dc = DistributedContextBuilder.CreateContext(list); - Assert.Empty(this.serializer.ToByteArray(dc)); - } - - private static void EncodeString(string input, MemoryStream byteArrayOutPutStream) - { - VarInt.PutVarInt(input.Length, byteArrayOutPutStream); - var inpBytes = Encoding.UTF8.GetBytes(input); - byteArrayOutPutStream.Write(inpBytes, 0, inpBytes.Length); - } - - private static void RotateRight(IList sequence, int count) - { - var tmp = sequence[count - 1]; - sequence.RemoveAt(count - 1); - sequence.Insert(0, tmp); - } - - private static IEnumerable> Permutate(IList sequence, int count) - { - if (count == 0) - { - yield return sequence; - } - else - { - for (var i = 0; i < count; i++) - { - foreach (var perm in Permutate(sequence, count - 1)) - { - yield return perm; - } - - RotateRight(sequence, count); - } - } - } - - private void TestSerialize(params CorrelationContextEntry[] tags) - { - var list = new List(tags); - - var actual = this.serializer.ToByteArray(DistributedContextBuilder.CreateContext(list)); - var tagsList = tags.ToList(); - var tagPermutation = Permutate(tagsList, tagsList.Count); - ISet possibleOutPuts = new HashSet(); - - foreach (var distributedContextEntries in tagPermutation) - { - var l = (List)distributedContextEntries; - var expected = new MemoryStream(); - expected.WriteByte(SerializationUtils.VersionId); - - foreach (var tag in l) - { - expected.WriteByte(SerializationUtils.TagFieldId); - EncodeString(tag.Key, expected); - EncodeString(tag.Value, expected); - } - - var bytes = expected.ToArray(); - possibleOutPuts.Add(Encoding.UTF8.GetString(bytes)); - } - - var exp = Encoding.UTF8.GetString(actual); - Assert.Contains(exp, possibleOutPuts); - } - } -} diff --git a/test/OpenTelemetry.Tests/Trace/ActivitySourceAdapterTest.cs b/test/OpenTelemetry.Tests/Trace/ActivitySourceAdapterTest.cs index ae31ae2d752..e7267bdf9da 100644 --- a/test/OpenTelemetry.Tests/Trace/ActivitySourceAdapterTest.cs +++ b/test/OpenTelemetry.Tests/Trace/ActivitySourceAdapterTest.cs @@ -221,6 +221,8 @@ public void ActivitySourceAdapterPopulatesSamplingParamsCorrectlyForActivityWith public void Dispose() { Activity.Current = null; + this.testProcessor.Dispose(); + GC.SuppressFinalize(this); } } } diff --git a/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs b/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs index 06502bbeef3..762db1bd518 100644 --- a/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs +++ b/test/OpenTelemetry.Tests/Trace/CompositeActivityProcessorTests.cs @@ -35,10 +35,10 @@ public void CompositeActivityProcessor_CallsAllProcessorSequentially() { var result = string.Empty; - var p1 = new TestActivityProcessor( + using var p1 = new TestActivityProcessor( activity => { result += "1"; }, activity => { result += "3"; }); - var p2 = new TestActivityProcessor( + using var p2 = new TestActivityProcessor( activity => { result += "2"; }, activity => { result += "4"; }); @@ -56,7 +56,7 @@ public void CompositeActivityProcessor_CallsAllProcessorSequentially() [Fact] public void CompositeActivityProcessor_ProcessorThrows() { - var p1 = new TestActivityProcessor( + using var p1 = new TestActivityProcessor( activity => { throw new Exception("Start exception"); }, activity => { throw new Exception("End exception"); }); @@ -72,8 +72,8 @@ public void CompositeActivityProcessor_ProcessorThrows() [Fact] public void CompositeActivityProcessor_ShutsDownAll() { - var p1 = new TestActivityProcessor(null, null); - var p2 = new TestActivityProcessor(null, null); + using var p1 = new TestActivityProcessor(null, null); + using var p2 = new TestActivityProcessor(null, null); using (var processor = new CompositeActivityProcessor(new[] { p1, p2 })) { @@ -86,8 +86,8 @@ public void CompositeActivityProcessor_ShutsDownAll() [Fact] public void CompositeActivityProcessor_ForceFlush() { - var p1 = new TestActivityProcessor(null, null); - var p2 = new TestActivityProcessor(null, null); + using var p1 = new TestActivityProcessor(null, null); + using var p2 = new TestActivityProcessor(null, null); using (var processor = new CompositeActivityProcessor(new[] { p1, p2 })) { diff --git a/test/OpenTelemetry.Tests/Trace/Propagation/B3FormatTest.cs b/test/OpenTelemetry.Tests/Trace/Propagation/B3FormatTest.cs index 5d48e900d45..2c6c381cb48 100644 --- a/test/OpenTelemetry.Tests/Trace/Propagation/B3FormatTest.cs +++ b/test/OpenTelemetry.Tests/Trace/Propagation/B3FormatTest.cs @@ -56,7 +56,7 @@ public B3FormatTest(ITestOutputHelper output) public void Serialize_SampledContext() { var carrier = new Dictionary(); - this.b3Format.Inject(new ActivityContext(TraceId, SpanId, TraceOptions), carrier, Setter); + this.b3Format.Inject(new PropagationContext(new ActivityContext(TraceId, SpanId, TraceOptions), null), carrier, Setter); this.ContainsExactly(carrier, new Dictionary { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "1" } }); } @@ -66,7 +66,7 @@ public void Serialize_NotSampledContext() var carrier = new Dictionary(); var context = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); this.output.WriteLine(context.ToString()); - this.b3Format.Inject(context, carrier, Setter); + this.b3Format.Inject(new PropagationContext(context, null), carrier, Setter); this.ContainsExactly(carrier, new Dictionary { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 } }); } @@ -78,7 +78,7 @@ public void ParseMissingSampledAndMissingFlag() { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, }; var spanContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); - Assert.Equal(spanContext, this.b3Format.Extract(default, headersNotSampled, Getter)); + Assert.Equal(new PropagationContext(spanContext, null), this.b3Format.Extract(default, headersNotSampled, Getter)); } [Fact] @@ -88,7 +88,8 @@ public void ParseSampled() { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "1" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3Format.Extract(default, headersSampled, Getter)); + var activityContext = new ActivityContext(TraceId, SpanId, TraceOptions); + Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersSampled, Getter)); } [Fact] @@ -98,7 +99,8 @@ public void ParseZeroSampled() { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "0" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(default, headersNotSampled, Getter)); + var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); + Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersNotSampled, Getter)); } [Fact] @@ -108,7 +110,8 @@ public void ParseFlag() { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Flags, "1" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3Format.Extract(default, headersFlagSampled, Getter)); + var activityContext = new ActivityContext(TraceId, SpanId, TraceOptions); + Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersFlagSampled, Getter)); } [Fact] @@ -118,7 +121,8 @@ public void ParseZeroFlag() { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Flags, "0" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(default, headersFlagNotSampled, Getter)); + var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); + Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersFlagNotSampled, Getter)); } [Fact] @@ -130,7 +134,8 @@ public void ParseEightBytesTraceId() { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "1" }, }; - Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions), this.b3Format.Extract(default, headersEightBytes, Getter)); + var activityContext = new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions); + Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersEightBytes, Getter)); } [Fact] @@ -140,7 +145,8 @@ public void ParseEightBytesTraceId_NotSampledSpanContext() { { B3Format.XB3TraceId, TraceIdBase16EightBytes }, { B3Format.XB3SpanId, SpanIdBase16 }, }; - Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(default, headersEightBytes, Getter)); + var activityContext = new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None); + Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersEightBytes, Getter)); } [Fact] @@ -202,7 +208,8 @@ public void ParseMissingSpanId() public void Serialize_SampledContext_SingleHeader() { var carrier = new Dictionary(); - this.b3FormatSingleHeader.Inject(new ActivityContext(TraceId, SpanId, TraceOptions), carrier, Setter); + var activityContext = new ActivityContext(TraceId, SpanId, TraceOptions); + this.b3FormatSingleHeader.Inject(new PropagationContext(activityContext, null), carrier, Setter); this.ContainsExactly(carrier, new Dictionary { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-1" } }); } @@ -210,9 +217,9 @@ public void Serialize_SampledContext_SingleHeader() public void Serialize_NotSampledContext_SingleHeader() { var carrier = new Dictionary(); - var context = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); - this.output.WriteLine(context.ToString()); - this.b3FormatSingleHeader.Inject(context, carrier, Setter); + var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); + this.output.WriteLine(activityContext.ToString()); + this.b3FormatSingleHeader.Inject(new PropagationContext(activityContext, null), carrier, Setter); this.ContainsExactly(carrier, new Dictionary { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}" } }); } @@ -223,8 +230,8 @@ public void ParseMissingSampledAndMissingFlag_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}" }, }; - var spanContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); - Assert.Equal(spanContext, this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter)); + var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); + Assert.Equal(new PropagationContext(activityContext, null), this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter)); } [Fact] @@ -234,7 +241,10 @@ public void ParseSampled_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-1" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(default, headersSampled, Getter)); + + Assert.Equal( + new PropagationContext(new ActivityContext(TraceId, SpanId, TraceOptions), null), + this.b3FormatSingleHeader.Extract(default, headersSampled, Getter)); } [Fact] @@ -244,7 +254,10 @@ public void ParseZeroSampled_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-0" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter)); + + Assert.Equal( + new PropagationContext(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), null), + this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter)); } [Fact] @@ -254,7 +267,8 @@ public void ParseFlag_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-1" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(default, headersFlagSampled, Getter)); + var activityContext = new ActivityContext(TraceId, SpanId, TraceOptions); + Assert.Equal(new PropagationContext(activityContext, null), this.b3FormatSingleHeader.Extract(default, headersFlagSampled, Getter)); } [Fact] @@ -264,7 +278,8 @@ public void ParseZeroFlag_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-0" }, }; - Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(default, headersFlagNotSampled, Getter)); + var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); + Assert.Equal(new PropagationContext(activityContext, null), this.b3FormatSingleHeader.Extract(default, headersFlagNotSampled, Getter)); } [Fact] @@ -274,7 +289,8 @@ public void ParseEightBytesTraceId_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16EightBytes}-{SpanIdBase16}-1" }, }; - Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(default, headersEightBytes, Getter)); + var activityContext = new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions); + Assert.Equal(new PropagationContext(activityContext, null), this.b3FormatSingleHeader.Extract(default, headersEightBytes, Getter)); } [Fact] @@ -284,7 +300,8 @@ public void ParseEightBytesTraceId_NotSampledSpanContext_SingleHeader() { { B3Format.XB3Combined, $"{TraceIdBase16EightBytes}-{SpanIdBase16}" }, }; - Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(default, headersEightBytes, Getter)); + var activityContext = new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None); + Assert.Equal(new PropagationContext(activityContext, null), this.b3FormatSingleHeader.Extract(default, headersEightBytes, Getter)); } [Fact] diff --git a/test/OpenTelemetry.Tests/Trace/Propagation/BaggageFormatTest.cs b/test/OpenTelemetry.Tests/Trace/Propagation/BaggageFormatTest.cs new file mode 100644 index 00000000000..455445e4b0e --- /dev/null +++ b/test/OpenTelemetry.Tests/Trace/Propagation/BaggageFormatTest.cs @@ -0,0 +1,160 @@ +// +// 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.Linq; +using Xunit; + +namespace OpenTelemetry.Context.Propagation.Tests +{ + public class BaggageFormatTest + { + private static readonly Func, string, IEnumerable> Getter = + (d, k) => + { + d.TryGetValue(k, out var v); + return new string[] { v }; + }; + + private static readonly Func>, string, IEnumerable> GetterList = + (d, k) => + { + return d.Where(i => i.Key == k).Select(i => i.Value); + }; + + private static readonly Action, string, string> Setter = (carrier, name, value) => + { + carrier[name] = value; + }; + + private readonly BaggageFormat baggage = new BaggageFormat(); + + [Fact] + public void ValidateFieldsProperty() + { + Assert.Equal(new HashSet { BaggageFormat.BaggageHeaderName }, this.baggage.Fields); + Assert.Single(this.baggage.Fields); + } + + [Fact] + public void ValidateDefaultCarrierExtraction() + { + var propagationContext = this.baggage.Extract(default, null, null); + Assert.Equal(default, propagationContext); + } + + [Fact] + public void ValidateDefaultGetterExtraction() + { + var carrier = new Dictionary(); + var propagationContext = this.baggage.Extract(default, carrier, null); + Assert.Equal(default, propagationContext); + } + + [Fact] + public void ValidateNoBaggageExtraction() + { + var carrier = new Dictionary(); + var propagationContext = this.baggage.Extract(default, carrier, Getter); + Assert.Equal(default, propagationContext); + } + + [Fact] + public void ValidateOneBaggageExtraction() + { + var carrier = new Dictionary + { + { BaggageFormat.BaggageHeaderName, "name=test" }, + }; + var propagationContext = this.baggage.Extract(default, carrier, Getter); + Assert.False(propagationContext == default); + Assert.Single(propagationContext.ActivityBaggage); + + var array = propagationContext.ActivityBaggage.ToArray(); + + Assert.Equal("name", array[0].Key); + Assert.Equal("test", array[0].Value); + } + + [Fact] + public void ValidateMultipleBaggageExtraction() + { + var carrier = new List> + { + new KeyValuePair(BaggageFormat.BaggageHeaderName, "name1=test1"), + new KeyValuePair(BaggageFormat.BaggageHeaderName, "name2=test2"), + new KeyValuePair(BaggageFormat.BaggageHeaderName, "name2=test2"), + }; + + var propagationContext = this.baggage.Extract(default, carrier, GetterList); + + Assert.False(propagationContext == default); + Assert.True(propagationContext.ActivityContext == default); + + Assert.Equal(2, propagationContext.ActivityBaggage.Count()); + + var array = propagationContext.ActivityBaggage.ToArray(); + + Assert.Equal("name1", array[0].Key); + Assert.Equal("test1", array[0].Value); + + Assert.Equal("name2", array[1].Key); + Assert.Equal("test2", array[1].Value); + } + + [Fact] + public void ValidateLongBaggageExtraction() + { + var carrier = new Dictionary + { + { BaggageFormat.BaggageHeaderName, $"name={new string('x', 8186)},clientId=1234" }, + }; + var propagationContext = this.baggage.Extract(default, carrier, Getter); + Assert.False(propagationContext == default); + Assert.Single(propagationContext.ActivityBaggage); + + var array = propagationContext.ActivityBaggage.ToArray(); + + Assert.Equal("name", array[0].Key); + Assert.Equal(new string('x', 8186), array[0].Value); + } + + [Fact] + public void ValidateEmptyBaggageInjection() + { + var carrier = new Dictionary(); + this.baggage.Inject(default, carrier, Setter); + + Assert.Empty(carrier); + } + + [Fact] + public void ValidateBaggageInjection() + { + var carrier = new Dictionary(); + var propagationContext = new PropagationContext(default, new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" }, + }); + + this.baggage.Inject(propagationContext, carrier, Setter); + + Assert.Single(carrier); + Assert.Equal("key1=value1,key2=value2", carrier[BaggageFormat.BaggageHeaderName]); + } + } +} diff --git a/test/OpenTelemetry.Tests/Trace/Propagation/CompositePropagatorTest.cs b/test/OpenTelemetry.Tests/Trace/Propagation/CompositePropagatorTest.cs index 0890f5173c8..251a79b6549 100644 --- a/test/OpenTelemetry.Tests/Trace/Propagation/CompositePropagatorTest.cs +++ b/test/OpenTelemetry.Tests/Trace/Propagation/CompositePropagatorTest.cs @@ -24,7 +24,6 @@ namespace OpenTelemetry.Context.Propagation.Tests { public class CompositePropagatorTest { - private const string TraceParent = "traceparent"; private static readonly string[] Empty = new string[0]; private static readonly Func, string, IEnumerable> Getter = (headers, name) => { @@ -63,14 +62,13 @@ public void CompositePropagator_TestPropagator() }); var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); + PropagationContext propagationContext = new PropagationContext(activityContext, null); var carrier = new Dictionary(); + var activity = new Activity("test"); - compositePropagator.Inject(activityContext, carrier, Setter); + compositePropagator.Inject(propagationContext, carrier, Setter); Assert.Contains(carrier, kv => kv.Key == "custom-traceparent-1"); Assert.Contains(carrier, kv => kv.Key == "custom-traceparent-2"); - - bool isInjected = compositePropagator.IsInjected(carrier, Getter); - Assert.True(isInjected); } [Fact] @@ -86,24 +84,55 @@ public void CompositePropagator_UsingSameTag() }); var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); + PropagationContext propagationContext = new PropagationContext(activityContext, null); + var carrier = new Dictionary(); - compositePropagator.Inject(activityContext, carrier, Setter); + compositePropagator.Inject(propagationContext, carrier, Setter); Assert.Contains(carrier, kv => kv.Key == "custom-traceparent"); // checking if the latest propagator is the one with the data. So, it will replace the previous one. Assert.Equal($"00-{this.traceId}-{this.spanId}-{header02.Split('-').Last()}", carrier["custom-traceparent"]); - bool isInjected = compositePropagator.IsInjected(carrier, Getter); - Assert.True(isInjected); - // resetting counter count = 0; - ActivityContext newContext = compositePropagator.Extract(default, carrier, Getter); + compositePropagator.Extract(default, carrier, Getter); // checking if we accessed only two times: header/headerstate options // if that's true, we skipped the first one since we have a logic to for the default result Assert.Equal(2, count); } + + [Fact] + public void CompositePropagator_ActivityContext_Baggage() + { + var compositePropagator = new CompositePropagator(new List + { + new TraceContextFormat(), + new BaggageFormat(), + }); + + var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null, isRemote: true); + var baggage = new Dictionary { ["key1"] = "value1" }; + + PropagationContext propagationContextActivityOnly = new PropagationContext(activityContext, null); + PropagationContext propagationContextBaggageOnly = new PropagationContext(default, baggage); + PropagationContext propagationContextBoth = new PropagationContext(activityContext, baggage); + + var carrier = new Dictionary(); + compositePropagator.Inject(propagationContextActivityOnly, carrier, Setter); + PropagationContext extractedContext = compositePropagator.Extract(default, carrier, Getter); + Assert.Equal(propagationContextActivityOnly, extractedContext); + + carrier = new Dictionary(); + compositePropagator.Inject(propagationContextBaggageOnly, carrier, Setter); + extractedContext = compositePropagator.Extract(default, carrier, Getter); + Assert.Equal(propagationContextBaggageOnly, extractedContext); + + carrier = new Dictionary(); + compositePropagator.Inject(propagationContextBoth, carrier, Setter); + extractedContext = compositePropagator.Extract(default, carrier, Getter); + Assert.Equal(propagationContextBoth, extractedContext); + } } } diff --git a/test/OpenTelemetry.Tests/Trace/Propagation/TestPropagator.cs b/test/OpenTelemetry.Tests/Trace/Propagation/TestPropagator.cs index 0078b103b6b..a52ffbea1ee 100644 --- a/test/OpenTelemetry.Tests/Trace/Propagation/TestPropagator.cs +++ b/test/OpenTelemetry.Tests/Trace/Propagation/TestPropagator.cs @@ -36,23 +36,23 @@ public TestPropagator(string idHeaderName, string stateHeaderName, bool defaultC public ISet Fields => new HashSet() { this.idHeaderName, this.stateHeaderName }; - public ActivityContext Extract(ActivityContext activityContext, T carrier, Func> getter) + public PropagationContext Extract(PropagationContext context, T carrier, Func> getter) { if (this.defaultContext) { - return activityContext; + return context; } IEnumerable id = getter(carrier, this.idHeaderName); if (id.Count() <= 0) { - return activityContext; + return context; } var traceparentParsed = TraceContextFormat.TryExtractTraceparent(id.First(), out var traceId, out var spanId, out var traceoptions); if (!traceparentParsed) { - return activityContext; + return context; } string tracestate = string.Empty; @@ -62,31 +62,25 @@ public ActivityContext Extract(ActivityContext activityContext, T carrier, Fu TraceContextFormat.TryExtractTracestate(tracestateCollection.ToArray(), out tracestate); } - return new ActivityContext(traceId, spanId, traceoptions, tracestate); + return new PropagationContext( + new ActivityContext(traceId, spanId, traceoptions, tracestate), + context.ActivityBaggage); } - public void Inject(ActivityContext activityContext, T carrier, Action setter) + public void Inject(PropagationContext context, T carrier, Action setter) { string headerNumber = this.stateHeaderName.Split('-').Last(); - var traceparent = string.Concat("00-", activityContext.TraceId.ToHexString(), "-", activityContext.SpanId.ToHexString()); + var traceparent = string.Concat("00-", context.ActivityContext.TraceId.ToHexString(), "-", context.ActivityContext.SpanId.ToHexString()); traceparent = string.Concat(traceparent, "-", headerNumber); setter(carrier, this.idHeaderName, traceparent); - string tracestateStr = activityContext.TraceState; + string tracestateStr = context.ActivityContext.TraceState; if (tracestateStr?.Length > 0) { setter(carrier, this.stateHeaderName, tracestateStr); } } - - public bool IsInjected(T carrier, Func> getter) - { - var traceparentCollection = getter(carrier, this.idHeaderName); - - // There must be a single traceparent - return traceparentCollection != null && traceparentCollection.Count() == 1; - } } } diff --git a/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextTest.cs b/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextTest.cs index d5bac5315f9..0b9f1e522d6 100644 --- a/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextTest.cs +++ b/test/OpenTelemetry.Tests/Trace/Propagation/TraceContextTest.cs @@ -55,14 +55,14 @@ public void TraceContextFormatCanParseExampleFromSpec() var f = new TraceContextFormat(); var ctx = f.Extract(default, headers, Getter); - Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.TraceId); - Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.SpanId); + Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.ActivityContext.TraceId); + Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.ActivityContext.SpanId); - Assert.True(ctx.IsRemote); - Assert.True(ctx.IsValid()); - Assert.True((ctx.TraceFlags & ActivityTraceFlags.Recorded) != 0); + Assert.True(ctx.ActivityContext.IsRemote); + Assert.True(ctx.ActivityContext.IsValid()); + Assert.True((ctx.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0); - Assert.Equal($"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{TraceId}-00f067aa0ba902b7-01", ctx.TraceState); + Assert.Equal($"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{TraceId}-00f067aa0ba902b7-01", ctx.ActivityContext.TraceState); } [Fact] @@ -76,12 +76,12 @@ public void TraceContextFormatNotSampled() var f = new TraceContextFormat(); var ctx = f.Extract(default, headers, Getter); - Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.TraceId); - Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.SpanId); - Assert.True((ctx.TraceFlags & ActivityTraceFlags.Recorded) == 0); + Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.ActivityContext.TraceId); + Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.ActivityContext.SpanId); + Assert.True((ctx.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) == 0); - Assert.True(ctx.IsRemote); - Assert.True(ctx.IsValid()); + Assert.True(ctx.ActivityContext.IsRemote); + Assert.True(ctx.ActivityContext.IsValid()); } [Fact] @@ -92,7 +92,7 @@ public void TraceContextFormat_IsBlankIfNoHeader() var f = new TraceContextFormat(); var ctx = f.Extract(default, headers, Getter); - Assert.False(ctx.IsValid()); + Assert.False(ctx.ActivityContext.IsValid()); } [Fact] @@ -106,7 +106,7 @@ public void TraceContextFormat_IsBlankIfInvalid() var f = new TraceContextFormat(); var ctx = f.Extract(default, headers, Getter); - Assert.False(ctx.IsValid()); + Assert.False(ctx.ActivityContext.IsValid()); } [Fact] @@ -120,7 +120,7 @@ public void TraceContextFormat_TracestateToStringEmpty() var f = new TraceContextFormat(); var ctx = f.Extract(default, headers, Getter); - Assert.Empty(ctx.TraceState); + Assert.Null(ctx.ActivityContext.TraceState); } [Fact] @@ -135,7 +135,7 @@ public void TraceContextFormat_TracestateToString() var f = new TraceContextFormat(); var ctx = f.Extract(default, headers, Getter); - Assert.Equal("k1=v1,k2=v2,k3=v3", ctx.TraceState); + Assert.Equal("k1=v1,k2=v2,k3=v3", ctx.ActivityContext.TraceState); } [Fact] @@ -149,9 +149,10 @@ public void TraceContextFormat_Inject_NoTracestate() }; var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, traceState: null); + PropagationContext propagationContext = new PropagationContext(activityContext, null); var carrier = new Dictionary(); var f = new TraceContextFormat(); - f.Inject(activityContext, carrier, Setter); + f.Inject(propagationContext, carrier, Setter); Assert.Equal(expectedHeaders, carrier); } @@ -168,9 +169,10 @@ public void TraceContextFormat_Inject_WithTracestate() }; var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, expectedHeaders[TraceState]); + PropagationContext propagationContext = new PropagationContext(activityContext, null); var carrier = new Dictionary(); var f = new TraceContextFormat(); - f.Inject(activityContext, carrier, Setter); + f.Inject(propagationContext, carrier, Setter); Assert.Equal(expectedHeaders, carrier); } diff --git a/test/OpenTelemetry.Tests/Trace/TracerProvideSdkTest.cs b/test/OpenTelemetry.Tests/Trace/TracerProvideSdkTest.cs index 35c37e4022a..c83e86b58c5 100644 --- a/test/OpenTelemetry.Tests/Trace/TracerProvideSdkTest.cs +++ b/test/OpenTelemetry.Tests/Trace/TracerProvideSdkTest.cs @@ -21,7 +21,7 @@ namespace OpenTelemetry.Trace.Tests { - public class TracerProvideSdkTest + public class TracerProvideSdkTest : IDisposable { private const string ActivitySourceName = "TraceSdkTest"; @@ -151,7 +151,7 @@ public void TracerSdkSetsActivityDataRequestBasedOnSamplingDecision() public void ProcessorDoesNotReceiveNotRecordDecisionSpan() { var testSampler = new TestSampler(); - TestActivityProcessor testActivityProcessor = new TestActivityProcessor(); + using TestActivityProcessor testActivityProcessor = new TestActivityProcessor(); bool startCalled = false; bool endCalled = false; @@ -189,7 +189,7 @@ public void ProcessorDoesNotReceiveNotRecordDecisionSpan() [Fact] public void TracerProvideSdkCreatesActivitySource() { - TestActivityProcessor testActivityProcessor = new TestActivityProcessor(); + using TestActivityProcessor testActivityProcessor = new TestActivityProcessor(); bool startCalled = false; bool endCalled = false; @@ -277,6 +277,11 @@ public void TracerProvideSdkCreatesAndDiposesInstrumentation() Assert.True(testInstrumentation.IsDisposed); } + public void Dispose() + { + GC.SuppressFinalize(this); + } + private class TestSampler : Sampler { public SamplingResult DesiredSamplingResult { get; set; } = new SamplingResult(SamplingDecision.RecordAndSampled); diff --git a/test/OpenTelemetry.Tests/Trace/TracerTest.cs b/test/OpenTelemetry.Tests/Trace/TracerTest.cs index 29353d9b7bf..21d6e1b4074 100644 --- a/test/OpenTelemetry.Tests/Trace/TracerTest.cs +++ b/test/OpenTelemetry.Tests/Trace/TracerTest.cs @@ -23,11 +23,13 @@ namespace OpenTelemetry.Trace.Tests public class TracerTest : IDisposable { // TODO: This is only a basic test. This must cover the entire shim API scenarios. + private readonly TracerProvider tracerProvider; private readonly Tracer tracer; public TracerTest() { - this.tracer = TracerProvider.Default.GetTracer("tracername", "tracerversion"); + this.tracerProvider = TracerProvider.Default; + this.tracer = this.tracerProvider.GetTracer("tracername", "tracerversion"); } [Fact] @@ -248,6 +250,8 @@ public void CreateSpan_NotSampled() public void Dispose() { Activity.Current = null; + this.tracerProvider.Dispose(); + GC.SuppressFinalize(this); } private static bool IsNoopSpan(TelemetrySpan span)