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 // https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md#span-name
var activityName = $"{ea.RoutingKey} receive"; 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 try
{ {

View File

@ -61,7 +61,7 @@ namespace Utils.Messaging
if (activity != null) if (activity != null)
{ {
// Inject the ActivityContext into the message headers to propagate trace context to the receiving service. // 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. // The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here.
RabbitMqHelper.AddMessagingTags(activity); RabbitMqHelper.AddMessagingTags(activity);

View File

@ -2,6 +2,16 @@
## Unreleased ## 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` * Renaming from `ot` to `otel`
([#1046](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1046)) ([#1046](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1046))
* Added `RuntimeContext` API * Added `RuntimeContext` API

View File

@ -16,6 +16,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
namespace OpenTelemetry.Context namespace OpenTelemetry.Context
@ -25,27 +26,33 @@ namespace OpenTelemetry.Context
/// </summary> /// </summary>
public readonly struct CorrelationContext : IEquatable<CorrelationContext> public readonly struct CorrelationContext : IEquatable<CorrelationContext>
{ {
private static readonly List<CorrelationContextEntry> EmptyList = new List<CorrelationContextEntry>(); internal static readonly CorrelationContext Empty = new CorrelationContext(null);
private readonly List<CorrelationContextEntry> entries; internal static readonly IEnumerable<KeyValuePair<string, string>> EmptyBaggage = new KeyValuePair<string, string>[0];
private readonly Activity activity;
/// <summary> internal CorrelationContext(in Activity activity)
/// Initializes a new instance of the <see cref="CorrelationContext"/> struct.
/// </summary>
/// <param name="entries">Entries for correlation context.</param>
internal CorrelationContext(List<CorrelationContextEntry> entries)
{ {
this.entries = entries; this.activity = activity;
} }
/// <summary> /// <summary>
/// Gets empty object of <see cref="CorrelationContext"/> struct. /// Gets the current <see cref="CorrelationContext"/>.
/// </summary> /// </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> /// <summary>
/// Gets all the <see cref="CorrelationContextEntry"/> in this <see cref="CorrelationContext"/>. /// Gets the correlation values.
/// </summary> /// </summary>
public IEnumerable<CorrelationContextEntry> Entries => this.entries; public IEnumerable<KeyValuePair<string, string>> Correlations => this.activity?.Baggage ?? EmptyBaggage;
/// <summary> /// <summary>
/// Compare two entries of <see cref="CorrelationContext"/> for equality. /// 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); public static bool operator !=(CorrelationContext left, CorrelationContext right) => !(left == right);
/// <summary> /// <summary>
/// Gets the <see cref="CorrelationContextEntry"/> with the specified name. /// Retrieves a correlation item.
/// </summary> /// </summary>
/// <param name="key">Name of the <see cref="CorrelationContextEntry"/> to get.</param> /// <param name="key">Correlation item key.</param>
/// <returns>The <see cref="string"/> with the specified name. If not found - null.</returns> /// <returns>Retrieved correlation value or <see langword="null"/> if no match was found.</returns>
public string GetEntryValue(string key) => this.entries.LastOrDefault(x => x.Key == key).Value; 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/> /// <inheritdoc/>
public bool Equals(CorrelationContext other) 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; 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; return false;
} }
@ -96,7 +142,7 @@ namespace OpenTelemetry.Context
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace OpenTelemetry.Context.Propagation namespace OpenTelemetry.Context.Propagation
{ {
@ -33,42 +31,32 @@ namespace OpenTelemetry.Context.Propagation
/// Initializes a new instance of the <see cref="CompositePropagator"/> class. /// Initializes a new instance of the <see cref="CompositePropagator"/> class.
/// </summary> /// </summary>
/// <param name="textFormats">List of <see cref="ITextFormat"/> wire context propagator.</param> /// <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/> /// <inheritdoc/>
public ISet<string> Fields => EmptyFields; public ISet<string> Fields => EmptyFields;
/// <inheritdoc/> /// <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) foreach (var textFormat in this.textFormats)
{ {
activityContext = textFormat.Extract(activityContext, carrier, getter); context = textFormat.Extract(context, carrier, getter);
if (activityContext.IsValid())
{
return activityContext;
}
} }
return activityContext; return context;
} }
/// <inheritdoc/> /// <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) foreach (var textFormat in this.textFormats)
{ {
textFormat.Inject(activityContext, carrier, setter); textFormat.Inject(context, carrier, setter);
} }
} }
/// <inheritdoc/>
public bool IsInjected<T>(T carrier, Func<T, string, IEnumerable<string>> getter)
{
return this.textFormats.All(textFormat => textFormat.IsInjected(carrier, getter));
}
} }
} }

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 // Copyright The OpenTelemetry Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
@ -16,22 +16,22 @@
namespace OpenTelemetry.Context.Propagation 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/> /// <summary>
public override byte[] ToByteArray(DistributedContext context) /// Was not parsed.
{ /// </summary>
return SerializationUtils.SerializeBinary(context); NotParsed,
}
/// <inheritdoc/> /// <summary>
public override DistributedContext FromByteArray(byte[] bytes) /// Invalid format.
{ /// </summary>
return SerializationUtils.DeserializeBinary(bytes); 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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// </copyright> // </copyright>
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
namespace OpenTelemetry.Context.Propagation namespace OpenTelemetry.Context.Propagation
{ {
@ -36,28 +36,19 @@ namespace OpenTelemetry.Context.Propagation
/// Injects textual representation of activity context to transmit over the wire. /// Injects textual representation of activity context to transmit over the wire.
/// </summary> /// </summary>
/// <typeparam name="T">Type of an object to set context on. Typically HttpRequest or similar.</typeparam> /// <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="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> /// <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> /// <summary>
/// Extracts activity context from textual representation. /// Extracts activity context from textual representation.
/// </summary> /// </summary>
/// <typeparam name="T">Type of object to extract context from. Typically HttpRequest or similar.</typeparam> /// <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="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> /// <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> /// <returns>Context from it's text representation.</returns>
ActivityContext Extract<T>(ActivityContext activityContext, T carrier, Func<T, string, IEnumerable<string>> getter); PropagationContext Extract<T>(PropagationContext context, 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);
} }
} }

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 // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// </copyright> // </copyright>
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -42,49 +43,24 @@ namespace OpenTelemetry.Context.Propagation
public ISet<string> Fields => new HashSet<string> { TraceState, TraceParent }; public ISet<string> Fields => new HashSet<string> { TraceState, TraceParent };
/// <inheritdoc/> /// <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) if (carrier == null)
{ {
OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null carrier"); OpenTelemetryApiEventSource.Log.FailedToExtractActivityContext(nameof(TraceContextFormat), "null carrier");
return false; return context;
} }
if (getter == null) if (getter == null)
{ {
OpenTelemetryApiEventSource.Log.FailedToExtractContext("null getter"); OpenTelemetryApiEventSource.Log.FailedToExtractActivityContext(nameof(TraceContextFormat), "null getter");
return false; return context;
}
try
{
var traceparentCollection = getter(carrier, TraceParent);
// There must be a single traceparent
return traceparentCollection != null && traceparentCollection.Count() == 1;
}
catch (Exception ex)
{
OpenTelemetryApiEventSource.Log.ActivityContextExtractException(ex);
}
// in case of exception indicate to upstream that there is no parseable context from the top
return false;
}
/// <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;
} }
try try
@ -94,7 +70,7 @@ namespace OpenTelemetry.Context.Propagation
// There must be a single traceparent // There must be a single traceparent
if (traceparentCollection == null || traceparentCollection.Count() != 1) if (traceparentCollection == null || traceparentCollection.Count() != 1)
{ {
return activityContext; return context;
} }
var traceparent = traceparentCollection.First(); var traceparent = traceparentCollection.First();
@ -102,54 +78,56 @@ namespace OpenTelemetry.Context.Propagation
if (!traceparentParsed) if (!traceparentParsed)
{ {
return activityContext; return context;
} }
string tracestate = string.Empty; string tracestate = null;
var tracestateCollection = getter(carrier, TraceState); var tracestateCollection = getter(carrier, TraceState);
if (tracestateCollection?.Any() ?? false) if (tracestateCollection?.Any() ?? false)
{ {
TryExtractTracestate(tracestateCollection.ToArray(), out tracestate); 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) 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 // in case of exception indicate to upstream that there is no parseable context from the top
return activityContext; return context;
} }
/// <inheritdoc/> /// <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; return;
} }
if (carrier == null) if (carrier == null)
{ {
OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null carrier"); OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext(nameof(TraceContextFormat), "null carrier");
return; return;
} }
if (setter == null) if (setter == null)
{ {
OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext("null setter"); OpenTelemetryApiEventSource.Log.FailedToInjectActivityContext(nameof(TraceContextFormat), "null setter");
return; return;
} }
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, (activityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0 ? "-01" : "-00"); traceparent = string.Concat(traceparent, (context.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0 ? "-01" : "-00");
setter(carrier, TraceParent, traceparent); setter(carrier, TraceParent, traceparent);
string tracestateStr = activityContext.TraceState; string tracestateStr = context.ActivityContext.TraceState;
if (tracestateStr?.Length > 0) if (tracestateStr?.Length > 0)
{ {
setter(carrier, TraceState, tracestateStr); setter(carrier, TraceState, tracestateStr);

View File

@ -28,11 +28,20 @@ namespace OpenTelemetry.Internal
public static OpenTelemetryApiEventSource Log = new OpenTelemetryApiEventSource(); public static OpenTelemetryApiEventSource Log = new OpenTelemetryApiEventSource();
[NonEvent] [NonEvent]
public void ActivityContextExtractException(Exception ex) public void ActivityContextExtractException(string format, Exception ex)
{ {
if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) 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); this.WriteEvent(7, methodName, argumentName, issue);
} }
[Event(8, Message = "Failed to extract activity context: '{0}'", Level = EventLevel.Warning)] [Event(8, Message = "Failed to extract activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)]
public void FailedToExtractActivityContext(string exception) 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)] [Event(9, Message = "Failed to inject activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)]
public void FailedToInjectActivityContext(string error) 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)] [Event(10, Message = "Failed to extract baggage in format: '{0}', baggage: '{1}'.", Level = EventLevel.Warning)]
public void FailedToExtractContext(string error) 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> /// </summary>
/// <param name="context">the associated distributed context.</param> /// <param name="context">the associated distributed context.</param>
/// <param name="value">value by which the bound counter metric should be added.</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> /// </summary>
/// <param name="context">the associated distributed context.</param> /// <param name="context">the associated distributed context.</param>
/// <param name="value">the measurement to be recorded.</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="context">the associated distributed context.</param>
/// <param name="value">value by which the counter should be incremented.</param> /// <param name="value">value by which the counter should be incremented.</param>
/// <param name="labelset">The labelset associated with this value.</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> /// <summary>
/// Adds or Increments the counter. /// Adds or Increments the counter.
@ -57,7 +57,7 @@ namespace OpenTelemetry.Metrics
/// <param name="context">the associated distributed context.</param> /// <param name="context">the associated distributed context.</param>
/// <param name="value">value by which the counter should be incremented.</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> /// <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> /// <summary>
/// Gets the bound counter metric with given labelset. /// 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="context">the associated distributed context.</param>
/// <param name="value">value to record.</param> /// <param name="value">value to record.</param>
/// <param name="labelset">The labelset associated with this value.</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> /// <summary>
/// Records a measure. /// Records a measure.
@ -57,7 +57,7 @@ namespace OpenTelemetry.Metrics
/// <param name="context">the associated distributed context.</param> /// <param name="context">the associated distributed context.</param>
/// <param name="value">value to record.</param> /// <param name="value">value to record.</param>
/// <param name="labels">The labels or dimensions associated with this value.</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> /// <summary>
/// Gets the bound measure metric with given labelset. /// Gets the bound measure metric with given labelset.

View File

@ -37,7 +37,7 @@ namespace OpenTelemetry.Metrics
} }
/// <inheritdoc/> /// <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/> /// <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/> /// <inheritdoc/>
public override void Add(in DistributedContext context, T value, LabelSet labelset) public override void Add(in CorrelationContext context, T value, LabelSet labelset)
{ {
} }
/// <inheritdoc/> /// <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 static readonly TelemetrySpan NoopInstance = new TelemetrySpan(null);
internal readonly Activity Activity; internal readonly Activity Activity;
private static readonly IEnumerable<KeyValuePair<string, string>> EmptyBaggage = new KeyValuePair<string, string>[0];
internal TelemetrySpan(Activity activity) 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> /// <summary>
/// Sets the status of the span execution. /// Sets the status of the span execution.
/// </summary> /// </summary>
@ -272,6 +278,31 @@ namespace OpenTelemetry.Trace
this.Activity?.Stop(); 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/> /// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose() public void Dispose()

