Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
176 changes: 176 additions & 0 deletions src/Sentry/Protocol/SentryAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using Sentry.Extensibility;

namespace Sentry.Protocol;

internal class SentryAttributes : Dictionary<string, SentryAttribute>, ISentryJsonSerializable
{
public SentryAttributes() : base(StringComparer.Ordinal)
{
}

public SentryAttributes(int capacity) : base(capacity, StringComparer.Ordinal)
{
}

/// <summary>
/// Gets the attribute value associated with the specified key.
/// </summary>
/// <remarks>
/// Returns <see langword="true"/> if this <see cref="SentryMetric"/> contains an attribute with the specified key which is of type <typeparamref name="TAttribute"/> and it's value is not <see langword="null"/>.
/// Otherwise <see langword="false"/>.
/// Supported types:
/// <list type="table">
/// <listheader>
/// <term>Type</term>
/// <description>Range</description>
/// </listheader>
/// <item>
/// <term>string</term>
/// <description><see langword="string"/> and <see langword="char"/></description>
/// </item>
/// <item>
/// <term>boolean</term>
/// <description><see langword="false"/> and <see langword="true"/></description>
/// </item>
/// <item>
/// <term>integer</term>
/// <description>64-bit signed integral numeric types</description>
/// </item>
/// <item>
/// <term>double</term>
/// <description>64-bit floating-point numeric types</description>
/// </item>
/// </list>
/// Unsupported types:
/// <list type="table">
/// <listheader>
/// <term>Type</term>
/// <description>Result</description>
/// </listheader>
/// <item>
/// <term><see langword="object"/></term>
/// <description><c>ToString</c> as <c>"type": "string"</c></description>
/// </item>
/// <item>
/// <term>Collections</term>
/// <description><c>ToString</c> as <c>"type": "string"</c></description>
/// </item>
/// <item>
/// <term><see langword="null"/></term>
/// <description>ignored</description>
/// </item>
/// </list>
/// </remarks>
/// <seealso href="https://develop.sentry.dev/sdk/telemetry/metrics/"/>
public bool TryGetAttribute<TAttribute>(string key, [MaybeNullWhen(false)] out TAttribute value)
{
if (TryGetValue(key, out var attribute) && attribute.Value is TAttribute attributeValue)
{
value = attributeValue;
return true;
}

value = default;
return false;
}

/// <summary>
/// Set a key-value pair of data attached to the metric.
/// </summary>
public void SetAttribute<TAttribute>(string key, TAttribute value) where TAttribute : notnull
{
if (value is null)
{
return;
}

this[key] = new SentryAttribute(value);
}

internal void SetAttribute(string key, string value)
{
this[key] = new SentryAttribute(value, "string");
}

internal void SetAttribute(string key, char value)
{
this[key] = new SentryAttribute(value.ToString(), "string");
}

internal void SetAttribute(string key, int value)
{
this[key] = new SentryAttribute(value, "integer");
}

internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk)
{
var environment = options.SettingLocator.GetEnvironment();
SetAttribute("sentry.environment", environment);

var release = options.SettingLocator.GetRelease();
if (release is not null)
{
SetAttribute("sentry.release", release);
}

if (sdk.Name is { } name)
{
SetAttribute("sentry.sdk.name", name);
}
if (sdk.Version is { } version)
{
SetAttribute("sentry.sdk.version", version);
}
}

internal void SetAttributes(IEnumerable<KeyValuePair<string, object>>? attributes)
{
if (attributes is null)
{
return;
}

#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
if (attributes.TryGetNonEnumeratedCount(out var count))
{
_ = EnsureCapacity(Count + count);
}
#endif

foreach (var attribute in attributes)
{
this[attribute.Key] = new SentryAttribute(attribute.Value);
}
}

internal void SetAttributes(ReadOnlySpan<KeyValuePair<string, object>> attributes)
{
if (attributes.IsEmpty)
{
return;
}

#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
_ = EnsureCapacity(Count + attributes.Length);
#endif

foreach (var attribute in attributes)
{
this[attribute.Key] = new SentryAttribute(attribute.Value);
}
}

/// <inheritdoc cref="ISentryJsonSerializable.WriteTo(Utf8JsonWriter, IDiagnosticLogger)" />
public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
writer.WritePropertyName("attributes");
writer.WriteStartObject();

foreach (var attribute in this)
{
SentryAttributeSerializer.WriteAttribute(writer, attribute.Key, attribute.Value, logger);
}

writer.WriteEndObject();
}
}
6 changes: 3 additions & 3 deletions src/Sentry/SentryMetric.Factory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@ private static SentryMetric<T> CreateCore<T>(IHub hub, SentryOptions options, IS
};

scope ??= hub.GetScope();
metric.SetDefaultAttributes(options, scope?.Sdk ?? SdkVersion.Instance);
metric.Attributes.SetDefaultAttributes(options, scope?.Sdk ?? SdkVersion.Instance);

return metric;
}

internal static SentryMetric<T> Create<T>(IHub hub, SentryOptions options, ISystemClock clock, SentryMetricType type, string name, T value, string? unit, IEnumerable<KeyValuePair<string, object>>? attributes, Scope? scope) where T : struct
{
var metric = CreateCore<T>(hub, options, clock, type, name, value, unit, scope);
metric.SetAttributes(attributes);
metric.Attributes.SetAttributes(attributes);
return metric;
}

