Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ea8459b
Baggage context part 1.
CodeBlanch Aug 20, 2020
804746a
A little cleanup.
CodeBlanch Aug 20, 2020
d26ec72
Merge branch 'master' into baggage-context
cijothomas Aug 20, 2020
cfef67a
Return strings instead of objects. Removed default value in ctor beca…
CodeBlanch Aug 20, 2020
e9e82ba
Merge branch 'baggage-context' of https://github.com/CodeBlanch/opent…
CodeBlanch Aug 20, 2020
6b6598a
Updated SDK & API projects for BaggageContext.
CodeBlanch Aug 21, 2020
b9a3b31
Fixed broken areas, except for CorrelationContext tests.
CodeBlanch Aug 21, 2020
06e0a2e
First round of tests and bug fixes.
CodeBlanch Aug 21, 2020
cea26fb
Merging latest from master.
CodeBlanch Aug 22, 2020
666655f
FIxed up HTTP instrumentation tests.
CodeBlanch Aug 22, 2020
b584536
Fixed up shim tests.
CodeBlanch Aug 22, 2020
9c532c0
More tests and improvements.
CodeBlanch Aug 22, 2020
be923f2
Merging from master.
CodeBlanch Aug 25, 2020
4f1758a
Fixed broken test.
CodeBlanch Aug 25, 2020
3e6d3bd
Test coverage and bug fixes.
CodeBlanch Aug 25, 2020
6955142
Deterministic GetHashCode + tests.
CodeBlanch Aug 26, 2020
144093f
Merging latest from master.
CodeBlanch Aug 26, 2020
6ced696
Added a link to the baggage api.
CodeBlanch Aug 26, 2020
abe67ae
Updated CHANGELOG.
CodeBlanch Aug 26, 2020
f7a1528
Renamed BaggageContext -> Baggage.
CodeBlanch Aug 26, 2020
32ccef4
CHANGELOG update.
CodeBlanch Aug 26, 2020
3c89146
Merge branch 'master' into baggage-context
CodeBlanch Aug 27, 2020
d2bf842
Merge branch 'master' into baggage-context
cijothomas Aug 27, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System.Diagnostics;
using System.Text;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Context.Propagation;
using RabbitMQ.Client;

