Baggage + CorrelationContext improvements by Eddy & Mike (#1048)

* Support W3C Baggage spec.

* Moved baggage propagation to its own ITextFormat. Removed IsInjected.

* updating some tests

* creating nw files

* updating files

* buildable in release

* adding baggage tests

* updating tests

* updating default textformat for http instrumentation

* Removed a few null checks.

* Removed DistributedContext. Drive CorrelationContext off of Activity.Baggage.

* updating issues after merge

* updating based on sanity check

* updating baggage test

* updating tests

* reiley's comments

* move to using

* Updates for http-in and http-out. Updated CHANGELOGs.

* Adding tests.

* updating correlation context

* Added test for TraceContextFormat + BaggageFormat used together.

* Fixed broken tests.

* Code review.

* Test fixup.

* updating order

* updating tests

* updating tests, adding dispose, clearing objects

* updating changelog

* Use "Baggage" instead of "baggage" as the header name.

* Added some basic support for the Baggage limits specified in the spec.

* Fixed and improved ITextFormat log messages.

* Rename TextFormatContext -> PropagationContext.

* Updated ITextFormat implementations so they don't double-extract.

Co-authored-by: Eddy Nakamura <ednakamu@microsoft.com>
Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
Mikel Blanchard 2020-08-18 15:39:59 -07:00 committed by GitHub
parent 61d9cc2fcc
commit dcaea5bd45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 1658 additions and 2539 deletions

View File

@ -63,7 +63,7 @@ namespace Utils.Messaging
// https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name
var activityName = $"{ea.RoutingKey} receive";
using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Consumer, parentContext))
using (var activity = ActivitySource.StartActivity(activityName, ActivityKind.Consumer, parentContext.ActivityContext))
{
try
{

View File

@ -61,7 +61,7 @@ namespace Utils.Messaging
if (activity != null)
{
// Inject the ActivityContext into the message headers to propagate trace context to the receiving service.
TextFormat.Inject(activity.Context, props, this.InjectTraceContextIntoBasicProperties);
TextFormat.Inject(new PropagationContext(activity.Context, activity.Baggage), props, this.InjectTraceContextIntoBasicProperties);
// The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here.
RabbitMqHelper.AddMessagingTags(activity);

View File

@ -2,6 +2,16 @@
## Unreleased
* `PropagationContext` is now used instead of `ActivityContext` in the
`ITextFormat` API
([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048))
* Added `BaggageFormat` an `ITextFormat` implementation for managing Baggage
propagation via the [W3C
Baggage](https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md)
header
([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048))
* Removed `DistributedContext` as it is no longer part of the spec
([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048)))
* Renaming from `ot` to `otel`
([#1046](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1046))
* Added `RuntimeContext` API

View File

@ -16,6 +16,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace OpenTelemetry.Context
@ -25,27 +26,33 @@ namespace OpenTelemetry.Context
/// </summary>
public readonly struct CorrelationContext : IEquatable<CorrelationContext>
{
private static readonly List<CorrelationContextEntry> EmptyList = new List<CorrelationContextEntry>();
private readonly List<CorrelationContextEntry> entries;
internal static readonly CorrelationContext Empty = new CorrelationContext(null);
internal static readonly IEnumerable<KeyValuePair<string, string>> EmptyBaggage = new KeyValuePair<string, string>[0];
private readonly Activity activity;
/// <summary>
/// Initializes a new instance of the <see cref="CorrelationContext"/> struct.
/// </summary>
/// <param name="entries">Entries for correlation context.</param>
internal CorrelationContext(List<CorrelationContextEntry> entries)
internal CorrelationContext(in Activity activity)
{
this.entries = entries;
this.activity = activity;
}
/// <summary>
/// Gets empty object of <see cref="CorrelationContext"/> struct.
/// Gets the current <see cref="CorrelationContext"/>.
/// </summary>
public static CorrelationContext Empty { get; } = new CorrelationContext(EmptyList);
public static CorrelationContext Current
{
get
{
Activity activity = Activity.Current;
return activity == null
? Empty
: new CorrelationContext(activity);
}
}
/// <summary>
/// Gets all the <see cref="CorrelationContextEntry"/> in this <see cref="CorrelationContext"/>.
/// Gets the correlation values.
/// </summary>
public IEnumerable<CorrelationContextEntry> Entries => this.entries;
public IEnumerable<KeyValuePair<string, string>> Correlations => this.activity?.Baggage ?? EmptyBaggage;
/// <summary>
/// Compare two entries of <see cref="CorrelationContext"/> for equality.
@ -62,23 +69,62 @@ namespace OpenTelemetry.Context
public static bool operator !=(CorrelationContext left, CorrelationContext right) => !(left == right);
/// <summary>
/// Gets the <see cref="CorrelationContextEntry"/> with the specified name.
/// Retrieves a correlation item.
/// </summary>
/// <param name="key">Name of the <see cref="CorrelationContextEntry"/> to get.</param>
/// <returns>The <see cref="string"/> with the specified name. If not found - null.</returns>
public string GetEntryValue(string key) => this.entries.LastOrDefault(x => x.Key == key).Value;
/// <param name="key">Correlation item key.</param>
/// <returns>Retrieved correlation value or <see langword="null"/> if no match was found.</returns>
public string GetCorrelation(string key)
=> this.activity?.GetBaggageItem(key);
/// <summary>
/// Adds a correlation item.
/// </summary>
/// <param name="key">Correlation item key.</param>
/// <param name="value">Correlation item value.</param>
/// <returns>The <see cref="CorrelationContext"/> instance for chaining.</returns>
public CorrelationContext AddCorrelation(string key, string value)
{
this.activity?.AddBaggage(key, value);
return this;
}
/// <summary>
/// Adds correlation items.
/// </summary>
/// <param name="correlations">Correlation items.</param>
/// <returns>The <see cref="CorrelationContext"/> instance for chaining.</returns>
public CorrelationContext AddCorrelation(IEnumerable<KeyValuePair<string, string>> correlations)
{
if (correlations != null)
{
foreach (KeyValuePair<string, string> correlation in correlations)
{
this.activity?.AddBaggage(correlation.Key, correlation.Value);
}
}
return this;
}
/// <inheritdoc/>
public bool Equals(CorrelationContext other)
{
if (this.entries.Count != other.entries.Count)
var thisCorrelations = this.Correlations;
var otherCorrelations = other.Correlations;
if (thisCorrelations.Count() != otherCorrelations.Count())
{
return false;
}
foreach (CorrelationContextEntry entry in this.entries)
var thisEnumerator = thisCorrelations.GetEnumerator();
var otherEnumerator = otherCorrelations.GetEnumerator();
while (thisEnumerator.MoveNext() && otherEnumerator.MoveNext())
{
if (other.GetEntryValue(entry.Key) != entry.Value)
if (thisEnumerator.Current.Key != otherEnumerator.Current.Key
|| thisEnumerator.Current.Value != otherEnumerator.Current.Value)
{
return false;
}
@ -96,7 +142,7 @@ namespace OpenTelemetry.Context
/// <inheritdoc/>
public override int GetHashCode()
{
return this.entries.GetHashCode();
return this.Correlations.GetHashCode();
}
}
}

View File

@ -1,238 +0,0 @@
// <copyright file="CorrelationContextBuilder.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System.Collections.Generic;
namespace OpenTelemetry.Context
{
/// <summary>
/// Correlation context Builder.
/// </summary>
public struct CorrelationContextBuilder : System.IEquatable<CorrelationContextBuilder>
{
private List<CorrelationContextEntry> entries;
/// <summary>
/// Initializes a new instance of the <see cref="CorrelationContextBuilder"/> struct.
/// </summary>
/// <param name="inheritCurrentContext">Flag to allow inheriting the current context entries.</param>
public CorrelationContextBuilder(bool inheritCurrentContext)
{
this.entries = null;
if (DistributedContext.Carrier is NoopDistributedContextCarrier)
{
return;
}
if (inheritCurrentContext)
{
this.entries = new List<CorrelationContextEntry>(DistributedContext.Current.CorrelationContext.Entries);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CorrelationContextBuilder"/> struct using some context.
/// </summary>
/// <param name="context">Initial context.</param>
public CorrelationContextBuilder(CorrelationContext context)
{
if (DistributedContext.Carrier is NoopDistributedContextCarrier)
{
this.entries = null;
return;
}
this.entries = new List<CorrelationContextEntry>(context.Entries);
}
/// <summary>
/// Compare two entries of <see cref="CorrelationContextBuilder"/> for equality.
/// </summary>
/// <param name="left">First Entry to compare.</param>
/// <param name="right">Second Entry to compare.</param>
public static bool operator ==(CorrelationContextBuilder left, CorrelationContextBuilder right)
{
return left.Equals(right);
}
/// <summary>
/// Compare two entries of <see cref="CorrelationContextBuilder"/> for equality.
/// </summary>
/// <param name="left">First Entry to compare.</param>
/// <param name="right">Second Entry to compare.</param>
public static bool operator !=(CorrelationContextBuilder left, CorrelationContextBuilder right)
{
return !(left == right);
}
/// <summary>
/// Create <see cref="CorrelationContext"/> instance from key and value entry.
/// </summary>
/// <param name="key">Entry key.</param>
/// <param name="value">Entry value.</param>
/// <returns>Instance of <see cref="CorrelationContext"/>.</returns>
public static CorrelationContext CreateContext(string key, string value) =>
new CorrelationContextBuilder(inheritCurrentContext: false).Add(key, value).Build();
/// <summary>
/// Create <see cref="CorrelationContext"/> instance from entry.
/// </summary>
/// <param name="entry">Entry to add to the context.</param>
/// <returns>Instance of <see cref="CorrelationContext"/>.</returns>
public static CorrelationContext CreateContext(CorrelationContextEntry entry) =>
new CorrelationContextBuilder(inheritCurrentContext: false).Add(entry).Build();
/// <summary>
/// Create <see cref="CorrelationContext"/> instance from entry.
/// </summary>
/// <param name="entries">List of entries to add to the context.</param>
/// <returns>Instance of <see cref="CorrelationContext"/>.</returns>
public static CorrelationContext CreateContext(IEnumerable<CorrelationContextEntry> entries) =>
new CorrelationContextBuilder(inheritCurrentContext: false).Add(entries).Build();
/// <summary>
/// Add Distributed Context entry to the builder.
/// </summary>
/// <param name="entry">Entry to add to the context.</param>
/// <returns>The current <see cref="CorrelationContextBuilder"/> instance.</returns>
public CorrelationContextBuilder Add(CorrelationContextEntry entry)
{
if (DistributedContext.Carrier is NoopDistributedContextCarrier || entry == default)
{
return this;
}
if (this.entries == null)
{
this.entries = new List<CorrelationContextEntry>();
}
else
{
for (int i = 0; i < this.entries.Count; i++)
{
if (this.entries[i].Key == entry.Key)
{
this.entries[i] = entry;
return this;
}
}
}
this.entries.Add(entry);
return this;
}
/// <summary>
/// Add Distributed Context entry to the builder.
/// </summary>
/// <param name="key">Entry key.</param>
/// <param name="value">Entry value.</param>
/// <param name="metadata">Entry metadata.</param>
/// <returns>The current <see cref="CorrelationContextBuilder"/> instance.</returns>
public CorrelationContextBuilder Add(string key, string value, EntryMetadata metadata)
{
return this.Add(new CorrelationContextEntry(key, value, metadata));
}
/// <summary>
/// Add Distributed Context entry to the builder.
/// </summary>
/// <param name="key">Entry key.</param>
/// <param name="value">Entry value.</param>
/// <returns>The current <see cref="CorrelationContextBuilder"/> instance.</returns>
public CorrelationContextBuilder Add(string key, string value)
{
return this.Add(new CorrelationContextEntry(key, value));
}
/// <summary>
/// Add Distributed Context entry to the builder.
/// </summary>
/// <param name="entries">List of entries to add to the context.</param>
/// <returns>The current <see cref="CorrelationContextBuilder"/> instance.</returns>
public CorrelationContextBuilder Add(IEnumerable<CorrelationContextEntry> entries)
{
if (DistributedContext.Carrier is NoopDistributedContextCarrier || entries == null)
{
return this;
}
foreach (var entry in entries)
{
this.Add(entry);
}
return this;
}
/// <summary>
/// Remove Distributed Context entry from the context.
/// </summary>
/// <param name="key">Entry key.</param>
/// <returns>The current <see cref="CorrelationContextBuilder"/> instance.</returns>
public CorrelationContextBuilder Remove(string key)
{
if (key == null || DistributedContext.Carrier is NoopDistributedContextCarrier || this.entries == null)
{
return this;
}
int index = this.entries.FindIndex(entry => entry.Key == key);
if (index >= 0)
{
this.entries.RemoveAt(index);
}
return this;
}
/// <summary>
/// Build a Correlation Context from current builder.
/// </summary>
/// <returns><see cref="CorrelationContext"/> instance.</returns>
public CorrelationContext Build()
{
if (DistributedContext.Carrier is NoopDistributedContextCarrier || this.entries == null)
{
return CorrelationContext.Empty;
}
var context = new CorrelationContext(this.entries);
this.entries = null; // empty current builder entries.
return context;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is CorrelationContextBuilder builder &&
EqualityComparer<List<CorrelationContextEntry>>.Default.Equals(this.entries, builder.entries);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.entries.GetHashCode();
}
/// <inheritdoc/>
public bool Equals(CorrelationContextBuilder other)
{
return EqualityComparer<List<CorrelationContextEntry>>.Default.Equals(this.entries, other.entries);
}
}
}

View File

@ -1,125 +0,0 @@
// <copyright file="CorrelationContextEntry.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using OpenTelemetry.Internal;
namespace OpenTelemetry.Context
{
/// <summary>
/// Distributed Context entry with the key, value and metadata.
/// </summary>
public readonly struct CorrelationContextEntry : System.IEquatable<CorrelationContextEntry>
{
/// <summary>
/// Initializes a new instance of the <see cref="CorrelationContextEntry"/> struct with the key and value.
/// </summary>
/// <param name="key">Key name for the entry.</param>
/// <param name="value">Value associated with the key name.</param>
public CorrelationContextEntry(string key, string value)
: this(key, value, EntryMetadata.NoPropagationEntry)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CorrelationContextEntry"/> struct with the key, value, and metadata.
/// </summary>
/// <param name="key">Key name for the entry.</param>
/// <param name="value">Value associated with the key name.</param>
/// <param name="metadata">Entry metadata.</param>
public CorrelationContextEntry(string key, string value, in EntryMetadata metadata)
{
if (key == null)
{
this.Key = string.Empty;
OpenTelemetryApiEventSource.Log.InvalidArgument(nameof(CorrelationContextEntry), nameof(key), "is null");
}
else
{
this.Key = key;
}
if (value == null)
{
this.Value = string.Empty;
OpenTelemetryApiEventSource.Log.InvalidArgument(nameof(CorrelationContextEntry), nameof(value), "is null");
}
else
{
this.Value = value;
}
this.Metadata = metadata;
}
/// <summary>
/// Gets the tag key.
/// </summary>
public string Key { get; }
/// <summary>
/// Gets the tag value.
/// </summary>
public string Value { get; }
/// <summary>
/// Gets the metadata associated with this entry.
/// </summary>
public EntryMetadata Metadata { get; }
/// <summary>
/// Compare two entries of <see cref="CorrelationContextEntry"/> for equality.
/// </summary>
/// <param name="entry1">First Entry to compare.</param>
/// <param name="entry2">Second Entry to compare.</param>
public static bool operator ==(CorrelationContextEntry entry1, CorrelationContextEntry entry2) => entry1.Equals(entry2);
/// <summary>
/// Compare two entries of <see cref="CorrelationContextEntry"/> for not equality.
/// </summary>
/// <param name="entry1">First Entry to compare.</param>
/// <param name="entry2">Second Entry to compare.</param>
public static bool operator !=(CorrelationContextEntry entry1, CorrelationContextEntry entry2) => !entry1.Equals(entry2);
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is CorrelationContextEntry that && this.Key == that.Key && this.Value == that.Value;
}
/// <inheritdoc/>
public override string ToString()
{
return $"{nameof(CorrelationContextEntry)}{{{nameof(this.Key)}={this.Key}, {nameof(this.Value)}={this.Value}}}";
}
/// <inheritdoc/>
public override int GetHashCode()
{
var h = 1;
h *= 1000003;
h ^= this.Key.GetHashCode();
h *= 1000003;
h ^= this.Value.GetHashCode();
return h;
}
/// <inheritdoc/>
public bool Equals(CorrelationContextEntry other)
{
return this.Key == other.Key && this.Value == other.Value;
}
}
}

View File

@ -1,117 +0,0 @@
// <copyright file="DistributedContext.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using OpenTelemetry.Internal;
namespace OpenTelemetry.Context
{
/// <summary>
/// Distributed context.
/// </summary>
public readonly struct DistributedContext : IEquatable<DistributedContext>
{
private static DistributedContextCarrier carrier = NoopDistributedContextCarrier.Instance;
private readonly CorrelationContext correlationContext;
/// <summary>
/// Initializes a new instance of the <see cref="DistributedContext"/> struct.
/// </summary>
/// <param name="correlationContext">The correlation context.</param>
internal DistributedContext(CorrelationContext correlationContext)
{
this.correlationContext = correlationContext;
}
/// <summary>
/// Gets empty object of <see cref="DistributedContext"/> struct.
/// </summary>
public static DistributedContext Empty { get; } = new DistributedContext(CorrelationContext.Empty);
/// <summary>
/// Gets the current <see cref="CorrelationContext"/>.
/// </summary>
public static DistributedContext Current => carrier.Current;
/// <summary>
/// Gets or sets the default carrier instance of the <see cref="DistributedContextCarrier"/> class.
/// SDK will need to override the value to AsyncLocalDistributedContextCarrier.Instance.
/// </summary>
public static DistributedContextCarrier Carrier
{
get => carrier;
set
{
if (value is null)
{
OpenTelemetryApiEventSource.Log.InvalidArgument("set_Carrier", nameof(value), "is null");
}
carrier = value ?? NoopDistributedContextCarrier.Instance;
}
}
/// <summary>
/// Gets the <see cref="CorrelationContext"/> for the current distributed context.
/// </summary>
public CorrelationContext CorrelationContext => this.correlationContext;
/// <summary>
/// Compare two entries of <see cref="DistributedContext"/> for equality.
/// </summary>
/// <param name="left">First Entry to compare.</param>
/// <param name="right">Second Entry to compare.</param>
public static bool operator ==(DistributedContext left, DistributedContext right)
{
return left.Equals(right);
}
/// <summary>
/// Compare two entries of <see cref="DistributedContext"/> for equality.
/// </summary>
/// <param name="left">First Entry to compare.</param>
/// <param name="right">Second Entry to compare.</param>
public static bool operator !=(DistributedContext left, DistributedContext right)
{
return !(left == right);
}
/// <summary>
/// Sets the current <see cref="DistributedContext"/>.
/// </summary>
/// <param name="context">Context to set as current.</param>
/// <returns>Scope object. On disposal - original context will be restored.</returns>
public static IDisposable SetCurrent(in DistributedContext context) => carrier.SetCurrent(context);
/// <inheritdoc/>
public bool Equals(DistributedContext other)
{
return this.CorrelationContext.Equals(other.CorrelationContext);
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is DistributedContext context && this.Equals(context);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.correlationContext.GetHashCode();
}
}
}

View File

@ -1,90 +0,0 @@
// <copyright file="DistributedContextBuilder.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using System.Collections.Generic;
namespace OpenTelemetry.Context
{
/// <summary>
/// Distributed context Builder.
/// </summary>
public struct DistributedContextBuilder
{
private CorrelationContextBuilder correlationContextBuilder;
/// <summary>
/// Initializes a new instance of the <see cref="DistributedContextBuilder"/> struct.
/// </summary>
/// <param name="inheritCurrentContext">Flag to allow inheriting the current context entries.</param>
public DistributedContextBuilder(bool inheritCurrentContext)
{
this.correlationContextBuilder = new CorrelationContextBuilder(false);
if (DistributedContext.Carrier is NoopDistributedContextCarrier)
{
return;
}
if (inheritCurrentContext)
{
this.correlationContextBuilder.Add(DistributedContext.Current.CorrelationContext.Entries);
}
}
/// <summary>
/// Create context.
/// </summary>
/// <param name="key">The correlation key.</param>
/// <param name="value">The correlation value.</param>
/// <returns>A <see cref="DistributedContext"/> instance.</returns>
public static DistributedContext CreateContext(string key, string value) =>
new DistributedContext(new CorrelationContextBuilder(inheritCurrentContext: false).Add(key, value).Build());
/// <summary>
/// Create context.
/// </summary>
/// <param name="entries">A list of correlations to create the context with.</param>
/// <returns>A <see cref="DistributedContext"/> instance.</returns>
public static DistributedContext CreateContext(IEnumerable<CorrelationContextEntry> entries) =>
new DistributedContext(new CorrelationContextBuilder(inheritCurrentContext: false).Add(entries).Build());
/// <summary>
/// Configures correlations to be used with the context.
/// </summary>
/// <param name="configureCorrelations">An <see cref="Action{CorrelationContextBuilder}"/> used to configure correlations.</param>
/// <returns>The current <see cref="DistributedContextBuilder"/> instance.</returns>
public DistributedContextBuilder Correlations(Action<CorrelationContextBuilder> configureCorrelations)
{
configureCorrelations?.Invoke(this.correlationContextBuilder);
return this;
}
/// <summary>
/// Build a Distributed Context from current builder.
/// </summary>
/// <returns><see cref="DistributedContext"/> instance.</returns>
public DistributedContext Build()
{
if (DistributedContext.Carrier is NoopDistributedContextCarrier)
{
return DistributedContext.Empty;
}
return new DistributedContext(this.correlationContextBuilder.Build());
}
}
}

View File

@ -1,38 +0,0 @@
// <copyright file="DistributedContextCarrier.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
namespace OpenTelemetry.Context
{
/// <summary>
/// Abstraction to the carrier of the DistributedContext.Current object.
/// </summary>
public abstract class DistributedContextCarrier
{
/// <summary>
/// Gets the current <see cref="DistributedContext"/>.
/// </summary>
public abstract DistributedContext Current { get; }
/// <summary>
/// Sets the current <see cref="DistributedContext"/>.
/// </summary>
/// <param name="context">Context to set as current.</param>
/// <returns>Scope object. On disposal - original context will be restored.</returns>
public abstract IDisposable SetCurrent(in DistributedContext context);
}
}

View File

@ -1,65 +0,0 @@
// <copyright file="NoopDistributedContextCarrier.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
namespace OpenTelemetry.Context
{
/// <summary>
/// No-op Distributed context carrier.
/// </summary>
public class NoopDistributedContextCarrier : DistributedContextCarrier
{
/// <summary>
/// Initializes a new instance of the <see cref="NoopDistributedContextCarrier"/> class.
/// </summary>
private NoopDistributedContextCarrier()
{
}
/// <summary>
/// Gets the instance of <see cref="NoopDistributedContextCarrier"/>.
/// </summary>
public static NoopDistributedContextCarrier Instance { get; } = new NoopDistributedContextCarrier();
/// <summary>
/// Gets the current <see cref="DistributedContext"/>.
/// </summary>
public override DistributedContext Current => DistributedContext.Empty;
/// <summary>
/// Sets the current <see cref="DistributedContext"/>.
/// </summary>
/// <param name="context">Context to set as current.</param>
/// <returns>Scope object. On disposal - original context will be restored.</returns>
public override IDisposable SetCurrent(in DistributedContext context) => EmptyDisposable.Instance;
private class EmptyDisposable : IDisposable
{
public static EmptyDisposable Instance { get; } = new EmptyDisposable();
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
}
}
}
}

View File

@ -0,0 +1,157 @@
// <copyright file="BaggageFormat.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using OpenTelemetry.Internal;
namespace OpenTelemetry.Context.Propagation
{
/// <summary>
/// W3C baggage: https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md.
/// </summary>
public class BaggageFormat : ITextFormat
{
internal const string BaggageHeaderName = "Baggage";
private const int MaxBaggageLength = 8192;
private const int MaxBaggageItems = 180;
/// <inheritdoc/>
public ISet<string> Fields => new HashSet<string> { BaggageHeaderName };
/// <inheritdoc/>
public PropagationContext Extract<T>(PropagationContext context, T carrier, Func<T, string, IEnumerable<string>> getter)
{
if (context.ActivityBaggage != null)
{
// If baggage has already been extracted, perform a noop.
return context;
}
if (carrier == null)
{
OpenTelemetryApiEventSource.Log.FailedToExtractBaggage(nameof(BaggageFormat), "null carrier");
return context;
}
if (getter == null)
{
OpenTelemetryApiEventSource.Log.FailedToExtractBaggage(nameof(BaggageFormat), "null getter");
return context;
}
try
{
IEnumerable<KeyValuePair<string, string>> baggage = null;
var baggageCollection = getter(carrier, BaggageHeaderName);
if (baggageCollection?.Any() ?? false)
{
TryExtractBaggage(baggageCollection.ToArray(), out baggage);
}
return new PropagationContext(
context.ActivityContext,
baggage ?? context.ActivityBaggage);
}
catch (Exception ex)
{
OpenTelemetryApiEventSource.Log.BaggageExtractException(nameof(BaggageFormat), ex);
}
return context;
}
/// <inheritdoc/>
public void Inject<T>(PropagationContext context, T carrier, Action<T, string, string> setter)
{
if (carrier == null)
{
OpenTelemetryApiEventSource.Log.FailedToInjectBaggage(nameof(BaggageFormat), "null carrier");
return;
}
if (setter == null)
{
OpenTelemetryApiEventSource.Log.FailedToInjectBaggage(nameof(BaggageFormat), "null setter");
return;
}
using IEnumerator<KeyValuePair<string, string>> e = context.ActivityBaggage?.GetEnumerator();
if (e?.MoveNext() == true)
{
int itemCount = 1;
StringBuilder baggage = new StringBuilder();
do
{
KeyValuePair<string, string> item = e.Current;
baggage.Append(WebUtility.UrlEncode(item.Key)).Append('=').Append(WebUtility.UrlEncode(item.Value)).Append(',');
}
while (e.MoveNext() && itemCount++ < MaxBaggageItems && baggage.Length < MaxBaggageLength);
baggage.Remove(baggage.Length - 1, 1);
setter(carrier, BaggageHeaderName, baggage.ToString());
}
}
internal static bool TryExtractBaggage(string[] baggageCollection, out IEnumerable<KeyValuePair<string, string>> baggage)
{
int baggageLength = -1;
bool done = false;
Dictionary<string, string> baggageDictionary = null;
foreach (var item in baggageCollection)
{
if (done)
{
break;
}
if (string.IsNullOrEmpty(item))
{
continue;
}
foreach (var pair in item.Split(','))
{
baggageLength += pair.Length + 1; // pair and comma
if (baggageLength >= MaxBaggageLength || baggageDictionary?.Count >= MaxBaggageItems)
{
done = true;
break;
}
if (NameValueHeaderValue.TryParse(pair, out NameValueHeaderValue baggageItem))
{
if (baggageDictionary == null)
{
baggageDictionary = new Dictionary<string, string>();
}
baggageDictionary[baggageItem.Name] = baggageItem.Value;
}
}
}
baggage = baggageDictionary;
return baggageDictionary != null;
}
}
}

View File

@ -0,0 +1,83 @@
// <copyright file="BaseHeaderParser.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
namespace OpenTelemetry.Context.Propagation
{
// Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/BaseHeaderParser.cs
internal abstract class BaseHeaderParser<T> : HttpHeaderParser<T>
{
protected BaseHeaderParser(bool supportsMultipleValues)
: base(supportsMultipleValues)
{
}
public sealed override bool TryParseValue(string value, ref int index, out T parsedValue)
{
parsedValue = default(T);
// If multiple values are supported (i.e. list of values), then accept an empty string: The header may
// be added multiple times to the request/response message. E.g.
// Accept: text/xml; q=1
// Accept:
// Accept: text/plain; q=0.2
if (string.IsNullOrEmpty(value) || (index == value.Length))
{
return this.SupportsMultipleValues;
}
var separatorFound = false;
var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, this.SupportsMultipleValues, out separatorFound);
if (separatorFound && !this.SupportsMultipleValues)
{
return false; // leading separators not allowed if we don't support multiple values.
}
if (current == value.Length)
{
if (this.SupportsMultipleValues)
{
index = current;
}
return this.SupportsMultipleValues;
}
T result;
var length = this.GetParsedValueLength(value, current, out result);
if (length == 0)
{
return false;
}
current = current + length;
current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, this.SupportsMultipleValues, out separatorFound);
// If we support multiple values and we've not reached the end of the string, then we must have a separator.
if ((separatorFound && !this.SupportsMultipleValues) || (!separatorFound && (current < value.Length)))
{
return false;
}
index = current;
parsedValue = result;
return true;
}
protected abstract int GetParsedValueLength(string value, int startIndex, out T parsedValue);
}
}

View File

@ -16,8 +16,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace OpenTelemetry.Context.Propagation
{
@ -33,42 +31,32 @@ namespace OpenTelemetry.Context.Propagation
/// Initializes a new instance of the <see cref="CompositePropagator"/> class.
/// </summary>
/// <param name="textFormats">List of <see cref="ITextFormat"/> wire context propagator.</param>
public CompositePropagator(List<ITextFormat> textFormats)
public CompositePropagator(IEnumerable<ITextFormat> textFormats)
{
this.textFormats = textFormats ?? throw new ArgumentNullException(nameof(textFormats));
this.textFormats = new List<ITextFormat>(textFormats ?? throw new ArgumentNullException(nameof(textFormats)));
}
/// <inheritdoc/>
public ISet<string> Fields => EmptyFields;
/// <inheritdoc/>
public ActivityContext Extract<T>(ActivityContext activityContext, T carrier, Func<T, string, IEnumerable<string>> getter)
public PropagationContext Extract<T>(PropagationContext context, T carrier, Func<T, string, IEnumerable<string>> getter)
{
foreach (var textFormat in this.textFormats)
{
activityContext = textFormat.Extract(activityContext, carrier, getter);
if (activityContext.IsValid())
{
return activityContext;
}
context = textFormat.Extract(context, carrier, getter);
}
return activityContext;
return context;
}
/// <inheritdoc/>
public void Inject<T>(ActivityContext activityContext, T carrier, Action<T, string, string> setter)
public void Inject<T>(PropagationContext context, T carrier, Action<T, string, string> setter)
{
foreach (var textFormat in this.textFormats)
{
textFormat.Inject(activityContext, carrier, setter);
}
}
/// <inheritdoc/>
public bool IsInjected<T>(T carrier, Func<T, string, IEnumerable<string>> getter)
{
return this.textFormats.All(textFormat => textFormat.IsInjected(carrier, getter));
textFormat.Inject(context, carrier, setter);
}
}
}
}

View File

@ -1,89 +0,0 @@
// <copyright file="EntryPropagationFilter.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
namespace OpenTelemetry.Context.Propagation
{
/// <summary>
/// Filter defining propagation rules for <see cref="CorrelationContextEntry"/>.
/// </summary>
public readonly struct EntryPropagationFilter
{
/// <summary>
/// Initializes a new instance of the <see cref="EntryPropagationFilter"/> struct.
/// </summary>
/// <param name="op">Operator to apply.</param>
/// <param name="matchString">String to apply the operator with.</param>
/// <param name="action">Action to execute on entry.</param>
public EntryPropagationFilter(FilterMatchOperator op, string matchString, Action<CorrelationContextEntry> action)
{
this.Operator = op;
this.MatchString = matchString;
this.Action = action;
}
/// <summary>
/// Operator to use in a filter.
/// </summary>
public enum FilterMatchOperator
{
/// <summary>
/// Equals operator.
/// </summary>
Equal,
/// <summary>
/// Not equals operator.
/// </summary>
NotEqual,
/// <summary>
/// Operator checking the prefix.
/// </summary>
HasPrefix,
}
internal FilterMatchOperator Operator { get; }
internal string MatchString { get; }
internal Action<CorrelationContextEntry> Action { get; }
/// <summary>
/// Check whether <see cref="CorrelationContextEntry"/> matches this filter pattern.
/// </summary>
/// <param name="entry">Distributed Context entry to check.</param>
/// <returns>True if <see cref="CorrelationContextEntry"/> matches this filter, false - otherwise.</returns>
public bool IsMatch(CorrelationContextEntry entry)
{
bool result = false;
switch (this.Operator)
{
case FilterMatchOperator.Equal: result = entry.Key.Equals(this.MatchString); break;
case FilterMatchOperator.NotEqual: result = !entry.Key.Equals(this.MatchString); break;
case FilterMatchOperator.HasPrefix: result = entry.Key.StartsWith(this.MatchString, StringComparison.Ordinal); break;
}
if (result)
{
this.Action(entry);
}
return result;
}
}
}

View File

@ -0,0 +1,44 @@
// <copyright file="GenericHeaderParser.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
namespace OpenTelemetry.Context.Propagation
{
// Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/GenericHeaderParser.cs
internal sealed class GenericHeaderParser<T> : BaseHeaderParser<T>
{
private GetParsedValueLengthDelegate getParsedValueLength;
internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength)
: base(supportsMultipleValues)
{
if (getParsedValueLength == null)
{
throw new ArgumentNullException(nameof(getParsedValueLength));
}
this.getParsedValueLength = getParsedValueLength;
}
internal delegate int GetParsedValueLengthDelegate(string value, int startIndex, out T parsedValue);
protected override int GetParsedValueLength(string value, int startIndex, out T parsedValue)
{
return this.getParsedValueLength(value, startIndex, out parsedValue);
}
}
}