View File

@ -26,18 +26,17 @@ namespace OpenTelemetry.Instrumentation.AspNet
public class AspNetInstrumentationOptions public class AspNetInstrumentationOptions
{ {
/// <summary> /// <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> /// </summary>
public ITextFormat TextFormat { get; set; } = new TraceContextFormat(); public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[]
{
new TraceContextFormat(),
new BaggageFormat(),
});
/// <summary> /// <summary>
/// Gets or sets a hook to exclude calls based on domain or other per-request criterion. /// Gets or sets a hook to exclude calls based on domain or other per-request criterion.
/// </summary> /// </summary>
internal Predicate<HttpContext> RequestFilter { get; set; } = DefaultFilter; internal Predicate<HttpContext> RequestFilter { get; set; }
private static bool DefaultFilter(HttpContext httpContext)
{
return true;
}
} }
} }

View File

@ -2,6 +2,13 @@
## Unreleased ## 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 ## 0.4.0-beta.2
Released 2020-07-24 Released 2020-07-24

View File

@ -49,7 +49,7 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
return; return;
} }
if (this.options.RequestFilter != null && !this.options.RequestFilter(context)) if (this.options.RequestFilter?.Invoke(context) == false)
{ {
AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); AspNetInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName);
activity.IsAllDataRequested = false; activity.IsAllDataRequested = false;
@ -61,17 +61,16 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
if (!(this.options.TextFormat is TraceContextFormat)) 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); 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. // 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 // This makes the new activity as a "sibling" of the activity created by
// Asp.Net. // ASP.NET.
Activity newOne = new Activity(ActivityNameByHttpInListener); Activity newOne = new Activity(ActivityNameByHttpInListener);
newOne.SetParentId(ctx.TraceId, ctx.SpanId, ctx.TraceFlags); newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags);
newOne.TraceStateString = ctx.TraceState; newOne.TraceStateString = ctx.ActivityContext.TraceState;
// Starting the new activity make it the Activity.Current one. // Starting the new activity make it the Activity.Current one.
newOne.Start(); newOne.Start();
@ -83,6 +82,14 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
activity.SetCustomProperty("ActivityByHttpInListener", newOne); activity.SetCustomProperty("ActivityByHttpInListener", newOne);
activity = 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 // 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) public override void OnStopActivity(Activity activity, object payload)
{ {
Activity activityToEnrich = activity; Activity activityToEnrich = activity;
Activity createdActivity = null;
if (!(this.options.TextFormat is TraceContextFormat)) if (!(this.options.TextFormat is TraceContextFormat))
{ {
@ -125,9 +133,10 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
if (activity.OperationName.Equals("Microsoft.AspNet.HttpReqIn.Start")) if (activity.OperationName.Equals("Microsoft.AspNet.HttpReqIn.Start"))
{ {
// This block is hit if Asp.Net did restore Current to its own activity, // 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 we need to retrieve the one created by HttpInListener,
// and populate tags to it. // or an additional activity was never created.
activityToEnrich = (Activity)activity.GetCustomProperty("ActivityByHttpInListener"); createdActivity = (Activity)activity.GetCustomProperty("ActivityByHttpInListener");
activityToEnrich = createdActivity ?? activity;
} }
} }
@ -190,13 +199,12 @@ namespace OpenTelemetry.Instrumentation.AspNet.Implementation
var activityByAspNet = (Activity)activity.GetCustomProperty("ActivityByAspNet"); var activityByAspNet = (Activity)activity.GetCustomProperty("ActivityByAspNet");
Activity.Current = 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, // This block is hit if Asp.Net did restore Current to its own activity,
// then we need to retrieve the one created by HttpInListener // then we need to retrieve the one created by HttpInListener
// and stop it. // and stop it.
var activityByHttpInListener = (Activity)activity.GetCustomProperty("ActivityByHttpInListener"); createdActivity.Stop();
activityByHttpInListener.Stop();
// Restore current back to the one created by Asp.Net // Restore current back to the one created by Asp.Net
Activity.Current = activity; Activity.Current = activity;

View File

@ -26,18 +26,17 @@ namespace OpenTelemetry.Instrumentation.AspNetCore
public class AspNetCoreInstrumentationOptions public class AspNetCoreInstrumentationOptions
{ {
/// <summary> /// <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> /// </summary>
public ITextFormat TextFormat { get; set; } = new TraceContextFormat(); public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[]
{
new TraceContextFormat(),
new BaggageFormat(),
});
/// <summary> /// <summary>
/// Gets or sets a hook to exclude calls based on domain or other per-request criterion. /// Gets or sets a hook to exclude calls based on domain or other per-request criterion.
/// </summary> /// </summary>
internal Predicate<HttpContext> RequestFilter { get; set; } = DefaultFilter; internal Predicate<HttpContext> RequestFilter { get; set; }
private static bool DefaultFilter(HttpContext httpContext)
{
return true;
}
} }
} }