Expand Down Expand Up @@ -61,7 +62,7 @@ public string SendMessage()
if (activity != null)
{
// Inject the ActivityContext into the message headers to propagate trace context to the receiving service.
TextFormat.Inject(new PropagationContext(activity.Context, activity.Baggage), props, this.InjectTraceContextIntoBasicProperties);
TextFormat.Inject(new PropagationContext(activity.Context, Baggage.Current), props, this.InjectTraceContextIntoBasicProperties);

// The OpenTelemetry messaging specification defines a number of attributes. These attributes are added here.
RabbitMqHelper.AddMessagingTags(activity);
Expand Down
305 changes: 305 additions & 0 deletions src/OpenTelemetry.Api/Baggage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
// <copyright file="Baggage.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 OpenTelemetry.Context;

namespace OpenTelemetry
{
/// <summary>
/// Baggage implementation.
/// </summary>
/// <remarks>
/// Spec reference: <a href="https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/baggage/api.md">Baggage API</a>.
/// </remarks>
public readonly struct Baggage : IEquatable<Baggage>
{
private static readonly RuntimeContextSlot<Baggage> RuntimeContextSlot = RuntimeContext.RegisterSlot<Baggage>("otel.baggage");
private static readonly Dictionary<string, string> EmptyBaggage = new Dictionary<string, string>();

private readonly Dictionary<string, string> baggage;

/// <summary>
/// Initializes a new instance of the <see cref="Baggage"/> struct.
/// </summary>
/// <param name="baggage">Baggage key/value pairs.</param>
internal Baggage(Dictionary<string, string> baggage)
{
this.baggage = baggage;
}

/// <summary>
/// Gets or sets the current <see cref="Baggage"/>.
/// </summary>
public static Baggage Current
{
get => RuntimeContextSlot.Get();
set => RuntimeContextSlot.Set(value);
}

/// <summary>
/// Gets the number of key/value pairs in the baggage.
/// </summary>
public int Count => this.baggage?.Count ?? 0;

/// <summary>
/// Compare two entries of <see cref="Baggage"/> for equality.
/// </summary>
/// <param name="left">First Entry to compare.</param>
/// <param name="right">Second Entry to compare.</param>
public static bool operator ==(Baggage left, Baggage right) => left.Equals(right);

/// <summary>
/// Compare two entries of <see cref="Baggage"/> for not equality.
/// </summary>
/// <param name="left">First Entry to compare.</param>
/// <param name="right">Second Entry to compare.</param>
public static bool operator !=(Baggage left, Baggage right) => !(left == right);

/// <summary>
/// Create a <see cref="Baggage"/> instance from dictionary of baggage key/value pairs.
/// </summary>
/// <param name="baggageItems">Baggage key/value pairs.</param>
/// <returns><see cref="Baggage"/>.</returns>
public static Baggage Create(Dictionary<string, string> baggageItems = null)
{
if (baggageItems == null)
{
return default;
}

Dictionary<string, string> baggageCopy = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, string> baggageItem in baggageItems)
{
if (string.IsNullOrEmpty(baggageItem.Value))
{
baggageCopy.Remove(baggageItem.Key);
continue;
}

baggageCopy[baggageItem.Key] = baggageItem.Value;
}

return new Baggage(baggageCopy);
}

/// <summary>
/// Returns the name/value pairs in the <see cref="Baggage"/>.
/// </summary>
/// <param name="baggage">Optional <see cref="Baggage"/>. <see cref="Current"/> is used if not specified.</param>
/// <returns>Baggage key/value pairs.</returns>
public static IReadOnlyDictionary<string, string> GetBaggage(Baggage baggage = default)
=> baggage == default ? Current.GetBaggage() : baggage.GetBaggage();

/// <summary>
/// Returns an enumerator that iterates through the <see cref="Baggage"/>.
/// </summary>
/// <param name="baggage">Optional <see cref="Baggage"/>. <see cref="Current"/> is used if not specified.</param>
/// <returns><see cref="Dictionary{TKey, TValue}.Enumerator"/>.</returns>
public static Dictionary<string, string>.Enumerator GetEnumerator(Baggage baggage = default)
=> baggage == default ? Current.GetEnumerator() : baggage.GetEnumerator();

/// <summary>
/// Returns the value associated with the given name, or <see langword="null"/> if the given name is not present.
/// </summary>
/// <param name="name">Baggage item name.</param>
/// <param name="baggage">Optional <see cref="Baggage"/>. <see cref="Current"/> is used if not specified.</param>
/// <returns>Baggage item or <see langword="null"/> if nothing was found.</returns>
public static string GetBaggage(string name, Baggage baggage = default)
=> baggage == default ? Current.GetBaggage(name) : baggage.GetBaggage(name);

/// <summary>
/// Returns a new <see cref="Baggage"/> which contains the new key/value pair.
/// </summary>
/// <param name="name">Baggage item name.</param>
/// <param name="value">Baggage item value.</param>
/// <param name="baggage">Optional <see cref="Baggage"/>. <see cref="Current"/> is used if not specified.</param>
/// <returns>New <see cref="Baggage"/> containing the key/value pair.</returns>
public static Baggage SetBaggage(string name, string value, Baggage baggage = default)
=> baggage == default ? Current.SetBaggage(name, value) : baggage.SetBaggage(name, value);

/// <summary>
/// Returns a new <see cref="Baggage"/> which contains the new key/value pair.
/// </summary>
/// <param name="baggageItems">Baggage key/value pairs.</param>
/// <param name="baggage">Optional <see cref="Baggage"/>. <see cref="Current"/> is used if not specified.</param>
/// <returns>New <see cref="Baggage"/> containing the key/value pair.</returns>
public static Baggage SetBaggage(IEnumerable<KeyValuePair<string, string>> baggageItems, Baggage baggage = default)
=> baggage == default ? Current.SetBaggage(baggageItems) : baggage.SetBaggage(baggageItems);

/// <summary>
/// Returns a new <see cref="Baggage"/> with the key/value pair removed.
/// </summary>
/// <param name="name">Baggage item name.</param>
/// <param name="baggage">Optional <see cref="Baggage"/>. <see cref="Current"/> is used if not specified.</param>
/// <returns>New <see cref="Baggage"/> containing the key/value pair.</returns>
public static Baggage RemoveBaggage(string name, Baggage baggage = default)
=> baggage == default ? Current.RemoveBaggage(name) : baggage.RemoveBaggage(name);

/// <summary>
/// Returns a new <see cref="Baggage"/> with all the key/value pairs removed.
/// </summary>
/// <param name="baggage">Optional <see cref="Baggage"/>. <see cref="Current"/> is used if not specified.</param>
/// <returns>New <see cref="Baggage"/> containing the key/value pair.</returns>
public static Baggage ClearBaggage(Baggage baggage = default)
=> baggage == default ? Current.ClearBaggage() : baggage.ClearBaggage();

/// <summary>
/// Returns the name/value pairs in the <see cref="Baggage"/>.
/// </summary>
/// <returns>Baggage key/value pairs.</returns>
public IReadOnlyDictionary<string, string> GetBaggage()
=> this.baggage ?? EmptyBaggage;

/// <summary>
/// Returns the value associated with the given name, or <see langword="null"/> if the given name is not present.
/// </summary>
/// <param name="name">Baggage item name.</param>
/// <returns>Baggage item or <see langword="null"/> if nothing was found.</returns>
public string GetBaggage(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentNullException(nameof(name));
}

return this.baggage != null && this.baggage.TryGetValue(name, out string value)
? value
: null;
}

/// <summary>
/// Returns a new <see cref="Baggage"/> which contains the new key/value pair.
/// </summary>
/// <param name="name">Baggage item name.</param>
/// <param name="value">Baggage item value.</param>
/// <returns>New <see cref="Baggage"/> containing the key/value pair.</returns>
public Baggage SetBaggage(string name, string value)
{
if (string.IsNullOrEmpty(value))
{
return this.RemoveBaggage(name);
}

return Current = new Baggage(
new Dictionary<string, string>(this.baggage ?? EmptyBaggage, StringComparer.OrdinalIgnoreCase)
{
[name] = value,
});
}

/// <summary>
/// Returns a new <see cref="Baggage"/> which contains the new key/value pair.
/// </summary>
/// <param name="baggageItems">Baggage key/value pairs.</param>
/// <returns>New <see cref="Baggage"/> containing the key/value pair.</returns>
public Baggage SetBaggage(params KeyValuePair<string, string>[] baggageItems)
=> this.SetBaggage((IEnumerable<KeyValuePair<string, string>>)baggageItems);

/// <summary>
/// Returns a new <see cref="Baggage"/> which contains the new key/value pair.
/// </summary>
/// <param name="baggageItems">Baggage key/value pairs.</param>
/// <returns>New <see cref="Baggage"/> containing the key/value pair.</returns>
public Baggage SetBaggage(IEnumerable<KeyValuePair<string, string>> baggageItems)
{
if ((baggageItems?.Count() ?? 0) <= 0)
{
return this;
}

var newBaggage = new Dictionary<string, string>(this.baggage ?? EmptyBaggage, StringComparer.OrdinalIgnoreCase);

foreach (var item in baggageItems)
{
if (string.IsNullOrEmpty(item.Value))
{
newBaggage.Remove(item.Key);
}
else
{
newBaggage[item.Key] = item.Value;
}
}

return Current = new Baggage(newBaggage);
}

/// <summary>
/// Returns a new <see cref="Baggage"/> with the key/value pair removed.
/// </summary>
/// <param name="name">Baggage item name.</param>
/// <returns>New <see cref="Baggage"/> containing the key/value pair.</returns>
public Baggage RemoveBaggage(string name)
{
var baggage = new Dictionary<string, string>(this.baggage ?? EmptyBaggage, StringComparer.OrdinalIgnoreCase);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: if the key does not exist, we might be able to return the same baggage?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it is not important at this stage, and we probably will replace dictionary with a better data structure before GA anyways.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I'll leave as is. I did consider that case. I figured if user is calling RemoveBaggage, they probably know it exists. So I didn't want to waste perf doing a hash + lookup where in most cases it would be wasteful? My thinking could be off tho.

baggage.Remove(name);

return Current = new Baggage(baggage);
}

/// <summary>
/// Returns a new <see cref="Baggage"/> with all the key/value pairs removed.
/// </summary>
/// <returns>New <see cref="Baggage"/> containing the key/value pair.</returns>
public Baggage ClearBaggage()
=> Current = default;

/// <summary>
/// Returns an enumerator that iterates through the <see cref="Baggage"/>.
/// </summary>
/// <returns><see cref="Dictionary{TKey, TValue}.Enumerator"/>.</returns>
public Dictionary<string, string>.Enumerator GetEnumerator()
=> (this.baggage ?? EmptyBaggage).GetEnumerator();

/// <inheritdoc/>
public bool Equals(Baggage other)
{
bool baggageIsNullOrEmpty = this.baggage == null || this.baggage.Count <= 0;

if (baggageIsNullOrEmpty != (other.baggage == null || other.baggage.Count <= 0))
{
return false;
}

return baggageIsNullOrEmpty || this.baggage.SequenceEqual(other.baggage);
}

/// <inheritdoc/>
public override bool Equals(object obj)
=> (obj is Baggage baggage) && this.Equals(baggage);

/// <inheritdoc/>
public override int GetHashCode()
{
var baggage = this.baggage ?? EmptyBaggage;

unchecked
{
int res = 17;
foreach (var item in baggage)
{
res = (res * 23) + baggage.Comparer.GetHashCode(item.Key);
res = (res * 23) + item.Value.GetHashCode();
}

return res;
}
}
}
}
5 changes: 5 additions & 0 deletions src/OpenTelemetry.Api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
* Changed `StartSpan` to not set the created span as Active to match the spec
([#994](https://github.com/open-telemetry/opentelemetry-dotnet/pull/994))
* Updated System.Diagnostics.DiagnosticSource to version 5.0.0-preview.8.20407.11.
* Removed `CorrelationContext` and added `Baggage`, an implementation of the
[`Baggage
API`](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/baggage/api.md)
spec
([#1106](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1106))

## 0.4.0-beta.2

Expand Down
Loading