View File

@ -0,0 +1,54 @@
// <copyright file="HeaderUtilities.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
namespace OpenTelemetry.Context.Propagation
{
// Adoption of the code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HeaderUtilities.cs
internal static class HeaderUtilities
{
internal static int GetNextNonEmptyOrWhitespaceIndex(
string input,
int startIndex,
bool skipEmptyValues,
out bool separatorFound)
{
separatorFound = false;
var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
if ((current == input.Length) || (input[current] != ','))
{
return current;
}
// If we have a separator, skip the separator and all following whitespaces. If we support
// empty values, continue until the current character is neither a separator nor a whitespace.
separatorFound = true;
current++; // skip delimiter.
current = current + HttpRuleParser.GetWhitespaceLength(input, current);
if (skipEmptyValues)
{
while ((current < input.Length) && (input[current] == ','))
{
current++; // skip delimiter.
current = current + HttpRuleParser.GetWhitespaceLength(input, current);
}
}
return current;
}
}
}

View File

@ -0,0 +1,40 @@
// <copyright file="HttpHeaderParser.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
namespace OpenTelemetry.Context.Propagation
{
// Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpHeaderParser.cs
internal abstract class HttpHeaderParser<T>
{
private bool supportsMultipleValues;
protected HttpHeaderParser(bool supportsMultipleValues)
{
this.supportsMultipleValues = supportsMultipleValues;
}
public bool SupportsMultipleValues
{
get { return this.supportsMultipleValues; }
}
// If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index'
// pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0
// for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first
// non-whitespace after the separator ','.
public abstract bool TryParseValue(string value, ref int index, out T parsedValue);
}
}

View File

@ -1,4 +1,4 @@
// <copyright file="DistributedContextBinarySerializer.cs" company="OpenTelemetry Authors">
// <copyright file="HttpParseResult.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
@ -16,22 +16,22 @@
namespace OpenTelemetry.Context.Propagation
{
internal sealed class DistributedContextBinarySerializer : DistributedContextBinarySerializerBase
// Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpParseResult.cs
internal enum HttpParseResult
{
internal DistributedContextBinarySerializer()
{
}
/// <summary>
/// Parsed successfully.
/// </summary>
Parsed,
/// <inheritdoc/>
public override byte[] ToByteArray(DistributedContext context)
{
return SerializationUtils.SerializeBinary(context);
}
/// <summary>
/// Was not parsed.
/// </summary>
NotParsed,
/// <inheritdoc/>
public override DistributedContext FromByteArray(byte[] bytes)
{
return SerializationUtils.DeserializeBinary(bytes);
}
/// <summary>
/// Invalid format.
/// </summary>
InvalidFormat,
}
}

View File

@ -0,0 +1,263 @@
// <copyright file="HttpRuleParser.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
namespace OpenTelemetry.Context.Propagation
{
// Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/HttpRuleParser.cs
internal static class HttpRuleParser
{
internal const char CR = '\r';
internal const char LF = '\n';
internal const char SP = ' ';
internal const char Tab = '\t';
internal const int MaxInt64Digits = 19;
internal const int MaxInt32Digits = 10;
private const int MaxNestedCount = 5;
private static readonly bool[] TokenChars = CreateTokenChars();
internal static bool IsTokenChar(char character)
{
// Must be between 'space' (32) and 'DEL' (127)
if (character > 127)
{
return false;
}
return TokenChars[character];
}
internal static int GetTokenLength(string input, int startIndex)
{
if (startIndex >= input.Length)
{
return 0;
}
var current = startIndex;
while (current < input.Length)
{
if (!IsTokenChar(input[current]))
{
return current - startIndex;
}
current++;
}
return input.Length - startIndex;
}
internal static int GetWhitespaceLength(string input, int startIndex)
{
if (startIndex >= input.Length)
{
return 0;
}
var current = startIndex;
char c;
while (current < input.Length)
{
c = input[current];
if ((c == SP) || (c == Tab))
{
current++;
continue;
}
if (c == CR)
{
// If we have a #13 char, it must be followed by #10 and then at least one SP or HT.
if ((current + 2 < input.Length) && (input[current + 1] == LF))
{
char spaceOrTab = input[current + 2];
if ((spaceOrTab == SP) || (spaceOrTab == Tab))
{
current += 3;
continue;
}
}
}
return current - startIndex;
}
// All characters between startIndex and the end of the string are LWS characters.
return input.Length - startIndex;
}
internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length)
{
var nestedCount = 0;
return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length);
}
// quoted-pair = "\" CHAR
// CHAR = <any US-ASCII character (octets 0 - 127)>
internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length)
{
length = 0;
if (input[startIndex] != '\\')
{
return HttpParseResult.NotParsed;
}
// Quoted-char has 2 characters. Check whether there are 2 chars left ('\' + char)
// If so, check whether the character is in the range 0-127. If not, it's an invalid value.
if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127))
{
return HttpParseResult.InvalidFormat;
}
// We don't care what the char next to '\' is.
length = 2;
return HttpParseResult.Parsed;
}
private static bool[] CreateTokenChars()
{
// token = 1*<any CHAR except CTLs or separators>
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
var tokenChars = new bool[128]; // everything is false
for (int i = 33; i < 127; i++)
{
// skip Space (32) & DEL (127)
tokenChars[i] = true;
}
// remove separators: these are not valid token characters
tokenChars[(byte)'('] = false;
tokenChars[(byte)')'] = false;
tokenChars[(byte)'<'] = false;
tokenChars[(byte)'>'] = false;
tokenChars[(byte)'@'] = false;
tokenChars[(byte)','] = false;
tokenChars[(byte)';'] = false;
tokenChars[(byte)':'] = false;
tokenChars[(byte)'\\'] = false;
tokenChars[(byte)'"'] = false;
tokenChars[(byte)'/'] = false;
tokenChars[(byte)'['] = false;
tokenChars[(byte)']'] = false;
tokenChars[(byte)'?'] = false;
tokenChars[(byte)'='] = false;
tokenChars[(byte)'{'] = false;
tokenChars[(byte)'}'] = false;
return tokenChars;
}
// TEXT = <any OCTET except CTLs, but including LWS>
// LWS = [CRLF] 1*( SP | HT )
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
//
// Since we don't really care about the content of a quoted string or comment, we're more tolerant and
// allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment).
//
// 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like
// "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested
// comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any)
// is unusual.
private static HttpParseResult GetExpressionLength(
string input,
int startIndex,
char openChar,
char closeChar,
bool supportsNesting,
ref int nestedCount,
out int length)
{
length = 0;
if (input[startIndex] != openChar)
{
return HttpParseResult.NotParsed;
}
var current = startIndex + 1; // Start parsing with the character next to the first open-char
while (current < input.Length)
{
// Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e.
// quoted char + closing char). Otherwise the closing char may be considered part of the quoted char.
var quotedPairLength = 0;
if ((current + 2 < input.Length) &&
(GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed))
{
// We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair,
// but we actually have a quoted-string: e.g. "\u" ('\' followed by a char >127 - quoted-pair only
// allows ASCII chars after '\'; qdtext allows both '\' and >127 chars).
current = current + quotedPairLength;
continue;
}
// If we support nested expressions and we find an open-char, then parse the nested expressions.
if (supportsNesting && (input[current] == openChar))
{
nestedCount++;
try
{
// Check if we exceeded the number of nested calls.
if (nestedCount > MaxNestedCount)
{
return HttpParseResult.InvalidFormat;
}
var nestedLength = 0;
HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar, supportsNesting, ref nestedCount, out nestedLength);
switch (nestedResult)
{
case HttpParseResult.Parsed:
current += nestedLength; // add the length of the nested expression and continue.
break;
case HttpParseResult.NotParsed:
break;
case HttpParseResult.InvalidFormat:
// If the nested expression is invalid, we can't continue, so we fail with invalid format.
return HttpParseResult.InvalidFormat;
default:
break;
}
}
finally
{
nestedCount--;
}
}
if (input[current] == closeChar)
{
length = current - startIndex + 1;
return HttpParseResult.Parsed;
}
current++;
}
// We didn't see the final quote, therefore we have an invalid expression string.
return HttpParseResult.InvalidFormat;
}
}
}