View File

@ -2,12 +2,18 @@
## Unreleased ## 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). * Introduced support for Grpc.AspNetCore (#803).
* Attributes are added to gRPC invocations: `rpc.system`, `rpc.service`, * Attributes are added to gRPC invocations: `rpc.system`, `rpc.service`,
`rpc.method`. These attributes are added to an existing span generated by `rpc.method`. These attributes are added to an existing span generated by
the instrumentation. This is unlike the instrumentation for client-side the instrumentation. This is unlike the instrumentation for client-side gRPC
gRPC calls where one span is created for the gRPC call and a separate span calls where one span is created for the gRPC call and a separate span is
is created for the underlying HTTP call in the event both gRPC and HTTP created for the underlying HTTP call in the event both gRPC and HTTP
instrumentation are enabled. instrumentation are enabled.
## 0.4.0-beta.2 ## 0.4.0-beta.2

View File

@ -57,7 +57,7 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
return; return;
} }
if (this.options.RequestFilter != null && !this.options.RequestFilter(context)) if (this.options.RequestFilter?.Invoke(context) == false)
{ {
AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName); AspNetCoreInstrumentationEventSource.Log.RequestIsFilteredOut(activity.OperationName);
activity.IsAllDataRequested = false; activity.IsAllDataRequested = false;
@ -67,24 +67,29 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Implementation
var request = context.Request; var request = context.Request;
if (!this.hostingSupportsW3C || !(this.options.TextFormat is TraceContextFormat)) 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); 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. // 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 // This makes the new activity as a "sibling" of the activity created by
// Asp.Net Core. // Asp.Net Core.
Activity newOne = new Activity(ActivityNameByHttpInListener); Activity newOne = new Activity(ActivityNameByHttpInListener);
newOne.SetParentId(ctx.TraceId, ctx.SpanId, ctx.TraceFlags); newOne.SetParentId(ctx.ActivityContext.TraceId, ctx.ActivityContext.SpanId, ctx.ActivityContext.TraceFlags);
newOne.TraceStateString = ctx.TraceState; newOne.TraceStateString = ctx.ActivityContext.TraceState;
// Starting the new activity make it the Activity.Current one. // Starting the new activity make it the Activity.Current one.
newOne.Start(); newOne.Start();
activity = newOne; activity = newOne;
} }
if (ctx.ActivityBaggage != null)
{
foreach (var baggageItem in ctx.ActivityBaggage)
{
activity.AddBaggage(baggageItem.Key, baggageItem.Value);
}
}
} }
activity.SetKind(ActivityKind.Server); activity.SetKind(ActivityKind.Server);