internal static SentryMetric<T> Create<T>(IHub hub, SentryOptions options, ISystemClock clock, SentryMetricType type, string name, T value, string? unit, ReadOnlySpan<KeyValuePair<string, object>> attributes, Scope? scope) where T : struct
{
var metric = CreateCore<T>(hub, options, clock, type, name, value, unit, scope);
metric.SetAttributes(attributes);
metric.Attributes.SetAttributes(attributes);
return metric;
}

Expand Down
109 changes: 6 additions & 103 deletions src/Sentry/SentryMetric.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ namespace Sentry;
[DebuggerDisplay(@"SentryMetric \{ Type = {Type}, Name = '{Name}', Value = {Value} \}")]
public abstract partial class SentryMetric
{
private readonly Dictionary<string, SentryAttribute> _attributes;

[SetsRequiredMembers]
private protected SentryMetric(DateTimeOffset timestamp, SentryId traceId, SentryMetricType type, string name)
{
Expand All @@ -24,7 +22,7 @@ private protected SentryMetric(DateTimeOffset timestamp, SentryId traceId, Sentr
Type = type;
Name = name;
// 7 is the number of built-in attributes, so we start with that.
_attributes = new Dictionary<string, SentryAttribute>(7);
Attributes = new SentryAttributes(7);
}

/// <summary>
Expand Down Expand Up @@ -114,6 +112,8 @@ private protected SentryMetric(DateTimeOffset timestamp, SentryId traceId, Sentr
/// </remarks>
public string? Unit { get; init; }

internal SentryAttributes Attributes { get; }

/// <summary>
/// Gets the metric value if it is of the specified type <typeparamref name="TValue"/>.
/// </summary>
Expand Down Expand Up @@ -174,102 +174,13 @@ private protected SentryMetric(DateTimeOffset timestamp, SentryId traceId, Sentr
/// </remarks>
/// <seealso href="https://develop.sentry.dev/sdk/telemetry/metrics/"/>
public bool TryGetAttribute<TAttribute>(string key, [MaybeNullWhen(false)] out TAttribute value)
{
if (_attributes.TryGetValue(key, out var attribute) && attribute.Value is TAttribute attributeValue)
{
value = attributeValue;
return true;
}

value = default;
return false;
}
=> Attributes.TryGetAttribute(key, out value);

/// <summary>
/// Set a key-value pair of data attached to the metric.
/// </summary>
public void SetAttribute<TAttribute>(string key, TAttribute value) where TAttribute : notnull
{
if (value is null)
{
return;
}

_attributes[key] = new SentryAttribute(value);
}

internal void SetAttribute(string key, string value)
{
_attributes[key] = new SentryAttribute(value, "string");
}

internal void SetAttribute(string key, char value)
{
_attributes[key] = new SentryAttribute(value.ToString(), "string");
}

internal void SetAttribute(string key, int value)
{
_attributes[key] = new SentryAttribute(value, "integer");
}

internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk)
{
var environment = options.SettingLocator.GetEnvironment();
SetAttribute("sentry.environment", environment);

var release = options.SettingLocator.GetRelease();
if (release is not null)
{
SetAttribute("sentry.release", release);
}

if (sdk.Name is { } name)
{
SetAttribute("sentry.sdk.name", name);
}
if (sdk.Version is { } version)
{
SetAttribute("sentry.sdk.version", version);
}
}

internal void SetAttributes(IEnumerable<KeyValuePair<string, object>>? attributes)
{
if (attributes is null)
{
return;
}

#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
if (attributes.TryGetNonEnumeratedCount(out var count))
{
_ = _attributes.EnsureCapacity(_attributes.Count + count);
}
#endif

foreach (var attribute in attributes)
{
_attributes[attribute.Key] = new SentryAttribute(attribute.Value);
}
}

internal void SetAttributes(ReadOnlySpan<KeyValuePair<string, object>> attributes)
{
if (attributes.IsEmpty)
{
return;
}

#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
_ = _attributes.EnsureCapacity(_attributes.Count + attributes.Length);
#endif

foreach (var attribute in attributes)
{
_attributes[attribute.Key] = new SentryAttribute(attribute.Value);
}
}
=> Attributes.SetAttribute(key, value);

/// <inheritdoc cref="ISentryJsonSerializable.WriteTo(Utf8JsonWriter, IDiagnosticLogger)" />
internal void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
Expand Down Expand Up @@ -300,15 +211,7 @@ internal void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
writer.WriteString("unit", Unit);
}

writer.WritePropertyName("attributes");
writer.WriteStartObject();

foreach (var attribute in _attributes)
{
SentryAttributeSerializer.WriteAttribute(writer, attribute.Key, attribute.Value, logger);
}

writer.WriteEndObject();
Attributes.WriteTo(writer, logger);

writer.WriteEndObject();
}
Expand Down
16 changes: 16 additions & 0 deletions test/Sentry.Testing/SentryAttributesExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Sentry.Testing;

public static class SentryAttributesExtensions
{
internal static void ShouldContain<T>(this SentryAttributes attributes, string key, T expected)
{
attributes.TryGetAttribute<T>(key, out var value).Should().BeTrue();
value.Should().Be(expected);
}

internal static void ShouldNotContain<T>(this SentryAttributes attributes, string key, T expected)
{
attributes.TryGetAttribute<T>(key, out var value).Should().BeFalse();
value.Should().Be(default(T));
}
}
Loading
Loading