View File

@ -13,9 +13,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace OpenTelemetry.Context.Propagation
{
@ -36,28 +36,19 @@ namespace OpenTelemetry.Context.Propagation
/// Injects textual representation of activity context to transmit over the wire.
/// </summary>
/// <typeparam name="T">Type of an object to set context on. Typically HttpRequest or similar.</typeparam>
/// <param name="activityContext">Activity context to transmit over the wire.</param>
/// <param name="context">The default context to transmit over the wire.</param>
/// <param name="carrier">Object to set context on. Instance of this object will be passed to setter.</param>
/// <param name="setter">Action that will set name and value pair on the object.</param>
void Inject<T>(ActivityContext activityContext, T carrier, Action<T, string, string> setter);
void Inject<T>(PropagationContext context, T carrier, Action<T, string, string> setter);
/// <summary>
/// Extracts activity context from textual representation.
/// </summary>
/// <typeparam name="T">Type of object to extract context from. Typically HttpRequest or similar.</typeparam>
/// <param name="activityContext">The default activity context to be used if Extract fails.</param>
/// <param name="context">The default context to be used if Extract fails.</param>
/// <param name="carrier">Object to extract context from. Instance of this object will be passed to the getter.</param>
/// <param name="getter">Function that will return string value of a key with the specified name.</param>
/// <returns>Activity context from it's text representation.</returns>
ActivityContext Extract<T>(ActivityContext activityContext, T carrier, Func<T, string, IEnumerable<string>> getter);
/// <summary>
/// Tests if an activity context has been injected into a carrier.
/// </summary>
/// <typeparam name="T">Type of object to extract context from. Typically HttpRequest or similar.</typeparam>
/// <param name="carrier">Object to extract context from. Instance of this object will be passed to the getter.</param>
/// <param name="getter">Function that will return string value of a key with the specified name.</param>
/// <returns><see langword="true" /> if the carrier has been injected with an activity context.</returns>
bool IsInjected<T>(T carrier, Func<T, string, IEnumerable<string>> getter);
/// <returns>Context from it's text representation.</returns>
PropagationContext Extract<T>(PropagationContext context, T carrier, Func<T, string, IEnumerable<string>> getter);
}
}

View File

@ -0,0 +1,123 @@
// <copyright file="NameValueHeaderValue.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
namespace OpenTelemetry.Context.Propagation
{
// Adoptation of code from https://github.com/aspnet/HttpAbstractions/blob/07d115400e4f8c7a66ba239f230805f03a14ee3d/src/Microsoft.Net.Http.Headers/NameValueHeaderValue.cs
internal class NameValueHeaderValue
{
private static readonly HttpHeaderParser<NameValueHeaderValue> SingleValueParser
= new GenericHeaderParser<NameValueHeaderValue>(false, GetNameValueLength);
private string name;
private string value;
private NameValueHeaderValue()
{
// Used by the parser to create a new instance of this type.
}
public string Name
{
get { return this.name; }
}
public string Value
{
get { return this.value; }
}
public static bool TryParse(string input, out NameValueHeaderValue parsedValue)
{
var index = 0;
return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
}
internal static int GetValueLength(string input, int startIndex)
{
if (startIndex >= input.Length)
{
return 0;
}
var valueLength = HttpRuleParser.GetTokenLength(input, startIndex);
if (valueLength == 0)
{
// A value can either be a token or a quoted string. Check if it is a quoted string.
if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed)
{
// We have an invalid value. Reset the name and return.
return 0;
}
}
return valueLength;
}
private static int GetNameValueLength(string input, int startIndex, out NameValueHeaderValue parsedValue)
{
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
// Parse the name, i.e. <name> in name/value string "<name>=<value>". Caller must remove
// leading whitespaces.
var nameLength = HttpRuleParser.GetTokenLength(input, startIndex);
if (nameLength == 0)
{
return 0;
}
var name = input.Substring(startIndex, nameLength);
var current = startIndex + nameLength;
current += HttpRuleParser.GetWhitespaceLength(input, current);
// Parse the separator between name and value
if ((current == input.Length) || (input[current] != '='))
{
// We only have a name and that's OK. Return.
parsedValue = new NameValueHeaderValue
{
name = name,
};
current += HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces
return current - startIndex;
}
current++; // skip delimiter.
current += HttpRuleParser.GetWhitespaceLength(input, current);
// Parse the value, i.e. <value> in name/value string "<name>=<value>"
int valueLength = GetValueLength(input, current);
// Value after the '=' may be empty
// Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation.
parsedValue = new NameValueHeaderValue
{
name = name,
value = input.Substring(current, valueLength),
};
current += valueLength;
current += HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces
return current - startIndex;
}
}
}

View File

@ -0,0 +1,110 @@
// <copyright file="PropagationContext.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace OpenTelemetry.Context.Propagation
{
/// <summary>
/// Stores propagation data.
/// </summary>
public readonly struct PropagationContext : IEquatable<PropagationContext>
{
/// <summary>
/// Initializes a new instance of the <see cref="PropagationContext"/> struct.
/// </summary>
/// <param name="activityContext">Entries for activity context.</param>
/// <param name="activityBaggage">Entries for activity baggage.</param>
public PropagationContext(ActivityContext activityContext, IEnumerable<KeyValuePair<string, string>> activityBaggage)
{
this.ActivityContext = activityContext;
this.ActivityBaggage = activityBaggage;
}
/// <summary>
/// Gets <see cref="ActivityContext"/>.
/// </summary>
public ActivityContext ActivityContext { get; }
/// <summary>
/// Gets ActivityBaggage.
/// </summary>
public IEnumerable<KeyValuePair<string, string>> ActivityBaggage { get; }
/// <summary>
/// Compare two entries of <see cref="PropagationContext"/> for equality.
/// </summary>
/// <param name="left">First Entry to compare.</param>
/// <param name="right">Second Entry to compare.</param>
public static bool operator ==(PropagationContext left, PropagationContext right) => left.Equals(right);
/// <summary>
/// Compare two entries of <see cref="PropagationContext"/> for not equality.
/// </summary>
/// <param name="left">First Entry to compare.</param>
/// <param name="right">Second Entry to compare.</param>
public static bool operator !=(PropagationContext left, PropagationContext right) => !(left == right);
/// <inheritdoc/>
public bool Equals(PropagationContext value)
{
if (this.ActivityContext != value.ActivityContext
|| this.ActivityBaggage is null != value.ActivityBaggage is null)
{
return false;
}
if (this.ActivityBaggage is null)
{
return true;
}
if (this.ActivityBaggage.Count() != value.ActivityBaggage.Count())
{
return false;
}
var thisEnumerator = this.ActivityBaggage.GetEnumerator();
var valueEnumerator = value.ActivityBaggage.GetEnumerator();
while (thisEnumerator.MoveNext() && valueEnumerator.MoveNext())
{
if (thisEnumerator.Current.Key != valueEnumerator.Current.Key
|| thisEnumerator.Current.Value != valueEnumerator.Current.Value)
{
return false;
}
}
return true;
}
/// <inheritdoc/>
public override bool Equals(object obj) => (obj is PropagationContext context) && this.Equals(context);
/// <inheritdoc/>
public override int GetHashCode()
{
var hashCode = 323591981;
hashCode = (hashCode * -1521134295) + this.ActivityContext.GetHashCode();
hashCode = (hashCode * -1521134295) + EqualityComparer<IEnumerable<KeyValuePair<string, string>>>.Default.GetHashCode(this.ActivityBaggage);
return hashCode;
}
}
}

View File

@ -13,6 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -42,49 +43,24 @@ namespace OpenTelemetry.Context.Propagation
public ISet<string> Fields => new HashSet<string> { TraceState, TraceParent };
/// <inheritdoc/>
public bool IsInjected<T>(T carrier, Func<T, string, IEnumerable<string>> getter)
public PropagationContext Extract<T>(PropagationContext context, T carrier, Func<T, string, IEnumerable<string>> getter)
{
if (context.ActivityContext.IsValid())
{
// If a valid context has already been extracted, perform a noop.
return context;
}
if (carrier == null)
{
OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null carrier");
return false;
OpenTelemetryApiEventSource.Log.FailedToExtractActivityContext(nameof(TraceContextFormat), "null carrier");
return context;
}
if (getter == null)
{
OpenTelemetryApiEventSource.Log.FailedToExtractContext("null getter");
return false;
}
try
{
var traceparentCollection = getter(carrier, TraceParent);
// There must be a single traceparent
return traceparentCollection != null && traceparentCollection.Count() == 1;
}
catch (Exception ex)
{
OpenTelemetryApiEventSource.Log.ActivityContextExtractException(ex);
}
// in case of exception indicate to upstream that there is no parseable context from the top
return false;
}
/// <inheritdoc/>
public ActivityContext Extract<T>(ActivityContext activityContext, T carrier, Func<T, string, IEnumerable<string>> getter)
{
if (carrier == null)
{
OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null carrier");
return activityContext;
}
if (getter == null)
{
OpenTelemetryApiEventSource.Log.FailedToExtractContext("null getter");
return activityContext;
OpenTelemetryApiEventSource.Log.FailedToExtractActivityContext(nameof(TraceContextFormat), "null getter");
return context;
}
try
@ -94,7 +70,7 @@ namespace OpenTelemetry.Context.Propagation
// There must be a single traceparent
if (traceparentCollection == null || traceparentCollection.Count() != 1)
{
return activityContext;
return context;
}
var traceparent = traceparentCollection.First();
@ -102,54 +78,56 @@ namespace OpenTelemetry.Context.Propagation
if (!traceparentParsed)
{
return activityContext;
return context;
}
string tracestate = string.Empty;
string tracestate = null;
var tracestateCollection = getter(carrier, TraceState);
if (tracestateCollection?.Any() ?? false)
{
TryExtractTracestate(tracestateCollection.ToArray(), out tracestate);
}
return new ActivityContext(traceId, spanId, traceoptions, tracestate, isRemote: true);
return new PropagationContext(
new ActivityContext(traceId, spanId, traceoptions, tracestate, isRemote: true),
context.ActivityBaggage);
}
catch (Exception ex)
{
OpenTelemetryApiEventSource.Log.ActivityContextExtractException(ex);
OpenTelemetryApiEventSource.Log.ActivityContextExtractException(nameof(TraceContextFormat), ex);
}
// in case of exception indicate to upstream that there is no parseable context from the top
return activityContext;
return context;
}
/// <inheritdoc/>
public void Inject<T>(ActivityContext activityContext, T carrier, Action<T, string, string> setter)
public void Inject<T>(PropagationContext context, T carrier, Action<T, string, string> setter)
{
if (activityContext == default)
if (context.ActivityContext.TraceId == default || context.ActivityContext.SpanId == default)
{
OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("Invalid context");
OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext(nameof(TraceContextFormat), "Invalid context");
return;
}
if (carrier == null)
{
OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null carrier");
OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext(nameof(TraceContextFormat), "null carrier");
return;
}
if (setter == null)
{
OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null setter");
OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext(nameof(TraceContextFormat), "null setter");
return;
}
var traceparent = string.Concat("00-", activityContext.TraceId.ToHexString(), "-", activityContext.SpanId.ToHexString());
traceparent = string.Concat(traceparent, (activityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0 ? "-01" : "-00");
var traceparent = string.Concat("00-", context.ActivityContext.TraceId.ToHexString(), "-", context.ActivityContext.SpanId.ToHexString());
traceparent = string.Concat(traceparent, (context.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0 ? "-01" : "-00");
setter(carrier, TraceParent, traceparent);
string tracestateStr = activityContext.TraceState;
string tracestateStr = context.ActivityContext.TraceState;
if (tracestateStr?.Length > 0)
{
setter(carrier, TraceState, tracestateStr);

View File

@ -28,11 +28,20 @@ namespace OpenTelemetry.Internal
public static OpenTelemetryApiEventSource Log = new OpenTelemetryApiEventSource();
[NonEvent]
public void ActivityContextExtractException(Exception ex)
public void ActivityContextExtractException(string format, Exception ex)
{
if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1)))
{
this.FailedToExtractActivityContext(ex.ToInvariantString());
this.FailedToExtractActivityContext(format, ex.ToInvariantString());
}
}
[NonEvent]
public void BaggageExtractException(string format, Exception ex)
{
if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1)))
{
this.FailedToExtractBaggage(format, ex.ToInvariantString());
}
}
@ -93,22 +102,28 @@ namespace OpenTelemetry.Internal
this.WriteEvent(7, methodName, argumentName, issue);
}
[Event(8, Message = "Failed to extract activity context: '{0}'", Level = EventLevel.Warning)]
public void FailedToExtractActivityContext(string exception)
[Event(8, Message = "Failed to extract activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)]
public void FailedToExtractActivityContext(string format, string exception)
{
this.WriteEvent(8, exception);
this.WriteEvent(8, format, exception);
}
[Event(9, Message = "Failed to inject activity context: '{0}'", Level = EventLevel.Warning)]
public void FailedToInjectActivityContext(string error)
[Event(9, Message = "Failed to inject activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)]
public void FailedToInjectActivityContext(string format, string error)
{
this.WriteEvent(9, error);
this.WriteEvent(9, format, error);
}
[Event(10, Message = "Failed to extract span context: '{0}'", Level = EventLevel.Warning)]
public void FailedToExtractContext(string error)
[Event(10, Message = "Failed to extract baggage in format: '{0}', baggage: '{1}'.", Level = EventLevel.Warning)]
public void FailedToExtractBaggage(string format, string exception)
{
this.WriteEvent(10, error);
this.WriteEvent(10, format, exception);
}
[Event(11, Message = "Failed to inject baggage in format: '{0}', baggage: '{1}'.", Level = EventLevel.Warning)]
public void FailedToInjectBaggage(string format, string error)
{
this.WriteEvent(11, format, error);
}
}
}

View File

@ -38,6 +38,6 @@ namespace OpenTelemetry.Metrics
/// </summary>
/// <param name="context">the associated distributed context.</param>
/// <param name="value">value by which the bound counter metric should be added.</param>
public abstract void Add(in DistributedContext context, T value);
public abstract void Add(in CorrelationContext context, T value);
}
}

View File

@ -38,6 +38,6 @@ namespace OpenTelemetry.Metrics
/// </summary>
/// <param name="context">the associated distributed context.</param>
/// <param name="value">the measurement to be recorded.</param>
public abstract void Record(in DistributedContext context, T value);
public abstract void Record(in CorrelationContext context, T value);
}
}

View File

@ -49,7 +49,7 @@ namespace OpenTelemetry.Metrics
/// <param name="context">the associated distributed context.</param>
/// <param name="value">value by which the counter should be incremented.</param>
/// <param name="labelset">The labelset associated with this value.</param>
public abstract void Add(in DistributedContext context, T value, LabelSet labelset);
public abstract void Add(in CorrelationContext context, T value, LabelSet labelset);
/// <summary>
/// Adds or Increments the counter.
@ -57,7 +57,7 @@ namespace OpenTelemetry.Metrics
/// <param name="context">the associated distributed context.</param>
/// <param name="value">value by which the counter should be incremented.</param>
/// <param name="labels">The labels or dimensions associated with this value.</param>
public abstract void Add(in DistributedContext context, T value, IEnumerable<KeyValuePair<string, string>> labels);
public abstract void Add(in CorrelationContext context, T value, IEnumerable<KeyValuePair<string, string>> labels);
/// <summary>
/// Gets the bound counter metric with given labelset.

View File

@ -49,7 +49,7 @@ namespace OpenTelemetry.Metrics
/// <param name="context">the associated distributed context.</param>
/// <param name="value">value to record.</param>
/// <param name="labelset">The labelset associated with this value.</param>
public void Record(in DistributedContext context, T value, LabelSet labelset) => this.Bind(labelset).Record(context, value);
public void Record(in CorrelationContext context, T value, LabelSet labelset) => this.Bind(labelset).Record(context, value);
/// <summary>
/// Records a measure.
@ -57,7 +57,7 @@ namespace OpenTelemetry.Metrics
/// <param name="context">the associated distributed context.</param>
/// <param name="value">value to record.</param>
/// <param name="labels">The labels or dimensions associated with this value.</param>
public void Record(in DistributedContext context, T value, IEnumerable<KeyValuePair<string, string>> labels) => this.Bind(labels).Record(context, value);
public void Record(in CorrelationContext context, T value, IEnumerable<KeyValuePair<string, string>> labels) => this.Bind(labels).Record(context, value);
/// <summary>
/// Gets the bound measure metric with given labelset.

View File

@ -37,7 +37,7 @@ namespace OpenTelemetry.Metrics
}
/// <inheritdoc/>
public override void Add(in DistributedContext context, T value)
public override void Add(in CorrelationContext context, T value)
{
}
}

View File

@ -37,7 +37,7 @@ namespace OpenTelemetry.Metrics
}
/// <inheritdoc/>
public override void Record(in DistributedContext context, T value)
public override void Record(in CorrelationContext context, T value)
{
}
}

View File

@ -43,12 +43,12 @@ namespace OpenTelemetry.Metrics
}
/// <inheritdoc/>
public override void Add(in DistributedContext context, T value, LabelSet labelset)
public override void Add(in CorrelationContext context, T value, LabelSet labelset)
{
}
/// <inheritdoc/>
public override void Add(in DistributedContext context, T value, IEnumerable<KeyValuePair<string, string>> labels)
public override void Add(in CorrelationContext context, T value, IEnumerable<KeyValuePair<string, string>> labels)
{
}

View File

