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

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

writer.WriteEndObject();
}
}
77 changes: 13 additions & 64 deletions src/Sentry/SentryLog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Sentry;
[DebuggerDisplay(@"SentryLog \{ Level = {Level}, Message = '{Message}' \}")]
public sealed class SentryLog
{
private readonly Dictionary<string, SentryAttribute> _attributes;
private readonly SentryAttributes _attributes;

[SetsRequiredMembers]
internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentryLogLevel level, string message)
Expand All @@ -24,7 +24,7 @@ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentryLogLevel le
Level = level;
Message = message;
// 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 @@ -115,78 +115,27 @@ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentryLogLevel le
/// </list>
/// </remarks>
/// <seealso href="https://develop.sentry.dev/sdk/telemetry/logs/"/>
public bool TryGetAttribute(string key, [NotNullWhen(true)] out object? value)
{
if (_attributes.TryGetValue(key, out var attribute) && attribute.Value is not null)
{
value = attribute.Value;
return true;
}

value = null;
return false;
}
public bool TryGetAttribute(string key, [NotNullWhen(true)] out object? value) =>
_attributes.TryGetAttribute(key, out value);

internal bool TryGetAttribute(string key, [NotNullWhen(true)] out string? value)
{
if (_attributes.TryGetValue(key, out var attribute) && attribute.Type == "string" && attribute.Value is not null)
{
value = (string)attribute.Value;
return true;
}

value = null;
return false;
}
internal bool TryGetAttribute(string key, [NotNullWhen(true)] out string? value) =>
_attributes.TryGetAttribute(key, out value);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Dropped Type metadata check in SentryLog.TryGetAttribute

Low Severity

The internal SentryLog.TryGetAttribute(string, out string?) previously checked attribute.Type == "string" (metadata-based), but the new delegation to SentryAttributes.TryGetAttribute<string> only checks attribute.Value is string (runtime type). This means attributes stored via the public SetAttribute(string, object) with a string value (which sets Type = null) will now be returned by the string overload, where before they would not. While likely an improvement, it's a silent semantic change that could affect callers relying on the stricter Type-based filtering.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@Flash0ver what do you think about this? Do you want to restore the previous code/behaviour or OK with this?


/// <summary>
/// Set a key-value pair of data attached to the log.
/// </summary>
public void SetAttribute(string key, object value)
{
_attributes[key] = new SentryAttribute(value);
}

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

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

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

internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk)
{
var environment = options.SettingLocator.GetEnvironment();
SetAttribute("sentry.environment", environment);
internal void SetAttribute(string key, int value) => _attributes.SetAttribute(key, value);

var release = options.SettingLocator.GetRelease();
if (release is not null)
{
SetAttribute("sentry.release", release);
}
internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk) =>
_attributes.SetDefaultAttributes(options, sdk);

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

internal void SetOrigin(string origin)
{
SetAttribute("sentry.origin", origin);
}
internal void SetOrigin(string origin) => SetAttribute("sentry.origin", origin);

internal void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
Expand Down
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
Loading
Loading