Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
12 changes: 12 additions & 0 deletions benchmarks/Sentry.Benchmarks/SpanIdBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using BenchmarkDotNet.Attributes;

namespace Sentry.Benchmarks;

public class SpanIdBenchmarks
{
[Benchmark(Description = "Creates a Span ID")]
public void CreateSpanId()
{
SpanId.Create();
}
}
75 changes: 31 additions & 44 deletions src/Sentry/SpanId.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Sentry.Extensibility;
using Sentry.Internal;

namespace Sentry;

Expand All @@ -7,58 +8,60 @@ namespace Sentry;
/// </summary>
public readonly struct SpanId : IEquatable<SpanId>, IJsonSerializable
{
private readonly string _value;
private static readonly RandomValuesFactory Random = new SynchronizedRandomValuesFactory();

private const string EmptyValue = "0000000000000000";
private readonly long _value;

private long GetValue() => _value;

/// <summary>
/// An empty Sentry span ID.
/// </summary>
public static readonly SpanId Empty = new(EmptyValue);
public static readonly SpanId Empty = new(0);

/// <summary>
/// Creates a new instance of a Sentry span Id.
/// </summary>
public SpanId(string value) => _value = value;

// This method is used to return a string with all zeroes in case
// the `_value` is equal to null.
// It can be equal to null because this is a struct and always has
// a parameterless constructor that evaluates to an instance with
// all fields initialized to default values.
// Effectively, using this method instead of just referencing `_value`
// makes the behavior more consistent, for example:
// default(SpanId).ToString() -> "0000000000000000"
// default(SpanId) == SpanId.Empty -> true
private string GetNormalizedValue() => !string.IsNullOrWhiteSpace(_value)
? _value
: EmptyValue;
// TODO: mark as internal in version 4
public SpanId(string value) => long.TryParse(value, NumberStyles.HexNumber, null, out _value);

/// <summary>
/// Creates a new instance of a Sentry span Id.
/// </summary>
/// <param name="value"></param>
public SpanId(long value) => _value = value;

/// <inheritdoc />
public bool Equals(SpanId other) => StringComparer.Ordinal.Equals(
GetNormalizedValue(),
other.GetNormalizedValue());
public bool Equals(SpanId other) => GetValue() == other.GetValue();

/// <inheritdoc />
public override bool Equals(object? obj) => obj is SpanId other && Equals(other);

/// <inheritdoc />
public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(GetNormalizedValue());
public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(_value);

/// <inheritdoc />
public override string ToString() => GetNormalizedValue();
public override string ToString() => _value.ToString("x8");

// Note: spans are sentry IDs with only 16 characters, rest being truncated.
// This is obviously a bad idea as it invalidates GUID's uniqueness properties
// (https://devblogs.microsoft.com/oldnewthing/20080627-00/?p=21823)
// but all other SDKs do it this way, so we have no choice but to comply.
/// <summary>
/// Generates a new Sentry ID.
/// </summary>
public static SpanId Create() => new(Guid.NewGuid().ToString("n")[..16]);
public static SpanId Create()
{
var buf = new byte[8];
long random;

do
{
Random.NextBytes(buf);
random = BitConverter.ToInt64(buf, 0);
} while (random == 0);

return new SpanId(random);
}

/// <inheritdoc />
public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? _) => writer.WriteStringValue(GetNormalizedValue());
public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? _) => writer.WriteStringValue(ToString());

/// <summary>
/// Parses from string.
Expand Down Expand Up @@ -91,20 +94,4 @@ public static SpanId FromJson(JsonElement json)
/// The <see cref="Guid"/> from the <see cref="SentryId"/>.
/// </summary>
public static implicit operator string(SpanId id) => id.ToString();

// Note: no implicit conversion from `string` to `SpanId` as that leads to serious bugs.
// For example, given a method:
// transaction.StartChild(SpanId parentSpanId, string operation)
// And an *extension* method:
// transaction.StartChild(string operation, string description)
// The following code:
// transaction.StartChild("foo", "bar")
// Will resolve to the first method and not the second, which is incorrect.

/*
/// <summary>
/// A <see cref="SentryId"/> from a <see cref="Guid"/>.
/// </summary>
public static implicit operator SpanId(string value) => new(value);
*/
}
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,7 @@ namespace Sentry
public readonly struct SpanId : Sentry.IJsonSerializable, System.IEquatable<Sentry.SpanId>
{
public static readonly Sentry.SpanId Empty;
public SpanId(long value) { }
public SpanId(string value) { }
public bool Equals(Sentry.SpanId other) { }
public override bool Equals(object? obj) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,7 @@ namespace Sentry
public readonly struct SpanId : Sentry.IJsonSerializable, System.IEquatable<Sentry.SpanId>
{
public static readonly Sentry.SpanId Empty;
public SpanId(long value) { }
public SpanId(string value) { }
public bool Equals(Sentry.SpanId other) { }
public override bool Equals(object? obj) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,7 @@ namespace Sentry
public readonly struct SpanId : Sentry.IJsonSerializable, System.IEquatable<Sentry.SpanId>
{
public static readonly Sentry.SpanId Empty;
public SpanId(long value) { }
public SpanId(string value) { }
public bool Equals(Sentry.SpanId other) { }
public override bool Equals(object? obj) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,7 @@ namespace Sentry
public readonly struct SpanId : Sentry.IJsonSerializable, System.IEquatable<Sentry.SpanId>
{
public static readonly Sentry.SpanId Empty;
public SpanId(long value) { }
public SpanId(string value) { }
public bool Equals(Sentry.SpanId other) { }
public override bool Equals(object? obj) { }
Expand Down