@ -30,6 +30,7 @@ namespace OpenTelemetry.Trace
{
internal static readonly TelemetrySpan NoopInstance = new TelemetrySpan(null);
internal readonly Activity Activity;
private static readonly IEnumerable<KeyValuePair<string, string>> EmptyBaggage = new KeyValuePair<string, string>[0];
internal TelemetrySpan(Activity activity)
{
@ -65,6 +66,11 @@ namespace OpenTelemetry.Trace
}
}
/// <summary>
/// Gets the span baggage.
/// </summary>
public IEnumerable<KeyValuePair<string, string>> Baggage => this.Activity?.Baggage ?? EmptyBaggage;
/// <summary>
/// Sets the status of the span execution.
/// </summary>
@ -272,6 +278,31 @@ namespace OpenTelemetry.Trace
this.Activity?.Stop();
}
/// <summary>
/// Retrieves a baggage item.
/// </summary>
/// <param name="key">Baggage item key.</param>
/// <returns>Retrieved baggage value or <see langword="null"/> if no match was found.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string GetBaggageItem(string key)
{
return this.Activity?.GetBaggageItem(key);
}
/// <summary>
/// Adds a baggage item to the <see cref="TelemetrySpan"/>.
/// </summary>
/// <param name="key">Baggage item key.</param>
/// <param name="value">Baggage item value.</param>
/// <returns>The <see cref="TelemetrySpan"/> instance for chaining.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TelemetrySpan AddBaggage(string key, string value)
{
this.Activity?.AddBaggage(key, value);
return this;
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()

View File

@ -26,18 +26,17 @@ namespace OpenTelemetry.Instrumentation.AspNet
public class AspNetInstrumentationOptions
{
/// <summary>
/// Gets or sets <see cref="ITextFormat"/> for context propagation.
/// Gets or sets <see cref="ITextFormat"/> for context propagation. Default value: <see cref="CompositePropagator"/> with <see cref="TraceContextFormat"/> &amp; <see cref="BaggageFormat"/>.
/// </summary>
public ITextFormat TextFormat { get; set; } = new TraceContextFormat();
public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[]
{
new TraceContextFormat(),
new BaggageFormat(),
});
/// <summary>
/// Gets or sets a hook to exclude calls based on domain or other per-request criterion.
/// </summary>
internal Predicate<HttpContext> RequestFilter { get; set; } = DefaultFilter;
private static bool DefaultFilter(HttpContext httpContext)
{
return true;
}
internal Predicate<HttpContext> RequestFilter { get; set; }
}
}

View File

@ -2,6 +2,13 @@
## Unreleased
* Changed the default propagation to support W3C Baggage
([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048))
* The default ITextFormat is now `CompositePropagator(TraceContextFormat,
BaggageFormat)`. Baggage sent via the [W3C
Baggage](https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md)
header will now be parsed and set on incoming Http spans.
## 0.4.0-beta.2
Released 2020-07-24

View File

@ -49,7 +49,7 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
return;
}
if (this.options.RequestFilter != null && !this.options.RequestFilter(context))
if (this.options.RequestFilter?.Invoke(context) == false)
{
AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName);
activity.IsAllDataRequested = false;
@ -61,17 +61,16 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
if (!(this.options.TextFormat is TraceContextFormat))
{
// This requires to ignore the current activity and create a new one
// using the context extracted using the format TextFormat supports.
var ctx = this.options.TextFormat.Extract(default, request, HttpRequestHeaderValuesGetter);
if (ctx != default)
if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context)
{
// Create a new activity with its parent set from the extracted context.
// This makes the new activity as a "sibling" of the activity created by
// Asp.Net.
// ASP.NET.
Activity newOne = new Activity(ActivityNameByHttpInListener);
newOne.SetParentId(ctx.TraceId, ctx.SpanId, ctx.TraceFlags);
newOne.TraceStateString = ctx.TraceState;
newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags);
newOne.TraceStateString = ctx.ActivityContext.TraceState;
// Starting the new activity make it the Activity.Current one.
newOne.Start();
@ -83,6 +82,14 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
activity.SetCustomProperty("ActivityByHttpInListener", newOne);
activity = newOne;
}
if (ctx.ActivityBaggage != null)
{
foreach (var baggageItem in ctx.ActivityBaggage)
{
activity.AddBaggage(baggageItem.Key, baggageItem.Value);
}
}
}
// see the spec https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
@ -114,6 +121,7 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
public override void OnStopActivity(Activity activity, object payload)
{
Activity activityToEnrich = activity;
Activity createdActivity = null;
if (!(this.options.TextFormat is TraceContextFormat))
{
@ -125,9 +133,10 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
if (activity.OperationName.Equals("Microsoft.AspNet.HttpReqIn.Start"))
{
// This block is hit if Asp.Net did restore Current to its own activity,
// then we need to retrieve the one created by HttpInListener
// and populate tags to it.
activityToEnrich = (Activity)activity.GetCustomProperty("ActivityByHttpInListener");
// and we need to retrieve the one created by HttpInListener,
// or an additional activity was never created.
createdActivity = (Activity)activity.GetCustomProperty("ActivityByHttpInListener");
activityToEnrich = createdActivity ?? activity;
}
}
@ -190,13 +199,12 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
var activityByAspNet = (Activity)activity.GetCustomProperty("ActivityByAspNet");
Activity.Current = activityByAspNet;
}
else if (activity.OperationName.Equals("Microsoft.AspNet.HttpReqIn.Start"))
else if (createdActivity != null)
{
// This block is hit if Asp.Net did restore Current to its own activity,
// then we need to retrieve the one created by HttpInListener
// and stop it.
var activityByHttpInListener = (Activity)activity.GetCustomProperty("ActivityByHttpInListener");
activityByHttpInListener.Stop();
createdActivity.Stop();
// Restore current back to the one created by Asp.Net
Activity.Current = activity;

View File

@ -26,18 +26,17 @@ namespace OpenTelemetry.Instrumentation.AspNetCore
public class AspNetCoreInstrumentationOptions
{
/// <summary>
/// Gets or sets <see cref="ITextFormat"/> for context propagation.
/// Gets or sets <see cref="ITextFormat"/> for context propagation. Default value: <see cref="CompositePropagator"/> with <see cref="TraceContextFormat"/> &amp; <see cref="BaggageFormat"/>.
/// </summary>
public ITextFormat TextFormat { get; set; } = new TraceContextFormat();
public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[]
{
new TraceContextFormat(),
new BaggageFormat(),
});
/// <summary>
/// Gets or sets a hook to exclude calls based on domain or other per-request criterion.
/// </summary>
internal Predicate<HttpContext> RequestFilter { get; set; } = DefaultFilter;
private static bool DefaultFilter(HttpContext httpContext)
{
return true;
}
internal Predicate<HttpContext> RequestFilter { get; set; }
}
}

View File

@ -2,12 +2,18 @@
## Unreleased
* Changed the default propagation to support W3C Baggage
([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048))
* The default ITextFormat is now `CompositePropagator(TraceContextFormat,
BaggageFormat)`. Baggage sent via the [W3C
Baggage](https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md)
header will now be parsed and set on incoming Http spans.
* Introduced support for Grpc.AspNetCore (#803).
* Attributes are added to gRPC invocations: `rpc.system`, `rpc.service`,
`rpc.method`. These attributes are added to an existing span generated by
the instrumentation. This is unlike the instrumentation for client-side
gRPC calls where one span is created for the gRPC call and a separate span
is created for the underlying HTTP call in the event both gRPC and HTTP
the instrumentation. This is unlike the instrumentation for client-side gRPC
calls where one span is created for the gRPC call and a separate span is
created for the underlying HTTP call in the event both gRPC and HTTP
instrumentation are enabled.
## 0.4.0-beta.2

View File

@ -57,7 +57,7 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
return;
}
if (this.options.RequestFilter != null && !this.options.RequestFilter(context))
if (this.options.RequestFilter?.Invoke(context) == false)
{
AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName);
activity.IsAllDataRequested = false;
@ -67,24 +67,29 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
var request = context.Request;
if (!this.hostingSupportsW3C || !(this.options.TextFormat is TraceContextFormat))
{
// This requires to ignore the current activity and create a new one
// using the context extracted from w3ctraceparent header or
// using the format TextFormat supports.
var ctx = this.options.TextFormat.Extract(default, request, HttpRequestHeaderValuesGetter);
if (ctx != default)
if (ctx.ActivityContext.IsValid() && ctx.ActivityContext != activity.Context)
{
// Create a new activity with its parent set from the extracted context.
// This makes the new activity as a "sibling" of the activity created by
// Asp.Net Core.
Activity newOne = new Activity(ActivityNameByHttpInListener);
newOne.SetParentId(ctx.TraceId, ctx.SpanId, ctx.TraceFlags);
newOne.TraceStateString = ctx.TraceState;
newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags);
newOne.TraceStateString = ctx.ActivityContext.TraceState;
// Starting the new activity make it the Activity.Current one.
newOne.Start();
activity = newOne;
}
if (ctx.ActivityBaggage != null)
{
foreach (var baggageItem in ctx.ActivityBaggage)
{
activity.AddBaggage(baggageItem.Key, baggageItem.Value);
}
}
}
activity.SetKind(ActivityKind.Server);

View File

@ -2,6 +2,13 @@
## Unreleased
* Changed the default propagation to support W3C Baggage
([#1048](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1048))
* The default ITextFormat is now `CompositePropagator(TraceContextFormat,
BaggageFormat)`. Outgoing Http request will now send Baggage using the [W3C
Baggage](https://github.com/w3c/baggage/blob/master/baggage/HTTP_HEADER_FORMAT.md)
header. Previously Baggage was sent using the `Correlation-Context` header,
which is now outdated.
* Removed `AddHttpInstrumentation` and `AddHttpWebRequestInstrumentation` (.NET
Framework) `TracerProviderBuilderExtensions`. `AddHttpClientInstrumentation`
will now register `HttpClient` instrumentation on .NET Core and `HttpClient` +

View File

@ -31,9 +31,13 @@ namespace OpenTelemetry.Instrumentation.Http
public bool SetHttpFlavor { get; set; }
/// <summary>
/// Gets or sets <see cref="ITextFormat"/> for context propagation. Default value: <see cref="TraceContextFormat"/>.
/// Gets or sets <see cref="ITextFormat"/> for context propagation. Default value: <see cref="CompositePropagator"/> with <see cref="TraceContextFormat"/> &amp; <see cref="BaggageFormat"/>.
/// </summary>
public ITextFormat TextFormat { get; set; } = new TraceContextFormat();
public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[]
{
new TraceContextFormat(),
new BaggageFormat(),
});
/// <summary>
/// Gets or sets an optional callback method for filtering <see cref="HttpRequestMessage"/> requests that are sent through the instrumentation.

View File

@ -32,9 +32,13 @@ namespace OpenTelemetry.Instrumentation.Http
public bool SetHttpFlavor { get; set; }
/// <summary>
/// Gets or sets <see cref="ITextFormat"/> for context propagation. Default value: <see cref="TraceContextFormat"/>.
/// Gets or sets <see cref="ITextFormat"/> for context propagation. Default value: <see cref="CompositePropagator"/> with <see cref="TraceContextFormat"/> &amp; <see cref="BaggageFormat"/>.
/// </summary>
public ITextFormat TextFormat { get; set; } = new TraceContextFormat();
public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[]
{
new TraceContextFormat(),
new BaggageFormat(),
});
/// <summary>
/// Gets or sets an optional callback method for filtering <see cref="HttpWebRequest"/> requests that are sent through the instrumentation.

View File

@ -81,7 +81,7 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
return;
}
if (this.options.TextFormat.IsInjected(request, HttpRequestMessageHeaderValuesGetter))
if (this.options.TextFormat.Extract(default, request, HttpRequestMessageHeaderValuesGetter) != default)
{
// this request is already instrumented, we should back off
activity.IsAllDataRequested = false;
@ -107,7 +107,7 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
if (!(this.httpClientSupportsW3C && this.options.TextFormat is TraceContextFormat))
{
this.options.TextFormat.Inject(activity.Context, request, HttpRequestMessageHeaderValueSetter);
this.options.TextFormat.Inject(new PropagationContext(activity.Context, activity.Baggage), request, HttpRequestMessageHeaderValueSetter);
}
}

View File

@ -23,6 +23,7 @@ using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Trace;
namespace OpenTelemetry.Instrumentation.Http.Implementation
@ -44,8 +45,6 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
internal static HttpWebRequestInstrumentationOptions Options = new HttpWebRequestInstrumentationOptions();
private const string CorrelationContextHeaderName = "Correlation-Context";
private static readonly Version Version = typeof(HttpWebRequestActivitySource).Assembly.GetName().Version;
private static readonly ActivitySource WebRequestActivitySource = new ActivitySource(ActivitySourceName, Version.ToString());
@ -188,32 +187,11 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InstrumentRequest(HttpWebRequest request, Activity activity)
{
Options.TextFormat.Inject(activity.Context, request, HttpWebRequestHeaderValuesSetter);
if (request.Headers.Get(CorrelationContextHeaderName) == null)
{
// we expect baggage to be empty or contain a few items
using IEnumerator<KeyValuePair<string, string>> e = activity.Baggage.GetEnumerator();
if (e.MoveNext())
{
StringBuilder baggage = new StringBuilder();
do
{
KeyValuePair<string, string> item = e.Current;
baggage.Append(WebUtility.UrlEncode(item.Key)).Append('=').Append(WebUtility.UrlEncode(item.Value)).Append(',');
}
while (e.MoveNext());
baggage.Remove(baggage.Length - 1, 1);
request.Headers.Add(CorrelationContextHeaderName, baggage.ToString());
}
}
}
=> Options.TextFormat.Inject(new PropagationContext(activity.Context, activity.Baggage), request, HttpWebRequestHeaderValuesSetter);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsRequestInstrumented(HttpWebRequest request)
=> Options.TextFormat.IsInjected(request, HttpWebRequestHeaderValuesGetter);
=> Options.TextFormat.Extract(default, request, HttpWebRequestHeaderValuesGetter) != default;
private static void ProcessRequest(HttpWebRequest request)
{

View File

@ -22,7 +22,9 @@ namespace OpenTelemetry.Shims.OpenTracing
{
public sealed class SpanContextShim : ISpanContext
{
public SpanContextShim(in Trace.SpanContext spanContext)
private readonly IEnumerable<KeyValuePair<string, string>> baggage;
public SpanContextShim(in Trace.SpanContext spanContext, IEnumerable<KeyValuePair<string, string>> baggage = null)
{
if (!spanContext.IsValid)
{
@ -30,6 +32,7 @@ namespace OpenTelemetry.Shims.OpenTracing
}
this.SpanContext = spanContext;
this.baggage = baggage;
}
public Trace.SpanContext SpanContext { get; private set; }
@ -42,9 +45,6 @@ namespace OpenTelemetry.Shims.OpenTracing
/// <inheritdoc/>
public IEnumerable<KeyValuePair<string, string>> GetBaggageItems()
{
// TODO
throw new NotImplementedException();
}
=> this.baggage;
}
}

View File

@ -52,7 +52,7 @@ namespace OpenTelemetry.Shims.OpenTracing
throw new ArgumentException(nameof(this.Span.Context));
}
this.spanContextShim = new SpanContextShim(this.Span.Context);
this.spanContextShim = new SpanContextShim(this.Span.Context, this.Span.Baggage);
}
public ISpanContext Context => this.spanContextShim;

View File

@ -60,7 +60,7 @@ namespace OpenTelemetry.Shims.OpenTracing
throw new ArgumentNullException(nameof(carrier));
}
ActivityContext activityContext = default;
PropagationContext propagationContext = default;
if ((format == BuiltinFormats.TextMap || format == BuiltinFormats.HttpHeaders) && carrier is ITextMap textMapCarrier)
{
@ -71,7 +71,7 @@ namespace OpenTelemetry.Shims.OpenTracing
carrierMap.Add(entry.Key, new[] { entry.Value });
}
IEnumerable<string> GetCarrierKeyValue(Dictionary<string, IEnumerable<string>> source, string key)
static IEnumerable<string> GetCarrierKeyValue(Dictionary<string, IEnumerable<string>> source, string key)
{
if (key == null || !source.TryGetValue(key, out var value))
{
@ -81,10 +81,10 @@ namespace OpenTelemetry.Shims.OpenTracing
return value;
}
activityContext = this.textFormat.Extract(default, carrierMap, GetCarrierKeyValue);
propagationContext = this.textFormat.Extract(propagationContext, carrierMap, GetCarrierKeyValue);
}
return !activityContext.IsValid() ? null : new SpanContextShim(new Trace.SpanContext(activityContext));
return !propagationContext.ActivityContext.IsValid() ? null : new SpanContextShim(new Trace.SpanContext(propagationContext.ActivityContext), propagationContext.ActivityBaggage);
}
/// <inheritdoc/>
@ -115,7 +115,10 @@ namespace OpenTelemetry.Shims.OpenTracing
if ((format == BuiltinFormats.TextMap || format == BuiltinFormats.HttpHeaders) && carrier is ITextMap textMapCarrier)
{
this.textFormat.Inject(shim.SpanContext, textMapCarrier, (instrumentation, key, value) => instrumentation.Set(key, value));
this.textFormat.Inject(
new PropagationContext(shim.SpanContext, shim.GetBaggageItems()),
textMapCarrier,
(instrumentation, key, value) => instrumentation.Set(key, value));
}
}
}

View File

@ -1,102 +0,0 @@
// <copyright file="AsyncLocalDistributedContextCarrier.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
#if NET452
using System.Collections;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
#endif
using System.Threading;
namespace OpenTelemetry.Context
{
/// <summary>
/// Distributed Context carrier using AsyncLocal.
/// </summary>
public sealed class AsyncLocalDistributedContextCarrier : DistributedContextCarrier
{
#if NET452
// A special workaround to suppress context propagation cross AppDomains.
//
// By default the value added to System.Runtime.Remoting.Messaging.CallContext
// will be marshalled/unmarshalled across AppDomain boundary. This will cause
// serious issue if the destination AppDomain doesn't have the corresponding type
// to unmarshal data (which is DistributedContext in this case).
// The worst case is AppDomain crash with ReflectionLoadTypeException.
//
// The workaround is to use a well known type that exists in all AppDomains, and
// put the actual payload (DistributedContext instance) as a non-public field so
// the field is ignored during marshalling.
private const string ContextSlotName = "OpenTelemetry.DistributedContext";
private static readonly FieldInfo WrapperField = typeof(BitArray).GetField("_syncRoot", BindingFlags.Instance | BindingFlags.NonPublic);
#else
private static AsyncLocal<DistributedContext> carrier = new AsyncLocal<DistributedContext>();
#endif
private AsyncLocalDistributedContextCarrier()
{
this.OverwriteCurrent(DistributedContext.Empty);
}
/// <summary>
/// Gets the instance of <see cref="AsyncLocalDistributedContextCarrier"/>.
/// </summary>
public static DistributedContextCarrier Instance { get; } = new AsyncLocalDistributedContextCarrier();
/// <summary>
/// Gets the current <see cref="DistributedContext"/>.
/// </summary>
public override DistributedContext Current
{
get
{
#if NET452
var wrapper = CallContext.LogicalGetData(ContextSlotName) as BitArray;
if (wrapper == null)
{
var context = default(DistributedContext);
this.OverwriteCurrent(context);
return context;
}
return (DistributedContext)WrapperField.GetValue(wrapper);
#else
return carrier.Value;
#endif
}
}
/// <summary>
/// Sets the current <see cref="DistributedContext"/>.
/// </summary>
/// <param name="context">Context to set as current.</param>
/// <returns>Scope object. On disposal - original context will be restored.</returns>
public override IDisposable SetCurrent(in DistributedContext context) => new DistributedContextState(in context);
internal void OverwriteCurrent(in DistributedContext context)
{
#if NET452
var wrapper = new BitArray(0);
WrapperField.SetValue(wrapper, context);
CallContext.LogicalSetData(ContextSlotName, wrapper);
#else
carrier.Value = context;
#endif
}
}
}

View File

@ -1,36 +0,0 @@
// <copyright file="DistributedContextState.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
namespace OpenTelemetry.Context
{
internal struct DistributedContextState : IDisposable
{
private DistributedContext context;
internal DistributedContextState(in DistributedContext context)
{
this.context = AsyncLocalDistributedContextCarrier.Instance.Current;
((AsyncLocalDistributedContextCarrier)AsyncLocalDistributedContextCarrier.Instance).OverwriteCurrent(in context);
}
public void Dispose()
{
((AsyncLocalDistributedContextCarrier)AsyncLocalDistributedContextCarrier.Instance).OverwriteCurrent(in this.context);
}
}
}

View File

@ -1,56 +0,0 @@
// <copyright file="NoopDistributedContextBinarySerializer.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
#if !NET452
using System;
#endif
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Internal;
namespace OpenTelemetry.Context
{
public class NoopDistributedContextBinarySerializer : DistributedContextBinarySerializerBase
{
internal static readonly DistributedContextBinarySerializerBase Instance = new NoopDistributedContextBinarySerializer();
#if NET452
private static readonly byte[] EmptyByteArray = { };
#else
private static readonly byte[] EmptyByteArray = Array.Empty<byte>();
#endif
/// <inheritdoc/>
public override byte[] ToByteArray(DistributedContext context)
{
if (context.CorrelationContext.Entries is null)
{
OpenTelemetrySdkEventSource.Log.FailedToInjectContext("entries are null");
}
return EmptyByteArray;
}
/// <inheritdoc/>
public override DistributedContext FromByteArray(byte[] bytes)
{
if (bytes == null)
{
OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null bytes");
}
return DistributedContext.Empty;
}
}
}

