public ActivityEvent(string name, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object>>? attributes) { throw null; }
public string Name { get { throw null; } }
public System.DateTimeOffset Timestamp { get { throw null; } }
- public System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object>>? Attributes { get { throw null; } }
+ public System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object>> Attributes { get { throw null; } }
}
public readonly struct ActivityContext : System.IEquatable<System.Diagnostics.ActivityContext>
{
</ItemGroup>
<ItemGroup Condition=" '$(TargetsNetCoreApp)' == 'true' or '$(TargetFramework)' == 'netstandard2.1'">
<Compile Include="System\Diagnostics\Activity.GenerateRootId.netcoreapp.cs" />
+ <Compile Include="System\Diagnostics\ActivityContext.netcoreapp.cs" />
+ <Compile Include="System\Diagnostics\ActivityLink.netcoreapp.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetsNetCoreApp)' != 'true' and '$(TargetFramework)' != 'netstandard2.1' and '$(TargetFramework)' != 'netstandard1.1'">
<Compile Include="System\Diagnostics\Activity.GenerateRootId.netfx.cs" />
+ <Compile Include="System\Diagnostics\ActivityContext.netfx.cs" />
+ <Compile Include="System\Diagnostics\ActivityLink.netfx.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsNetFx)' == 'true'">
<Compile Include="System\Diagnostics\HttpHandlerDiagnosticListener.cs" />
private static readonly IEnumerable<ActivityLink> s_emptyLinks = new ActivityLink[0];
private static readonly IEnumerable<ActivityEvent> s_emptyEvents = new ActivityEvent[0];
#pragma warning restore CA1825
- private static ActivitySource s_defaultSource = new ActivitySource(string.Empty);
+ private static readonly ActivitySource s_defaultSource = new ActivitySource(string.Empty);
private const byte ActivityTraceFlagsIsSet = 0b_1_0000000; // Internal flag to indicate if flags have been set
private const int RequestIdMaxLength = 1024;
private byte _w3CIdFlags;
- private LinkedListNode<KeyValuePair<string, string?>>? _tags;
- private LinkedListNode<KeyValuePair<string, string?>>? _baggage;
- private LinkedListNode<ActivityLink>? _links;
- private LinkedListNode<ActivityEvent>? _events;
+ private LinkedList<KeyValuePair<string, string?>>? _tags;
+ private LinkedList<KeyValuePair<string, string?>>? _baggage;
+ private LinkedList<ActivityLink>? _links;
+ private LinkedList<ActivityEvent>? _events;
private ConcurrentDictionary<string, object>? _customProperties;
private string? _displayName;
/// <summary>
- /// Kind describes the relationship between the Activity, its parents, and its children in a Trace.
+ /// Gets the relationship between the Activity, its parents, and its children in a Trace.
/// </summary>
public ActivityKind Kind { get; private set; } = ActivityKind.Internal;
/// </summary>
public string OperationName { get; } = null!;
- /// <summary>
- /// DisplayName is name mainly intended to be used in the UI and not necessary has to be
- /// same as OperationName.
- /// </summary>
+ /// <summary>Gets or sets the display name of the Activity</summary>
+ /// <remarks>
+ /// DisplayName is intended to be used in a user interface and need not be the same as OperationName.
+ /// </remarks>
public string DisplayName
{
get => _displayName ?? OperationName;
- set => _displayName = value;
+ set => _displayName = value ?? throw new ArgumentNullException(nameof(value));
}
- /// <summary>
- /// Get the ActivitySource object associated with this Activity.
- /// All Activities created from public constructors will have a singleton source where the source name is empty string.
- /// Otherwise, the source will be holding the object that created the Activity through ActivitySource.StartActivity.
- /// </summary>
+ /// <summary>Get the ActivitySource object associated with this Activity.</summary>
+ /// <remarks>
+ /// All Activities created from public constructors will have a singleton source where the source name is an empty string.
+ /// Otherwise, the source will hold the object that created the Activity through ActivitySource.StartActivity.
+ /// </remarks>
public ActivitySource Source { get; private set; }
/// <summary>
/// <summary>
/// Tags are string-string key-value pairs that represent information that will
- /// be logged along with the Activity to the logging system. This information
+ /// be logged along with the Activity to the logging system. This information
/// however is NOT passed on to the children of this activity.
/// </summary>
/// <seealso cref="Baggage"/>
public IEnumerable<KeyValuePair<string, string?>> Tags
{
- get
- {
- LinkedListNode<KeyValuePair<string, string?>>? tags = _tags;
- return tags != null ?
- Iterate(tags) :
- s_emptyBaggageTags;
-
- static IEnumerable<KeyValuePair<string, string?>> Iterate(LinkedListNode<KeyValuePair<string, string?>>? tags)
- {
- do
- {
- yield return tags!.Value;
- tags = tags.Next;
- }
- while (tags != null);
- }
- }
+ get => _tags != null ? _tags.Enumerate() : s_emptyBaggageTags;
}
/// <summary>
/// </summary>
public IEnumerable<ActivityEvent> Events
{
- get
- {
- LinkedListNode<ActivityEvent>? events = _events;
- return events != null ? Iterate(events) : s_emptyEvents;
-
- static IEnumerable<ActivityEvent> Iterate(LinkedListNode<ActivityEvent>? events)
- {
- do
- {
- yield return events!.Value;
- events = events.Next;
- }
- while (events != null);
- }
- }
+ get => _events != null ? _events.Enumerate() : s_emptyEvents;
}
/// <summary>
/// </summary>
public IEnumerable<ActivityLink> Links
{
- get
- {
- LinkedListNode<ActivityLink>? links = _links;
- return links != null ? Iterate(links) : s_emptyLinks;
-
- static IEnumerable<ActivityLink> Iterate(LinkedListNode<ActivityLink>? links)
- {
- do
- {
- yield return links!.Value;
- links = links.Next;
- }
- while (links != null);
- }
- }
+ get => _links != null ? _links.Enumerate() : s_emptyLinks;
}
/// <summary>
Debug.Assert(activity != null);
do
{
- for (LinkedListNode<KeyValuePair<string, string?>>? baggage = activity._baggage; baggage != null; baggage = baggage.Next)
+ if (activity._baggage != null)
{
- yield return baggage.Value;
+ for (LinkedListNode<KeyValuePair<string, string?>>? current = activity._baggage.First; current != null; current = current.Next)
+ {
+ yield return current.Value;
+ }
}
activity = activity.Parent;
- }
- while (activity != null);
+ } while (activity != null);
}
}
}
/// <returns>'this' for convenient chaining</returns>
public Activity AddTag(string key, string? value)
{
- LinkedListNode<KeyValuePair<string, string?>>? currentTags = _tags;
- LinkedListNode<KeyValuePair<string, string?>> newTags = new LinkedListNode<KeyValuePair<string, string?>>(new KeyValuePair<string, string?>(key, value));
- do
+ KeyValuePair<string, string?> kvp = new KeyValuePair<string, string?>(key, value);
+
+ if (_tags != null || Interlocked.CompareExchange(ref _tags, new LinkedList<KeyValuePair<string, string?>>(kvp), null) != null)
{
- newTags.Next = currentTags;
- currentTags = Interlocked.CompareExchange(ref _tags, newTags, currentTags);
- } while (!ReferenceEquals(newTags.Next, currentTags));
+ _tags.Add(kvp);
+ }
return this;
}
/// <returns>'this' for convenient chaining</returns>
public Activity AddEvent(ActivityEvent e)
{
- LinkedListNode<ActivityEvent>? currentEvents = _events;
- LinkedListNode<ActivityEvent> newEvents = new LinkedListNode<ActivityEvent>(e);
- do
+ if (_events != null || Interlocked.CompareExchange(ref _events, new LinkedList<ActivityEvent>(e), null) != null)
{
- newEvents.Next = currentEvents;
- currentEvents = Interlocked.CompareExchange(ref _events, newEvents, currentEvents);
- } while (!ReferenceEquals(newEvents.Next, currentEvents));
+ _events.Add(e);
+ }
return this;
}
/// <returns>'this' for convenient chaining</returns>
public Activity AddBaggage(string key, string? value)
{
- LinkedListNode<KeyValuePair<string, string?>>? currentBaggage = _baggage;
- LinkedListNode<KeyValuePair<string, string?>> newBaggage = new LinkedListNode<KeyValuePair<string, string?>>(new KeyValuePair<string, string?>(key, value));
+ KeyValuePair<string, string?> kvp = new KeyValuePair<string, string?>(key, value);
- do
+ if (_baggage != null || Interlocked.CompareExchange(ref _baggage, new LinkedList<KeyValuePair<string, string?>>(kvp), null) != null)
{
- newBaggage.Next = currentBaggage;
- currentBaggage = Interlocked.CompareExchange(ref _baggage, newBaggage, currentBaggage);
- } while (!ReferenceEquals(newBaggage.Next, currentBaggage));
+ _baggage.Add(kvp);
+ }
return this;
}
if (links != null)
{
- foreach (ActivityLink link in links)
+ using (IEnumerator<ActivityLink> enumerator = links.GetEnumerator())
{
- activity._links = new LinkedListNode<ActivityLink>(link, activity._links);
+ if (enumerator.MoveNext())
+ {
+ activity._links = new LinkedList<ActivityLink>(enumerator);
+ }
}
}
if (tags != null)
{
- foreach (KeyValuePair<string, string?> tag in tags)
+ using (IEnumerator<KeyValuePair<string, string?>> enumerator = tags.GetEnumerator())
{
- activity._tags = new LinkedListNode<KeyValuePair<string, string?>>(tag, activity._tags);
+ if (enumerator.MoveNext())
+ {
+ activity._tags = new LinkedList<KeyValuePair<string, string?>>(enumerator);
+ }
}
}
private set => _state = (_state & ~State.FormatFlags) | (State)((byte)value & (byte)State.FormatFlags);
}
- /// <summary>
- /// Having our own key-value linked list allows us to be more efficient
- /// </summary>
private partial class LinkedListNode<T>
{
public LinkedListNode(T value) => Value = value;
- public LinkedListNode(T value, LinkedListNode<T>? next)
- {
- Value = value;
- Next = next;
- }
public T Value;
public LinkedListNode<T>? Next;
}
+ // We are not using the public LinkedList<T> because we need to ensure thread safety operation on the list.
+ private class LinkedList<T>
+ {
+ private LinkedListNode<T> _first;
+ private LinkedListNode<T> _last;
+
+ public LinkedList(T firstValue) => _last = _first = new LinkedListNode<T>(firstValue);
+
+ public LinkedList(IEnumerator<T> e)
+ {
+ _last = _first = new LinkedListNode<T>(e.Current);
+
+ while (e.MoveNext())
+ {
+ _last.Next = new LinkedListNode<T>(e.Current);
+ _last = _last.Next;
+ }
+ }
+
+ public LinkedListNode<T> First => _first;
+
+ public void Add(T value)
+ {
+ LinkedListNode<T> newNode = new LinkedListNode<T>(value);
+
+ lock (_first)
+ {
+ _last.Next = newNode;
+ _last = newNode;
+ }
+ }
+
+ public IEnumerable<T> Enumerate()
+ {
+ LinkedListNode<T>? current = _first;
+ do
+ {
+ yield return current.Value;
+ current = current.Next;
+ } while (current != null);
+ }
+ }
+
[Flags]
private enum State : byte
{
/// ActivityContext representation conforms to the w3c TraceContext specification. It contains two identifiers
/// a TraceId and a SpanId - along with a set of common TraceFlags and system-specific TraceState values.
/// </summary>
- public readonly struct ActivityContext : IEquatable<ActivityContext>
+ public readonly partial struct ActivityContext : IEquatable<ActivityContext>
{
/// <summary>
/// Construct a new object of ActivityContext.
public ActivitySpanId SpanId { get; }
/// <summary>
- /// The flags for the details about the trace.
+ /// These flags are defined by the W3C standard along with the ID for the activity.
/// </summary>
public ActivityTraceFlags TraceFlags { get; }
/// <summary>
- /// system-specific configuration data.
+ /// Holds the W3C 'tracestate' header as a string.
/// </summary>
public string? TraceState { get; }
public override bool Equals(object? obj) => (obj is ActivityContext context) ? Equals(context) : false;
public static bool operator ==(ActivityContext left, ActivityContext right) => left.Equals(right);
public static bool operator !=(ActivityContext left, ActivityContext right) => !(left == right);
-
- public override int GetHashCode()
- {
- if (this == default)
- return 0;
-
- // HashCode.Combine would be the best but we need to compile for the full framework which require adding dependency
- // on the extensions package. Considering this simple type and hashing is not expected to be used much, we are implementing
- // the hashing manually.
- int hash = 5381;
- hash = ((hash << 5) + hash) + TraceId.GetHashCode();
- hash = ((hash << 5) + hash) + SpanId.GetHashCode();
- hash = ((hash << 5) + hash) + (int) TraceFlags;
- hash = ((hash << 5) + hash) + (TraceState == null ? 0 : TraceState.GetHashCode());
-
- return hash;
- }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ /// <summary>
+ /// ActivityContext representation conforms to the w3c TraceContext specification. It contains two identifiers
+ /// a TraceId and a SpanId - along with a set of common TraceFlags and system-specific TraceState values.
+ /// </summary>
+ public readonly partial struct ActivityContext : IEquatable<ActivityContext>
+ {
+ public override int GetHashCode() => HashCode.Combine(TraceId, SpanId, TraceFlags, TraceState);
+ }
+}
\ No newline at end of file
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics
+{
+ /// <summary>
+ /// ActivityContext representation conforms to the w3c TraceContext specification. It contains two identifiers
+ /// a TraceId and a SpanId - along with a set of common TraceFlags and system-specific TraceState values.
+ /// </summary>
+ public readonly partial struct ActivityContext : IEquatable<ActivityContext>
+ {
+ public override int GetHashCode()
+ {
+ if (this == default)
+ return 0;
+
+ // HashCode.Combine would be the best but we need to compile for the full framework which require adding dependency
+ // on the extensions package. Considering this simple type and hashing is not expected to be used much, we are implementing
+ // the hashing manually.
+ int hash = 5381;
+ hash = ((hash << 5) + hash) + TraceId.GetHashCode();
+ hash = ((hash << 5) + hash) + SpanId.GetHashCode();
+ hash = ((hash << 5) + hash) + (int) TraceFlags;
+ hash = ((hash << 5) + hash) + (TraceState == null ? 0 : TraceState.GetHashCode());
+
+ return hash;
+ }
+ }
+}
\ No newline at end of file
/// </summary>
/// <param name="name">Event name.</param>
/// <param name="attributes">Event attributes.</param>
- public ActivityEvent(string name, IEnumerable<KeyValuePair<string, object>>? attributes)
+ public ActivityEvent(string name, IEnumerable<KeyValuePair<string, object>>? attributes) : this(name, default, attributes)
{
- this.Name = name ?? string.Empty;
- this.Attributes = attributes ?? s_emptyAttributes;
- this.Timestamp = DateTimeOffset.UtcNow;
}
/// <summary>
/// <param name="attributes">Event attributes.</param>
public ActivityEvent(string name, DateTimeOffset timestamp, IEnumerable<KeyValuePair<string, object>>? attributes)
{
- this.Name = name ?? string.Empty;
- this.Attributes = attributes ?? s_emptyAttributes;
- this.Timestamp = timestamp != default ? timestamp : DateTimeOffset.UtcNow;
+ Name = name ?? string.Empty;
+ Attributes = attributes ?? s_emptyAttributes;
+ Timestamp = timestamp != default ? timestamp : DateTimeOffset.UtcNow;
}
/// <summary>
/// <summary>
/// Gets the collection of attributes associated with the event.
/// </summary>
- public IEnumerable<KeyValuePair<string, object>>? Attributes { get; }
+ public IEnumerable<KeyValuePair<string, object>> Attributes { get; }
}
}
\ No newline at end of file
/// Links can be used to represent batched operations where a Activity was initiated by multiple initiating Activities,
/// each representing a single incoming item being processed in the batch.
/// </summary>
- public readonly struct ActivityLink : IEquatable<ActivityLink>
+ public readonly partial struct ActivityLink : IEquatable<ActivityLink>
{
/// <summary>
/// Construct a new <see cref="ActivityLink"/> object which can be linked to an Activity object.
public bool Equals(ActivityLink value) => Context == value.Context && value.Attributes == Attributes;
public static bool operator ==(ActivityLink left, ActivityLink right) => left.Equals(right);
public static bool operator !=(ActivityLink left, ActivityLink right) => !left.Equals(right);
-
- public override int GetHashCode()
- {
- if (this == default)
- return 0;
-
- // HashCode.Combine would be the best but we need to compile for the full framework which require adding dependency
- // on the extensions package. Considering this simple type and hashing is not expected to be used, we are implementing
- // the hashing manually.
- int hash = 5381;
- hash = ((hash << 5) + hash) + this.Context.GetHashCode();
- if (Attributes != null)
- {
- foreach (KeyValuePair<string, object> kvp in Attributes)
- {
- hash = ((hash << 5) + hash) + kvp.Key.GetHashCode();
- if (kvp.Value != null)
- {
- hash = ((hash << 5) + hash) + kvp.Value.GetHashCode();
- }
- }
- }
-
- return hash;
- }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.Diagnostics
+{
+ /// <summary>
+ /// Activity may be linked to zero or more other <see cref="ActivityContext"/> that are causally related.
+ /// Links can point to ActivityContexts inside a single Trace or across different Traces.
+ /// Links can be used to represent batched operations where a Activity was initiated by multiple initiating Activities,
+ /// each representing a single incoming item being processed in the batch.
+ /// </summary>
+ public readonly partial struct ActivityLink : IEquatable<ActivityLink>
+ {
+ public override int GetHashCode()
+ {
+ HashCode hashCode = default;
+ hashCode.Add(Context);
+ if (Attributes != null)
+ {
+ foreach (KeyValuePair<string, object> kvp in Attributes)
+ {
+ hashCode.Add(kvp.Key);
+ hashCode.Add(kvp.Value);
+ }
+ }
+ return hashCode.ToHashCode();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.Diagnostics
+{
+ /// <summary>
+ /// Activity may be linked to zero or more other <see cref="ActivityContext"/> that are causally related.
+ /// Links can point to ActivityContexts inside a single Trace or across different Traces.
+ /// Links can be used to represent batched operations where a Activity was initiated by multiple initiating Activities,
+ /// each representing a single incoming item being processed in the batch.
+ /// </summary>
+ public readonly partial struct ActivityLink : IEquatable<ActivityLink>
+ {
+ public override int GetHashCode()
+ {
+ if (this == default)
+ return 0;
+
+ // HashCode.Combine would be the best but we need to compile for the full framework which require adding dependency
+ // on the extensions package. Considering this simple type and hashing is not expected to be used, we are implementing
+ // the hashing manually.
+ int hash = 5381;
+ hash = ((hash << 5) + hash) + this.Context.GetHashCode();
+ if (Attributes != null)
+ {
+ foreach (KeyValuePair<string, object> kvp in Attributes)
+ {
+ hash = ((hash << 5) + hash) + kvp.Key.GetHashCode();
+ if (kvp.Value != null)
+ {
+ hash = ((hash << 5) + hash) + kvp.Value.GetHashCode();
+ }
+ }
+ }
+ return hash;
+ }
+ }
+}
\ No newline at end of file
/// </summary>
public ActivityListener()
{
-
}
/// <summary>
{
public sealed class ActivitySource : IDisposable
{
- private static SynchronizedList<ActivitySource> s_activeSources = new SynchronizedList<ActivitySource>();
- private static SynchronizedList<ActivityListener> s_allListeners = new SynchronizedList<ActivityListener>();
+ private static readonly SynchronizedList<ActivitySource> s_activeSources = new SynchronizedList<ActivitySource>();
+ private static readonly SynchronizedList<ActivityListener> s_allListeners = new SynchronizedList<ActivityListener>();
private SynchronizedList<ActivityListener>? _listeners;
- private ActivitySource() { throw new InvalidOperationException(); }
-
/// <summary>
/// Construct an ActivitySource object with the input name
/// </summary>
// SynchronizedList<T> is a helper collection which ensure thread safety on the collection
// and allow enumerating the collection items and execute some action on the enumerated item and can detect any change in the collection
// during the enumeration which force restarting the enumeration again.
- // Causion: We can have the action executed on the same item more than once which is ok in our scenarios.
+ // Caution: We can have the action executed on the same item more than once which is ok in our scenarios.
internal class SynchronizedList<T>
{
- private List<T> _list;
+ private readonly List<T> _list;
private uint _version;
public SynchronizedList() => _list = new List<T>();
Assert.Equal("world", anotherActivity.GetBaggageItem("hello"));
Assert.Equal(4, anotherActivity.Baggage.Count());
Assert.Equal(new KeyValuePair<string, string>("hello", "world"), anotherActivity.Baggage.First());
- Assert.Equal(new KeyValuePair<string, string>(Key + 2, Value + 2), anotherActivity.Baggage.Skip(1).First());
+ Assert.Equal(new KeyValuePair<string, string>(Key + 0, Value + 0), anotherActivity.Baggage.Skip(1).First());
Assert.Equal(new KeyValuePair<string, string>(Key + 1, Value + 1), anotherActivity.Baggage.Skip(2).First());
- Assert.Equal(new KeyValuePair<string, string>(Key + 0, Value + 0), anotherActivity.Baggage.Skip(3).First());
+ Assert.Equal(new KeyValuePair<string, string>(Key + 2, Value + 2), anotherActivity.Baggage.Skip(3).First());
}
finally
{
Assert.Equal(activity, activity.AddTag(Key + i, Value + i));
List<KeyValuePair<string, string>> tags = activity.Tags.ToList();
Assert.Equal(i + 1, tags.Count);
- Assert.Equal(tags[tags.Count - i - 1].Key, Key + i);
- Assert.Equal(tags[tags.Count - i - 1].Value, Value + i);
+ Assert.Equal(tags[i].Key, Key + i);
+ Assert.Equal(tags[i].Value, Value + i);
}
}
Assert.True(object.ReferenceEquals(activity, activity.AddEvent(new ActivityEvent("Event2", ts2))));
Assert.Equal(2, activity.Events.Count());
- Assert.Equal("Event2", activity.Events.ElementAt(0).Name);
- Assert.Equal(ts2, activity.Events.ElementAt(0).Timestamp);
+ Assert.Equal("Event1", activity.Events.ElementAt(0).Name);
+ Assert.Equal(ts1, activity.Events.ElementAt(0).Timestamp);
Assert.Equal(0, activity.Events.ElementAt(0).Attributes.Count());
- Assert.Equal("Event1", activity.Events.ElementAt(1).Name);
- Assert.Equal(ts1, activity.Events.ElementAt(1).Timestamp);
+ Assert.Equal("Event2", activity.Events.ElementAt(1).Name);
+ Assert.Equal(ts2, activity.Events.ElementAt(1).Timestamp);
Assert.Equal(0, activity.Events.ElementAt(1).Attributes.Count());
}