View File

@ -2,6 +2,13 @@
## Unreleased ## 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 * Removed `AddHttpInstrumentation` and `AddHttpWebRequestInstrumentation` (.NET
Framework) `TracerProviderBuilderExtensions`. `AddHttpClientInstrumentation` Framework) `TracerProviderBuilderExtensions`. `AddHttpClientInstrumentation`
will now register `HttpClient` instrumentation on .NET Core and `HttpClient` + 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; } public bool SetHttpFlavor { get; set; }
/// <summary> /// <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> /// </summary>
public ITextFormat TextFormat { get; set; } = new TraceContextFormat(); public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[]
{
new TraceContextFormat(),
new BaggageFormat(),
});
/// <summary> /// <summary>
/// Gets or sets an optional callback method for filtering <see cref="HttpRequestMessage"/> requests that are sent through the instrumentation. /// 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; } public bool SetHttpFlavor { get; set; }
/// <summary> /// <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> /// </summary>
public ITextFormat TextFormat { get; set; } = new TraceContextFormat(); public ITextFormat TextFormat { get; set; } = new CompositePropagator(new ITextFormat[]
{
new TraceContextFormat(),
new BaggageFormat(),
});
/// <summary> /// <summary>
/// Gets or sets an optional callback method for filtering <see cref="HttpWebRequest"/> requests that are sent through the instrumentation. /// 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; 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 // this request is already instrumented, we should back off
activity.IsAllDataRequested = false; activity.IsAllDataRequested = false;
@ -107,7 +107,7 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
if (!(this.httpClientSupportsW3C && this.options.TextFormat is TraceContextFormat)) 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.Reflection.Emit;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Trace; using OpenTelemetry.Trace;
namespace OpenTelemetry.Instrumentation.Http.Implementation namespace OpenTelemetry.Instrumentation.Http.Implementation
@ -44,8 +45,6 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
internal static HttpWebRequestInstrumentationOptions Options = new HttpWebRequestInstrumentationOptions(); 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 Version Version = typeof(HttpWebRequestActivitySource).Assembly.GetName().Version;
private static readonly ActivitySource WebRequestActivitySource = new ActivitySource(ActivitySourceName, Version.ToString()); private static readonly ActivitySource WebRequestActivitySource = new ActivitySource(ActivitySourceName, Version.ToString());
@ -188,32 +187,11 @@ namespace OpenTelemetry.Instrumentation.Http.Implementation
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InstrumentRequest(HttpWebRequest request, Activity activity) private static void InstrumentRequest(HttpWebRequest request, Activity activity)
{ => Options.TextFormat.Inject(new PropagationContext(activity.Context, activity.Baggage), request, HttpWebRequestHeaderValuesSetter);
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());
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsRequestInstrumented(HttpWebRequest request) private static bool IsRequestInstrumented(HttpWebRequest request)
=> Options.TextFormat.IsInjected(request, HttpWebRequestHeaderValuesGetter); => Options.TextFormat.Extract(default, request, HttpWebRequestHeaderValuesGetter) != default;
private static void ProcessRequest(HttpWebRequest request) private static void ProcessRequest(HttpWebRequest request)
{ {

View File

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

View File

@ -52,7 +52,7 @@ namespace OpenTelemetry.Shims.OpenTracing
throw new ArgumentException(nameof(this.Span.Context)); 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; public ISpanContext Context => this.spanContextShim;

View File

@ -60,7 +60,7 @@ namespace OpenTelemetry.Shims.OpenTracing
throw new ArgumentNullException(nameof(carrier)); throw new ArgumentNullException(nameof(carrier));
} }
ActivityContext activityContext = default; PropagationContext propagationContext = default;
if ((format == BuiltinFormats.TextMap || format == BuiltinFormats.HttpHeaders) && carrier is ITextMap textMapCarrier) 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 }); 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)) if (key == null || !source.TryGetValue(key, out var value))
{ {
@ -81,10 +81,10 @@ namespace OpenTelemetry.Shims.OpenTracing
return value; 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/> /// <inheritdoc/>
@ -115,7 +115,10 @@ namespace OpenTelemetry.Shims.OpenTracing
if ((format == BuiltinFormats.TextMap || format == BuiltinFormats.HttpHeaders) && carrier is ITextMap textMapCarrier) 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; public ISet<string> Fields => AllFields;
/// <inheritdoc/> /// <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) if (carrier == null)
{ {
OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null carrier"); OpenTelemetrySdkEventSource.Log.FailedToExtractActivityContext(nameof(B3Format), "null carrier");
return false; return context;
} }
if (getter == null) if (getter == null)
{ {
OpenTelemetrySdkEventSource.Log.FailedToExtractContext("null getter"); OpenTelemetrySdkEventSource.Log.FailedToExtractActivityContext(nameof(B3Format), "null getter");
return false; return context;
}
try
{
if (this.singleHeader)
{
var header = getter(carrier, XB3Combined)?.FirstOrDefault();
return !string.IsNullOrWhiteSpace(header);
}
else
{
var traceIdStr = getter(carrier, XB3TraceId)?.FirstOrDefault();
var spanIdStr = getter(carrier, XB3SpanId)?.FirstOrDefault();
return traceIdStr != null && spanIdStr != null;
}
}
catch (Exception e)
{
OpenTelemetrySdkEventSource.Log.ContextExtractException(e);
return false;
}
}
/// <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;
} }
if (this.singleHeader) if (this.singleHeader)
{ {
return ExtractFromSingleHeader(activityContext, carrier, getter); return ExtractFromSingleHeader(context, carrier, getter);
} }
else else
{ {
return ExtractFromMultipleHeaders(activityContext, carrier, getter); return ExtractFromMultipleHeaders(context, carrier, getter);
} }
} }
/// <inheritdoc/> /// <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; return;
} }
if (carrier == null) if (carrier == null)
{ {
OpenTelemetrySdkEventSource.Log.FailedToInjectContext("null carrier"); OpenTelemetrySdkEventSource.Log.FailedToInjectActivityContext(nameof(B3Format), "null carrier");
return; return;
} }
if (setter == null) if (setter == null)
{ {
OpenTelemetrySdkEventSource.Log.FailedToInjectContext("null setter"); OpenTelemetrySdkEventSource.Log.FailedToInjectActivityContext(nameof(B3Format), "null setter");
return; return;
} }
if (this.singleHeader) if (this.singleHeader)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append(activityContext.TraceId.ToHexString()); sb.Append(context.ActivityContext.TraceId.ToHexString());
sb.Append(XB3CombinedDelimiter); sb.Append(XB3CombinedDelimiter);
sb.Append(activityContext.SpanId.ToHexString()); sb.Append(context.ActivityContext.SpanId.ToHexString());
if ((activityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0) if ((context.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0)
{ {
sb.Append(XB3CombinedDelimiter); sb.Append(XB3CombinedDelimiter);
sb.Append(SampledValue); sb.Append(SampledValue);
@ -168,16 +137,16 @@ namespace OpenTelemetry.Context.Propagation
} }
else else
{ {
setter(carrier, XB3TraceId, activityContext.TraceId.ToHexString()); setter(carrier, XB3TraceId, context.ActivityContext.TraceId.ToHexString());
setter(carrier, XB3SpanId, activityContext.SpanId.ToHexString()); setter(carrier, XB3SpanId, context.ActivityContext.SpanId.ToHexString());
if ((activityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0) if ((context.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) != 0)
{ {
setter(carrier, XB3Sampled, SampledValue); 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 try
{ {
@ -195,7 +164,7 @@ namespace OpenTelemetry.Context.Propagation
} }
else else
{ {
return activityContext; return context;
} }
ActivitySpanId spanId; ActivitySpanId spanId;
@ -206,7 +175,7 @@ namespace OpenTelemetry.Context.Propagation
} }
else else
{ {
return activityContext; return context;
} }
var traceOptions = ActivityTraceFlags.None; var traceOptions = ActivityTraceFlags.None;
@ -216,35 +185,37 @@ namespace OpenTelemetry.Context.Propagation
traceOptions |= ActivityTraceFlags.Recorded; 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) catch (Exception e)
{ {
OpenTelemetrySdkEventSource.Log.ContextExtractException(e); OpenTelemetrySdkEventSource.Log.ActivityContextExtractException(nameof(B3Format), e);
return activityContext; 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 try
{ {
var header = getter(carrier, XB3Combined)?.FirstOrDefault(); var header = getter(carrier, XB3Combined)?.FirstOrDefault();
if (string.IsNullOrWhiteSpace(header)) if (string.IsNullOrWhiteSpace(header))
{ {
return activityContext; return context;
} }
var parts = header.Split(XB3CombinedDelimiter); var parts = header.Split(XB3CombinedDelimiter);
if (parts.Length < 2 || parts.Length > 4) if (parts.Length < 2 || parts.Length > 4)
{ {
return activityContext; return context;
} }
var traceIdStr = parts[0]; var traceIdStr = parts[0];
if (string.IsNullOrWhiteSpace(traceIdStr)) if (string.IsNullOrWhiteSpace(traceIdStr))
{ {
return activityContext; return context;
} }
if (traceIdStr.Length == 16) if (traceIdStr.Length == 16)
@ -258,7 +229,7 @@ namespace OpenTelemetry.Context.Propagation
var spanIdStr = parts[1]; var spanIdStr = parts[1];
if (string.IsNullOrWhiteSpace(spanIdStr)) if (string.IsNullOrWhiteSpace(spanIdStr))
{ {
return activityContext; return context;
} }
var spanId = ActivitySpanId.CreateFromString(spanIdStr.AsSpan()); 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) catch (Exception e)
{ {
OpenTelemetrySdkEventSource.Log.ContextExtractException(e); OpenTelemetrySdkEventSource.Log.ActivityContextExtractException(nameof(B3Format), e);
return activityContext; 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] [NonEvent]
public void ContextExtractException(Exception ex) public void ActivityContextExtractException(string format, Exception ex)
{ {
if (this.IsEnabled(EventLevel.Warning, (EventKeywords)(-1))) 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); this.WriteEvent(8, methodName, argumentName, issue);
} }
[Event(9, Message = "Failed to extract span context: '{0}'", Level = EventLevel.Warning)] [Event(9, Message = "Failed to extract activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)]
public void FailedToExtractContext(string error) 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)] [Event(10, Message = "Failed to inject activity context in format: '{0}', context: '{1}'.", Level = EventLevel.Warning)]
public void FailedToInjectContext(string error) 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)] [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); 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); this.sumAggregator.Update(value);
} }

View File

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

View File

@ -39,13 +39,13 @@ namespace OpenTelemetry.Metrics
this.Bind(new LabelSetSdk(labels), isShortLived: true).Add(context, value); 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. // user not using bound instrument. Hence create a short-lived bound instrument.
this.Bind(labelset, isShortLived: true).Add(context, value); 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. // user not using bound instrument. Hence create a short-lived bound instrument.
this.Bind(new LabelSetSdk(labels), isShortLived: true).Add(context, value); this.Bind(new LabelSetSdk(labels), isShortLived: true).Add(context, value);

View File

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

View File

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

View File

@ -39,13 +39,13 @@ namespace OpenTelemetry.Metrics
this.Bind(new LabelSetSdk(labels), isShortLived: true).Add(context, value); 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. // user not using bound instrument. Hence create a short-lived bound instrument.
this.Bind(labelset, isShortLived: true).Add(context, value); 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. // user not using bound instrument. Hence create a short-lived bound instrument.
this.Bind(new LabelSetSdk(labels), isShortLived: true).Add(context, value); 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("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("http://localhost:1880/api/weatherforecast", 3, "api/{controller}/{id}", "TraceContext")]
[InlineData("https://localhost:1843/subroute/10", 4, "subroute/{customerId}", "TraceContext")] [InlineData("https://localhost:1843/subroute/10", 4, "subroute/{customerId}", "TraceContext")]
[InlineData("http://localhost/api/value", 0, null, "TraceContext", "/api/value")] // Request will be filtered
// TODO: Reenable this tests once filtering mechanism is designed. [InlineData("http://localhost/api/value", 0, null, "TraceContext", "{ThrowException}")] // Filter user code will throw an exception
// [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/2", 0, null, "CustomContext", "/api/value")] // Request will not be filtered [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 [InlineData("http://localhost/api/value/2", 0, null, "CustomContext", "/api/value", true)] // Request will not be filtered
public void AspNetRequestsAreCollectedSuccessfully( public void AspNetRequestsAreCollectedSuccessfully(
@ -129,10 +127,12 @@ namespace OpenTelemetry.Instrumentation.AspNet.Tests
var expectedTraceId = ActivityTraceId.CreateRandom(); var expectedTraceId = ActivityTraceId.CreateRandom();
var expectedSpanId = ActivitySpanId.CreateRandom(); var expectedSpanId = ActivitySpanId.CreateRandom();
var textFormat = new Mock<ITextFormat>(); 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(
expectedTraceId, new ActivityContext(
expectedSpanId, expectedTraceId,
ActivityTraceFlags.Recorded)); expectedSpanId,
ActivityTraceFlags.Recorded),
null));
var activity = new Activity(ActivityNameAspNet).AddBaggage("Stuff", "123"); var activity = new Activity(ActivityNameAspNet).AddBaggage("Stuff", "123");
activity.SetParentId(expectedTraceId, expectedSpanId, ActivityTraceFlags.Recorded); activity.SetParentId(expectedTraceId, expectedSpanId, ActivityTraceFlags.Recorded);
@ -186,7 +186,19 @@ namespace OpenTelemetry.Instrumentation.AspNet.Tests
if (HttpContext.Current.Request.Path == filter || filter == "{ThrowException}") 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; return;
} }