View File

@ -70,95 +70,64 @@ namespace OpenTelemetry.Context.Propagation
public ISet<string> Fields => AllFields;
/// <inheritdoc/>
public bool IsInjected<T>(T carrier, Func<T, string, IEnumerable<string>> getter)
public PropagationContext Extract<T>(PropagationContext context, T carrier, Func<T, string, IEnumerable<string>> getter)
{
if (context.ActivityContext.IsValid())
{
// If a valid context has already been extracted, perform a noop.
return context;
}
if (carrier == null)
{
OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null carrier");
return false;
OpenTelemetrySdkEventSource.Log.FailedToExtractActivityContext(nameof(B3Format), "null carrier");
return context;
}
if (getter == null)
{
OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null getter");
return false;
}
try
{
if (this.singleHeader)
{
var header = getter(carrier, XB3Combined)?.FirstOrDefault();
return !string.IsNullOrWhiteSpace(header);
}
else
{
var traceIdStr = getter(carrier, XB3TraceId)?.FirstOrDefault();
var spanIdStr = getter(carrier, XB3SpanId)?.FirstOrDefault();
return traceIdStr != null && spanIdStr != null;
}
}
catch (Exception e)
{
OpenTelemetrySdkEventSource.Log.ContextExtractException(e);
return false;
}
}
/// <inheritdoc/>
public ActivityContext Extract<T>(ActivityContext activityContext, T carrier, Func<T, string, IEnumerable<string>> getter)
{
if (carrier == null)
{
OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null carrier");
return activityContext;
}
if (getter == null)
{
OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null getter");
return activityContext;
OpenTelemetrySdkEventSource.Log.FailedToExtractActivityContext(nameof(B3Format), "null getter");
return context;
}
if (this.singleHeader)
{
return ExtractFromSingleHeader(activityContext, carrier, getter);
return ExtractFromSingleHeader(context, carrier, getter);
}
else
{
return ExtractFromMultipleHeaders(activityContext, carrier, getter);
return ExtractFromMultipleHeaders(context, carrier, getter);
}
}
/// <inheritdoc/>
public void Inject<T>(ActivityContext activityContext, T carrier, Action<T, string, string> setter)
public void Inject<T>(PropagationContext context, T carrier, Action<T, string, string> setter)
{
if (!activityContext.IsValid())
if (context.ActivityContext.TraceId == default || context.ActivityContext.SpanId == default)
{
OpenTelemetrySdkEventSource.Log.FailedToInjectContext("invalid context");
OpenTelemetrySdkEventSource.Log.FailedToInjectActivityContext(nameof(B3Format), "invalid context");
return;
}
if (carrier == null)
{
OpenTelemetrySdkEventSource.Log.FailedToInjectContext("null carrier");
OpenTelemetrySdkEventSource.Log.FailedToInjectActivityContext(nameof(B3Format), "null carrier");
return;
}
if (setter == null)
{
OpenTelemetrySdkEventSource.Log.FailedToInjectContext("null setter");
OpenTelemetrySdkEventSource.Log.FailedToInjectActivityContext(nameof(B3Format), "null setter");
return;
}
if (this.singleHeader)
{
var sb = new StringBuilder();
sb.Append(activityContext.TraceId.ToHexString());
sb.Append(context.ActivityContext.TraceId.ToHexString());
sb.Append(XB3CombinedDelimiter);
sb.Append(activityContext.SpanId.ToHexString());
if ((activityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0)
sb.Append(context.ActivityContext.SpanId.ToHexString());
if ((context.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0)
{
sb.Append(XB3CombinedDelimiter);
sb.Append(SampledValue);
@ -168,16 +137,16 @@ namespace OpenTelemetry.Context.Propagation
}
else
{
setter(carrier, XB3TraceId, activityContext.TraceId.ToHexString());
setter(carrier, XB3SpanId, activityContext.SpanId.ToHexString());
if ((activityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0)
setter(carrier, XB3TraceId, context.ActivityContext.TraceId.ToHexString());
setter(carrier, XB3SpanId, context.ActivityContext.SpanId.ToHexString());
if ((context.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0)
{
setter(carrier, XB3Sampled, SampledValue);
}
}
}
private static ActivityContext ExtractFromMultipleHeaders<T>(ActivityContext activityContext, T carrier, Func<T, string, IEnumerable<string>> getter)
private static PropagationContext ExtractFromMultipleHeaders<T>(PropagationContext context, T carrier, Func<T, string, IEnumerable<string>> getter)
{
try
{
@ -195,7 +164,7 @@ namespace OpenTelemetry.Context.Propagation
}
else
{
return activityContext;
return context;
}
ActivitySpanId spanId;
@ -206,7 +175,7 @@ namespace OpenTelemetry.Context.Propagation
}
else
{
return activityContext;
return context;
}
var traceOptions = ActivityTraceFlags.None;
@ -216,35 +185,37 @@ namespace OpenTelemetry.Context.Propagation
traceOptions |= ActivityTraceFlags.Recorded;
}
return new ActivityContext(traceId, spanId, traceOptions, isRemote: true);
return new PropagationContext(
new ActivityContext(traceId, spanId, traceOptions, isRemote: true),
context.ActivityBaggage);
}
catch (Exception e)
{
OpenTelemetrySdkEventSource.Log.ContextExtractException(e);
return activityContext;
OpenTelemetrySdkEventSource.Log.ActivityContextExtractException(nameof(B3Format), e);
return context;
}
}
private static ActivityContext ExtractFromSingleHeader<T>(ActivityContext activityContext, T carrier, Func<T, string, IEnumerable<string>> getter)
private static PropagationContext ExtractFromSingleHeader<T>(PropagationContext context, T carrier, Func<T, string, IEnumerable<string>> getter)
{
try
{
var header = getter(carrier, XB3Combined)?.FirstOrDefault();
if (string.IsNullOrWhiteSpace(header))
{
return activityContext;
return context;
}
var parts = header.Split(XB3CombinedDelimiter);
if (parts.Length < 2 || parts.Length > 4)
{
return activityContext;
return context;
}
var traceIdStr = parts[0];
if (string.IsNullOrWhiteSpace(traceIdStr))
{
return activityContext;
return context;
}
if (traceIdStr.Length == 16)
@ -258,7 +229,7 @@ namespace OpenTelemetry.Context.Propagation
var spanIdStr = parts[1];
if (string.IsNullOrWhiteSpace(spanIdStr))
{
return activityContext;
return context;
}
var spanId = ActivitySpanId.CreateFromString(spanIdStr.AsSpan());
@ -274,12 +245,14 @@ namespace OpenTelemetry.Context.Propagation
}
}
return new ActivityContext(traceId, spanId, traceOptions, isRemote: true);
return new PropagationContext(
new ActivityContext(traceId, spanId, traceOptions, isRemote: true),
context.ActivityBaggage);
}
catch (Exception e)
{
OpenTelemetrySdkEventSource.Log.ContextExtractException(e);
return activityContext;
OpenTelemetrySdkEventSource.Log.ActivityContextExtractException(nameof(B3Format), e);
return context;
}
}
}

View File

@ -1,38 +0,0 @@
// <copyright file="DistributedContextBinarySerializerBase.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
namespace OpenTelemetry.Context.Propagation
{
/// <summary>
/// DistributedContextBinarySerializerBase base class.
/// </summary>
public abstract class DistributedContextBinarySerializerBase
{
/// <summary>
/// Deserializes input to <see cref="DistributedContext"/> based on the binary format standard.
/// </summary>
/// <param name="bytes">Array of bytes in binary format standard.</param>
/// <returns><see cref="DistributedContext"/>.</returns>
public abstract DistributedContext FromByteArray(byte[] bytes);
/// <summary>
/// Serializes a <see cref="DistributedContext"/> to the on-the-wire format.
/// </summary>
/// <param name="tags"><see cref="DistributedContext"/>.</param>
/// <returns>Serialized <see cref="DistributedContext"/>.</returns>
public abstract byte[] ToByteArray(DistributedContext tags);
}
}

View File

@ -1,186 +0,0 @@
// <copyright file="SerializationUtils.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenTelemetry.Internal;
namespace OpenTelemetry.Context.Propagation
{
internal static class SerializationUtils
{
internal const int VersionId = 0;
internal const int TagFieldId = 0;
// This size limit only applies to the bytes representing tag keys and values.
internal const int TagContextSerializedSizeLimit = 8192;
#if NET452
private static readonly byte[] InvalidContext = new byte[0];
#else
private static readonly byte[] InvalidContext = Array.Empty<byte>();
#endif
// Serializes a DistributedContext to the on-the-wire format.
// Encoded tags are of the form: <version_id><encoded_tags>
internal static byte[] SerializeBinary(DistributedContext dc)
{
// Use a ByteArrayDataOutput to avoid needing to handle IOExceptions.
// ByteArrayDataOutput byteArrayDataOutput = ByteStreams.newDataOutput();
var byteArrayDataOutput = new MemoryStream();
byteArrayDataOutput.WriteByte(VersionId);
var totalChars = 0; // Here chars are equivalent to bytes, since we're using ascii chars.
foreach (var tag in dc.CorrelationContext.Entries)
{
totalChars += tag.Key.Length;
totalChars += tag.Value.Length;
EncodeTag(tag, byteArrayDataOutput);
}
// for (Iterator<Tag> i = InternalUtils.getTags(tags); i.hasNext();) {
// Tag tag = i.next();
// totalChars += tag.getKey().getName().length();
// totalChars += tag.getValue().asString().length();
// encodeTag(tag, byteArrayDataOutput);
// }
if (totalChars > TagContextSerializedSizeLimit)
{
OpenTelemetrySdkEventSource.Log.FailedToInjectContext("Size of DistributedContext exceeds the maximum serialized size "
+ TagContextSerializedSizeLimit);
return InvalidContext;
}
return byteArrayDataOutput.ToArray();
}
// Deserializes input to DistributedContext based on the binary format standard.
// The encoded tags are of the form: <version_id><encoded_tags>
internal static DistributedContext DeserializeBinary(byte[] bytes)
{
if (bytes.Length == 0)
{
OpenTelemetrySdkEventSource.Log.FailedToExtractContext("Input byte[] can not be empty.");
return DistributedContext.Empty;
}
try
{
var buffer = new MemoryStream(bytes);
var versionId = buffer.ReadByte();
if (versionId > VersionId || versionId < 0)
{
OpenTelemetrySdkEventSource.Log.FailedToExtractContext("Wrong Version ID: " + versionId + ". Currently supports version up to: " + VersionId);
return DistributedContext.Empty;
}
if (TryParseTags(buffer, out var tags))
{
return DistributedContextBuilder.CreateContext(tags);
}
}
catch (Exception e)
{
OpenTelemetrySdkEventSource.Log.ContextExtractException(e);
}
return DistributedContext.Empty;
}
internal static bool TryParseTags(MemoryStream buffer, out List<CorrelationContextEntry> tags)
{
tags = new List<CorrelationContextEntry>();
var limit = buffer.Length;
var totalChars = 0; // Here chars are equivalent to bytes, since we're using ascii chars.
while (buffer.Position < limit)
{
var type = buffer.ReadByte();
if (type == TagFieldId)
{
var key = CreateTagKey(DecodeString(buffer));
var val = CreateTagValue(key, DecodeString(buffer));
totalChars += key.Length;
totalChars += val.Length;
tags.Add(new CorrelationContextEntry(key, val));
}
else
{
// Stop parsing at the first unknown field ID, since there is no way to know its length.
// TODO(sebright): Consider storing the rest of the byte array in the TagContext.
break;
}
}
if (totalChars > TagContextSerializedSizeLimit)
{
OpenTelemetrySdkEventSource.Log.FailedToExtractContext(
"Size of TagContext exceeds the maximum serialized size "
+ TagContextSerializedSizeLimit);
return false;
}
return true;
}
// TODO(sebright): Consider exposing a string name validation method to avoid needing to catch an
// IllegalArgumentException here.
private static string CreateTagKey(string name)
{
return name;
}
// TODO(sebright): Consider exposing a string validation method to avoid needing to catch
// an IllegalArgumentException here.
private static string CreateTagValue(string key, string value)
{
return value;
}
private static void EncodeTag(CorrelationContextEntry tag, MemoryStream byteArrayDataOutput)
{
byteArrayDataOutput.WriteByte(TagFieldId);
EncodeString(tag.Key, byteArrayDataOutput);
EncodeString(tag.Value, byteArrayDataOutput);
}
private static void EncodeString(string input, MemoryStream byteArrayDataOutput)
{
PutVarInt(input.Length, byteArrayDataOutput);
var bytes = Encoding.UTF8.GetBytes(input);
byteArrayDataOutput.Write(bytes, 0, bytes.Length);
}
private static void PutVarInt(int input, MemoryStream byteArrayDataOutput)
{
var output = new byte[VarInt.VarIntSize(input)];
VarInt.PutVarInt(input, output, 0);
byteArrayDataOutput.Write(output, 0, output.Length);
}
private static string DecodeString(MemoryStream buffer)
{
var length = VarInt.GetVarInt(buffer);
var builder = new StringBuilder();
for (var i = 0; i < length; i++)
{
builder.Append((char)buffer.ReadByte());
}
return builder.ToString();
}
}
}

View File

@ -38,11 +38,11 @@ namespace OpenTelemetry.Internal
}
[NonEvent]
public void ContextExtractException(Exception ex)
public void ActivityContextExtractException(string format, Exception ex)
{
if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1)))
{
this.FailedToExtractContext(ex.ToInvariantString());
this.FailedToExtractActivityContext(format, ex.ToInvariantString());
}
}
@ -139,16 +139,16 @@ namespace OpenTelemetry.Internal
this.WriteEvent(8, methodName, argumentName, issue);
}
[Event(9, Message = "Failed to extract span context: '{0}'", Level = EventLevel.Warning)]
public void FailedToExtractContext(string error)
[Event(9, Message = "Failed to extract activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)]
public void FailedToExtractActivityContext(string format, string error)
{
this.WriteEvent(9, error);
this.WriteEvent(9, format, error);
}
[Event(10, Message = "Failed to inject span context: '{0}'", Level = EventLevel.Warning)]
public void FailedToInjectContext(string error)
[Event(10, Message = "Failed to inject activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)]
public void FailedToInjectActivityContext(string format, string error)
{
this.WriteEvent(10, error);
this.WriteEvent(10, format, error);
}
[Event(11, Message = "Failed to parse tracestate: too many items", Level = EventLevel.Warning)]

View File

@ -34,7 +34,7 @@ namespace OpenTelemetry.Metrics
this.sumAggregator.Update(value);
}
public override void Add(in DistributedContext context, double value)
public override void Add(in CorrelationContext context, double value)
{
this.sumAggregator.Update(value);
}

View File

@ -29,7 +29,7 @@ namespace OpenTelemetry.Metrics
this.measureAggregator.Update(value);
}
public override void Record(in DistributedContext context, double value)
public override void Record(in CorrelationContext context, double value)
{
this.measureAggregator.Update(value);
}

View File

@ -39,13 +39,13 @@ namespace OpenTelemetry.Metrics
this.Bind(new LabelSetSdk(labels), isShortLived: true).Add(context, value);
}
public override void Add(in DistributedContext context, double value, LabelSet labelset)
public override void Add(in CorrelationContext context, double value, LabelSet labelset)
{
// user not using bound instrument. Hence create a short-lived bound instrument.
this.Bind(labelset, isShortLived: true).Add(context, value);
}
public override void Add(in DistributedContext context, double value, IEnumerable<KeyValuePair<string, string>> labels)
public override void Add(in CorrelationContext context, double value, IEnumerable<KeyValuePair<string, string>> labels)
{
// user not using bound instrument. Hence create a short-lived bound instrument.
this.Bind(new LabelSetSdk(labels), isShortLived: true).Add(context, value);

View File

@ -34,7 +34,7 @@ namespace OpenTelemetry.Metrics
this.sumAggregator.Update(value);
}
public override void Add(in DistributedContext context, long value)
public override void Add(in CorrelationContext context, long value)
{
this.sumAggregator.Update(value);
}

View File

@ -29,7 +29,7 @@ namespace OpenTelemetry.Metrics
this.measureAggregator.Update(value);
}
public override void Record(in DistributedContext context, long value)
public override void Record(in CorrelationContext context, long value)
{
this.measureAggregator.Update(value);
}

View File

