Support .NET Activities (#140)

* StartSpanWithActivity

* Sampler builder tests

* cleanup start span

* span tests

* optimize tracestate

* public tracestate parsing

* more tracestate tests

* Scopes and scope tests

* minor merge issues

* Do not make Span's Activity Current until WithSpan is called

* CurrentSpanUtils to static

* Tracestate parsing is not public API, reuse code without exposing it

* more tests and fixes

* rename

* rename FromCurrentActivity to SetCreateChild

* fix rebase issues

* fix build warning

* undo separate test project for Abstractions.Tests to fix build

* up
This commit is contained in:
Liudmila Molkova 2019-07-12 18:31:21 -07:00 committed by GitHub
parent 1ae543c575
commit c5f33e1742
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1814 additions and 406 deletions

View File

@ -20,9 +20,8 @@ namespace OpenTelemetry.Context.Propagation
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using OpenTelemetry.Abstractions.Context.Propagation;
using OpenTelemetry.Trace;
using OpenTelemetry.Utils;
/// <summary>
/// W3C trace context text wire protocol formatter. See https://github.com/w3c/distributed-tracing/.
@ -63,97 +62,13 @@ namespace OpenTelemetry.Context.Propagation
return SpanContext.Blank;
}
var tracestateResult = Tracestate.Empty;
try
var tracestate = Tracestate.Empty;
if (tracestateCollection != null)
{
var entries = new List<KeyValuePair<string, string>>();
var names = new HashSet<string>();
var discardTracestate = false;
if (tracestateCollection != null)
{
foreach (var tracestate in tracestateCollection)
{
if (string.IsNullOrWhiteSpace(tracestate))
{
continue;
}
// tracestate: rojo=00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01,congo=BleGNlZWRzIHRohbCBwbGVhc3VyZS4
var keyStartIdx = 0;
var length = tracestate.Length;
while (keyStartIdx < length)
{
// first skip any prefix commas and OWS
var c = tracestate[keyStartIdx];
while (c == ' ' || c == '\t' || c == ',')
{
keyStartIdx++;
if (keyStartIdx == length)
{
break;
}
c = tracestate[keyStartIdx];
}
if (keyStartIdx == length)
{
break;
}
var keyEndIdx = tracestate.IndexOf("=", keyStartIdx);
if (keyEndIdx == -1)
{
discardTracestate = true;
break;
}
var valueStartIdx = keyEndIdx + 1;
var valueEndIdx = tracestate.IndexOf(",", valueStartIdx);
valueEndIdx = valueEndIdx == -1 ? length : valueEndIdx;
// this will throw for duplicated keys
var key = tracestate.Substring(keyStartIdx, keyEndIdx - keyStartIdx).TrimStart();
if (names.Add(key))
{
entries.Add(
new KeyValuePair<string, string>(
key,
tracestate.Substring(valueStartIdx, valueEndIdx - valueStartIdx).TrimEnd()));
}
else
{
discardTracestate = true;
break;
}
keyStartIdx = valueEndIdx + 1;
}
}
}
if (!discardTracestate)
{
var tracestateBuilder = Tracestate.Builder;
entries.Reverse();
foreach (var entry in entries)
{
tracestateBuilder.Set(entry.Key, entry.Value);
}
tracestateResult = tracestateBuilder.Build();
}
}
catch (Exception)
{
// failure to parse tracestate should not disregard traceparent
// TODO: logging
this.TryExtractTracestate(tracestateCollection.ToArray(), out tracestate);
}
return SpanContext.Create(traceId, spanId, traceoptions, tracestateResult);
return SpanContext.Create(traceId, spanId, traceoptions, tracestate);
}
catch (Exception)
{
@ -172,26 +87,10 @@ namespace OpenTelemetry.Context.Propagation
setter(carrier, "traceparent", traceparent);
var sb = new StringBuilder();
var isFirst = true;
foreach (var entry in spanContext.Tracestate.Entries)
string tracestateStr = spanContext.Tracestate.ToString();
if (tracestateStr.Length > 0)
{
if (isFirst)
{
isFirst = false;
}
else
{
sb.Append(",");
}
sb.Append(entry.Key).Append("=").Append(entry.Value);
}
if (sb.Length > 0)
{
setter(carrier, "tracestate", sb.ToString());
setter(carrier, "tracestate", tracestateStr);
}
}
@ -210,7 +109,7 @@ namespace OpenTelemetry.Context.Propagation
return false;
}
// if version does not end with delimeter
// if version does not end with delimiter
if (traceparent[VersionPrefixIdLength - 1] != '-')
{
return false;
@ -268,7 +167,7 @@ namespace OpenTelemetry.Context.Propagation
try
{
options0 = this.HexCharToByte(traceparent[VersionAndTraceIdAndSpanIdLength]);
options1 = this.HexCharToByte(traceparent[VersionAndTraceIdAndSpanIdLength]);
options1 = this.HexCharToByte(traceparent[VersionAndTraceIdAndSpanIdLength + 1]);
}
catch (ArgumentOutOfRangeException)
{
@ -276,7 +175,7 @@ namespace OpenTelemetry.Context.Propagation
return false;
}
if ((options0 | 1) == 1)
if ((options1 & 1) == 1)
{
traceoptions |= ActivityTraceFlags.Recorded;
}
@ -316,5 +215,37 @@ namespace OpenTelemetry.Context.Propagation
throw new ArgumentOutOfRangeException("Invalid character: " + c);
}
private bool TryExtractTracestate(string[] tracestateCollection, out Tracestate tracestateResult)
{
tracestateResult = Tracestate.Empty;
var tracestateBuilder = Tracestate.Builder;
try
{
var names = new HashSet<string>();
if (tracestateCollection != null)
{
// Iterate in reverse order because when call builder set the elements is added in the
// front of the list.
for (int i = tracestateCollection.Length - 1; i >= 0; i--)
{
if (!TracestateUtils.TryExtractTracestate(tracestateCollection[i], tracestateBuilder))
{
return false;
}
}
}
tracestateResult = tracestateBuilder.Build();
return true;
}
catch (Exception)
{
// failure to parse tracestate should not disregard traceparent
// TODO: logging
}
return false;
}
}
}

View File

@ -0,0 +1,115 @@
// <copyright file="TracestateUtils.cs" company="OpenTelemetry Authors">
// Copyright 2018, 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.
// </copyright>
#if ABSTRACTIONS
namespace OpenTelemetry.Abstractions.Context.Propagation
#else
namespace OpenTelemetry.Context.Propagation
#endif
{
using System;
using System.Collections.Generic;
using OpenTelemetry.Trace;
/// <summary>
/// Extension methods to extract Tracestate from string.
/// </summary>
internal static class TracestateUtils
{
/// <summary>
/// Extracts <see cref="Tracestate"/> from the given string and sets it on provided <see cref="Tracestate.TracestateBuilder"/>.
/// </summary>
/// <param name="tracestateString">String with comma separated tracestate key value pairs.</param>
/// <param name="tracestateBuilder"><see cref="Tracestate.TracestateBuilder"/> to set tracestate pairs on.</param>
/// <returns>True if string was parsed successfully and tracestate was recognized, false otherwise.</returns>
internal static bool TryExtractTracestate(string tracestateString, Tracestate.TracestateBuilder tracestateBuilder)
{
if (string.IsNullOrWhiteSpace(tracestateString))
{
return true;
}
try
{
var names = new HashSet<string>();
var tracestate = tracestateString.AsSpan().Trim(' ').Trim(',');
do
{
// tracestate: rojo=00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01,congo=BleGNlZWRzIHRohbCBwbGVhc3VyZS4
// Iterate in reverse order because when call builder set the elements is added in the
// front of the list.
int pairStart = tracestate.LastIndexOf(',') + 1;
if (!TryParseKeyValue(tracestate.Slice(pairStart, tracestate.Length - pairStart), out var key, out var value))
{
return false;
}
var keyStr = key.ToString();
if (names.Add(keyStr))
{
tracestateBuilder.Set(keyStr, value.ToString());
}
else
{
return false;
}
if (pairStart == 0)
{
break;
}
tracestate = tracestate.Slice(0, pairStart - 1);
}
while (tracestate.Length > 0);
return true;
}
catch (Exception)
{
// failure to parse tracestate
// TODO: logging
}
return false;
}
private static bool TryParseKeyValue(ReadOnlySpan<char> pair, out ReadOnlySpan<char> key, out ReadOnlySpan<char> value)
{
key = default;
value = default;
var keyEndIdx = pair.IndexOf('=');
if (keyEndIdx <= 0)
{
return false;
}
var valueStartIdx = keyEndIdx + 1;
if (valueStartIdx >= pair.Length)
{
return false;
}
key = pair.Slice(0, keyEndIdx).Trim();
value = pair.Slice(valueStartIdx, pair.Length - valueStartIdx).Trim();
return true;
}
}
}

View File

@ -4,6 +4,7 @@
<TargetFrameworks Condition="$(OS) != 'Windows_NT'">netstandard2.0</TargetFrameworks>
<Description>OpenTelemetry .NET API abstractions</Description>
<RootNamespace>OpenTelemetry</RootNamespace>
<DefineConstants>$(DefineConstants);ABSTRACTIONS</DefineConstants>
</PropertyGroup>
<ItemGroup>

View File