View File

@ -144,10 +144,11 @@ namespace OpenTelemetry.Instrumentation.AspNetCore.Tests
var expectedSpanId = ActivitySpanId.CreateRandom(); var expectedSpanId = ActivitySpanId.CreateRandom();
var textFormat = new Mock<ITextFormat>(); 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, expectedTraceId,
expectedSpanId, expectedSpanId,
ActivityTraceFlags.Recorded)); ActivityTraceFlags.Recorded), null));
// Arrange // Arrange
using (var testFactory = this.factory 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 // Ensure that the header value func does not throw if the header key can't be found
var mockTextFormat = new Mock<ITextFormat>(); var mockTextFormat = new Mock<ITextFormat>();
var isInjectedHeaderValueGetterThrows = false;
mockTextFormat // var isInjectedHeaderValueGetterThrows = false;
.Setup(x => x.IsInjected(It.IsAny<HttpRequestMessage>(), It.IsAny<Func<HttpRequestMessage, string, IEnumerable<string>>>())) // mockTextFormat
.Callback<HttpRequestMessage, Func<HttpRequestMessage, string, IEnumerable<string>>>( // .Setup(x => x.IsInjected(It.IsAny<HttpRequestMessage>(), It.IsAny<Func<HttpRequestMessage, string, IEnumerable<string>>>()))
(carrier, getter) => // .Callback<HttpRequestMessage, Func<HttpRequestMessage, string, IEnumerable<string>>>(
{ // (carrier, getter) =>
try // {
{ // try
// traceparent doesn't exist // {
getter(carrier, "traceparent"); // // traceparent doesn't exist
} // getter(carrier, "traceparent");
catch // }
{ // catch
isInjectedHeaderValueGetterThrows = true; // {
} // isInjectedHeaderValueGetterThrows = true;
}); // }
// });
using (Sdk.CreateTracerProviderBuilder() using (Sdk.CreateTracerProviderBuilder()
.AddHttpClientInstrumentation(o => o.TextFormat = mockTextFormat.Object) .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($"00-{span.Context.TraceId}-{span.Context.SpanId}-01", traceparents.Single());
Assert.Equal("k1=v1,k2=v2", tracestates.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] [Fact]
public async Task HttpClientInstrumentationInjectsHeadersAsync_CustomFormat() public async Task HttpClientInstrumentationInjectsHeadersAsync_CustomFormat()
{ {
var textFormat = new Mock<ITextFormat>(); var textFormat = new Mock<ITextFormat>();
textFormat.Setup(m => m.Inject<HttpRequestMessage>(It.IsAny<ActivityContext>(), It.IsAny<HttpRequestMessage>(), It.IsAny<Action<HttpRequestMessage, string, string>>())) textFormat.Setup(m => m.Inject<HttpRequestMessage>(It.IsAny<PropagationContext>(), It.IsAny<HttpRequestMessage>(), It.IsAny<Action<HttpRequestMessage, string, string>>()))
.Callback<ActivityContext, HttpRequestMessage, Action<HttpRequestMessage, string, string>>((context, message, action) => .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); action(message, "custom_tracestate", Activity.Current.TraceStateString);
}); });

View File

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

View File

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

View File

@ -50,7 +50,8 @@ namespace OpenTelemetry.Shims.OpenTracing.Tests
public void GetBaggage() public void GetBaggage()
{ {
var shim = GetSpanContextShim(); var shim = GetSpanContextShim();
Assert.Throws<NotImplementedException>(() => shim.GetBaggageItems()); var baggage = shim.GetBaggageItems();
Assert.Null(baggage);
} }
internal static SpanContextShim GetSpanContextShim() 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. // limitations under the License.
// </copyright> // </copyright>
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using Xunit; using Xunit;
namespace OpenTelemetry.Context.Tests namespace OpenTelemetry.Context.Tests
{ {
public class CorrelationContextTest public class CorrelationContextTest
{ {
private const string K1 = "k1"; private const string K1 = "Key1";
private const string K2 = "k2"; private const string K2 = "Key2";
private const string V1 = "v1"; private const string V1 = "Value1";
private const string V2 = "v2"; private const string V2 = "Value2";
public CorrelationContextTest()
{
DistributedContext.Carrier = AsyncLocalDistributedContextCarrier.Instance;
}
[Fact] [Fact]
public void EmptyContext() public void EmptyContext()
{ {
var dc = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>()); var cc = CorrelationContext.Current;
Assert.Empty(dc.Entries); Assert.Empty(cc.Correlations);
Assert.Equal(CorrelationContext.Empty, dc); Assert.Equal(CorrelationContext.Empty, cc);
cc.AddCorrelation(K1, V1);
Assert.Empty(cc.Correlations);
Assert.Null(cc.GetCorrelation(K1));
} }
[Fact] [Fact]
public void NonEmptyContext() public void NonEmptyContext()
{ {
var list = new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) }; using Activity activity = new Activity("TestActivity");
var dc = CorrelationContextBuilder.CreateContext(list); activity.Start();
Assert.Equal(list, dc.Entries);
}
[Fact] var list = new List<KeyValuePair<string, string>>(2)
public void AddExtraKey() {
{ new KeyValuePair<string, string>(K1, V1),
var list = new List<CorrelationContextEntry>(1) { new CorrelationContextEntry(K1, V1) }; new KeyValuePair<string, string>(K2, V2),
var dc = CorrelationContextBuilder.CreateContext(list); };
Assert.Equal(list, dc.Entries);
list.Add(new CorrelationContextEntry(K2, V2)); var cc = CorrelationContext.Current;
var dc1 = CorrelationContextBuilder.CreateContext(list);
Assert.Equal(list, dc1.Entries); 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] [Fact]
public void AddExistingKey() public void AddExistingKey()
{ {
var list = new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K1, V2) }; using Activity activity = new Activity("TestActivity");
var dc = CorrelationContextBuilder.CreateContext(list); activity.Start();
Assert.Equal(new List<CorrelationContextEntry>(1) { new CorrelationContextEntry(K1, V2) }, dc.Entries);
}
[Fact] var list = new List<KeyValuePair<string, string>>(2)
public void UseDefaultEntry() {
{ new KeyValuePair<string, string>(K1, V1),
Assert.Equal(CorrelationContext.Empty, CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(1) { default })); new KeyValuePair<string, string>(K1, V1),
Assert.Equal(CorrelationContext.Empty, CorrelationContextBuilder.CreateContext(null)); };
}
[Fact] var cc = CorrelationContext.Current;
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);
list.RemoveAt(0); cc.AddCorrelation(K1, V1);
cc.AddCorrelation(K1, V1);
dc = CorrelationContextBuilder.CreateContext(list); Assert.Equal(list, cc.Correlations);
Assert.Equal(list, dc.Entries);
list.Clear();
dc = CorrelationContextBuilder.CreateContext(list);
Assert.Equal(CorrelationContext.Empty, dc);
} }
[Fact] [Fact]
public void TestIterator() public void TestIterator()
{ {
var list = new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) }; using Activity activity = new Activity("TestActivity");
var dc = CorrelationContextBuilder.CreateContext(list); 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()); Assert.True(i.MoveNext());
var tag1 = i.Current; var tag1 = i.Current;
Assert.True(i.MoveNext()); Assert.True(i.MoveNext());
var tag2 = i.Current; var tag2 = i.Current;
Assert.False(i.MoveNext()); 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] [Fact]
public void TestEquals() public void TestEquals()
{ {
var dc1 = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) }); var cc1 = CreateCorrelationContext(new KeyValuePair<string, string>(K1, V1), new KeyValuePair<string, string>(K2, V2));
var dc2 = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V2) }); var cc2 = CreateCorrelationContext(new KeyValuePair<string, string>(K1, V1), new KeyValuePair<string, string>(K2, V2));
object odc2 = dc2; var cc3 = CreateCorrelationContext(new KeyValuePair<string, string>(K2, V2), new KeyValuePair<string, string>(K1, V1));
var dc3 = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K2, V2), new CorrelationContextEntry(K1, V1) }); var cc4 = CreateCorrelationContext(new KeyValuePair<string, string>(K1, V1), new KeyValuePair<string, string>(K2, V1));
var dc4 = CorrelationContextBuilder.CreateContext(new List<CorrelationContextEntry>(2) { new CorrelationContextEntry(K1, V1), new CorrelationContextEntry(K2, V1) }); var cc5 = CreateCorrelationContext(new KeyValuePair<string, string>(K1, V2), new KeyValuePair<string, string>(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) });
Assert.True(dc1.Equals(dc2)); Assert.True(cc1.Equals(cc2));
Assert.True(dc1.Equals(odc2));
Assert.True(dc1 == dc2);
Assert.True(dc1.Equals(dc3));
Assert.False(dc1.Equals(dc4)); Assert.False(cc1.Equals(cc3));
Assert.True(dc1 != dc4); Assert.False(cc1.Equals(cc4));
Assert.False(dc2.Equals(dc4)); Assert.False(cc2.Equals(cc4));
Assert.False(dc3.Equals(dc4)); Assert.False(cc3.Equals(cc4));
Assert.False(dc5.Equals(dc4)); Assert.False(cc5.Equals(cc4));
Assert.False(dc4.Equals(dc5)); Assert.False(cc4.Equals(cc5));
Assert.False(dc5.Equals(dc6)); }
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() public void Dispose()
{ {
Activity.Current = null; Activity.Current = null;
this.testProcessor.Dispose();
GC.SuppressFinalize(this);
} }
} }
} }