@ -39,13 +39,13 @@ namespace OpenTelemetry.Metrics
this.Bind(new LabelSetSdk(labels), isShortLived: true).Add(context, value);
}
public override void Add(in DistributedContext context, long value, LabelSet labelset)
public override void Add(in CorrelationContext context, long value, LabelSet labelset)
{
// user not using bound instrument. Hence create a short-lived bound instrument.
this.Bind(labelset, isShortLived: true).Add(context, value);
}
public override void Add(in DistributedContext context, long value, IEnumerable<KeyValuePair<string, string>> labels)
public override void Add(in CorrelationContext context, long value, IEnumerable<KeyValuePair<string, string>> labels)
{
// user not using bound instrument. Hence create a short-lived bound instrument.
this.Bind(new LabelSetSdk(labels), isShortLived: true).Add(context, value);

View File

@ -53,10 +53,8 @@ namespace OpenTelemetry.Instrumentation.AspNet.Tests
[InlineData("https://localhost:443/about_attr_route/10", 2, "about_attr_route/{customerId}", "TraceContext")]
[InlineData("http://localhost:1880/api/weatherforecast", 3, "api/{controller}/{id}", "TraceContext")]
[InlineData("https://localhost:1843/subroute/10", 4, "subroute/{customerId}", "TraceContext")]
// TODO: Reenable this tests once filtering mechanism is designed.
// [InlineData("http://localhost/api/value", 0, null, "/api/value")] // Request will be filtered
// [InlineData("http://localhost/api/value", 0, null, "{ThrowException}")] // Filter user code will throw an exception
[InlineData("http://localhost/api/value", 0, null, "TraceContext", "/api/value")] // Request will be filtered
[InlineData("http://localhost/api/value", 0, null, "TraceContext", "{ThrowException}")] // Filter user code will throw an exception
[InlineData("http://localhost/api/value/2", 0, null, "CustomContext", "/api/value")] // Request will not be filtered
[InlineData("http://localhost/api/value/2", 0, null, "CustomContext", "/api/value", true)] // Request will not be filtered
public void AspNetRequestsAreCollectedSuccessfully(
@ -129,10 +127,12 @@ namespace OpenTelemetry.Instrumentation.AspNet.Tests
var expectedTraceId = ActivityTraceId.CreateRandom();
var expectedSpanId = ActivitySpanId.CreateRandom();
var textFormat = new Mock<ITextFormat>();
textFormat.Setup(m => m.Extract<HttpRequest>(It.IsAny<ActivityContext>(), It.IsAny<HttpRequest>(), It.IsAny<Func<HttpRequest, string, IEnumerable<string>>>())).Returns(new ActivityContext(
textFormat.Setup(m => m.Extract<HttpRequest>(It.IsAny<PropagationContext>(), It.IsAny<HttpRequest>(), It.IsAny<Func<HttpRequest, string, IEnumerable<string>>>())).Returns(new PropagationContext(
new ActivityContext(
expectedTraceId,
expectedSpanId,
ActivityTraceFlags.Recorded));
ActivityTraceFlags.Recorded),
null));
var activity = new Activity(ActivityNameAspNet).AddBaggage("Stuff", "123");
activity.SetParentId(expectedTraceId, expectedSpanId, ActivityTraceFlags.Recorded);
@ -186,7 +186,19 @@ namespace OpenTelemetry.Instrumentation.AspNet.Tests
if (HttpContext.Current.Request.Path == filter || filter == "{ThrowException}")
{
Assert.Equal(0, activityProcessor.Invocations.Count); // Nothing was called because request was filtered.
if (filter == "{ThrowException}")
{
// This behavior is not good. If filter throws, Stop is called without Start.
// Need to do something here, but user can't currently set the filter
// so it wil always noop. When we support user filter,
// treat this as a todo: define exception behavior.
Assert.Equal(2, activityProcessor.Invocations.Count); // Stop & Disposed called.
}
else
{
Assert.Equal(1, activityProcessor.Invocations.Count); // Only disposed was called because request was filtered.
}
return;
}

View File

@ -144,10 +144,11 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
var expectedSpanId = ActivitySpanId.CreateRandom();
var textFormat = new Mock<ITextFormat>();
textFormat.Setup(m => m.Extract(It.IsAny<ActivityContext>(), It.IsAny<HttpRequest>(), It.IsAny<Func<HttpRequest, string, IEnumerable<string>>>())).Returns(new ActivityContext(
textFormat.Setup(m => m.Extract(It.IsAny<PropagationContext>(), It.IsAny<HttpRequest>(), It.IsAny<Func<HttpRequest, string, IEnumerable<string>>>())).Returns(new PropagationContext(
new ActivityContext(
expectedTraceId,
expectedSpanId,
ActivityTraceFlags.Recorded));
ActivityTraceFlags.Recorded), null));
// Arrange
using (var testFactory = this.factory

View File

@ -73,22 +73,23 @@ namespace OpenTelemetry.Instrumentation.Http.Tests
// Ensure that the header value func does not throw if the header key can't be found
var mockTextFormat = new Mock<ITextFormat>();
var isInjectedHeaderValueGetterThrows = false;
mockTextFormat
.Setup(x => x.IsInjected(It.IsAny<HttpRequestMessage>(), It.IsAny<Func<HttpRequestMessage, string, IEnumerable<string>>>()))
.Callback<HttpRequestMessage, Func<HttpRequestMessage, string, IEnumerable<string>>>(
(carrier, getter) =>
{
try
{
// traceparent doesn't exist
getter(carrier, "traceparent");
}
catch
{
isInjectedHeaderValueGetterThrows = true;
}
});
// var isInjectedHeaderValueGetterThrows = false;
// mockTextFormat
// .Setup(x => x.IsInjected(It.IsAny<HttpRequestMessage>(), It.IsAny<Func<HttpRequestMessage, string, IEnumerable<string>>>()))
// .Callback<HttpRequestMessage, Func<HttpRequestMessage, string, IEnumerable<string>>>(
// (carrier, getter) =>
// {
// try
// {
// // traceparent doesn't exist
// getter(carrier, "traceparent");
// }
// catch
// {
// isInjectedHeaderValueGetterThrows = true;
// }
// });
using (Sdk.CreateTracerProviderBuilder()
.AddHttpClientInstrumentation(o => o.TextFormat = mockTextFormat.Object)
@ -114,19 +115,16 @@ namespace OpenTelemetry.Instrumentation.Http.Tests
Assert.Equal($"00-{span.Context.TraceId}-{span.Context.SpanId}-01", traceparents.Single());
Assert.Equal("k1=v1,k2=v2", tracestates.Single());
mockTextFormat.Verify(x => x.IsInjected(It.IsAny<HttpRequestMessage>(), It.IsAny<Func<HttpRequestMessage, string, IEnumerable<string>>>()), Times.Once);
Assert.False(isInjectedHeaderValueGetterThrows);
}
[Fact]
public async Task HttpClientInstrumentationInjectsHeadersAsync_CustomFormat()
{
var textFormat = new Mock<ITextFormat>();
textFormat.Setup(m => m.Inject<HttpRequestMessage>(It.IsAny<ActivityContext>(), It.IsAny<HttpRequestMessage>(), It.IsAny<Action<HttpRequestMessage, string, string>>()))
.Callback<ActivityContext, HttpRequestMessage, Action<HttpRequestMessage, string, string>>((context, message, action) =>
textFormat.Setup(m => m.Inject<HttpRequestMessage>(It.IsAny<PropagationContext>(), It.IsAny<HttpRequestMessage>(), It.IsAny<Action<HttpRequestMessage, string, string>>()))
.Callback<PropagationContext, HttpRequestMessage, Action<HttpRequestMessage, string, string>>((context, message, action) =>
{
action(message, "custom_traceparent", $"00/{context.TraceId}/{context.SpanId}/01");
action(message, "custom_traceparent", $"00/{context.ActivityContext.TraceId}/{context.ActivityContext.SpanId}/01");
action(message, "custom_tracestate", Activity.Current.TraceStateString);
});

View File

@ -435,7 +435,7 @@ namespace OpenTelemetry.Instrumentation.Http.Tests
var traceparent = startRequest.Headers["traceparent"];
var tracestate = startRequest.Headers["tracestate"];
var correlationContext = startRequest.Headers["Correlation-Context"];
var correlationContext = startRequest.Headers["baggage"];
Assert.NotNull(traceparent);
Assert.Equal("some=state", tracestate);
Assert.Equal("k=v", correlationContext);
@ -742,7 +742,7 @@ namespace OpenTelemetry.Instrumentation.Http.Tests
Assert.Equal(1, eventRecords.Records.Count(rec => rec.Key == "Stop"));
WebRequest thisRequest = (WebRequest)eventRecords.Records.First().Value.GetCustomProperty("HttpWebRequest.Request");
string[] correlationContext = thisRequest.Headers["Correlation-Context"].Split(',');
string[] correlationContext = thisRequest.Headers["baggage"].Split(',');
Assert.Equal(3, correlationContext.Length);
Assert.Contains("key=value", correlationContext);

View File

@ -97,10 +97,10 @@ namespace OpenTelemetry.Instrumentation.Http.Tests
public async Task HttpWebRequestInstrumentationInjectsHeadersAsync_CustomFormat()
{
var textFormat = new Mock<ITextFormat>();
textFormat.Setup(m => m.Inject(It.IsAny<ActivityContext>(), It.IsAny<HttpWebRequest>(), It.IsAny<Action<HttpWebRequest, string, string>>()))
.Callback<ActivityContext, HttpWebRequest, Action<HttpWebRequest, string, string>>((context, message, action) =>
textFormat.Setup(m => m.Inject(It.IsAny<PropagationContext>(), It.IsAny<HttpWebRequest>(), It.IsAny<Action<HttpWebRequest, string, string>>()))
.Callback<PropagationContext, HttpWebRequest, Action<HttpWebRequest, string, string>>((context, message, action) =>
{
action(message, "custom_traceparent", $"00/{context.TraceId}/{context.SpanId}/01");
action(message, "custom_traceparent", $"00/{context.ActivityContext.TraceId}/{context.ActivityContext.SpanId}/01");
action(message, "custom_tracestate", Activity.Current.TraceStateString);
});

View File

@ -50,7 +50,8 @@ namespace OpenTelemetry.Shims.OpenTracing.Tests
public void GetBaggage()
{
var shim = GetSpanContextShim();
Assert.Throws<NotImplementedException>(() => shim.GetBaggageItems());
var baggage = shim.GetBaggageItems();
Assert.Null(baggage);
}
internal static SpanContextShim GetSpanContextShim()

View File

@ -1,132 +0,0 @@
// <copyright file="CorrelationContextBuilderTest.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System.Collections.Generic;
using Xunit;
namespace OpenTelemetry.Context.Tests
{
public class CorrelationContextBuilderTest
{
private const string Key1 = "key 1";
private const string Key2 = "key 2";
private const string Value1 = "value 1";
private const string Value2 = "value 2";
private static readonly List<CorrelationContextEntry> List1 = new List<CorrelationContextEntry>(1)
{ new CorrelationContextEntry(Key1, Value1) };
private static readonly List<CorrelationContextEntry> List2 = new List<CorrelationContextEntry>(2)
{
new CorrelationContextEntry(Key1, Value1),
new CorrelationContextEntry(Key2, Value2),
};
public CorrelationContextBuilderTest()
{
DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance;
}
[Fact]
public void ContextCreation()
{
CorrelationContext dc = CorrelationContextBuilder.CreateContext(null);
Assert.Equal(CorrelationContext.Empty, dc);
dc = CorrelationContextBuilder.CreateContext(CorrelationContext.Empty.Entries);
Assert.Equal(CorrelationContext.Empty, dc);
dc = CorrelationContextBuilder.CreateContext(Key1, Value1);
Assert.Equal(CorrelationContextBuilder.CreateContext(List1), dc);
Assert.Equal(dc, new CorrelationContextBuilder(dc).Build());
}
[Fact]
public void AddEntries()
{
Assert.Equal(CorrelationContext.Empty, new CorrelationContextBuilder(inheritCurrentContext: false).Build());
Assert.Equal(
CorrelationContextBuilder.CreateContext(List1), new CorrelationContextBuilder(inheritCurrentContext: false)
.Add(Key1, Value1)
.Build());
Assert.Equal(
CorrelationContextBuilder.CreateContext(List1), new CorrelationContextBuilder(inheritCurrentContext: false)
.Add(new CorrelationContextEntry(Key1, Value1))
.Build());
Assert.Equal(
CorrelationContextBuilder.CreateContext(List2), new CorrelationContextBuilder(inheritCurrentContext: false)
.Add(Key1, Value1)
.Add(Key2, Value2)
.Build());
Assert.Equal(
CorrelationContextBuilder.CreateContext(List2), new CorrelationContextBuilder(inheritCurrentContext: false)
.Add(new CorrelationContextEntry(Key1, Value1))
.Add(new CorrelationContextEntry(Key2, Value2))
.Build());
Assert.Equal(
CorrelationContextBuilder.CreateContext(List1), new CorrelationContextBuilder(inheritCurrentContext: false)
.Add(List1)
.Build());
Assert.Equal(
CorrelationContextBuilder.CreateContext(List2), new CorrelationContextBuilder(inheritCurrentContext: false)
.Add(List2)
.Build());
}
[Fact]
public void RemoveEntries()
{
Assert.Equal(
CorrelationContextBuilder.CreateContext(List1), new CorrelationContextBuilder(inheritCurrentContext: false)
.Add(List2)
.Remove(Key2)
.Build());
Assert.Equal(
CorrelationContext.Empty, new CorrelationContextBuilder(inheritCurrentContext: false)
.Add(List2)
.Remove(Key2)
.Remove(Key1)
.Build());
}
[Fact]
public void EnsureEmptyListAfterBuild()
{
var dcb = new CorrelationContextBuilder(inheritCurrentContext: false);
Assert.Equal(CorrelationContext.Empty, dcb.Build());
dcb.Add(List2);
Assert.Equal(CorrelationContextBuilder.CreateContext(List2), dcb.Build());
Assert.Equal(CorrelationContext.Empty, dcb.Build());
var dc = dcb.Add(List1).Build();
Assert.Equal(dc, dcb.Add(List1).Build());
dcb = new CorrelationContextBuilder(dc);
Assert.Equal(dc, dcb.Build());
Assert.Equal(CorrelationContext.Empty, dcb.Build());
}
}
}

View File

@ -1,102 +0,0 @@
// <copyright file="CorrelationContextEntryTest.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using Xunit;
namespace OpenTelemetry.Context.Tests
{
public class CorrelationContextEntryTest
{
[Fact]
public void TestGetKey()
{
Assert.Equal("k", new CorrelationContextEntry("k", "v").Key);
}
[Fact]
public void TestTagEquals()
{
var tag1 = new CorrelationContextEntry("Key", "foo");
var tag2 = new CorrelationContextEntry("Key", "foo");
var tag3 = new CorrelationContextEntry("Key", "bar");
var tag4 = new CorrelationContextEntry("Key2", "foo");
Assert.Equal(tag1, tag2);
Assert.NotEqual(tag1, tag3);
Assert.NotEqual(tag1, tag4);
Assert.NotEqual(tag2, tag3);
Assert.NotEqual(tag2, tag4);
Assert.NotEqual(tag3, tag4);
}
[Fact]
public void TestNullKeyNullValue()
{
var entry = new CorrelationContextEntry(null, null);
Assert.Empty(entry.Key);
Assert.Empty(entry.Value);
}
[Fact]
public void TestNullKey()
{
var entry = new CorrelationContextEntry(null, "foo");
Assert.Empty(entry.Key);
Assert.Equal("foo", entry.Value);
}
[Fact]
public void TestNullValue()
{
var entry = new CorrelationContextEntry("foo", null);
Assert.Equal("foo", entry.Key);
Assert.Empty(entry.Value);
}
[Fact]
public void TestEquality()
{
var entry1 = new CorrelationContextEntry("key", "value1");
var entry2 = new CorrelationContextEntry("key", "value1");
object entry3 = entry2;
var entry4 = new CorrelationContextEntry("key", "value2");
Assert.True(entry1 == entry2);
Assert.True(entry1.Equals(entry2));
Assert.True(entry1.Equals(entry3));
Assert.True(entry1 != entry4);
}
[Fact]
public void TestToString()
{
var entry1 = new CorrelationContextEntry("key1", "value1");
Assert.Equal("CorrelationContextEntry{Key=key1, Value=value1}", entry1.ToString());
var entry2 = new CorrelationContextEntry(null, "value1");
Assert.Equal("CorrelationContextEntry{Key=, Value=value1}", entry2.ToString());
}
[Fact]
public void TestGetHashCode()
{
var entry1 = new CorrelationContextEntry("key1", "value1");
Assert.NotEqual(0, entry1.GetHashCode());
var entry2 = new CorrelationContextEntry(null, "value1");
Assert.NotEqual(0, entry2.GetHashCode());
}
}
}

View File

@ -14,121 +14,135 @@
// limitations under the License.
// </copyright>
using System.Collections.Generic;
using System.Diagnostics;
using Xunit;
namespace OpenTelemetry.Context.Tests
{
public class CorrelationContextTest
{
private const string K1 = "k1";
private const string K2 = "k2";
private const string K1 = "Key1";
private const string K2 = "Key2";
private const string V1 = "v1";
private const string V2 = "v2";
public CorrelationContextTest()
{
DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance;
}
private const string V1 = "Value1";
private const string V2 = "Value2";
[Fact]
public void EmptyContext()
{
var dc = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>());
Assert.Empty(dc.Entries);
Assert.Equal(CorrelationContext.Empty, dc);
var cc = CorrelationContext.Current;
Assert.Empty(cc.Correlations);
Assert.Equal(CorrelationContext.Empty, cc);
cc.AddCorrelation(K1, V1);
Assert.Empty(cc.Correlations);
Assert.Null(cc.GetCorrelation(K1));
}
[Fact]
public void NonEmptyContext()
{
var list = new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) };
var dc = CorrelationContextBuilder.CreateContext(list);
Assert.Equal(list, dc.Entries);
}
using Activity activity = new Activity("TestActivity");
activity.Start();
[Fact]
public void AddExtraKey()
var list = new List<KeyValuePair<string, string>>(2)
{
var list = new List<CorrelationContextEntry>(1) { new CorrelationContextEntry(K1, V1) };
var dc = CorrelationContextBuilder.CreateContext(list);
Assert.Equal(list, dc.Entries);
new KeyValuePair<string, string>(K1, V1),
new KeyValuePair<string, string>(K2, V2),
};
list.Add(new CorrelationContextEntry(K2, V2));
var dc1 = CorrelationContextBuilder.CreateContext(list);
Assert.Equal(list, dc1.Entries);
var cc = CorrelationContext.Current;
cc.AddCorrelation(K1, V1);
cc.AddCorrelation(K2, V2);
Assert.NotEqual(CorrelationContext.Empty, cc);
Assert.Equal(list, cc.Correlations);
Assert.Equal(V1, cc.GetCorrelation(K1));
Assert.Null(cc.GetCorrelation(K1.ToLower()));
Assert.Null(cc.GetCorrelation(K1.ToUpper()));
Assert.Null(cc.GetCorrelation("NO_KEY"));
}
[Fact]
public void AddExistingKey()
{
var list = new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K1, V2) };
var dc = CorrelationContextBuilder.CreateContext(list);
Assert.Equal(new List<CorrelationContextEntry>(1) { new CorrelationContextEntry(K1, V2) }, dc.Entries);
}
using Activity activity = new Activity("TestActivity");
activity.Start();
[Fact]
public void UseDefaultEntry()
var list = new List<KeyValuePair<string, string>>(2)
{
Assert.Equal(CorrelationContext.Empty, CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(1) { default }));
Assert.Equal(CorrelationContext.Empty, CorrelationContextBuilder.CreateContext(null));
}
new KeyValuePair<string, string>(K1, V1),
new KeyValuePair<string, string>(K1, V1),
};
[Fact]
public void RemoveExistingKey()
{
var list = new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) };
var dc = CorrelationContextBuilder.CreateContext(list);
Assert.Equal(list, dc.Entries);
var cc = CorrelationContext.Current;
list.RemoveAt(0);
cc.AddCorrelation(K1, V1);
cc.AddCorrelation(K1, V1);
dc = CorrelationContextBuilder.CreateContext(list);
Assert.Equal(list, dc.Entries);
list.Clear();
dc = CorrelationContextBuilder.CreateContext(list);
Assert.Equal(CorrelationContext.Empty, dc);
Assert.Equal(list, cc.Correlations);
}
[Fact]
public void TestIterator()
{
var list = new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) };
var dc = CorrelationContextBuilder.CreateContext(list);
using Activity activity = new Activity("TestActivity");
activity.Start();
var list = new List<KeyValuePair<string, string>>(2)
{
new KeyValuePair<string, string>(K1, V1),
new KeyValuePair<string, string>(K2, V2),
};
var cc = CorrelationContext.Current;
cc.AddCorrelation(K1, V1);
cc.AddCorrelation(K2, V2);
var i = cc.Correlations.GetEnumerator();
var i = dc.Entries.GetEnumerator();
Assert.True(i.MoveNext());
var tag1 = i.Current;
Assert.True(i.MoveNext());
var tag2 = i.Current;
Assert.False(i.MoveNext());
Assert.Equal(new List<CorrelationContextEntry> { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) }, new List<CorrelationContextEntry> { tag1, tag2 });
Assert.Equal(list, new List<KeyValuePair<string, string>> { tag1, tag2 });
}
[Fact]
public void TestEquals()
{
var dc1 = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) });
var dc2 = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) });
object odc2 = dc2;
var dc3 = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K2, V2), new CorrelationContextEntry(K1, V1) });
var dc4 = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V1) });
var dc5 = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V2), new CorrelationContextEntry(K2, V1) });
var dc6 = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(3) { new CorrelationContextEntry(K1, V2) });
var cc1 = CreateCorrelationContext(new KeyValuePair<string, string>(K1, V1), new KeyValuePair<string, string>(K2, V2));
var cc2 = CreateCorrelationContext(new KeyValuePair<string, string>(K1, V1), new KeyValuePair<string, string>(K2, V2));
var cc3 = CreateCorrelationContext(new KeyValuePair<string, string>(K2, V2), new KeyValuePair<string, string>(K1, V1));
var cc4 = CreateCorrelationContext(new KeyValuePair<string, string>(K1, V1), new KeyValuePair<string, string>(K2, V1));
var cc5 = CreateCorrelationContext(new KeyValuePair<string, string>(K1, V2), new KeyValuePair<string, string>(K2, V1));
Assert.True(dc1.Equals(dc2));
Assert.True(dc1.Equals(odc2));
Assert.True(dc1 == dc2);
Assert.True(dc1.Equals(dc3));
Assert.True(cc1.Equals(cc2));
Assert.False(dc1.Equals(dc4));
Assert.True(dc1 != dc4);
Assert.False(dc2.Equals(dc4));
Assert.False(dc3.Equals(dc4));
Assert.False(dc5.Equals(dc4));
Assert.False(dc4.Equals(dc5));
Assert.False(dc5.Equals(dc6));
Assert.False(cc1.Equals(cc3));
Assert.False(cc1.Equals(cc4));
Assert.False(cc2.Equals(cc4));
Assert.False(cc3.Equals(cc4));
Assert.False(cc5.Equals(cc4));
Assert.False(cc4.Equals(cc5));
}
private static CorrelationContext CreateCorrelationContext(params KeyValuePair<string, string>[] correlations)
{
using Activity activity = new Activity("TestActivity");
activity.Start();
var cc = CorrelationContext.Current;
cc.AddCorrelation(correlations);
return cc;
}
}
}

View File