@ -17,6 +17,7 @@
namespace OpenTelemetry.Trace
{
using System.Collections.Generic;
using System.Diagnostics;
/// <summary>
/// Span builder.
@ -38,6 +39,14 @@ namespace OpenTelemetry.Trace
/// <returns>This span builder for chaining.</returns>
ISpanBuilder SetParent(ISpan parent);
/// <summary>
/// Sets the <see cref="Activity"/> to use as a parent for the new span.
/// Any parent that was set previously will be discarded.
/// </summary>
/// <param name="parent"><see cref="Activity"/> to set as parent.</param>
/// <returns>This span builder for chaining.</returns>
ISpanBuilder SetParent(Activity parent);
/// <summary>
/// Sets the remote <see cref="SpanContext"/> to use as a parent for the new span.
/// Any parent that was set previously will be discarded.
@ -53,6 +62,17 @@ namespace OpenTelemetry.Trace
/// <returns>This span builder for chaining.</returns>
ISpanBuilder SetNoParent();
/// <summary>
/// Sets flag indicating that new span should become a child of implicit context (Activity.Current)
/// or continue run in this context and inherit it
/// Use this method with value 'false' in auto-collectors when library is instrumented with Activities.
/// Any parent that was set previously will be discarded.
/// </summary>
/// <param name="createChild">If true, a new span will become a child of the existing implicit context.
/// If false, new span will continue this context.</param>
/// <returns>This span builder for chaining.</returns>
ISpanBuilder SetCreateChild(bool createChild);
/// <summary>
/// Set <see cref="SpanKind"/> on the span.
/// </summary>
@ -63,10 +83,17 @@ namespace OpenTelemetry.Trace
/// <summary>
/// Set the <see cref="Link"/> on the span.
/// </summary>
/// <param name="spanContext"><see cref="Link"/> context to set on span.</param>
/// <param name="spanContext"><see cref="SpanContext"/> context to set on span.</param>
/// <returns>This span builder for chaining.</returns>
ISpanBuilder AddLink(SpanContext spanContext);
/// <summary>
/// Set the <see cref="Link"/> on the span.
/// </summary>
/// <param name="activity"><see cref="Activity"/> context to set on span.</param>
/// <returns>This span builder for chaining.</returns>
ISpanBuilder AddLink(Activity activity);
/// <summary>
/// Set the <see cref="Link"/> on the span.
/// </summary>

View File

@ -19,7 +19,9 @@ namespace OpenTelemetry.Trace
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using OpenTelemetry.Abstractions.Context.Propagation;
using OpenTelemetry.Abstractions.Utils;
/// <inheritdoc/>
@ -39,6 +41,25 @@ namespace OpenTelemetry.Trace
/// <inheritdoc/>
public IDictionary<string, object> Attributes { get; }
/// <summary>
/// Creates a <see cref="ILink"/> from Activity.
/// </summary>
/// <param name="activity">Activity to create link from.</param>
/// <returns>New <see cref="ILink"/> instance.</returns>
public static ILink FromActivity(Activity activity)
{
var tracestate = Tracestate.Empty;
var tracestateBuilder = Tracestate.Builder;
if (TracestateUtils.TryExtractTracestate(activity.TraceStateString, tracestateBuilder))
{
tracestate = tracestateBuilder.Build();
}
return new Link(
SpanContext.Create(activity.TraceId, activity.SpanId, activity.ActivityTraceFlags, tracestate),
EmptyAttributes);
}
public static ILink FromSpanContext(SpanContext context)
{
return new Link(context, EmptyAttributes);

View File

@ -18,6 +18,7 @@ namespace OpenTelemetry.Trace
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
/// <summary>
/// No-op span builder.
@ -56,11 +57,22 @@ namespace OpenTelemetry.Trace
}
/// <inheritdoc/>
public ISpanBuilder SetParent(ISpan parent)
public ISpanBuilder SetParent(ISpan parentSpan)
{
if (parent == null)
if (parentSpan == null)
{
throw new ArgumentNullException(nameof(parent));
throw new ArgumentNullException(nameof(parentSpan));
}
return this;
}
/// <inheritdoc/>
public ISpanBuilder SetParent(Activity parentActivity)
{
if (parentActivity == null)
{
throw new ArgumentNullException(nameof(parentActivity));
}
return this;
@ -83,6 +95,33 @@ namespace OpenTelemetry.Trace
return this;
}
/// <inheritdoc/>
public ISpanBuilder SetCreateChild(bool createChild)
{
if (!createChild)
{
var currentActivity = Activity.Current;
if (currentActivity == null)
{
throw new ArgumentException("Current Activity cannot be null");
}
if (currentActivity.IdFormat != ActivityIdFormat.W3C)
{
throw new ArgumentException("Current Activity is not in W3C format");
}
if (currentActivity.StartTimeUtc == default || currentActivity.Duration != default)
{
throw new ArgumentException(
"Current Activity is not running: it has not been started or has been stopped");
}
}
return this;
}
/// <inheritdoc/>
public ISpanBuilder SetSpanKind(SpanKind spanKind)
{
@ -100,6 +139,17 @@ namespace OpenTelemetry.Trace
return this;
}
/// <inheritdoc/>
public ISpanBuilder AddLink(Activity activity)
{
if (activity == null)
{
throw new ArgumentNullException(nameof(activity));
}
return this;
}
/// <inheritdoc/>
public ISpanBuilder AddLink(ILink link)
{

View File

@ -19,6 +19,7 @@ namespace OpenTelemetry.Trace
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// Tracestate entries allowing different vendors to participate in a trace.
@ -89,6 +90,35 @@ namespace OpenTelemetry.Trace
return new TracestateBuilder(this);
}
/// <inheritdoc/>
public override string ToString()
{
if (this.Entries.Any())
{
var sb = new StringBuilder();
var isFirst = true;
foreach (var entry in this.Entries)
{
if (isFirst)
{
isFirst = false;
}
else
{
sb.Append(",");
}
sb.Append(entry.Key).Append("=").Append(entry.Value);
}
return sb.ToString();
}
return string.Empty;
}
private static bool ValidateKey(string key)
{
// Key is opaque string up to 256 characters printable. It MUST begin with a lowercase letter, and
@ -189,7 +219,6 @@ namespace OpenTelemetry.Trace
private static Tracestate Create(ICollection<Entry> entries)
{
// TODO: discard last entries instead of throwing
if (entries.Count > MaxKeyValuePairsCount)
{
throw new ArgumentException("Too many entries.", nameof(entries));
@ -273,8 +302,12 @@ namespace OpenTelemetry.Trace
/// <returns>Tracestate builder for chained calls.</returns>
public TracestateBuilder Set(string key, string value)
{
// Initially create the Entry to validate input.
if (this.entries != null && this.entries.Count > MaxKeyValuePairsCount)
{
throw new ArgumentException("Too many entries.");
}
// Initially create the Entry to validate input.
var entry = Entry.Create(key, value);
if (this.entries == null)

View File

@ -9,6 +9,10 @@
<CodeAnalysisRuleSet>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), 'OpenTelemetry.sln'))/build/OpenTelemetry.prod.loose.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\OpenTelemetry.Abstractions\Context\Propagation\TracestateUtils.cs" Link="TracestateUtils.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenTelemetry.Abstractions\OpenTelemetry.Abstractions.csproj" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" />

View File

@ -16,24 +16,62 @@
namespace OpenTelemetry.Trace
{
using System.Threading;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using OpenTelemetry.Context;
using OpenTelemetry.Trace.Internal;
internal static class CurrentSpanUtils
{
private static readonly AsyncLocal<ISpan> AsyncLocalContext = new AsyncLocal<ISpan>();
private static readonly ConditionalWeakTable<Activity, ISpan> ActivitySpanTable = new ConditionalWeakTable<Activity, ISpan>();
public static ISpan CurrentSpan => AsyncLocalContext.Value;
public static ISpan CurrentSpan
{
get
{
var currentActivity = Activity.Current;
if (currentActivity == null)
{
return BlankSpan.Instance;
}
if (ActivitySpanTable.TryGetValue(currentActivity, out var currentSpan))
{
return currentSpan;
}
return BlankSpan.Instance;
}
}
public static IScope WithSpan(ISpan span, bool endSpan)
{
return new ScopeInSpan(span, endSpan);
}
private static void SetSpan(Span span)
{
if (span.Activity == null)
{
// log error
return;
}
if (ActivitySpanTable.TryGetValue(span.Activity, out _))
{
// log warning
return;
}
ActivitySpanTable.Add(span.Activity, span);
}
private static void DetachSpanFromActivity(Activity activity)
{
ActivitySpanTable.Remove(activity);
}
private sealed class ScopeInSpan : IScope
{
private readonly ISpan origContext;
private readonly ISpan span;
private readonly bool endSpan;
@ -41,24 +79,42 @@ namespace OpenTelemetry.Trace
{
this.span = span;
this.endSpan = endSpan;
this.origContext = AsyncLocalContext.Value;
AsyncLocalContext.Value = span;
if (span is Span spanImpl)
{
if (spanImpl.OwnsActivity)
{
Activity.Current = spanImpl.Activity;
}
SetSpan(spanImpl);
}
}
public void Dispose()
{
var current = AsyncLocalContext.Value;
AsyncLocalContext.Value = this.origContext;
if (current != this.origContext)
bool safeToStopActivity = false;
var current = (Span)this.span;
if (current != null && current.Activity == Activity.Current)
{
// Log
if (!current.OwnsActivity)
{
DetachSpanFromActivity(current.Activity);
}
else
{
safeToStopActivity = true;
}
}
if (this.endSpan)
{
this.span.End();
}
else if (safeToStopActivity)
{
current.Activity.Stop();
}
}
}
}

View File

@ -31,7 +31,6 @@ namespace OpenTelemetry.Trace
/// </summary>
public sealed class Span : ISpan, IElement<Span>
{
private readonly ActivitySpanId parentSpanId;
private readonly ITraceParams traceParams;
private readonly IStartEndHandler startEndHandler;
private readonly DateTimeOffset startTime;
@ -43,20 +42,26 @@ namespace OpenTelemetry.Trace
private DateTimeOffset endTime;
private bool hasBeenEnded;
private bool sampleToLocalSpanStore;
private Lazy<SpanContext> spanContext;
private Span(
SpanContext context,
Activity activity,
Tracestate tracestate,
SpanOptions options,
string name,
SpanKind spanKind,
ActivitySpanId parentSpanId,
ITraceParams traceParams,
IStartEndHandler startEndHandler,
Timer timestampConverter)
Timer timestampConverter,
bool stopActivity)
{
this.Context = context;
this.Activity = activity;
this.spanContext = new Lazy<SpanContext>(() => SpanContext.Create(
this.Activity.TraceId,
this.Activity.SpanId,
this.Activity.ActivityTraceFlags,
tracestate));
this.Options = options;
this.parentSpanId = parentSpanId;
this.Name = name;
this.traceParams = traceParams ?? throw new ArgumentNullException(nameof(traceParams));
this.startEndHandler = startEndHandler;
@ -81,9 +86,13 @@ namespace OpenTelemetry.Trace
this.startTime = DateTimeOffset.MinValue;
this.TimestampConverter = timestampConverter;
}
this.OwnsActivity = stopActivity;
}
public SpanContext Context { get; }
public Activity Activity { get; }
public SpanContext Context => this.spanContext.Value;
public SpanOptions Options { get; }
@ -172,7 +181,7 @@ namespace OpenTelemetry.Trace
}
}
public ActivitySpanId ParentSpanId => this.parentSpanId;
public ActivitySpanId ParentSpanId => this.Activity.ParentSpanId;
public bool HasEnded => this.hasBeenEnded;
@ -184,6 +193,8 @@ namespace OpenTelemetry.Trace
/// </summary>
internal SpanKind? Kind { get; set; }
internal bool OwnsActivity { get; }
internal Timer TimestampConverter { get; private set; }
private AttributesWithCapacity InitializedAttributes
@ -377,6 +388,12 @@ namespace OpenTelemetry.Trace
/// <inheritdoc/>
public void End()
{
if (this.OwnsActivity && this.Activity == Activity.Current)
{
// TODO log if current is not span activity
this.Activity.Stop();
}
if (!this.IsRecordingEvents)
{
return;
@ -411,8 +428,8 @@ namespace OpenTelemetry.Trace
var linksSpanData = this.links == null ? LinkList.Create(new List<ILink>(), 0) : LinkList.Create(this.links.Events, this.links.NumberOfDroppedEvents);
return SpanData.Create(
this.Context,
this.parentSpanId,
this.Context, // TODO avoid using context, use Activity instead
this.ParentSpanId,
Resource.Empty, // TODO: determine what to do with Resource in this context
this.Name,
Timestamp.FromDateTimeOffset(this.startTime),
@ -495,24 +512,26 @@ namespace OpenTelemetry.Trace
}
internal static ISpan StartSpan(
SpanContext context,
Activity activity,
Tracestate tracestate,
SpanOptions options,
string name,
SpanKind spanKind,
ActivitySpanId parentSpanId,
ITraceParams traceParams,
IStartEndHandler startEndHandler,
Timer timestampConverter)
Timer timestampConverter,
bool ownsActivity = true)
{
var span = new Span(
context,
activity,
tracestate,
options,
name,
spanKind,
parentSpanId,
traceParams,
startEndHandler,
timestampConverter);
timestampConverter,
ownsActivity);
// Call onStart here instead of calling in the constructor to make sure the span is completely
// initialized.

View File

@ -19,6 +19,7 @@ namespace OpenTelemetry.Trace
using System;
using System.Collections.Generic;
using System.Diagnostics;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Internal;
using OpenTelemetry.Trace.Config;
@ -29,9 +30,11 @@ namespace OpenTelemetry.Trace
private readonly string name;
private SpanKind kind;
private ISpan parent;
private ISpan parentSpan;
private Activity parentActivity;
private Activity fromActivity;
private SpanContext parentSpanContext;
private ParentType parentType = ParentType.CurrentSpan;
private ContextSource contextSource = ContextSource.CurrentActivityParent;
private ISampler sampler;
private List<ILink> links;
private bool recordEvents;
@ -39,14 +42,20 @@ namespace OpenTelemetry.Trace
internal SpanBuilder(string name, SpanBuilderOptions options)
{
// TODO: remove with next DiagnosticSource preview, switch to Activity setidformat
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
Activity.ForceDefaultIdFormat = true;
this.name = name ?? throw new ArgumentNullException(nameof(name));
this.options = options;
this.options = options ?? throw new ArgumentNullException(nameof(options));
}
private enum ParentType
private enum ContextSource
{
CurrentSpan,
ExplicitParent,
CurrentActivityParent,
Activity,
ExplicitActivityParent,
ExplicitSpanParent,
ExplicitRemoteParent,
NoParent,
}
@ -59,12 +68,27 @@ namespace OpenTelemetry.Trace
}
/// <inheritdoc/>
public ISpanBuilder SetParent(ISpan parent)
public ISpanBuilder SetParent(ISpan parentSpan)
{
this.parent = parent ?? throw new ArgumentNullException(nameof(parent));
this.parentType = ParentType.ExplicitParent;
this.timestampConverter = ((Span)parent)?.TimestampConverter;
this.parentSpan = parentSpan ?? throw new ArgumentNullException(nameof(parentSpan));
this.contextSource = ContextSource.ExplicitSpanParent;
if (parentSpan is Span parentSpanImpl)
{
this.timestampConverter = parentSpanImpl.TimestampConverter;
}
this.parentSpanContext = null;
this.parentActivity = null;
return this;
}
/// <inheritdoc/>
public ISpanBuilder SetParent(Activity parentActivity)
{
this.parentActivity = parentActivity ?? throw new ArgumentNullException(nameof(parentActivity));
this.contextSource = ContextSource.ExplicitActivityParent;
this.parentSpanContext = null;
this.parentSpan = null;
return this;
}
@ -72,17 +96,56 @@ namespace OpenTelemetry.Trace
public ISpanBuilder SetParent(SpanContext remoteParent)
{
this.parentSpanContext = remoteParent ?? throw new ArgumentNullException(nameof(remoteParent));
this.parent = null;
this.parentType = ParentType.ExplicitRemoteParent;
this.parentSpan = null;
this.parentActivity = null;
this.contextSource = ContextSource.ExplicitRemoteParent;
return this;
}
/// <inheritdoc/>
public ISpanBuilder SetNoParent()
{
this.parentType = ParentType.NoParent;
this.contextSource = ContextSource.NoParent;
this.parentSpanContext = null;
this.parentSpanContext = null;
this.parentActivity = null;
return this;
}
/// <inheritdoc />
public ISpanBuilder SetCreateChild(bool createChild)
{
if (!createChild)
{
var currentActivity = Activity.Current;
if (currentActivity == null)
{
throw new ArgumentException("Current Activity cannot be null");
}
if (currentActivity.IdFormat != ActivityIdFormat.W3C)
{
throw new ArgumentException("Current Activity is not in W3C format");
}
if (currentActivity.StartTimeUtc == default || currentActivity.Duration != default)
{
throw new ArgumentException(
"Current Activity is not running: it has not been started or has been stopped");
}
this.fromActivity = currentActivity;
this.contextSource = ContextSource.Activity;
}
else
{
this.contextSource = ContextSource.CurrentActivityParent;
}
this.parentSpanContext = null;
this.parentSpanContext = null;
this.parentActivity = null;
return this;
}
@ -138,6 +201,17 @@ namespace OpenTelemetry.Trace
return this;
}
/// <inheritdoc/>
public ISpanBuilder AddLink(Activity activity)
{
if (activity == null)
{
throw new ArgumentNullException(nameof(activity));
}
return this.AddLink(Link.FromActivity(activity));
}
/// <inheritdoc/>
public ISpanBuilder SetRecordEvents(bool recordEvents)
{
@ -148,56 +222,47 @@ namespace OpenTelemetry.Trace
/// <inheritdoc/>
public ISpan StartSpan()
{
SpanContext parentContext = FindParent(this.parentType, this.parent, this.parentSpanContext);
var activityForSpan = this.CreateActivityForSpan(this.contextSource, this.parentSpan,
this.parentSpanContext, this.parentActivity, this.fromActivity);
var activeTraceParams = this.options.TraceConfig.ActiveTraceParams;
ActivityTraceId traceId;
var spanId = ActivitySpanId.CreateRandom();
ActivitySpanId parentSpanId = default;
ActivityTraceFlags traceOptions = ActivityTraceFlags.None;
if (parentContext == null || !parentContext.IsValid)
{
// New root span.
traceId = ActivityTraceId.CreateRandom();
}
else
{
// New child span.
traceId = parentContext.TraceId;
parentSpanId = parentContext.SpanId;
traceOptions = parentContext.TraceOptions;
}
bool sampledIn = MakeSamplingDecision(
parentContext,
this.name,
this.sampler,
this.links,
traceId,
spanId,
activeTraceParams);
this.parentSpanContext, // it is updated in CreateActivityForSpan
this.name,
this.sampler,
this.links,
activityForSpan.TraceId,
activityForSpan.SpanId,
activeTraceParams);
var spanOptions = SpanOptions.None;
if (sampledIn || this.recordEvents)
{
traceOptions |= ActivityTraceFlags.Recorded;
activityForSpan.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
spanOptions = SpanOptions.RecordEvents;
}
else
{
traceOptions &= ~ActivityTraceFlags.Recorded;
activityForSpan.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded;
}
var childTracestate = Tracestate.Empty;
if (this.parentSpanContext?.Tracestate != null && this.parentSpanContext.Tracestate != Tracestate.Empty)
{
childTracestate = this.parentSpanContext.Tracestate.ToBuilder().Build();
}
var span = Span.StartSpan(
SpanContext.Create(traceId, spanId, traceOptions, parentContext?.Tracestate ?? Tracestate.Empty),
activityForSpan,
childTracestate, // it is updated in CreateActivityForSpan,
spanOptions,
this.name,
this.kind,
parentSpanId,
activeTraceParams,
this.options.StartEndHandler,
this.timestampConverter);
this.timestampConverter,
ownsActivity: this.contextSource != ContextSource.Activity);
LinkSpans(span, this.links);
return span;
}
@ -257,22 +322,124 @@ namespace OpenTelemetry.Trace
return (parent.TraceOptions & ActivityTraceFlags.Recorded) != 0 || IsAnyParentLinkSampled(parentLinks);
}
private static SpanContext FindParent(ParentType parentType, ISpan explicitParent, SpanContext remoteParent)
private static SpanContext ParentContextFromActivity(Activity activity)
{
switch (parentType)
var tracestate = Tracestate.Empty;
var tracestateBuilder = Tracestate.Builder;
if (TracestateUtils.TryExtractTracestate(activity.TraceStateString, tracestateBuilder))
{
case ParentType.NoParent:
return null;
case ParentType.CurrentSpan:
ISpan currentSpan = CurrentSpanUtils.CurrentSpan;
return currentSpan?.Context;
case ParentType.ExplicitParent:
return explicitParent?.Context;
case ParentType.ExplicitRemoteParent:
return remoteParent;
default:
throw new ArgumentException($"Unknown parentType {parentType}");
tracestate = tracestateBuilder.Build();
}
return SpanContext.Create(
activity.TraceId,
activity.ParentSpanId,
ActivityTraceFlags.Recorded,
tracestate);
}
private Activity CreateActivityForSpan(ContextSource contextSource, ISpan explicitParent, SpanContext remoteParent, Activity explicitParentActivity, Activity fromActivity)
{
Activity spanActivity = null;
Activity originalActivity = Activity.Current;
bool needRestoreOriginal = true;
switch (contextSource)
{
case ContextSource.CurrentActivityParent:
{
// Activity will figure out its parent
spanActivity = new Activity(this.name).Start();
// chances are, Activity.Current has span attached
if (CurrentSpanUtils.CurrentSpan is Span currentSpan)
{
this.parentSpanContext = currentSpan.Context;
}
else
{
this.parentSpanContext = ParentContextFromActivity(spanActivity);
}
break;
}
case ContextSource.ExplicitActivityParent:
{
spanActivity = new Activity(this.name)
.SetParentId(this.parentActivity.TraceId,
this.parentActivity.SpanId,
this.parentActivity.ActivityTraceFlags)
.Start();
spanActivity.TraceStateString = this.parentActivity.TraceStateString;
this.parentSpanContext = ParentContextFromActivity(spanActivity);
break;
}
case ContextSource.NoParent:
{
// TODO fix after next DiagnosticSource preview comes out - this is a hack to force activity to become orphan
spanActivity = new Activity(this.name).SetParentId(" ").Start();
this.parentSpanContext = null;
break;
}
case ContextSource.Activity:
{
this.parentSpanContext = ParentContextFromActivity(this.fromActivity);
spanActivity = this.fromActivity;
needRestoreOriginal = false;
break;
}
case ContextSource.ExplicitRemoteParent:
{
spanActivity = new Activity(this.name);
if (this.parentSpanContext.IsValid)
{
spanActivity.SetParentId(this.parentSpanContext.TraceId,
this.parentSpanContext.SpanId,
this.parentSpanContext.TraceOptions);
}
spanActivity.TraceStateString = this.parentSpanContext.Tracestate.ToString();
spanActivity.Start();
break;
}
case ContextSource.ExplicitSpanParent:
{
spanActivity = new Activity(this.name);
if (this.parentSpan.Context.IsValid)
{
spanActivity.SetParentId(this.parentSpan.Context.TraceId,
this.parentSpan.Context.SpanId,
this.parentSpan.Context.TraceOptions);
}
spanActivity.TraceStateString = this.parentSpan.Context.Tracestate.ToString();
spanActivity.Start();
this.parentSpanContext = this.parentSpan.Context;
break;
}
default:
throw new ArgumentException($"Unknown parentType {contextSource}");
}
if (needRestoreOriginal)
{
// Activity Start always puts Activity on top of Current stack
// in OpenTelemetry we ask users to enable implicit propagation by calling WithSpan
// it will set Current Activity and attach span to it.
// we need to work with .NET team to allow starting Activities without updating Current
// As a workaround here we are undoing updating Current
Activity.Current = originalActivity;
}
return spanActivity;
}
}
}