View File

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

View File

@ -56,7 +56,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
public void Serialize_SampledContext() public void Serialize_SampledContext()
{ {
var carrier = new Dictionary<string, string>(); 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" } }); 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 carrier = new Dictionary<string, string>();
var context = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); var context = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
this.output.WriteLine(context.ToString()); 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 } }); 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 }, { B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 },
}; };
var spanContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); 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] [Fact]
@ -88,7 +88,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "1" }, { 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] [Fact]
@ -98,7 +99,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Sampled, "0" }, { 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] [Fact]
@ -108,7 +110,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Flags, "1" }, { 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] [Fact]
@ -118,7 +121,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3TraceId, TraceIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3Flags, "0" }, { 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] [Fact]
@ -130,7 +134,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ B3Format.XB3SpanId, SpanIdBase16 }, { B3Format.XB3SpanId, SpanIdBase16 },
{ B3Format.XB3Sampled, "1" }, { 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] [Fact]
@ -140,7 +145,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3TraceId, TraceIdBase16EightBytes }, { B3Format.XB3SpanId, SpanIdBase16 }, { 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] [Fact]
@ -202,7 +208,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
public void Serialize_SampledContext_SingleHeader() public void Serialize_SampledContext_SingleHeader()
{ {
var carrier = new Dictionary<string, string>(); 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" } }); 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() public void Serialize_NotSampledContext_SingleHeader()
{ {
var carrier = new Dictionary<string, string>(); var carrier = new Dictionary<string, string>();
var context = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
this.output.WriteLine(context.ToString()); this.output.WriteLine(activityContext.ToString());
this.b3FormatSingleHeader.Inject(context, carrier, Setter); this.b3FormatSingleHeader.Inject(new PropagationContext(activityContext, null), carrier, Setter);
this.ContainsExactly(carrier, new Dictionary<string, string> { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}" } }); this.ContainsExactly(carrier, new Dictionary<string, string> { { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}" } });
} }
@ -223,8 +230,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}" }, { B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}" },
}; };
var spanContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None); var activityContext = new ActivityContext(TraceId, SpanId, ActivityTraceFlags.None);
Assert.Equal(spanContext, this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter)); Assert.Equal(new PropagationContext(activityContext, null), this.b3FormatSingleHeader.Extract(default, headersNotSampled, Getter));
} }
[Fact] [Fact]
@ -234,7 +241,10 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-1" }, { 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] [Fact]
@ -244,7 +254,10 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-0" }, { 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] [Fact]
@ -254,7 +267,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-1" }, { 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] [Fact]
@ -264,7 +278,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3Combined, $"{TraceIdBase16}-{SpanIdBase16}-0" }, { 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] [Fact]
@ -274,7 +289,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3Combined, $"{TraceIdBase16EightBytes}-{SpanIdBase16}-1" }, { 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] [Fact]
@ -284,7 +300,8 @@ namespace OpenTelemetry.Context.Propagation.Tests
{ {
{ B3Format.XB3Combined, $"{TraceIdBase16EightBytes}-{SpanIdBase16}" }, { 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] [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 public class CompositePropagatorTest
{ {
private const string TraceParent = "traceparent";
private static readonly string[] Empty = new string[0]; private static readonly string[] Empty = new string[0];
private static readonly Func<IDictionary<string, string>, string, IEnumerable<string>> Getter = (headers, name) => 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); 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 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-1");
Assert.Contains(carrier, kv => kv.Key == "custom-traceparent-2"); Assert.Contains(carrier, kv => kv.Key == "custom-traceparent-2");
bool isInjected = compositePropagator.IsInjected(carrier, Getter);
Assert.True(isInjected);
} }
[Fact] [Fact]
@ -86,24 +84,55 @@ namespace OpenTelemetry.Context.Propagation.Tests
}); });
var activityContext = new ActivityContext(this.traceId, this.spanId, ActivityTraceFlags.Recorded, traceState: null); 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 carrier = new Dictionary<string, string>();
compositePropagator.Inject(activityContext, carrier, Setter); compositePropagator.Inject(propagationContext, carrier, Setter);
Assert.Contains(carrier, kv => kv.Key == "custom-traceparent"); 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. // 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"]); Assert.Equal($"00-{this.traceId}-{this.spanId}-{header02.Split('-').Last()}", carrier["custom-traceparent"]);
bool isInjected = compositePropagator.IsInjected(carrier, Getter);
Assert.True(isInjected);
// resetting counter // resetting counter
count = 0; count = 0;
ActivityContext newContext = compositePropagator.Extract(default, carrier, Getter); compositePropagator.Extract(default, carrier, Getter);
// checking if we accessed only two times: header/headerstate options // 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 // if that's true, we skipped the first one since we have a logic to for the default result
Assert.Equal(2, count); 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 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) if (this.defaultContext)
{ {
return activityContext; return context;
} }
IEnumerable<string> id = getter(carrier, this.idHeaderName); IEnumerable<string> id = getter(carrier, this.idHeaderName);
if (id.Count() <= 0) if (id.Count() <= 0)
{ {
return activityContext; return context;
} }
var traceparentParsed = TraceContextFormat.TryExtractTraceparent(id.First(), out var traceId, out var spanId, out var traceoptions); var traceparentParsed = TraceContextFormat.TryExtractTraceparent(id.First(), out var traceId, out var spanId, out var traceoptions);
if (!traceparentParsed) if (!traceparentParsed)
{ {
return activityContext; return context;
} }
string tracestate = string.Empty; string tracestate = string.Empty;
@ -62,31 +62,25 @@ namespace OpenTelemetry.Context.Propagation.Tests
TraceContextFormat.TryExtractTracestate(tracestateCollection.ToArray(), out tracestate); 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(); 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); traceparent = string.Concat(traceparent, "-", headerNumber);
setter(carrier, this.idHeaderName, traceparent); setter(carrier, this.idHeaderName, traceparent);
string tracestateStr = activityContext.TraceState; string tracestateStr = context.ActivityContext.TraceState;
if (tracestateStr?.Length > 0) if (tracestateStr?.Length > 0)
{ {
setter(carrier, this.stateHeaderName, tracestateStr); 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 f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter); var ctx = f.Extract(default, headers, Getter);
Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.TraceId); Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.ActivityContext.TraceId);
Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.SpanId); Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.ActivityContext.SpanId);
Assert.True(ctx.IsRemote); Assert.True(ctx.ActivityContext.IsRemote);
Assert.True(ctx.IsValid()); Assert.True(ctx.ActivityContext.IsValid());
Assert.True((ctx.TraceFlags & ActivityTraceFlags.Recorded) != 0); 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] [Fact]
@ -76,12 +76,12 @@ namespace OpenTelemetry.Context.Propagation.Tests
var f = new TraceContextFormat(); var f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter); var ctx = f.Extract(default, headers, Getter);
Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.TraceId); Assert.Equal(ActivityTraceId.CreateFromString(TraceId.AsSpan()), ctx.ActivityContext.TraceId);
Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.SpanId); Assert.Equal(ActivitySpanId.CreateFromString(SpanId.AsSpan()), ctx.ActivityContext.SpanId);
Assert.True((ctx.TraceFlags & ActivityTraceFlags.Recorded) == 0); Assert.True((ctx.ActivityContext.TraceFlags & ActivityTraceFlags.Recorded) == 0);
Assert.True(ctx.IsRemote); Assert.True(ctx.ActivityContext.IsRemote);
Assert.True(ctx.IsValid()); Assert.True(ctx.ActivityContext.IsValid());
} }
[Fact] [Fact]
@ -92,7 +92,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
var f = new TraceContextFormat(); var f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter); var ctx = f.Extract(default, headers, Getter);
Assert.False(ctx.IsValid()); Assert.False(ctx.ActivityContext.IsValid());
} }
[Fact] [Fact]
@ -106,7 +106,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
var f = new TraceContextFormat(); var f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter); var ctx = f.Extract(default, headers, Getter);
Assert.False(ctx.IsValid()); Assert.False(ctx.ActivityContext.IsValid());
} }
[Fact] [Fact]
@ -120,7 +120,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
var f = new TraceContextFormat(); var f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter); var ctx = f.Extract(default, headers, Getter);
Assert.Empty(ctx.TraceState); Assert.Null(ctx.ActivityContext.TraceState);
} }
[Fact] [Fact]
@ -135,7 +135,7 @@ namespace OpenTelemetry.Context.Propagation.Tests
var f = new TraceContextFormat(); var f = new TraceContextFormat();
var ctx = f.Extract(default, headers, Getter); 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] [Fact]
@ -149,9 +149,10 @@ namespace OpenTelemetry.Context.Propagation.Tests
}; };
var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, traceState: null); var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, traceState: null);
PropagationContext propagationContext = new PropagationContext(activityContext, null);
var carrier = new Dictionary<string, string>(); var carrier = new Dictionary<string, string>();
var f = new TraceContextFormat(); var f = new TraceContextFormat();
f.Inject(activityContext, carrier, Setter); f.Inject(propagationContext, carrier, Setter);
Assert.Equal(expectedHeaders, carrier); Assert.Equal(expectedHeaders, carrier);
} }
@ -168,9 +169,10 @@ namespace OpenTelemetry.Context.Propagation.Tests
}; };
var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, expectedHeaders[TraceState]); var activityContext = new ActivityContext(traceId, spanId, ActivityTraceFlags.Recorded, expectedHeaders[TraceState]);
PropagationContext propagationContext = new PropagationContext(activityContext, null);
var carrier = new Dictionary<string, string>(); var carrier = new Dictionary<string, string>();
var f = new TraceContextFormat(); var f = new TraceContextFormat();
f.Inject(activityContext, carrier, Setter); f.Inject(propagationContext, carrier, Setter);
Assert.Equal(expectedHeaders, carrier); Assert.Equal(expectedHeaders, carrier);
} }

View File

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

View File

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