@ -1,125 +0,0 @@
// <copyright file="DistributedContextsScopeTest.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
namespace OpenTelemetry.Context.Tests
{
public class DistributedContextsScopeTest
{
private const string Key1 = "key 1";
private const string Key2 = "key 2";
private const string Value1 = "value 1";
private const string Value2 = "value 2";
[Fact]
public void NoopContextCarrier()
{
DistributedContext.Carrier = NoopDistributedContextCarrier.Instance;
List<CorrelationContextEntry> list = new List<CorrelationContextEntry>(2)
{
new CorrelationContextEntry(Key1, Value1), new CorrelationContextEntry(Key2, Value2),
};
Assert.Equal(DistributedContext.Empty, DistributedContext.Current);
using (DistributedContext.SetCurrent(DistributedContextBuilder.CreateContext(Key1, Value1)))
{
Assert.Equal(DistributedContext.Empty, DistributedContext.Current);
using (DistributedContext.SetCurrent(DistributedContextBuilder.CreateContext(list)))
{
Assert.Equal(DistributedContext.Empty, DistributedContext.Current);
}
}
Assert.Equal(DistributedContext.Empty, DistributedContext.Current);
}
[Fact]
public async void AsyncContextCarrier()
{
DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance;
List<CorrelationContextEntry> list = new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(Key1, Value1), new CorrelationContextEntry(Key2, Value2), };
var dc1 = DistributedContextBuilder.CreateContext(Key1, Value1);
var dc2 = DistributedContextBuilder.CreateContext(list);
DistributedContext.SetCurrent(DistributedContext.Empty);
Assert.Equal(DistributedContext.Empty, DistributedContext.Current);
using (DistributedContext.SetCurrent(dc1))
{
Assert.Equal(dc1, DistributedContext.Current);
using (DistributedContext.SetCurrent(dc2))
{
Assert.Equal(dc2, DistributedContext.Current);
}
Assert.Equal(dc1, DistributedContext.Current);
using (DistributedContext.SetCurrent(dc2))
{
await Task.Run(() => Assert.Equal(dc2, DistributedContext.Current));
}
await Task.Run(() => Assert.Equal(dc1, DistributedContext.Current));
}
Assert.Equal(DistributedContext.Empty, DistributedContext.Current);
await Task.Run(() => Assert.Equal(DistributedContext.Empty, DistributedContext.Current));
}
[Fact]
public async void TestContextInheritance()
{
DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance;
var list1 = new List<CorrelationContextEntry>(1) { new CorrelationContextEntry(Key1, Value1) };
var list2 = new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(Key1, Value1), new CorrelationContextEntry(Key2, Value2) };
DistributedContext.SetCurrent(DistributedContext.Empty);
await Task.Run(() => Assert.Equal(DistributedContext.Empty, DistributedContext.Current));
using (DistributedContext.SetCurrent(DistributedContextBuilder.CreateContext(list1)))
{
await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list1), DistributedContext.Current));
using (DistributedContext.SetCurrent(new DistributedContextBuilder(inheritCurrentContext: true).Build()))
{
await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list1), DistributedContext.Current));
using (DistributedContext.SetCurrent(new DistributedContextBuilder(inheritCurrentContext: true).Correlations(b => b.Add(Key2, Value2)).Build()))
{
await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list2), DistributedContext.Current));
using (DistributedContext.SetCurrent(new DistributedContextBuilder(inheritCurrentContext: true).Correlations(b => b.Remove(Key2)).Build()))
{
await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list1), DistributedContext.Current));
}
}
await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list1), DistributedContext.Current));
using (DistributedContext.SetCurrent(new DistributedContextBuilder(inheritCurrentContext: false).Build()))
{
await Task.Run(() => Assert.Equal(DistributedContext.Empty, DistributedContext.Current));
}
await Task.Run(() => Assert.Equal(DistributedContextBuilder.CreateContext(list1), DistributedContext.Current));
}
}
}
}
}

View File

@ -1,272 +0,0 @@
// <copyright file="DistributedContextDeserializationTest.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenTelemetry.Internal;
using Xunit;
namespace OpenTelemetry.Context.Propagation.Tests
{
public class DistributedContextDeserializationTest
{
private readonly DistributedContextBinarySerializer serializer;
public DistributedContextDeserializationTest()
{
DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance;
this.serializer = new DistributedContextBinarySerializer();
}
[Fact]
public void TestConstants()
{
// Refer to the JavaDoc on SerializationUtils for the definitions on these constants.
Assert.Equal(0, SerializationUtils.VersionId);
Assert.Equal(0, SerializationUtils.TagFieldId);
Assert.Equal(8192, SerializationUtils.TagContextSerializedSizeLimit);
}
[Fact]
public void TestNoTagsSerialization()
{
var dc = this.serializer.FromByteArray(this.serializer.ToByteArray(DistributedContext.Empty));
Assert.Empty(dc.CorrelationContext.Entries);
dc = this.serializer.FromByteArray(new byte[] { SerializationUtils.VersionId }); // One byte that represents Version ID.
Assert.Empty(dc.CorrelationContext.Entries);
}
[Fact]
public void TestDeserializeEmptyByteArrayThrowException()
{
Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(new byte[0]));
}
[Fact]
public void TestDeserializeTooLargeByteArrayThrowException()
{
var output = new MemoryStream();
output.WriteByte(SerializationUtils.VersionId);
for (var i = 0; i < (SerializationUtils.TagContextSerializedSizeLimit / 8) - 1; i++)
{
// Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8.
string str = i.ToString("0000");
EncodeTagToOutPut(str, str, output);
}
// The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte
// more than limit.
EncodeTagToOutPut("last", "last1", output);
var bytes = output.ToArray();
Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(bytes));
}
// Deserializing this inPut should cause an error, even though it represents a relatively small
// TagContext.
[Fact]
public void TestDeserializeTooLargeByteArrayThrowException_WithDuplicateTagKeys()
{
var output = new MemoryStream();
output.WriteByte(SerializationUtils.VersionId);
for (var i = 0; i < (SerializationUtils.TagContextSerializedSizeLimit / 8) - 1; i++)
{
// Each tag will be with format {key : "key_", value : "0123"}, so the length of it is 8.
EncodeTagToOutPut("key_", i.ToString("0000"), output);
}
// The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte
// more than limit.
EncodeTagToOutPut("key_", "last1", output);
var bytes = output.ToArray();
Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(bytes));
}
[Fact]
public void TestDeserializeOneTag()
{
var output = new MemoryStream();
output.WriteByte(SerializationUtils.VersionId);
EncodeTagToOutPut("Key", "Value", output);
var expected = DistributedContextBuilder.CreateContext("Key", "Value");
Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray()));
}
[Fact]
public void TestDeserializeMultipleTags()
{
var output = new MemoryStream();
output.WriteByte(SerializationUtils.VersionId);
EncodeTagToOutPut("Key1", "Value1", output);
EncodeTagToOutPut("Key2", "Value2", output);
var expected = DistributedContextBuilder.CreateContext(
new List<CorrelationContextEntry>(2) { new CorrelationContextEntry("Key1", "Value1"), new CorrelationContextEntry("Key2", "Value2") });
Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray()));
}
[Fact]
public void TestDeserializeDuplicateKeys()
{
var output = new MemoryStream();
output.WriteByte(SerializationUtils.VersionId);
EncodeTagToOutPut("Key1", "Value1", output);
EncodeTagToOutPut("Key1", "Value2", output);
var expected = DistributedContextBuilder.CreateContext("Key1", "Value2");
Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray()));
}
[Fact]
public void TestDeserializeNonConsecutiveDuplicateKeys()
{
var output = new MemoryStream();
output.WriteByte(SerializationUtils.VersionId);
EncodeTagToOutPut("Key1", "Value1", output);
EncodeTagToOutPut("Key2", "Value2", output);
EncodeTagToOutPut("Key3", "Value3", output);
EncodeTagToOutPut("Key1", "Value4", output);
EncodeTagToOutPut("Key2", "Value5", output);
var expected = DistributedContextBuilder.CreateContext(
new List<CorrelationContextEntry>(3)
{
new CorrelationContextEntry("Key1", "Value4"),
new CorrelationContextEntry("Key2", "Value5"),
new CorrelationContextEntry("Key3", "Value3"),
});
Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray()));
}
[Fact]
public void TestDeserializeDuplicateTags()
{
var output = new MemoryStream();
output.WriteByte(SerializationUtils.VersionId);
EncodeTagToOutPut("Key1", "Value1", output);
EncodeTagToOutPut("Key1", "Value2", output);
var expected = DistributedContextBuilder.CreateContext("Key1", "Value2");
Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray()));
}
[Fact]
public void TestDeserializeNonConsecutiveDuplicateTags()
{
var output = new MemoryStream();
output.WriteByte(SerializationUtils.VersionId);
EncodeTagToOutPut("Key1", "Value1", output);
EncodeTagToOutPut("Key2", "Value2", output);
EncodeTagToOutPut("Key3", "Value3", output);
EncodeTagToOutPut("Key1", "Value1", output);
EncodeTagToOutPut("Key2", "Value2", output);
var expected = DistributedContextBuilder.CreateContext(
new List<CorrelationContextEntry>(3)
{
new CorrelationContextEntry("Key1", "Value1"),
new CorrelationContextEntry("Key2", "Value2"),
new CorrelationContextEntry("Key3", "Value3"),
});
Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray()));
}
[Fact]
public void StopParsingAtUnknownField()
{
var output = new MemoryStream();
output.WriteByte(SerializationUtils.VersionId);
EncodeTagToOutPut("Key1", "Value1", output);
EncodeTagToOutPut("Key2", "Value2", output);
// Write unknown field ID 1.
output.WriteByte(1);
output.Write(new byte[] { 1, 2, 3, 4 }, 0, 4);
EncodeTagToOutPut("Key3", "Value3", output);
var expected = DistributedContextBuilder.CreateContext(
new List<CorrelationContextEntry>(2)
{
new CorrelationContextEntry("Key1", "Value1"),
new CorrelationContextEntry("Key2", "Value2"),
});
Assert.Equal(expected, this.serializer.FromByteArray(output.ToArray()));
}
[Fact]
public void StopParsingAtUnknownTagAtStart()
{
var output = new MemoryStream();
output.WriteByte(SerializationUtils.VersionId);
// Write unknown field ID 1.
output.WriteByte(1);
output.Write(new byte[] { 1, 2, 3, 4 }, 0, 4);
EncodeTagToOutPut("Key", "Value", output);
Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(output.ToArray()));
}
[Fact]
public void TestDeserializeWrongFormat()
{
// encoded tags should follow the format <version_id>(<tag_field_id><tag_encoding>)*
Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(new byte[3]));
}
[Fact]
public void TestDeserializeWrongVersionId()
{
Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(new byte[] { SerializationUtils.VersionId + 1 }));
}
[Fact]
public void TestDeserializeNegativeVersionId()
{
Assert.Equal(DistributedContext.Empty, this.serializer.FromByteArray(new byte[] { 0xff }));
}
// <tag_encoding> ==
// <tag_key_len><tag_key><tag_val_len><tag_val>
// <tag_key_len> == varint encoded integer
// <tag_key> == tag_key_len bytes comprising tag key name
// <tag_val_len> == varint encoded integer
// <tag_val> == tag_val_len bytes comprising UTF-8 string
private static void EncodeTagToOutPut(string key, string value, MemoryStream output)
{
output.WriteByte(SerializationUtils.TagFieldId);
EncodeString(key, output);
EncodeString(value, output);
}
private static void EncodeString(string input, MemoryStream output)
{
var length = input.Length;
var bytes = new byte[VarInt.VarIntSize(length)];
VarInt.PutVarInt(length, bytes, 0);
output.Write(bytes, 0, bytes.Length);
var inPutBytes = Encoding.UTF8.GetBytes(input);
output.Write(inPutBytes, 0, inPutBytes.Length);
}
}
}

View File

@ -1,82 +0,0 @@
// <copyright file="DistributedContextRoundtripTest.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System.Collections.Generic;
using Xunit;
namespace OpenTelemetry.Context.Propagation.Tests
{
public class DistributedContextRoundtripTest
{
private const string K1 = "k1";
private const string K2 = "k2";
private const string K3 = "k3";
private const string V1 = "v1";
private const string V2 = "v2";
private const string V3 = "v3";
private readonly DistributedContextBinarySerializer serializer;
public DistributedContextRoundtripTest()
{
DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance;
this.serializer = new DistributedContextBinarySerializer();
}
[Fact]
public void TestRoundtripSerialization_NormalTagContext()
{
this.TestRoundtripSerialization(DistributedContext.Empty);
this.TestRoundtripSerialization(DistributedContextBuilder.CreateContext(K1, V1));
var expected = DistributedContextBuilder.CreateContext(
new List<CorrelationContextEntry>(3)
{
new CorrelationContextEntry(K1, V1),
new CorrelationContextEntry(K2, V2),
new CorrelationContextEntry(K3, V3),
});
this.TestRoundtripSerialization(expected);
this.TestRoundtripSerialization(DistributedContextBuilder.CreateContext(K1, string.Empty));
}
[Fact]
public void TestRoundtrip_TagContextWithMaximumSize()
{
var list = new List<CorrelationContextEntry>();
for (var i = 0; i < SerializationUtils.TagContextSerializedSizeLimit / 8; i++)
{
// Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8.
// Add 1024 tags, the total size should just be 8192.
var str = i.ToString("0000");
list.Add(new CorrelationContextEntry(str, str));
}
this.TestRoundtripSerialization(DistributedContextBuilder.CreateContext(list));
}
private void TestRoundtripSerialization(DistributedContext expected)
{
var bytes = this.serializer.ToByteArray(expected);
var actual = this.serializer.FromByteArray(bytes);
Assert.Equal(expected, actual);
}
}
}

View File

@ -1,153 +0,0 @@
// <copyright file="DistributedContextSerializationTest.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using OpenTelemetry.Internal;
using Xunit;
namespace OpenTelemetry.Context.Propagation.Tests
{
public class DistributedContextSerializationTest
{
private const string K1 = "k1";
private const string K2 = "k2";
private const string K3 = "k3";
private const string K4 = "k4";
private const string V1 = "v1";
private const string V2 = "v2";
private const string V3 = "v3";
private const string V4 = "v4";
private static readonly CorrelationContextEntry T1 = new CorrelationContextEntry(K1, V1);
private static readonly CorrelationContextEntry T2 = new CorrelationContextEntry(K2, V2);
private static readonly CorrelationContextEntry T3 = new CorrelationContextEntry(K3, V3);
private static readonly CorrelationContextEntry T4 = new CorrelationContextEntry(K4, V4);
private readonly DistributedContextBinarySerializer serializer;
public DistributedContextSerializationTest()
{
DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance;
this.serializer = new DistributedContextBinarySerializer();
}
[Fact]
public void TestSerializeDefault()
{
this.TestSerialize();
}
[Fact]
public void TestSerializeWithOneTag()
{
this.TestSerialize(T1);
}
[Fact]
public void TestSerializeWithMultipleTags()
{
this.TestSerialize(T1, T2, T3, T4);
}
[Fact]
public void TestSerializeTooLargeTagContext()
{
var list = new List<CorrelationContextEntry>();
for (var i = 0; i < (SerializationUtils.TagContextSerializedSizeLimit / 8) - 1; i++)
{
// Each tag will be with format {key : "0123", value : "0123"}, so the length of it is 8.
var str = i.ToString("0000");
list.Add(new CorrelationContextEntry(str, str));
}
// The last tag will be of size 9, so the total size of the TagContext (8193) will be one byte
// more than limit.
list.Add(new CorrelationContextEntry("last", "last1"));
var dc = DistributedContextBuilder.CreateContext(list);
Assert.Empty(this.serializer.ToByteArray(dc));
}
private static void EncodeString(string input, MemoryStream byteArrayOutPutStream)
{
VarInt.PutVarInt(input.Length, byteArrayOutPutStream);
var inpBytes = Encoding.UTF8.GetBytes(input);
byteArrayOutPutStream.Write(inpBytes, 0, inpBytes.Length);
}
private static void RotateRight(IList<CorrelationContextEntry> sequence, int count)
{
var tmp = sequence[count - 1];
sequence.RemoveAt(count - 1);
sequence.Insert(0, tmp);
}
private static IEnumerable<IList<CorrelationContextEntry>> Permutate(IList<CorrelationContextEntry> sequence, int count)
{
if (count == 0)
{
yield return sequence;
}
else
{
for (var i = 0; i < count; i++)
{
foreach (var perm in Permutate(sequence, count - 1))
{
yield return perm;
}
RotateRight(sequence, count);
}
}
}
private void TestSerialize(params CorrelationContextEntry[] tags)
{
var list = new List<CorrelationContextEntry>(tags);
var actual = this.serializer.ToByteArray(DistributedContextBuilder.CreateContext(list));
var tagsList = tags.ToList();
var tagPermutation = Permutate(tagsList, tagsList.Count);
ISet<string> possibleOutPuts = new HashSet<string>();
foreach (var distributedContextEntries in tagPermutation)
{
var l = (List<CorrelationContextEntry>)distributedContextEntries;
var expected = new MemoryStream();
expected.WriteByte(SerializationUtils.VersionId);
foreach (var tag in l)
{
expected.WriteByte(SerializationUtils.TagFieldId);
EncodeString(tag.Key, expected);
EncodeString(tag.Value, expected);
}
var bytes = expected.ToArray();
possibleOutPuts.Add(Encoding.UTF8.GetString(bytes));
}
var exp = Encoding.UTF8.GetString(actual);
Assert.Contains(exp, possibleOutPuts);
}
}
}

View File

@ -221,6 +221,8 @@ namespace OpenTelemetry.Trace.Tests
public void Dispose()
{
Activity.Current = null;
this.testProcessor.Dispose();
GC.SuppressFinalize(this);
}
}
}

View File