View File

@ -62,7 +62,7 @@ namespace OpenTelemetry.Trace
}
/// <inheritdoc/>
public ISpan CurrentSpan => CurrentSpanUtils.CurrentSpan ?? BlankSpan.Instance;
public ISpan CurrentSpan => CurrentSpanUtils.CurrentSpan;
/// <inheritdoc/>
public IBinaryFormat BinaryFormat { get; }
@ -89,7 +89,7 @@ namespace OpenTelemetry.Trace
throw new ArgumentNullException(nameof(span));
}
return CurrentSpanUtils.WithSpan(span, false);
return CurrentSpanUtils.WithSpan(span, true);
}
}
}

View File

@ -29,7 +29,7 @@
<ProjectReference Include="..\..\src\OpenTelemetry.Collector.AspNetCore\OpenTelemetry.Collector.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\OpenTelemetry\OpenTelemetry.csproj" />
<ProjectReference Include="..\TestApp.AspNetCore.2.0\TestApp.AspNetCore.2.0.csproj" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="2.1.2" />
</ItemGroup>

View File

@ -16,51 +16,268 @@
namespace OpenTelemetry.Trace.Test
{
using System;
using System.Diagnostics;
using Moq;
using OpenTelemetry.Trace.Internal;
using OpenTelemetry.Trace.Config;
using Xunit;
public class CurrentSpanUtilsTest
public class CurrentSpanUtilsTest: IDisposable
{
private ISpan span;
private SpanContext spanContext;
private SpanOptions spanOptions;
private readonly IStartEndHandler startEndHandler = Mock.Of<IStartEndHandler>();
public CurrentSpanUtilsTest()
{
spanContext =
SpanContext.Create(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.Recorded,
Tracestate.Empty);
spanOptions = SpanOptions.RecordEvents;
var mockSpan = new Mock<TestSpan>() { CallBase = true };
span = mockSpan.Object;
// TODO: remove with next DiagnosticSource preview, switch to Activity setidformat
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
Activity.ForceDefaultIdFormat = true;
}
[Fact]
public void CurrentSpan_WhenNoContext()
{
Assert.Null(CurrentSpanUtils.CurrentSpan);
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
}
[Fact]
public void WithSpan_CloseDetaches()
public void CurrentSpan_WhenNoSpanOnActivity()
{
Assert.Null(CurrentSpanUtils.CurrentSpan);
var ws = CurrentSpanUtils.WithSpan(span, false);
try
var a = new Activity("foo").Start();
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void WithSpan_CloseDetaches(bool stopSpan, bool recordEvents)
{
var activity = new Activity("foo").Start();
var span = Span.StartSpan(
activity,
Tracestate.Empty,
recordEvents ? SpanOptions.RecordEvents : SpanOptions.None,
"foo",
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
null);
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
using (CurrentSpanUtils.WithSpan(span, stopSpan))
{
Assert.Same(activity, Activity.Current);
Assert.Same(span, CurrentSpanUtils.CurrentSpan);
}
finally
Assert.Equal(stopSpan & recordEvents, span.HasEnded);
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
Assert.Null(Activity.Current);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void WithSpan_NotOwningActivity(bool stopSpan, bool recordEvents)
{
var activity = new Activity("foo").Start();
var span = Span.StartSpan(
activity,
Tracestate.Empty,
recordEvents ? SpanOptions.RecordEvents : SpanOptions.None,
"foo",
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
null,
false);
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
using (CurrentSpanUtils.WithSpan(span, stopSpan))
{
ws.Dispose();
Assert.Same(activity, Activity.Current);
Assert.Same(span, CurrentSpanUtils.CurrentSpan);
}
Assert.Null(CurrentSpanUtils.CurrentSpan);
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
Assert.Equal(activity, Activity.Current);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void WithSpan_NoopOnBrokenScope(bool stopSpan, bool recordEvents)
{
var parentActivity = new Activity("parent").Start();
var parentSpan = Span.StartSpan(
parentActivity,
Tracestate.Empty,
recordEvents ? SpanOptions.RecordEvents : SpanOptions.None,
"parent",
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
null);
var parentScope = CurrentSpanUtils.WithSpan(parentSpan, stopSpan);
var childActivity = new Activity("child").Start();
var childSpan = Span.StartSpan(
childActivity,
Tracestate.Empty,
recordEvents ? SpanOptions.RecordEvents : SpanOptions.None,
"child",
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
null);
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
var childScope = CurrentSpanUtils.WithSpan(childSpan, stopSpan);
parentScope.Dispose();
Assert.Equal(stopSpan & recordEvents, parentSpan.HasEnded);
Assert.False(childSpan.HasEnded);
Assert.Same(childSpan, CurrentSpanUtils.CurrentSpan);
Assert.Equal(childActivity, Activity.Current);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void WithSpan_RestoresParentScope(bool stopSpan, bool recordEvents)
{
var parentActivity = new Activity("parent").Start();
var parentSpan = Span.StartSpan(
parentActivity,
Tracestate.Empty,
recordEvents ? SpanOptions.RecordEvents : SpanOptions.None,
"parent",
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
null);
var parentScope = CurrentSpanUtils.WithSpan(parentSpan, stopSpan);
var childActivity = new Activity("child").Start();
var childSpan = Span.StartSpan(
childActivity,
Tracestate.Empty,
recordEvents ? SpanOptions.RecordEvents : SpanOptions.None,
"child",
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
null);
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
var childScope = CurrentSpanUtils.WithSpan(childSpan, stopSpan);
childScope.Dispose();
Assert.Equal(stopSpan & recordEvents, childSpan.HasEnded);
Assert.False(parentSpan.HasEnded);
Assert.Same(parentSpan, CurrentSpanUtils.CurrentSpan);
Assert.Equal(parentActivity, Activity.Current);
}
[Fact]
public void WithSpan_SameActivityCreateScopeTwice()
{
var activity = new Activity("foo").Start();
var span = Span.StartSpan(
activity,
Tracestate.Empty,
SpanOptions.RecordEvents,
"foo",
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
null);
using (CurrentSpanUtils.WithSpan(span, true))
using(CurrentSpanUtils.WithSpan(span, true))
{
Assert.Same(activity, Activity.Current);
Assert.Same(span, CurrentSpanUtils.CurrentSpan);
}
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
Assert.Null(Activity.Current);
}
[Fact]
public void WithSpan_NullActivity()
{
var activity = new Activity("foo").Start();
var span = Span.StartSpan(
activity,
Tracestate.Empty,
SpanOptions.RecordEvents,
"foo",
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
null);
activity.Stop();
using (CurrentSpanUtils.WithSpan(span, true))
{
Assert.Null(Activity.Current);
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
}
Assert.Null(Activity.Current);
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void WithSpan_WrongActivity(bool stopSpan, bool recordEvents)
{
var activity = new Activity("foo").Start();
var span = Span.StartSpan(
activity,
Tracestate.Empty,
recordEvents ? SpanOptions.RecordEvents : SpanOptions.None,
"foo",
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
null);
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
using (CurrentSpanUtils.WithSpan(span, stopSpan))
{
Assert.Same(activity, Activity.Current);
Assert.Same(span, CurrentSpanUtils.CurrentSpan);
var anotherActivity = new Activity("foo").Start();
}
Assert.Equal(stopSpan & recordEvents, span.HasEnded);
Assert.Same(BlankSpan.Instance, CurrentSpanUtils.CurrentSpan);
Assert.NotSame(activity, Activity.Current);
Assert.NotNull(Activity.Current);
}
public void Dispose()
{
Activity.Current = null;
}
}
}

View File

@ -17,6 +17,7 @@
namespace OpenTelemetry.Trace.Test
{
using System;
using System.Linq;
using System.Collections.Generic;
using OpenTelemetry.Utils;
using Xunit;
@ -99,7 +100,7 @@ namespace OpenTelemetry.Trace.Test
"MyStringAttributeKey", "MyStringAttributeValue");
@event = Event.Create("MyEventText2", attributes);
Assert.Contains("MyEventText2", @event.ToString());
Assert.Contains(Collections.ToString(attributes), @event.ToString());
Assert.Contains(string.Join(",", attributes.Select(kvp => $"{kvp.Key}={kvp.Value}")), @event.ToString());
}
}
}

View File

@ -14,6 +14,8 @@
// limitations under the License.
// </copyright>
using System;
namespace OpenTelemetry.Trace.Export.Test
{
using System.Diagnostics;
@ -23,7 +25,7 @@ namespace OpenTelemetry.Trace.Export.Test
using OpenTelemetry.Trace.Internal;
using Xunit;
public class InProcessRunningSpanStoreTest
public class InProcessRunningSpanStoreTest : IDisposable
{
private const string SpanName1 = "MySpanName/1";
private const string SpanName2 = "MySpanName/2";
@ -39,17 +41,13 @@ namespace OpenTelemetry.Trace.Export.Test
private ISpan CreateSpan(string spanName)
{
var spanContext =
SpanContext.Create(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None, Tracestate.Empty);
var activity = new Activity(spanName).Start();
return Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
spanName,
SpanKind.Internal,
ActivitySpanId.CreateRandom(),
TraceParams.Default,
startEndHandler,
null);
@ -125,6 +123,11 @@ namespace OpenTelemetry.Trace.Export.Test
span2.End();
}
public void Dispose()
{
Activity.Current = null;
}
// [Fact]
// public void getActiveSpans_SpansWithSameName()
// {

View File

@ -26,13 +26,10 @@ namespace OpenTelemetry.Trace.Export.Test
using OpenTelemetry.Utils;
using Xunit;
public class InProcessSampledSpanStoreTest
public class InProcessSampledSpanStoreTest : IDisposable
{
private static readonly string REGISTERED_SPAN_NAME = "MySpanName/1";
private static readonly string NOT_REGISTERED_SPAN_NAME = "MySpanName/2";
private readonly SpanContext sampledSpanContext;
private readonly SpanContext notSampledSpanContext;
private const string RegisteredSpanName = "MySpanName/1";
private const string NotRegisteredSpanName = "MySpanName/2";
private readonly ActivitySpanId parentSpanId;
private readonly SpanOptions recordSpanOptions = SpanOptions.RecordEvents;
@ -50,20 +47,18 @@ namespace OpenTelemetry.Trace.Export.Test
{
timestamp = Timestamp.FromDateTimeOffset(startTime);
timestampConverter = Timer.StartNew(startTime, () => interval);
sampledSpanContext = SpanContext.Create(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded, Tracestate.Empty);
notSampledSpanContext = SpanContext.Create(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None, Tracestate.Empty);
parentSpanId = ActivitySpanId.CreateRandom();
startEndHandler = new TestStartEndHandler(sampleStore);
sampleStore.RegisterSpanNamesForCollection(new List<string>() { REGISTERED_SPAN_NAME });
sampleStore.RegisterSpanNamesForCollection(new List<string>() { RegisteredSpanName });
}
[Fact]
public void AddSpansWithRegisteredNamesInAllLatencyBuckets()
{
AddSpanNameToAllLatencyBuckets(REGISTERED_SPAN_NAME);
AddSpanNameToAllLatencyBuckets(RegisteredSpanName);
var perSpanNameSummary = sampleStore.Summary.PerSpanNameSummary;
Assert.Equal(1, perSpanNameSummary.Count);
var latencyBucketsSummaries = perSpanNameSummary[REGISTERED_SPAN_NAME].NumbersOfLatencySampledSpans;
var latencyBucketsSummaries = perSpanNameSummary[RegisteredSpanName].NumbersOfLatencySampledSpans;
Assert.Equal(LatencyBucketBoundaries.Values.Count, latencyBucketsSummaries.Count);
foreach (var it in latencyBucketsSummaries)
{
@ -74,42 +69,42 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void AddSpansWithoutRegisteredNamesInAllLatencyBuckets()
{
AddSpanNameToAllLatencyBuckets(NOT_REGISTERED_SPAN_NAME);
AddSpanNameToAllLatencyBuckets(NotRegisteredSpanName);
var perSpanNameSummary = sampleStore.Summary.PerSpanNameSummary;
Assert.Equal(1, perSpanNameSummary.Count);
Assert.False(perSpanNameSummary.ContainsKey(NOT_REGISTERED_SPAN_NAME));
Assert.False(perSpanNameSummary.ContainsKey(NotRegisteredSpanName));
}
[Fact]
public void RegisterUnregisterAndListSpanNames()
{
Assert.Contains(REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection);
Assert.Contains(RegisteredSpanName, sampleStore.RegisteredSpanNamesForCollection);
Assert.Equal(1, sampleStore.RegisteredSpanNamesForCollection.Count);
sampleStore.RegisterSpanNamesForCollection(new List<string>() { NOT_REGISTERED_SPAN_NAME });
sampleStore.RegisterSpanNamesForCollection(new List<string>() { NotRegisteredSpanName });
Assert.Contains(REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection);
Assert.Contains(NOT_REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection);
Assert.Contains(RegisteredSpanName, sampleStore.RegisteredSpanNamesForCollection);
Assert.Contains(NotRegisteredSpanName, sampleStore.RegisteredSpanNamesForCollection);
Assert.Equal(2, sampleStore.RegisteredSpanNamesForCollection.Count);
sampleStore.UnregisterSpanNamesForCollection(new List<string>() { NOT_REGISTERED_SPAN_NAME });
sampleStore.UnregisterSpanNamesForCollection(new List<string>() { NotRegisteredSpanName });
Assert.Contains(REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection);
Assert.Contains(RegisteredSpanName, sampleStore.RegisteredSpanNamesForCollection);
Assert.Equal(1, sampleStore.RegisteredSpanNamesForCollection.Count);
}
[Fact]
public void RegisterSpanNamesViaSpanBuilderOption()
{
Assert.Contains(REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection);
Assert.Contains(RegisteredSpanName, sampleStore.RegisteredSpanNamesForCollection);
Assert.Equal(1, sampleStore.RegisteredSpanNamesForCollection.Count);
var span = CreateSampledSpan(NOT_REGISTERED_SPAN_NAME);
var span = CreateSampledSpan(NotRegisteredSpanName);
span.IsSampleToLocalSpanStore = true;
span.End();
Assert.Contains(REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection);
Assert.Contains(NOT_REGISTERED_SPAN_NAME, sampleStore.RegisteredSpanNamesForCollection);
Assert.Contains(RegisteredSpanName, sampleStore.RegisteredSpanNamesForCollection);
Assert.Contains(NotRegisteredSpanName, sampleStore.RegisteredSpanNamesForCollection);
Assert.Equal(2, sampleStore.RegisteredSpanNamesForCollection.Count);
}
@ -117,10 +112,10 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void AddSpansWithRegisteredNamesInAllErrorBuckets()
{
AddSpanNameToAllErrorBuckets(REGISTERED_SPAN_NAME);
AddSpanNameToAllErrorBuckets(RegisteredSpanName);
var perSpanNameSummary = sampleStore.Summary.PerSpanNameSummary;
Assert.Equal(1, perSpanNameSummary.Count);
var errorBucketsSummaries = perSpanNameSummary[REGISTERED_SPAN_NAME].NumbersOfErrorSampledSpans;
var errorBucketsSummaries = perSpanNameSummary[RegisteredSpanName].NumbersOfErrorSampledSpans;
var ccCount = Enum.GetValues(typeof(CanonicalCode)).Cast<CanonicalCode>().Count();
Assert.Equal(ccCount - 1, errorBucketsSummaries.Count);
foreach (var it in errorBucketsSummaries)
@ -132,23 +127,23 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void AddSpansWithoutRegisteredNamesInAllErrorBuckets()
{
AddSpanNameToAllErrorBuckets(NOT_REGISTERED_SPAN_NAME);
AddSpanNameToAllErrorBuckets(NotRegisteredSpanName);
var perSpanNameSummary = sampleStore.Summary.PerSpanNameSummary;
Assert.Equal(1, perSpanNameSummary.Count);
Assert.False(perSpanNameSummary.ContainsKey(NOT_REGISTERED_SPAN_NAME));
Assert.False(perSpanNameSummary.ContainsKey(NotRegisteredSpanName));
}
[Fact]
public void GetErrorSampledSpans()
{
var span = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(10);
span.Status = Status.Cancelled;
span.End();
var samples =
sampleStore.GetErrorSampledSpans(
SampledSpanStoreErrorFilter.Create(REGISTERED_SPAN_NAME, CanonicalCode.Cancelled, 0));
SampledSpanStoreErrorFilter.Create(RegisteredSpanName, CanonicalCode.Cancelled, 0));
Assert.Single(samples);
Assert.Contains(span.ToSpanData(), samples);
}
@ -156,21 +151,21 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void GetErrorSampledSpans_MaxSpansToReturn()
{
var span1 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span1 = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(10);
span1.Status = Status.Cancelled;
span1.End();
// Advance time to allow other spans to be sampled.
interval += TimeSpan.FromSeconds(5);
var span2 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span2 = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(10);
span2.Status = Status.Cancelled;
span2.End();
var samples =
sampleStore.GetErrorSampledSpans(
SampledSpanStoreErrorFilter.Create(REGISTERED_SPAN_NAME, CanonicalCode.Cancelled, 1));
SampledSpanStoreErrorFilter.Create(RegisteredSpanName, CanonicalCode.Cancelled, 1));
Assert.Single(samples);
// No order guaranteed so one of the spans should be in the list.
Assert.True(samples.Contains(span1.ToSpanData()) || samples.Contains(span2.ToSpanData()));
@ -179,19 +174,19 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void GetErrorSampledSpans_NullCode()
{
var span1 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span1 = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(10);
span1.Status = Status.Cancelled;;
span1.End();
var span2 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span2 = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(10);
span2.Status = Status.Unknown;
span2.End();
var samples =
sampleStore.GetErrorSampledSpans(SampledSpanStoreErrorFilter.Create(REGISTERED_SPAN_NAME, null, 0));
sampleStore.GetErrorSampledSpans(SampledSpanStoreErrorFilter.Create(RegisteredSpanName, null, 0));
Assert.Equal(2, samples.Count());
Assert.Contains(span1.ToSpanData(), samples);
Assert.Contains(span2.ToSpanData(), samples);
@ -200,17 +195,17 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void GetErrorSampledSpans_NullCode_MaxSpansToReturn()
{
var span1 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span1 = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(10);
span1.Status = Status.Cancelled;
span1.End();
var span2 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span2 = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(10);
span2.Status = Status.Unknown;
span2.End();
var samples =
sampleStore.GetErrorSampledSpans(SampledSpanStoreErrorFilter.Create(REGISTERED_SPAN_NAME, null, 1));
sampleStore.GetErrorSampledSpans(SampledSpanStoreErrorFilter.Create(RegisteredSpanName, null, 1));
Assert.Single(samples);
Assert.True(samples.Contains(span1.ToSpanData()) || samples.Contains(span2.ToSpanData()));
}
@ -218,13 +213,13 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void GetLatencySampledSpans()
{
var span = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(200); // 20 microseconds
span.End();
var samples =
sampleStore.GetLatencySampledSpans(
SampledSpanStoreLatencyFilter.Create(
REGISTERED_SPAN_NAME,
RegisteredSpanName,
TimeSpan.FromTicks(150),
TimeSpan.FromTicks(250),
0));
@ -235,13 +230,13 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void GetLatencySampledSpans_ExclusiveUpperBound()
{
var span = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(200); // 20 microseconds
span.End();
var samples =
sampleStore.GetLatencySampledSpans(
SampledSpanStoreLatencyFilter.Create(
REGISTERED_SPAN_NAME,
RegisteredSpanName,
TimeSpan.FromTicks(150),
TimeSpan.FromTicks(200),
0));
@ -251,13 +246,13 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void GetLatencySampledSpans_InclusiveLowerBound()
{
var span = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(200); // 20 microseconds
span.End();
var samples =
sampleStore.GetLatencySampledSpans(
SampledSpanStoreLatencyFilter.Create(
REGISTERED_SPAN_NAME,
RegisteredSpanName,
TimeSpan.FromTicks(150),
TimeSpan.FromTicks(250),
0));
@ -268,18 +263,18 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void GetLatencySampledSpans_QueryBetweenMultipleBuckets()
{
var span1 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span1 = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(200); // 20 microseconds
span1.End();
// Advance time to allow other spans to be sampled.
interval += TimeSpan.FromSeconds(5);
var span2 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span2 = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(2000); // 200 microseconds
span2.End();
var samples =
sampleStore.GetLatencySampledSpans(
SampledSpanStoreLatencyFilter.Create(
REGISTERED_SPAN_NAME,
RegisteredSpanName,
TimeSpan.FromTicks(150),
TimeSpan.FromTicks(2500),
0));
@ -291,18 +286,18 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void GetLatencySampledSpans_MaxSpansToReturn()
{
var span1 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span1 = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(200); // 20 microseconds
span1.End();
// Advance time to allow other spans to be sampled.
interval += TimeSpan.FromSeconds(5);
var span2 = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span2 = CreateSampledSpan(RegisteredSpanName) as Span;
interval += TimeSpan.FromTicks(2000); // 200 microseconds
span2.End();
var samples =
sampleStore.GetLatencySampledSpans(
SampledSpanStoreLatencyFilter.Create(
REGISTERED_SPAN_NAME,
RegisteredSpanName,
TimeSpan.FromTicks(150),
TimeSpan.FromTicks(2500),
1));
@ -313,23 +308,26 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void IgnoreNegativeSpanLatency()
{
var span = CreateSampledSpan(REGISTERED_SPAN_NAME) as Span;
var span = CreateSampledSpan(RegisteredSpanName) as Span;
interval -= TimeSpan.FromTicks(200); // 20 microseconds
span.End();
var samples =
sampleStore.GetLatencySampledSpans(
SampledSpanStoreLatencyFilter.Create(REGISTERED_SPAN_NAME, TimeSpan.Zero, TimeSpan.MaxValue, 0));
SampledSpanStoreLatencyFilter.Create(RegisteredSpanName, TimeSpan.Zero, TimeSpan.MaxValue, 0));
Assert.Empty(samples);
}
private Span CreateSampledSpan(string spanName)
{
var activity = new Activity(spanName).Start();
activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
return (Span)Span.StartSpan(
sampledSpanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
spanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -337,12 +335,15 @@ namespace OpenTelemetry.Trace.Export.Test
private Span CreateNotSampledSpan(string spanName)
{
var activity = new Activity(spanName).Start();
activity.ActivityTraceFlags = ActivityTraceFlags.None;
return (Span)Span.StartSpan(
notSampledSpanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
spanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -374,6 +375,8 @@ namespace OpenTelemetry.Trace.Export.Test
notSampledSpan.Status = code.ToStatus();
sampledSpan.End();
notSampledSpan.End();
sampledSpan.Activity.Stop();
notSampledSpan.Activity.Stop();
}
}
}
@ -397,5 +400,10 @@ namespace OpenTelemetry.Trace.Export.Test
sampleStore.ConsiderForSampling(span);
}
}
public void Dispose()
{
Activity.Current = null;
}
}
}

View File

@ -30,12 +30,12 @@ namespace OpenTelemetry.Trace.Export.Test
using OpenTelemetry.Trace.Internal;
using Xunit;
public class SpanExporterTest
public class SpanExporterTest : IDisposable
{
private const String SPAN_NAME_1 = "MySpanName/1";
private const String SPAN_NAME_2 = "MySpanName/2";
private readonly SpanContext sampledSpanContext;
private readonly SpanContext notSampledSpanContext;
private const string SpanName1 = "MySpanName/1";
private const string SpanName2 = "MySpanName/2";
private readonly Activity sampledActivity;
private readonly Activity notSampledActivity;
private readonly ISpanExporter spanExporter = SpanExporter.Create(4, Duration.Create(1, 0));
private readonly IRunningSpanStore runningSpanStore = new InProcessRunningSpanStore();
private readonly IStartEndHandler startEndHandler;
@ -45,8 +45,10 @@ namespace OpenTelemetry.Trace.Export.Test
public SpanExporterTest()
{
sampledSpanContext = SpanContext.Create(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded, Tracestate.Empty);
notSampledSpanContext = SpanContext.Create(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None, Tracestate.Empty);
sampledActivity = new Activity("foo");
sampledActivity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
notSampledActivity = new Activity("foo");
startEndHandler = new StartEndHandler(spanExporter, runningSpanStore, null, new SimpleEventQueue());
spanExporter.RegisterHandler("test.service", serviceHandler);
@ -54,13 +56,14 @@ namespace OpenTelemetry.Trace.Export.Test
private Span CreateSampledEndedSpan(string spanName)
{
sampledActivity.Start();
var span =
Span.StartSpan(
sampledSpanContext,
sampledActivity,
Tracestate.Empty,
recordSpanOptions,
spanName,
SpanKind.Internal,
default,
TraceParams.Default,
startEndHandler,
null);
@ -70,13 +73,14 @@ namespace OpenTelemetry.Trace.Export.Test
private Span CreateNotSampledEndedSpan(string spanName)
{
notSampledActivity.Start();
var span =
Span.StartSpan(
notSampledSpanContext,
notSampledActivity,
Tracestate.Empty,
recordSpanOptions,
spanName,
SpanKind.Internal,
default,
TraceParams.Default,
startEndHandler,
null);
@ -87,8 +91,8 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void ExportDifferentSampledSpans()
{
var span1 = CreateSampledEndedSpan(SPAN_NAME_1);
var span2 = CreateSampledEndedSpan(SPAN_NAME_2);
var span1 = CreateSampledEndedSpan(SpanName1);
var span2 = CreateSampledEndedSpan(SpanName2);
var exported = serviceHandler.WaitForExport(2);
Assert.Equal(2, exported.Count());
Assert.Contains(span1.ToSpanData(), exported);
@ -98,12 +102,12 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void ExportMoreSpansThanTheBufferSize()
{
var span1 = CreateSampledEndedSpan(SPAN_NAME_1);
var span2 = CreateSampledEndedSpan(SPAN_NAME_1);
var span3 = CreateSampledEndedSpan(SPAN_NAME_1);
var span4 = CreateSampledEndedSpan(SPAN_NAME_1);
var span5 = CreateSampledEndedSpan(SPAN_NAME_1);
var span6 = CreateSampledEndedSpan(SPAN_NAME_1);
var span1 = CreateSampledEndedSpan(SpanName1);
var span2 = CreateSampledEndedSpan(SpanName1);
var span3 = CreateSampledEndedSpan(SpanName1);
var span4 = CreateSampledEndedSpan(SpanName1);
var span5 = CreateSampledEndedSpan(SpanName1);
var span6 = CreateSampledEndedSpan(SpanName1);
var exported = serviceHandler.WaitForExport(6);
Assert.Equal(6, exported.Count());
Assert.Contains(span1.ToSpanData(), exported);
@ -135,13 +139,13 @@ namespace OpenTelemetry.Trace.Export.Test
// .when(mockServiceHandler)
// .export(anyListOf(SpanData));
spanExporter.RegisterHandler("mock.service", mockServiceHandler);
var span1 = CreateSampledEndedSpan(SPAN_NAME_1);
var span1 = CreateSampledEndedSpan(SpanName1);
var exported = serviceHandler.WaitForExport(1);
Assert.Single(exported);
Assert.Contains(span1.ToSpanData(), exported);
// assertThat(exported).containsExactly(span1.toSpanData());
// Continue to export after the exception was received.
var span2 = CreateSampledEndedSpan(SPAN_NAME_1);
var span2 = CreateSampledEndedSpan(SpanName1);
exported = serviceHandler.WaitForExport(1);
Assert.Single(exported);
Assert.Contains(span2.ToSpanData(), exported);
@ -153,8 +157,8 @@ namespace OpenTelemetry.Trace.Export.Test
{
var serviceHandler2 = new TestHandler();
spanExporter.RegisterHandler("test.service2", serviceHandler2);
var span1 = CreateSampledEndedSpan(SPAN_NAME_1);
var span2 = CreateSampledEndedSpan(SPAN_NAME_2);
var span1 = CreateSampledEndedSpan(SpanName1);
var span2 = CreateSampledEndedSpan(SpanName2);
var exported1 = serviceHandler.WaitForExport(2);
var exported2 = serviceHandler2.WaitForExport(2);
Assert.Equal(2, exported1.Count());
@ -168,8 +172,8 @@ namespace OpenTelemetry.Trace.Export.Test
[Fact]
public void ExportNotSampledSpans()
{
var span1 = CreateNotSampledEndedSpan(SPAN_NAME_1);
var span2 = CreateSampledEndedSpan(SPAN_NAME_2);
var span1 = CreateNotSampledEndedSpan(SpanName1);
var span2 = CreateSampledEndedSpan(SpanName2);
// Spans are recorded and exported in the same order as they are ended, we test that a non
// sampled span is not exported by creating and ending a sampled span after a non sampled span
// and checking that the first exported span is the sampled span (the non sampled did not get
@ -191,7 +195,7 @@ namespace OpenTelemetry.Trace.Export.Test
exporter.RegisterHandler("first", handler1.Object);
var span1 = CreateNotSampledEndedSpan(SPAN_NAME_1).ToSpanData();
var span1 = CreateNotSampledEndedSpan(SpanName1).ToSpanData();
await exporter.ExportAsync(span1, CancellationToken.None);
@ -202,6 +206,12 @@ namespace OpenTelemetry.Trace.Export.Test
(x) => x.Where((s) => s == span1).Count() > 0 &&
x.Count() == 1)));
}
public void Dispose()
{
spanExporter?.Dispose();
Activity.Current = null;
}
}
}

View File

@ -14,11 +14,12 @@
// limitations under the License.
// </copyright>
using System.Linq;
namespace OpenTelemetry.Trace.Test
{
using System.Collections.Generic;
using System.Diagnostics;
using OpenTelemetry.Trace.Internal;
using OpenTelemetry.Utils;
using Xunit;
@ -30,6 +31,10 @@ namespace OpenTelemetry.Trace.Test
public LinkTest()
{
// TODO: remove with next DiagnosticSource preview, switch to Activity setidformat
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
Activity.ForceDefaultIdFormat = true;
spanContext = SpanContext.Create(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None, Tracestate.Empty); ;
attributesMap.Add("MyAttributeKey0", AttributeValue<string>.Create("MyStringAttribute"));
attributesMap.Add("MyAttributeKey1", AttributeValue<long>.Create(10));
@ -71,38 +76,35 @@ namespace OpenTelemetry.Trace.Test
Assert.Equal(attributesMap, link.Attributes);
}
[Fact]
public void Link_EqualsAndHashCode()
{
// EqualsTester tester = new EqualsTester();
// tester
// .addEqualityGroup(
// Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN),
// Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN))
// .addEqualityGroup(
// Link.fromSpanContext(spanContext, Type.CHILD_LINKED_SPAN),
// Link.fromSpanContext(spanContext, Type.CHILD_LINKED_SPAN))
// .addEqualityGroup(Link.fromSpanContext(SpanContext.INVALID, Type.CHILD_LINKED_SPAN))
// .addEqualityGroup(Link.fromSpanContext(SpanContext.INVALID, Type.PARENT_LINKED_SPAN))
// .addEqualityGroup(
// Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN, attributesMap),
// Link.fromSpanContext(spanContext, Type.PARENT_LINKED_SPAN, attributesMap));
// tester.testEquals();
}
[Fact]
public void Link_ToString()
{
var link = Link.FromSpanContext(spanContext, attributesMap);
Assert.Contains(spanContext.TraceId.ToString(), link.ToString());
Assert.Contains(spanContext.SpanId.ToString(), link.ToString());
Assert.Contains(Collections.ToString(attributesMap), link.ToString());
Assert.Contains(string.Join(" ", attributesMap.Select(kvp => $"{kvp.Key}={kvp.Value}")), link.ToString());
link = Link.FromSpanContext(spanContext, attributesMap);
Assert.Contains(spanContext.TraceId.ToString(), link.ToString());
Assert.Contains(spanContext.SpanId.ToString(), spanContext.SpanId.ToString());
Assert.Contains(Collections.ToString(attributesMap), link.ToString());
Assert.Contains(string.Join(" ", attributesMap.Select(kvp => $"{kvp.Key}={kvp.Value}")), link.ToString());
}
[Fact]
public void FromSpanContext_FromActivity()
{
var activity = new Activity("foo").Start();
activity.TraceStateString = "k1=v1, k2=v2";
var link = Link.FromActivity(activity);
Assert.Equal(activity.TraceId, link.Context.TraceId);
Assert.Equal(activity.SpanId, link.Context.SpanId);
var entries = link.Context.Tracestate.Entries.ToArray();
Assert.Equal(2, entries.Length);
Assert.Equal("k1", entries[0].Key);
Assert.Equal("v1", entries[0].Value);
Assert.Equal("k2", entries[1].Key);
Assert.Equal("v2", entries[1].Value);
}
}
}

View File

@ -14,10 +14,10 @@
// limitations under the License.
// </copyright>
using System;
namespace OpenTelemetry.Tests.Impl.Trace
{
using System;
using System.Diagnostics;
using OpenTelemetry.Trace;
using Xunit;
@ -31,7 +31,20 @@ namespace OpenTelemetry.Tests.Impl.Trace
var spanBuilder = new NoopSpanBuilder("foo");
Assert.Throws<ArgumentNullException>(() => spanBuilder.SetParent((ISpan)null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.SetParent((SpanContext)null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.SetParent((Activity)null));
// no Activity.Current
Assert.Throws<ArgumentException>(() => spanBuilder.SetCreateChild(false));
// Activity.Current wrong format
Activity.DefaultIdFormat = ActivityIdFormat.Hierarchical;
Activity.ForceDefaultIdFormat = true;
var a = new Activity("foo").Start(); // TODO SetIdFormat
Assert.Throws<ArgumentException>(() => spanBuilder.SetCreateChild(false));
a.Stop();
Assert.Throws<ArgumentNullException>(() => spanBuilder.SetSampler(null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.AddLink((Activity)null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.AddLink((ILink)null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.AddLink((SpanContext)null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.AddLink(null, null));

View File

@ -14,10 +14,9 @@
// limitations under the License.
// </copyright>
using System;
namespace OpenTelemetry.Impl.Trace.Propagation
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@ -27,6 +26,17 @@ namespace OpenTelemetry.Impl.Trace.Propagation
public class TraceContextTest
{
private static readonly string[] empty = new string[0];
private static readonly Func<IDictionary<string, string>, string, IEnumerable<string>> getter = (headers, name) =>
{
if (headers.TryGetValue(name, out var value))
{
return new [] { value };
}
return empty;
};
[Fact]
public void TraceContextFormatCanParseExampleFromSpec()
{
@ -40,7 +50,7 @@ namespace OpenTelemetry.Impl.Trace.Propagation
};
var f = new TraceContextFormat();
var ctx = f.Extract(headers, (h, n) => new string[] {h[n]});
var ctx = f.Extract(headers, getter);
Assert.Equal(ActivityTraceId.CreateFromString("0af7651916cd43dd8448eb211c80319c".AsSpan()), ctx.TraceId);
Assert.Equal(ActivitySpanId.CreateFromString("b9c7c989f97918e1".AsSpan()), ctx.SpanId);
@ -59,13 +69,29 @@ namespace OpenTelemetry.Impl.Trace.Propagation
Assert.Equal("00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01", last.Value);
}
[Fact]
public void TraceContextFormatNotSampled()
{
var headers = new Dictionary<string, string>()
{
{"traceparent", "00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-00"},
};
var f = new TraceContextFormat();
var ctx = f.Extract(headers, getter);
Assert.Equal(ActivityTraceId.CreateFromString("0af7651916cd43dd8448eb211c80319c".AsSpan()), ctx.TraceId);
Assert.Equal(ActivitySpanId.CreateFromString("b9c7c989f97918e1".AsSpan()), ctx.SpanId);
Assert.True((ctx.TraceOptions & ActivityTraceFlags.Recorded) == 0);
}
[Fact]
public void TraceContextFormat_IsBlankIfNoHeader()
{
var headers = new Dictionary<string, string>();
var f = new TraceContextFormat();
var ctx = f.Extract(headers, (h, n) => new string[] { h[n] });
var ctx = f.Extract(headers, getter);
Assert.Same(SpanContext.Blank, ctx);
}
@ -79,9 +105,48 @@ namespace OpenTelemetry.Impl.Trace.Propagation
};
var f = new TraceContextFormat();
var ctx = f.Extract(headers, (h, n) => new string[] { h[n] });
var ctx = f.Extract(headers, getter);
Assert.Same(SpanContext.Blank, ctx);
}
[Fact]
public void TraceContextFormat_TracestateToStringEmpty()
{
var headers = new Dictionary<string, string>
{
{"traceparent", "00-abc7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"},
};
var f = new TraceContextFormat();
var ctx = f.Extract(headers, getter);
Assert.Empty(ctx.Tracestate.Entries);
Assert.Equal(string.Empty, ctx.Tracestate.ToString());
}
[Fact]
public void TraceContextFormat_TracestateToString()
{
var headers = new Dictionary<string, string>
{
{"traceparent", "00-abc7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01"},
{"tracestate", "k1=v1,k2=v2,k3=v3" },
};
var f = new TraceContextFormat();
var ctx = f.Extract(headers, getter);
var entries = ctx.Tracestate.Entries.ToArray();
Assert.Equal(3, entries.Length);
Assert.Equal("k1", entries[0].Key);
Assert.Equal("v1", entries[0].Value);
Assert.Equal("k2", entries[1].Key);
Assert.Equal("v2", entries[1].Value);
Assert.Equal("k3", entries[2].Key);
Assert.Equal("v3", entries[2].Value);
Assert.Equal("k1=v1,k2=v2,k3=v3", ctx.Tracestate.ToString());
}
}
}

View File

@ -0,0 +1,90 @@
// <copyright file="StringExtensionsTests.cs" company="OpenTelemetry Authors">
// Copyright 2018, 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.
// </copyright>
using System;
using System.Linq;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Trace;
using Xunit;
namespace OpenTelemetry.Tests.Impl.Trace.Propagation
{
public class TracestateUtilsTests
{
[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData(" ")]
[InlineData("\t")]
public void EmptyTracestate(string tracestate)
{
var builder = Tracestate.Builder;
Assert.True(TracestateUtils.TryExtractTracestate(tracestate, builder));
Assert.Empty(builder.Build().Entries);
}
[Theory]
[InlineData("k=", 0)]
[InlineData("=v", 0)]
[InlineData("kv", 0)]
[InlineData("k=v,k=v", 1)]
[InlineData("k1=v1,,,k2=v2", 1)]
[InlineData("k=morethan256......................................................................................................................................................................................................................................................", 0)]
[InlineData("v=morethan256......................................................................................................................................................................................................................................................", 0)]
public void InvalidTracestate(string tracestate, int validEntriesCount)
{
var builder = Tracestate.Builder;
Assert.False(TracestateUtils.TryExtractTracestate(tracestate, builder));
Assert.Equal(validEntriesCount, builder.Build().Entries.Count());
}
[Fact]
public void TooManyEntries()
{
var builder = Tracestate.Builder;
var tracestate =
"k0=v,k1=v,k2=v,k3=v,k4=v,k5=v,k6=v,k7=v1,k8=v,k9=v,k10=v,k11=v,k12=v,k13=v,k14=v,k15=v,k16=v,k17=v,k18=v,k19=v,k20=v,k21=v,k22=v,k23=v,k24=v,k25=v,k26=v,k27=v1,k28=v,k29=v,k30=v,k31=v,k32=v,k33=v";
Assert.False(TracestateUtils.TryExtractTracestate(tracestate, builder));
Assert.Throws<ArgumentException>(() => builder.Build());
}
[Theory]
[InlineData("k=v")]
[InlineData(" k=v ")]
[InlineData(" k = v ")]
[InlineData("\tk\t=\tv\t")]
[InlineData(",k=v,")]
[InlineData(", k = v, ")]
public void ValidPair(string pair)
{
var builder = Tracestate.Builder;
Assert.True(TracestateUtils.TryExtractTracestate(pair, builder));
Assert.Equal("k=v", builder.Build().ToString());
}
[Theory]
[InlineData("k1=v1,k2=v2")]
[InlineData(" k1=v1 , k2=v2")]
[InlineData(" ,k1=v1,k2=v2")]
[InlineData("k1=v1,k2=v2, ")]
public void ValidPairs(string tracestate)
{
var builder = Tracestate.Builder;
Assert.True(TracestateUtils.TryExtractTracestate(tracestate, builder));
Assert.Equal("k1=v1,k2=v2", builder.Build().ToString());
}
}
}

View File

@ -18,6 +18,9 @@ namespace OpenTelemetry.Trace.Test
{
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Moq;
using OpenTelemetry.Common;
using OpenTelemetry.Trace.Config;
@ -25,7 +28,7 @@ namespace OpenTelemetry.Trace.Test
using OpenTelemetry.Trace.Sampler;
using Xunit;
public class SpanBuilderTest
public class SpanBuilderTest : IDisposable
{
private static readonly string SpanName = "MySpanName";
private readonly SpanBuilderOptions spanBuilderOptions;
@ -36,14 +39,21 @@ namespace OpenTelemetry.Trace.Test
private readonly IStartEndHandler startEndHandler = Mock.Of<IStartEndHandler>();
private readonly ITraceConfig traceConfig = Mock.Of<ITraceConfig>();
private readonly ITracer tracer;
public SpanBuilderTest()
{
// TODO: remove with next DiagnosticSource preview, switch to Activity setidformat
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
Activity.ForceDefaultIdFormat = true;
// MockitoAnnotations.initMocks(this);
spanBuilderOptions =
new SpanBuilderOptions(startEndHandler, traceConfig);
var configMock = Mock.Get<ITraceConfig>(traceConfig);
configMock.Setup((c) => c.ActiveTraceParams).Returns(alwaysSampleTraceParams);
// when(traceConfig.getActiveTraceParams()).thenReturn(alwaysSampleTraceParams);
startEndHandler = Mock.Of<IStartEndHandler>();
tracer = new Tracer(startEndHandler, traceConfig);
}
[Fact]
@ -62,6 +72,11 @@ namespace OpenTelemetry.Trace.Test
Timestamp.FromDateTimeOffset(DateTimeOffset.Now).AddDuration(Duration.Create(-1, 0)),
Timestamp.FromDateTimeOffset(DateTimeOffset.Now).AddDuration(Duration.Create(1, 0)));
Assert.Equal(SpanName, spanData.Name);
var activity = ((Span)span).Activity;
Assert.Null(Activity.Current);
Assert.Equal(activity.TraceId, span.Context.TraceId);
Assert.Equal(activity.SpanId, span.Context.SpanId);
}
[Fact]
@ -144,6 +159,66 @@ namespace OpenTelemetry.Trace.Test
Assert.Equal(spanContext.SpanId, childSpan.ParentSpanId);
}
[Fact]
public void StartSpanLastParentWins5()
{
var spanContext =
SpanContext.Create(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None, Tracestate.Empty);
var activity = new Activity("foo").Start();
var childSpan = (Span)new SpanBuilder(SpanName, spanBuilderOptions)
.SetParent(spanContext)
.SetParent(activity)
.StartSpan();
Assert.True(childSpan.Context.IsValid);
Assert.Equal(activity.TraceId, childSpan.Context.TraceId);
Assert.Equal(activity.SpanId, childSpan.ParentSpanId);
}
[Fact]
public void StartSpanLastParentWins6()
{
var spanContext =
SpanContext.Create(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None, Tracestate.Empty);
var activity = new Activity("foo").Start();
var childSpan = (Span)new SpanBuilder(SpanName, spanBuilderOptions)
.SetParent(spanContext)
.SetCreateChild(false)
.StartSpan();
Assert.True(childSpan.Context.IsValid);
Assert.Equal(activity.TraceId, childSpan.Context.TraceId);
Assert.Equal(activity.SpanId, childSpan.Context.SpanId);
}
[Fact]
public void StartSpanLastParentWins7()
{
var spanContext =
SpanContext.Create(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None, Tracestate.Empty);
var activity = new Activity("foo").Start();
var childSpan = (Span)new SpanBuilder(SpanName, spanBuilderOptions)
.SetCreateChild(false)
.SetParent(spanContext)
.StartSpan();
Assert.True(childSpan.Context.IsValid);
Assert.Equal(spanContext.TraceId, childSpan.Context.TraceId);
Assert.Equal(spanContext.SpanId, childSpan.ParentSpanId);
}
[Fact]
public void StartSpanNullParentWithRecordEvents()
{
@ -180,6 +255,7 @@ namespace OpenTelemetry.Trace.Test
.SetSpanKind(SpanKind.Internal)
.SetNoParent()
.StartSpan();
Assert.True(rootSpan.Context.IsValid);
Assert.True(rootSpan.IsRecordingEvents);
Assert.True((rootSpan.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0);
@ -195,6 +271,150 @@ namespace OpenTelemetry.Trace.Test
Assert.Equal(((Span)rootSpan).TimestampConverter, ((Span)childSpan).TimestampConverter);
}
[Fact]
public void StartSpanInScopeOfCurrentActivity()
{
var parentActivity = new Activity(SpanName).Start();
parentActivity.TraceStateString = "k1=v1,k2=v2";
var childSpan = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.StartSpan();
Assert.True(childSpan.Context.IsValid);
Assert.Equal(parentActivity.TraceId, childSpan.Context.TraceId);
Assert.Equal(parentActivity.SpanId, ((Span)childSpan).ParentSpanId);
var activity = ((Span)childSpan).Activity;
Assert.Equal(parentActivity, Activity.Current);
Assert.Equal(activity.Parent, parentActivity);
Assert.Equal("k1=v1,k2=v2", childSpan.Context.Tracestate.ToString());
}
[Fact]
public void StartSpanInScopeOfCurrentActivityRecorded()
{
var parentActivity = new Activity(SpanName).Start();
parentActivity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
var childSpan = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.StartSpan();
Assert.True(childSpan.Context.IsValid);
Assert.Equal(parentActivity.TraceId, childSpan.Context.TraceId);
Assert.Equal(parentActivity.SpanId, ((Span)childSpan).ParentSpanId);
Assert.True((childSpan.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0);
parentActivity.Stop();
}
[Fact]
public void StartSpanInScopeOfCurrentActivityNoParent()
{
var parentActivity = new Activity(SpanName).Start();
parentActivity.TraceStateString = "k1=v1,k2=v2";
var childSpan = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.SetNoParent()
.StartSpan();
Assert.True(childSpan.Context.IsValid);
Assert.NotEqual(parentActivity.TraceId, childSpan.Context.TraceId);
Assert.True(((Span)childSpan).ToSpanData().ParentSpanId == default);
var activity = ((Span)childSpan).Activity;
Assert.Equal(parentActivity, Activity.Current);
Assert.Null(activity.Parent);
Assert.Equal(activity.TraceId, childSpan.Context.TraceId);
Assert.Equal(activity.SpanId, childSpan.Context.SpanId);
Assert.Empty(childSpan.Context.Tracestate.Entries);
}
[Fact]
public void StartSpanFromExplicitActivity()
{
var parentActivity = new Activity(SpanName).Start();
parentActivity.TraceStateString = "k1=v1,k2=v2";
parentActivity.Stop();
var childSpan = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.SetParent(parentActivity)
.StartSpan();
Assert.True(childSpan.Context.IsValid);
Assert.Equal(parentActivity.TraceId, childSpan.Context.TraceId);
Assert.Equal(parentActivity.SpanId, ((Span)childSpan).ParentSpanId);
var activity = ((Span)childSpan).Activity;
Assert.NotNull(activity);
Assert.Null(Activity.Current);
Assert.Equal(activity.TraceId, parentActivity.TraceId);
Assert.Equal(activity.ParentSpanId, parentActivity.SpanId);
Assert.Equal(activity.SpanId, childSpan.Context.SpanId);
Assert.Equal("k1=v1,k2=v2", childSpan.Context.Tracestate.ToString());
}
[Fact]
public void StartSpanFromExplicitRecordedActivity()
{
var parentActivity = new Activity(SpanName).Start();
parentActivity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
parentActivity.Stop();
var childSpan = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.SetParent(parentActivity)
.StartSpan();
Assert.True(childSpan.Context.IsValid);
Assert.Equal(parentActivity.TraceId, childSpan.Context.TraceId);
Assert.Equal(parentActivity.SpanId, ((Span)childSpan).ParentSpanId);
Assert.True((childSpan.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0);
}
[Fact]
public void StartSpanFromCurrentActivity()
{
var activity = new Activity(SpanName).Start();
activity.TraceStateString = "k1=v1,k2=v2";
var span = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.SetCreateChild(false)
.StartSpan();
Assert.True(span.Context.IsValid);
Assert.Equal(activity.TraceId, span.Context.TraceId);
Assert.Equal(activity.SpanId, span.Context.SpanId);
Assert.True(((Span)span).ParentSpanId == default);
Assert.NotNull(Activity.Current);
Assert.Equal(Activity.Current, activity);
Assert.Equal("k1=v1,k2=v2", span.Context.Tracestate.ToString());
}
[Fact]
public void StartSpanFromCurrentRecordedActivity()
{
var activity = new Activity(SpanName).Start();
activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
var span = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.SetCreateChild(false)
.StartSpan();
Assert.True(span.Context.IsValid);
Assert.Equal(activity.TraceId, span.Context.TraceId);
Assert.Equal(activity.SpanId, span.Context.SpanId);
Assert.True(((Span)span).ParentSpanId == default);
Assert.True((span.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0);
activity.Stop();
}
[Fact]
public void StartSpan_ExplicitNoParent()
{
@ -208,6 +428,12 @@ namespace OpenTelemetry.Trace.Test
Assert.True((span.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0);
var spanData = ((Span)span).ToSpanData();
Assert.True(spanData.ParentSpanId == default);
var activity = ((Span)span).Activity;
Assert.Null(Activity.Current);
Assert.Equal(activity.TraceId, span.Context.TraceId);
Assert.Equal(activity.SpanId, span.Context.SpanId);
Assert.Empty(span.Context.Tracestate.Entries);
}
[Fact]
@ -224,12 +450,50 @@ namespace OpenTelemetry.Trace.Test
Assert.True(spanData.ParentSpanId == default);
}
[Fact]
public void StartSpan_BlankSpanParent()
{
var span = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.SetParent(BlankSpan.Instance)
.StartSpan();
Assert.True(span.Context.IsValid);
Assert.True(span.IsRecordingEvents);
Assert.True((span.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0);
var spanData = ((Span)span).ToSpanData();
Assert.True(spanData.ParentSpanId == default);
}
[Fact]
public void StartSpan_BlankSpanContextParent()
{
var span = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.SetParent(SpanContext.Blank)
.StartSpan();
Assert.True(span.Context.IsValid);
Assert.True(span.IsRecordingEvents);
Assert.True((span.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0);
var spanData = ((Span)span).ToSpanData();
Assert.True(spanData.ParentSpanId == default);
}
[Fact]
public void StartSpan_CurrentSpanParent()
{
var rootSpan = new SpanBuilder(SpanName, spanBuilderOptions)
.SetParent(
SpanContext.Create(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None,
Tracestate.Builder.Set("k1", "v1").Build()))
.StartSpan();
using (CurrentSpanUtils.WithSpan(rootSpan, true))
using (tracer.WithSpan(rootSpan))
{
var childSpan = (Span)new SpanBuilder(SpanName, spanBuilderOptions)
.StartSpan();
@ -237,6 +501,7 @@ namespace OpenTelemetry.Trace.Test
Assert.True(childSpan.Context.IsValid);
Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId);
Assert.Equal(rootSpan.Context.SpanId, childSpan.ParentSpanId);
Assert.Equal("k1=v1", childSpan.Context.Tracestate.ToString());
}
}
@ -245,7 +510,7 @@ namespace OpenTelemetry.Trace.Test
{
var rootSpan = new SpanBuilder(SpanName, spanBuilderOptions)
.StartSpan();
using (CurrentSpanUtils.WithSpan(rootSpan, true))
using (tracer.WithSpan(rootSpan))
{
var childSpan = (Span)new SpanBuilder(SpanName, spanBuilderOptions)
.SetNoParent()
@ -278,7 +543,8 @@ namespace OpenTelemetry.Trace.Test
SpanContext.Create(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None, Tracestate.Empty);
ActivityTraceFlags.None,
Tracestate.Builder.Set("k1", "v1").Build());
var span = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
@ -291,6 +557,109 @@ namespace OpenTelemetry.Trace.Test
Assert.True((span.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0);
var spanData = ((Span)span).ToSpanData();
Assert.Equal(spanContext.SpanId, spanData.ParentSpanId);
Assert.Equal("k1=v1", span.Context.Tracestate.ToString());
}
[Fact]
public void StartSpan_WithLink()
{
var link = Link.FromSpanContext(
SpanContext.Create(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None, Tracestate.Empty));
var span = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.AddLink(link)
.StartSpan();
var spanData = ((Span)span).ToSpanData();
var links = spanData.Links.Links.ToArray();
Assert.Single(links);
Assert.Equal(link.Context.TraceId, links[0].Context.TraceId);
Assert.Equal(link.Context.SpanId, links[0].Context.SpanId);
Assert.Equal(link.Context.TraceOptions, links[0].Context.TraceOptions);
Assert.Equal(link.Context.Tracestate, links[0].Context.Tracestate);
Assert.Equal(0, links[0].Attributes.Count);
}
[Fact]
public void StartSpan_WithLinkFromActivity()
{
var activityLink = new Activity("foo").Start();
activityLink.Stop();
var span = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.AddLink(activityLink)
.StartSpan();
var spanData = ((Span)span).ToSpanData();
var links = spanData.Links.Links.ToArray();
Assert.Single(links);
Assert.Equal(activityLink.TraceId, links[0].Context.TraceId);
Assert.Equal(activityLink.SpanId, links[0].Context.SpanId);
Assert.Equal(activityLink.ActivityTraceFlags, links[0].Context.TraceOptions);
Assert.Empty(links[0].Context.Tracestate.Entries);
Assert.Equal(0, links[0].Attributes.Count);
}
[Fact]
public void StartSpan_WithLinkFromSpanContextAndAttributes()
{
var linkContext =
SpanContext.Create(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None, Tracestate.Empty);
var span = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.AddLink(linkContext, new Dictionary<string, object> { ["k"] = "v", })
.StartSpan();
var spanData = ((Span)span).ToSpanData();
var links = spanData.Links.Links.ToArray();
Assert.Single(links);
Assert.Equal(linkContext.TraceId, links[0].Context.TraceId);
Assert.Equal(linkContext.SpanId, links[0].Context.SpanId);
Assert.Equal(linkContext.TraceOptions, links[0].Context.TraceOptions);
Assert.Equal(linkContext.Tracestate, links[0].Context.Tracestate);
Assert.Equal(1, links[0].Attributes.Count);
Assert.True(links[0].Attributes.ContainsKey("k"));
Assert.Equal("v", links[0].Attributes["k"].ToString());
}
[Fact]
public void StartSpan_WithLinkFromSpanContext()
{
var linkContext =
SpanContext.Create(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None, Tracestate.Empty);
var span = new SpanBuilder(SpanName, spanBuilderOptions)
.SetSpanKind(SpanKind.Internal)
.AddLink(linkContext)
.StartSpan();
var spanData = ((Span)span).ToSpanData();
var links = spanData.Links.Links.ToArray();
Assert.Single(links);
Assert.Equal(linkContext.TraceId, links[0].Context.TraceId);
Assert.Equal(linkContext.SpanId, links[0].Context.SpanId);
Assert.Equal(linkContext.TraceOptions, links[0].Context.TraceOptions);
Assert.Equal(linkContext.Tracestate, links[0].Context.Tracestate);
}
[Fact]
@ -491,15 +860,35 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void SpanBuilder_BadArguments()
{
var spanBuilder = new SpanBuilder(SpanName, spanBuilderOptions);
Assert.Throws<ArgumentNullException>(() => new SpanBuilder(null, spanBuilderOptions));
Assert.Throws<ArgumentNullException>(() => new SpanBuilder(SpanName, null));
var spanBuilder = new SpanBuilder(SpanName, spanBuilderOptions);
Assert.Throws<ArgumentNullException>(() => spanBuilder.SetParent((ISpan)null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.SetParent((SpanContext)null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.SetParent((Activity)null));
// no Activity.Current
Assert.Throws<ArgumentException>(() => spanBuilder.SetCreateChild(false));
// Activity.Current wrong format
Activity.DefaultIdFormat = ActivityIdFormat.Hierarchical;
Activity.ForceDefaultIdFormat = true;
var a = new Activity("foo").Start(); // TODO SetIdFormat
Assert.Throws<ArgumentException>(() => spanBuilder.SetCreateChild(false));
a.Stop();
Assert.Throws<ArgumentNullException>(() => spanBuilder.SetSampler(null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.AddLink((Activity)null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.AddLink((ILink)null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.AddLink((SpanContext)null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.AddLink(null, null));
Assert.Throws<ArgumentNullException>(() => spanBuilder.AddLink(SpanContext.Blank, null));
}
public void Dispose()
{
Activity.Current = null;
}
}
}

View File

@ -25,32 +25,28 @@ namespace OpenTelemetry.Trace.Test
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;
using OpenTelemetry.Trace.Config;
using OpenTelemetry.Trace.Internal;
using Xunit;
public class SpanTest
public class SpanTest : IDisposable
{
private const string SpanName = "MySpanName";
private const string EventDescription = "MyEvent";
private readonly SpanContext spanContext;
private readonly ActivitySpanId parentSpanId;
private TimeSpan interval = TimeSpan.FromMilliseconds(0);
private readonly DateTimeOffset startTime = DateTimeOffset.Now;
private readonly Timestamp timestamp;
private readonly Timer timestampConverter;
private readonly SpanOptions noRecordSpanOptions = SpanOptions.None;
private readonly SpanOptions recordSpanOptions = SpanOptions.RecordEvents;
private readonly IDictionary<String, object> attributes = new Dictionary<String, object>();
private readonly IDictionary<String, object> expectedAttributes;
private readonly IDictionary<string, object> attributes = new Dictionary<String, object>();
private readonly IDictionary<string, object> expectedAttributes;
private readonly IStartEndHandler startEndHandler = Mock.Of<IStartEndHandler>();
public SpanTest()
{
timestamp = Timestamp.FromDateTimeOffset(startTime);
timestampConverter = Timer.StartNew(startTime, () => interval);
spanContext = SpanContext.Create(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None, Tracestate.Empty);
parentSpanId = ActivitySpanId.CreateRandom();
attributes.Add(
"MyStringAttributeKey", AttributeValue.StringAttributeValue("MyStringAttributeValue"));
attributes.Add("MyLongAttributeKey", AttributeValue.LongAttributeValue(123L));
@ -64,13 +60,18 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void ToSpanData_NoRecordEvents()
{
var activityLink = new Activity(SpanName).Start();
activityLink.Stop();
var activity = new Activity(SpanName).Start();
var span =
Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
noRecordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -82,22 +83,78 @@ namespace OpenTelemetry.Trace.Test
span.AddEvent(Event.Create(EventDescription));
span.AddEvent(EventDescription, attributes);
span.AddLink(Link.FromSpanContext(spanContext));
span.AddLink(Link.FromActivity(activityLink));
span.End();
// exception.expect(IllegalStateException);
Assert.Throws<InvalidOperationException>(() => ((Span)span).ToSpanData());
}
[Fact]
public void NoEventsRecordedAfterEnd()
public void GetSpanContextFromActivity()
{
var tracestate = Tracestate.Builder.Set("k1", "v1").Build();
var activity = new Activity(SpanName).Start();
activity.TraceStateString = tracestate.ToString();
var span =
Span.StartSpan(
spanContext,
activity,
tracestate,
recordSpanOptions,
SpanName,
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
timestampConverter);
Assert.NotNull(span.Context);
Assert.Equal(activity.TraceId, span.Context.TraceId);
Assert.Equal(activity.SpanId, span.Context.SpanId);
Assert.Equal(activity.ParentSpanId, ((Span)span).ParentSpanId);
Assert.Equal(activity.ActivityTraceFlags, span.Context.TraceOptions);
Assert.Same(tracestate, span.Context.Tracestate);
}
[Fact]
public void GetSpanContextFromActivityRecordedWithParent()
{
var tracestate = Tracestate.Builder.Set("k1", "v1").Build();
var parent = new Activity(SpanName).Start();
var activity = new Activity(SpanName).Start();
activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded;
var span =
Span.StartSpan(
activity,
tracestate,
recordSpanOptions,
SpanName,
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
timestampConverter);
Assert.NotNull(span.Context);
Assert.Equal(activity.TraceId, span.Context.TraceId);
Assert.Equal(activity.SpanId, span.Context.SpanId);
Assert.Equal(activity.ParentSpanId, ((Span)span).ParentSpanId);
Assert.Equal(activity.ActivityTraceFlags, span.Context.TraceOptions);
Assert.Same(tracestate, span.Context.Tracestate);
}
[Fact]
public void NoEventsRecordedAfterEnd()
{
var activityLink = new Activity(SpanName).Start();
activityLink.Stop();
var activity = new Activity(SpanName).Start();
var span =
Span.StartSpan(
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -114,7 +171,7 @@ namespace OpenTelemetry.Trace.Test
"MySingleStringAttributeValue");
span.AddEvent(Event.Create(EventDescription));
span.AddEvent(EventDescription, attributes);
span.AddLink(Link.FromSpanContext(spanContext));
span.AddLink(Link.FromActivity(activityLink));
var spanData = ((Span)span).ToSpanData();
Assert.Equal(timestamp, spanData.StartTimestamp);
Assert.Empty(spanData.Attributes.AttributeMap);
@ -127,13 +184,21 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void ToSpanData_ActiveSpan()
{
var activityLink = new Activity(SpanName);
activityLink.Stop();
var activity = new Activity(SpanName)
.SetParentId(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom())
.Start();
var span =
Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -152,12 +217,17 @@ namespace OpenTelemetry.Trace.Test
span.AddEvent(EventDescription, attributes);
interval = TimeSpan.FromMilliseconds(300);
interval = TimeSpan.FromMilliseconds(400);
var link = Link.FromSpanContext(spanContext);
var link = Link.FromActivity(activityLink);
span.AddLink(link);
var spanData = ((Span)span).ToSpanData();
Assert.Equal(spanContext, spanData.Context);
Assert.Equal(activity.TraceId, spanData.Context.TraceId);
Assert.Equal(activity.SpanId, spanData.Context.SpanId);
Assert.Equal(activity.ParentSpanId, spanData.ParentSpanId);
Assert.Equal(activity.ActivityTraceFlags, spanData.Context.TraceOptions);
Assert.Same(Tracestate.Empty, spanData.Context.Tracestate);
Assert.Equal(SpanName, spanData.Name);
Assert.Equal(parentSpanId, spanData.ParentSpanId);
Assert.Equal(activity.ParentSpanId, spanData.ParentSpanId);
Assert.Equal(0, spanData.Attributes.DroppedAttributesCount);
Assert.Equal(expectedAttributes, spanData.Attributes.AttributeMap);
Assert.Equal(0, spanData.Events.DroppedEventsCount);
@ -182,13 +252,20 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void GoSpanData_EndedSpan()
{
var activityLink = new Activity(SpanName).Start();
activityLink.Stop();
var activity = new Activity(SpanName)
.SetParentId(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom())
.Start();
var span =
(Span)Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -206,16 +283,20 @@ namespace OpenTelemetry.Trace.Test
interval = TimeSpan.FromMilliseconds(200);
span.AddEvent(EventDescription, attributes);
interval = TimeSpan.FromMilliseconds(300);
var link = Link.FromSpanContext(spanContext);
var link = Link.FromActivity(activityLink);
span.AddLink(link);
interval = TimeSpan.FromMilliseconds(400);
span.Status = Status.Cancelled;
span.End();
var spanData = ((Span)span).ToSpanData();
Assert.Equal(spanContext, spanData.Context);
Assert.Equal(activity.TraceId, spanData.Context.TraceId);
Assert.Equal(activity.SpanId, spanData.Context.SpanId);
Assert.Equal(activity.ParentSpanId, spanData.ParentSpanId);
Assert.Equal(activity.ActivityTraceFlags, spanData.Context.TraceOptions);
Assert.Equal(SpanName, spanData.Name);
Assert.Equal(parentSpanId, spanData.ParentSpanId);
Assert.Equal(activity.ParentSpanId, spanData.ParentSpanId);
Assert.Equal(0, spanData.Attributes.DroppedAttributesCount);
Assert.Equal(expectedAttributes, spanData.Attributes.AttributeMap);
Assert.Equal(0, spanData.Events.DroppedEventsCount);
@ -241,13 +322,15 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void Status_ViaSetStatus()
{
var activity = new Activity(SpanName).Start();
var span =
(Span)Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -265,13 +348,15 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void status_ViaEndSpanOptions()
{
var activity = new Activity(SpanName).Start();
var span =
(Span)Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -290,16 +375,18 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void DroppingAttributes()
{
var activity = new Activity(SpanName).Start();
var maxNumberOfAttributes = 8;
var traceParams =
TraceParams.Default.ToBuilder().SetMaxNumberOfAttributes(maxNumberOfAttributes).Build();
var span =
Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
traceParams,
startEndHandler,
timestampConverter);
@ -343,16 +430,18 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void DroppingAndAddingAttributes()
{
var activity = new Activity(SpanName).Start();
var maxNumberOfAttributes = 8;
var traceParams =
TraceParams.Default.ToBuilder().SetMaxNumberOfAttributes(maxNumberOfAttributes).Build();
var span =
Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
traceParams,
startEndHandler,
timestampConverter);
@ -414,16 +503,18 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void DroppingEvents()
{
var activity = new Activity(SpanName).Start();
var maxNumberOfEvents = 8;
var traceParams =
TraceParams.Default.ToBuilder().SetMaxNumberOfEvents(maxNumberOfEvents).Build();
var span =
Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
traceParams,
startEndHandler,
timestampConverter);
@ -466,20 +557,25 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void DroppingLinks()
{
var activityLink = new Activity(SpanName).Start();
activityLink.Stop();
var activity = new Activity(SpanName).Start();
var maxNumberOfLinks = 8;
var traceParams =
TraceParams.Default.ToBuilder().SetMaxNumberOfLinks(maxNumberOfLinks).Build();
var span =
Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
traceParams,
startEndHandler,
timestampConverter);
var link = Link.FromSpanContext(spanContext);
var link = Link.FromActivity(activityLink);
for (var i = 0; i < 2 * maxNumberOfLinks; i++)
{
span.AddLink(link);
@ -506,13 +602,15 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void SampleToLocalSpanStore()
{
var activity1 = new Activity(SpanName).Start();
var span =
(Span)Span.StartSpan(
spanContext,
activity1,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -520,13 +618,15 @@ namespace OpenTelemetry.Trace.Test
span.End();
Assert.True(((Span)span).IsSampleToLocalSpanStore);
var activity2 = new Activity(SpanName).Start();
var span2 =
Span.StartSpan(
spanContext,
activity2,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -543,13 +643,15 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void SampleToLocalSpanStore_RunningSpan()
{
var activity = new Activity(SpanName).Start();
var span =
Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -560,13 +662,15 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void BadArguments()
{
var activity = new Activity(SpanName).Start();
var span =
Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -588,12 +692,14 @@ namespace OpenTelemetry.Trace.Test
[Fact]
public void SetSampleTo()
{
var activity = new Activity(SpanName).Start();
var span = (Span)Span.StartSpan(
spanContext,
activity,
Tracestate.Empty,
recordSpanOptions,
SpanName,
SpanKind.Internal,
parentSpanId,
TraceParams.Default,
startEndHandler,
timestampConverter);
@ -602,5 +708,85 @@ namespace OpenTelemetry.Trace.Test
span.End();
Assert.True(span.IsSampleToLocalSpanStore);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void EndSpanStopsActivity(bool recordEvents)
{
var parentActivity = new Activity(SpanName).Start();
var activity = new Activity(SpanName).Start();
var span =
Span.StartSpan(
activity,
Tracestate.Empty,
recordEvents ? recordSpanOptions : noRecordSpanOptions,
SpanName,
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
timestampConverter,
ownsActivity: true);
span.End();
Assert.Same(parentActivity, Activity.Current);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void EndSpanDoesNotStopActivityWhenDoesNotOwnIt(bool recordEvents)
{
var activity = new Activity(SpanName).Start();
var span =
Span.StartSpan(
activity,
Tracestate.Empty,
recordEvents ? recordSpanOptions : noRecordSpanOptions,
SpanName,
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
timestampConverter,
ownsActivity: false);
span.End();
Assert.Equal(recordEvents, span.HasEnded);
Assert.Same(activity, Activity.Current);
}
[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void EndSpanStopActivity_NotCurrentActivity(bool recordEvents, bool ownsActivity)
{
var activity = new Activity(SpanName).Start();
var span =
Span.StartSpan(
activity,
Tracestate.Empty,
recordEvents ? recordSpanOptions : noRecordSpanOptions,
SpanName,
SpanKind.Internal,
TraceParams.Default,
startEndHandler,
timestampConverter,
ownsActivity: ownsActivity);
var anotherActivity = new Activity(SpanName).Start();
span.End();
Assert.Equal(recordEvents, span.HasEnded);
Assert.Same(anotherActivity, Activity.Current);
}
public void Dispose()
{
Activity.Current = null;
}
}
}