@ -35,10 +35,10 @@ namespace OpenTelemetry.Trace.Tests
{
var result = string.Empty;
var p1 = new TestActivityProcessor(
using var p1 = new TestActivityProcessor(
activity => { result += "1"; },
activity => { result += "3"; });
var p2 = new TestActivityProcessor(
using var p2 = new TestActivityProcessor(
activity => { result += "2"; },
activity => { result += "4"; });
@ -56,7 +56,7 @@ namespace OpenTelemetry.Trace.Tests
[Fact]
public void CompositeActivityProcessor_ProcessorThrows()
{
var p1 = new TestActivityProcessor(
using var p1 = new TestActivityProcessor(
activity => { throw new Exception("Start exception"); },
activity => { throw new Exception("End exception"); });
@ -72,8 +72,8 @@ namespace OpenTelemetry.Trace.Tests
[Fact]
public void CompositeActivityProcessor_ShutsDownAll()
{
var p1 = new TestActivityProcessor(null, null);
var p2 = new TestActivityProcessor(null, null);
using var p1 = new TestActivityProcessor(null, null);
using var p2 = new TestActivityProcessor(null, null);
using (var processor = new CompositeActivityProcessor(new[] { p1, p2 }))
{
@ -86,8 +86,8 @@ namespace OpenTelemetry.Trace.Tests
[Fact]
public void CompositeActivityProcessor_ForceFlush()
{
var p1 = new TestActivityProcessor(null, null);
var p2 = new TestActivityProcessor(null, null);
using var p1 = new TestActivityProcessor(null, null);
using var p2 = new TestActivityProcessor(null, null);
using (var processor = new CompositeActivityProcessor(new[] { p1, p2 }))
{

View File

@ -56,7 +56,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
public void Serialize_SampledContext()
{
var carrier = new Dictionary<string, string>();
this.b3Format.Inject(new ActivityContext(TraceId, SpanId, TraceOptions), carrier, Setter);
this.b3Format.Inject(new PropagationContext(new ActivityContext(TraceId, SpanId, TraceOptions), null), carrier, Setter);
this.ContainsExactly(carrier, new Dictionary<string, string> { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "1" } });
}
@ -66,7 +66,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
var carrier = new Dictionary<string, string>();
var context = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
this.output.WriteLine(context.ToString());
this.b3Format.Inject(context, carrier, Setter);
this.b3Format.Inject(new PropagationContext(context, null), carrier, Setter);
this.ContainsExactly(carrier, new Dictionary<string, string> { { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 } });
}
@ -78,7 +78,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 },
};
var spanContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
Assert.Equal(spanContext, this.b3Format.Extract(default, headersNotSampled, Getter));
Assert.Equal(new PropagationContext(spanContext, null), this.b3Format.Extract(default, headersNotSampled, Getter));
}
[Fact]
@ -88,7 +88,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "1" },
};
Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3Format.Extract(default, headersSampled, Getter));
var activityContext = new ActivityContext(TraceId, SpanId, TraceOptions);
Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersSampled, Getter));
}
[Fact]
@ -98,7 +99,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "0" },
};
Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(default, headersNotSampled, Getter));
var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersNotSampled, Getter));
}
[Fact]
@ -108,7 +110,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Flags, "1" },
};
Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3Format.Extract(default, headersFlagSampled, Getter));
var activityContext = new ActivityContext(TraceId, SpanId, TraceOptions);
Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersFlagSampled, Getter));
}
[Fact]
@ -118,7 +121,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Flags, "0" },
};
Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(default, headersFlagNotSampled, Getter));
var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersFlagNotSampled, Getter));
}
[Fact]
@ -130,7 +134,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ B3Format.XB3SpanId, SpanIdBase16 },
{ B3Format.XB3Sampled, "1" },
};
Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions), this.b3Format.Extract(default, headersEightBytes, Getter));
var activityContext = new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions);
Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersEightBytes, Getter));
}
[Fact]
@ -140,7 +145,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3TraceId, TraceIdBase16EightBytes }, { B3Format.XB3SpanId, SpanIdBase16 },
};
Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None), this.b3Format.Extract(default, headersEightBytes, Getter));
var activityContext = new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None);
Assert.Equal(new PropagationContext(activityContext, null), this.b3Format.Extract(default, headersEightBytes, Getter));
}
[Fact]
@ -202,7 +208,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
public void Serialize_SampledContext_SingleHeader()
{
var carrier = new Dictionary<string, string>();
this.b3FormatSingleHeader.Inject(new ActivityContext(TraceId, SpanId, TraceOptions), carrier, Setter);
var activityContext = new ActivityContext(TraceId, SpanId, TraceOptions);
this.b3FormatSingleHeader.Inject(new PropagationContext(activityContext, null), carrier, Setter);
this.ContainsExactly(carrier, new Dictionary<string, string> { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-1" } });
}
@ -210,9 +217,9 @@ namespace OpenTelemetry.Context.Propagation.Tests
public void Serialize_NotSampledContext_SingleHeader()
{
var carrier = new Dictionary<string, string>();
var context = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
this.output.WriteLine(context.ToString());
this.b3FormatSingleHeader.Inject(context, carrier, Setter);
var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
this.output.WriteLine(activityContext.ToString());
this.b3FormatSingleHeader.Inject(new PropagationContext(activityContext, null), carrier, Setter);
this.ContainsExactly(carrier, new Dictionary<string, string> { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}" } });
}
@ -223,8 +230,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}" },
};
var spanContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
Assert.Equal(spanContext, this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter));
var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
Assert.Equal(new PropagationContext(activityContext, null), this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter));
}
[Fact]
@ -234,7 +241,10 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-1" },
};
Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(default, headersSampled, Getter));
Assert.Equal(
new PropagationContext(new ActivityContext(TraceId, SpanId, TraceOptions), null),
this.b3FormatSingleHeader.Extract(default, headersSampled, Getter));
}
[Fact]
@ -244,7 +254,10 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-0" },
};
Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter));
Assert.Equal(
new PropagationContext(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), null),
this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter));
}
[Fact]
@ -254,7 +267,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-1" },
};
Assert.Equal(new ActivityContext(TraceId, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(default, headersFlagSampled, Getter));
var activityContext = new ActivityContext(TraceId, SpanId, TraceOptions);
Assert.Equal(new PropagationContext(activityContext, null), this.b3FormatSingleHeader.Extract(default, headersFlagSampled, Getter));
}
[Fact]
@ -264,7 +278,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-0" },
};
Assert.Equal(new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(default, headersFlagNotSampled, Getter));
var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
Assert.Equal(new PropagationContext(activityContext, null), this.b3FormatSingleHeader.Extract(default, headersFlagNotSampled, Getter));
}
[Fact]
@ -274,7 +289,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3Combined, $"{TraceIdBase16EightBytes}-{SpanIdBase16}-1" },
};
Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions), this.b3FormatSingleHeader.Extract(default, headersEightBytes, Getter));
var activityContext = new ActivityContext(TraceIdEightBytes, SpanId, TraceOptions);
Assert.Equal(new PropagationContext(activityContext, null), this.b3FormatSingleHeader.Extract(default, headersEightBytes, Getter));
}
[Fact]
@ -284,7 +300,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
{ B3Format.XB3Combined, $"{TraceIdBase16EightBytes}-{SpanIdBase16}" },
};
Assert.Equal(new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None), this.b3FormatSingleHeader.Extract(default, headersEightBytes, Getter));
var activityContext = new ActivityContext(TraceIdEightBytes, SpanId, ActivityTraceFlags.None);
Assert.Equal(new PropagationContext(activityContext, null), this.b3FormatSingleHeader.Extract(default, headersEightBytes, Getter));
}
[Fact]

View File

@ -0,0 +1,160 @@
// <copyright file="BaggageFormatTest.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace OpenTelemetry.Context.Propagation.Tests
{
public class BaggageFormatTest
{
private static readonly Func<IDictionary<string, string>, string, IEnumerable<string>> Getter =
(d, k) =>
{
d.TryGetValue(k, out var v);
return new string[] { v };
};
private static readonly Func<IList<KeyValuePair<string, string>>, string, IEnumerable<string>> GetterList =
(d, k) =>
{
return d.Where(i => i.Key == k).Select(i => i.Value);
};
private static readonly Action<IDictionary<string, string>, string, string> Setter = (carrier, name, value) =>
{
carrier[name] = value;
};
private readonly BaggageFormat baggage = new BaggageFormat();
[Fact]
public void ValidateFieldsProperty()
{
Assert.Equal(new HashSet<string> { BaggageFormat.BaggageHeaderName }, this.baggage.Fields);
Assert.Single(this.baggage.Fields);
}
[Fact]
public void ValidateDefaultCarrierExtraction()
{
var propagationContext = this.baggage.Extract<string>(default, null, null);
Assert.Equal(default, propagationContext);
}
[Fact]
public void ValidateDefaultGetterExtraction()
{
var carrier = new Dictionary<string, string>();
var propagationContext = this.baggage.Extract(default, carrier, null);
Assert.Equal(default, propagationContext);
}
[Fact]
public void ValidateNoBaggageExtraction()
{
var carrier = new Dictionary<string, string>();
var propagationContext = this.baggage.Extract(default, carrier, Getter);
Assert.Equal(default, propagationContext);
}
[Fact]
public void ValidateOneBaggageExtraction()
{
var carrier = new Dictionary<string, string>
{
{ BaggageFormat.BaggageHeaderName, "name=test" },
};
var propagationContext = this.baggage.Extract(default, carrier, Getter);
Assert.False(propagationContext == default);
Assert.Single(propagationContext.ActivityBaggage);
var array = propagationContext.ActivityBaggage.ToArray();
Assert.Equal("name", array[0].Key);
Assert.Equal("test", array[0].Value);
}
[Fact]
public void ValidateMultipleBaggageExtraction()
{
var carrier = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(BaggageFormat.BaggageHeaderName, "name1=test1"),
new KeyValuePair<string, string>(BaggageFormat.BaggageHeaderName, "name2=test2"),
new KeyValuePair<string, string>(BaggageFormat.BaggageHeaderName, "name2=test2"),
};
var propagationContext = this.baggage.Extract(default, carrier, GetterList);
Assert.False(propagationContext == default);
Assert.True(propagationContext.ActivityContext == default);
Assert.Equal(2, propagationContext.ActivityBaggage.Count());
var array = propagationContext.ActivityBaggage.ToArray();
Assert.Equal("name1", array[0].Key);
Assert.Equal("test1", array[0].Value);
Assert.Equal("name2", array[1].Key);
Assert.Equal("test2", array[1].Value);
}
[Fact]
public void ValidateLongBaggageExtraction()
{
var carrier = new Dictionary<string, string>
{
{ BaggageFormat.BaggageHeaderName, $"name={new string('x', 8186)},clientId=1234" },
};
var propagationContext = this.baggage.Extract(default, carrier, Getter);
Assert.False(propagationContext == default);
Assert.Single(propagationContext.ActivityBaggage);
var array = propagationContext.ActivityBaggage.ToArray();
Assert.Equal("name", array[0].Key);
Assert.Equal(new string('x', 8186), array[0].Value);
}
[Fact]
public void ValidateEmptyBaggageInjection()
{
var carrier = new Dictionary<string, string>();
this.baggage.Inject(default, carrier, Setter);
Assert.Empty(carrier);
}
[Fact]
public void ValidateBaggageInjection()
{
var carrier = new Dictionary<string, string>();
var propagationContext = new PropagationContext(default, new Dictionary<string, string>
{
{ "key1", "value1" },
{ "key2", "value2" },
});
this.baggage.Inject(propagationContext, carrier, Setter);
Assert.Single(carrier);
Assert.Equal("key1=value1,key2=value2", carrier[BaggageFormat.BaggageHeaderName]);
}
}
}

View File

@ -24,7 +24,6 @@ namespace OpenTelemetry.Context.Propagation.Tests
{
public class CompositePropagatorTest
{
private const string TraceParent = "traceparent";
private static readonly string[] Empty = new string[0];
private static readonly Func<IDictionary<string, string>, string, IEnumerable<string>> Getter = (headers, name) =>
{
@ -63,14 +62,13 @@ namespace OpenTelemetry.Context.Propagation.Tests
});
var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null);
PropagationContext propagationContext = new PropagationContext(activityContext, null);
var carrier = new Dictionary<string, string>();
var activity = new Activity("test");
compositePropagator.Inject(activityContext, carrier, Setter);
compositePropagator.Inject(propagationContext, carrier, Setter);
Assert.Contains(carrier, kv => kv.Key == "custom-traceparent-1");
Assert.Contains(carrier, kv => kv.Key == "custom-traceparent-2");
bool isInjected = compositePropagator.IsInjected(carrier, Getter);
Assert.True(isInjected);
}
[Fact]
@ -86,24 +84,55 @@ namespace OpenTelemetry.Context.Propagation.Tests
});
var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null);
PropagationContext propagationContext = new PropagationContext(activityContext, null);
var carrier = new Dictionary<string, string>();
compositePropagator.Inject(activityContext, carrier, Setter);
compositePropagator.Inject(propagationContext, carrier, Setter);
Assert.Contains(carrier, kv => kv.Key == "custom-traceparent");
// checking if the latest propagator is the one with the data. So, it will replace the previous one.
Assert.Equal($"00-{this.traceId}-{this.spanId}-{header02.Split('-').Last()}", carrier["custom-traceparent"]);
bool isInjected = compositePropagator.IsInjected(carrier, Getter);
Assert.True(isInjected);
// resetting counter
count = 0;
ActivityContext newContext = compositePropagator.Extract(default, carrier, Getter);
compositePropagator.Extract(default, carrier, Getter);
// checking if we accessed only two times: header/headerstate options
// if that's true, we skipped the first one since we have a logic to for the default result
Assert.Equal(2, count);
}
[Fact]
public void CompositePropagator_ActivityContext_Baggage()
{
var compositePropagator = new CompositePropagator(new List<ITextFormat>
{
new TraceContextFormat(),
new BaggageFormat(),
});
var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null, isRemote: true);
var baggage = new Dictionary<string, string> { ["key1"] = "value1" };
PropagationContext propagationContextActivityOnly = new PropagationContext(activityContext, null);
PropagationContext propagationContextBaggageOnly = new PropagationContext(default, baggage);
PropagationContext propagationContextBoth = new PropagationContext(activityContext, baggage);
var carrier = new Dictionary<string, string>();
compositePropagator.Inject(propagationContextActivityOnly, carrier, Setter);
PropagationContext extractedContext = compositePropagator.Extract(default, carrier, Getter);
Assert.Equal(propagationContextActivityOnly, extractedContext);
carrier = new Dictionary<string, string>();
compositePropagator.Inject(propagationContextBaggageOnly, carrier, Setter);
extractedContext = compositePropagator.Extract(default, carrier, Getter);
Assert.Equal(propagationContextBaggageOnly, extractedContext);
carrier = new Dictionary<string, string>();
compositePropagator.Inject(propagationContextBoth, carrier, Setter);
extractedContext = compositePropagator.Extract(default, carrier, Getter);
Assert.Equal(propagationContextBoth, extractedContext);
}
}
}

View File

@ -36,23 +36,23 @@ namespace OpenTelemetry.Context.Propagation.Tests
public ISet<string> Fields => new HashSet<string>() { this.idHeaderName, this.stateHeaderName };
public ActivityContext Extract<T>(ActivityContext activityContext, T carrier, Func<T, string, IEnumerable<string>> getter)
public PropagationContext Extract<T>(PropagationContext context, T carrier, Func<T, string, IEnumerable<string>> getter)
{
if (this.defaultContext)
{
return activityContext;
return context;
}
IEnumerable<string> id = getter(carrier, this.idHeaderName);
if (id.Count() <= 0)
{
return activityContext;
return context;
}
var traceparentParsed = TraceContextFormat.TryExtractTraceparent(id.First(), out var traceId, out var spanId, out var traceoptions);
if (!traceparentParsed)
{
return activityContext;
return context;
}
string tracestate = string.Empty;
@ -62,31 +62,25 @@ namespace OpenTelemetry.Context.Propagation.Tests
TraceContextFormat.TryExtractTracestate(tracestateCollection.ToArray(), out tracestate);
}
return new ActivityContext(traceId, spanId, traceoptions, tracestate);
return new PropagationContext(
new ActivityContext(traceId, spanId, traceoptions, tracestate),
context.ActivityBaggage);
}
public void Inject<T>(ActivityContext activityContext, T carrier, Action<T, string, string> setter)
public void Inject<T>(PropagationContext context, T carrier, Action<T, string, string> setter)
{
string headerNumber = this.stateHeaderName.Split('-').Last();
var traceparent = string.Concat("00-", activityContext.TraceId.ToHexString(), "-", activityContext.SpanId.ToHexString());
var traceparent = string.Concat("00-", context.ActivityContext.TraceId.ToHexString(), "-", context.ActivityContext.SpanId.ToHexString());
traceparent = string.Concat(traceparent, "-", headerNumber);
setter(carrier, this.idHeaderName, traceparent);
string tracestateStr = activityContext.TraceState;
string tracestateStr = context.ActivityContext.TraceState;
if (tracestateStr?.Length > 0)
{
setter(carrier, this.stateHeaderName, tracestateStr);
}
}
public bool IsInjected<T>(T carrier, Func<T, string, IEnumerable<string>> getter)
{
var traceparentCollection = getter(carrier, this.idHeaderName);
// There must be a single traceparent
return traceparentCollection != null && traceparentCollection.Count() == 1;
}
}
}

View File

@ -55,14 +55,14 @@ namespace OpenTelemetry.Context.Propagation.Tests
var f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter);
Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.TraceId);
Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.SpanId);
Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.ActivityContext.TraceId);
Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.ActivityContext.SpanId);
Assert.True(ctx.IsRemote);
Assert.True(ctx.IsValid());
Assert.True((ctx.TraceFlags & ActivityTraceFlags.Recorded) != 0);
Assert.True(ctx.ActivityContext.IsRemote);
Assert.True(ctx.ActivityContext.IsValid());
Assert.True((ctx.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0);
Assert.Equal($"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{TraceId}-00f067aa0ba902b7-01", ctx.TraceState);
Assert.Equal($"congo=lZWRzIHRoNhcm5hbCBwbGVhc3VyZS4,rojo=00-{TraceId}-00f067aa0ba902b7-01", ctx.ActivityContext.TraceState);
}
[Fact]
@ -76,12 +76,12 @@ namespace OpenTelemetry.Context.Propagation.Tests
var f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter);
Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.TraceId);
Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.SpanId);
Assert.True((ctx.TraceFlags & ActivityTraceFlags.Recorded) == 0);
Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.ActivityContext.TraceId);
Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.ActivityContext.SpanId);
Assert.True((ctx.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) == 0);
Assert.True(ctx.IsRemote);
Assert.True(ctx.IsValid());
Assert.True(ctx.ActivityContext.IsRemote);
Assert.True(ctx.ActivityContext.IsValid());
}
[Fact]
@ -92,7 +92,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
var f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter);
Assert.False(ctx.IsValid());
Assert.False(ctx.ActivityContext.IsValid());
}
[Fact]
@ -106,7 +106,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
var f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter);
Assert.False(ctx.IsValid());
Assert.False(ctx.ActivityContext.IsValid());
}
[Fact]
@ -120,7 +120,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
var f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter);
Assert.Empty(ctx.TraceState);
Assert.Null(ctx.ActivityContext.TraceState);
}
[Fact]
@ -135,7 +135,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
var f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter);
Assert.Equal("k1=v1,k2=v2,k3=v3", ctx.TraceState);
Assert.Equal("k1=v1,k2=v2,k3=v3", ctx.ActivityContext.TraceState);
}
[Fact]
@ -149,9 +149,10 @@ namespace OpenTelemetry.Context.Propagation.Tests
};
var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, traceState: null);
PropagationContext propagationContext = new PropagationContext(activityContext, null);
var carrier = new Dictionary<string, string>();
var f = new TraceContextFormat();
f.Inject(activityContext, carrier, Setter);
f.Inject(propagationContext, carrier, Setter);
Assert.Equal(expectedHeaders, carrier);
}
@ -168,9 +169,10 @@ namespace OpenTelemetry.Context.Propagation.Tests
};
var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, expectedHeaders[TraceState]);
PropagationContext propagationContext = new PropagationContext(activityContext, null);
var carrier = new Dictionary<string, string>();
var f = new TraceContextFormat();
f.Inject(activityContext, carrier, Setter);
f.Inject(propagationContext, carrier, Setter);
Assert.Equal(expectedHeaders, carrier);
}

View File

@ -21,7 +21,7 @@ using Xunit;
namespace OpenTelemetry.Trace.Tests
{
public class TracerProvideSdkTest
public class TracerProvideSdkTest : IDisposable
{
private const string ActivitySourceName = "TraceSdkTest";
@ -151,7 +151,7 @@ namespace OpenTelemetry.Trace.Tests
public void ProcessorDoesNotReceiveNotRecordDecisionSpan()
{
var testSampler = new TestSampler();
TestActivityProcessor testActivityProcessor = new TestActivityProcessor();
using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();
bool startCalled = false;
bool endCalled = false;
@ -189,7 +189,7 @@ namespace OpenTelemetry.Trace.Tests
[Fact]
public void TracerProvideSdkCreatesActivitySource()
{
TestActivityProcessor testActivityProcessor = new TestActivityProcessor();
using TestActivityProcessor testActivityProcessor = new TestActivityProcessor();
bool startCalled = false;
bool endCalled = false;
@ -277,6 +277,11 @@ namespace OpenTelemetry.Trace.Tests
Assert.True(testInstrumentation.IsDisposed);
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
private class TestSampler : Sampler
{
public SamplingResult DesiredSamplingResult { get; set; } = new SamplingResult(SamplingDecision.RecordAndSampled);

View File

@ -23,11 +23,13 @@ namespace OpenTelemetry.Trace.Tests
public class TracerTest : IDisposable
{
// TODO: This is only a basic test. This must cover the entire shim API scenarios.
private readonly TracerProvider tracerProvider;
private readonly Tracer tracer;
public TracerTest()
{
this.tracer = TracerProvider.Default.GetTracer("tracername", "tracerversion");
this.tracerProvider = TracerProvider.Default;
this.tracer = this.tracerProvider.GetTracer("tracername", "tracerversion");
}
[Fact]
@ -248,6 +250,8 @@ namespace OpenTelemetry.Trace.Tests
public void Dispose()
{
Activity.Current = null;
this.tracerProvider.Dispose();
GC.SuppressFinalize(this);
}
private static bool IsNoopSpan(TelemetrySpan span)