Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Support DI for custom transaction processors ([#1993](https://github.com/getsentry/sentry-dotnet/pull/1993))
- Mark Transaction as aborted when unhandled exception occurs ([#1996](https://github.com/getsentry/sentry-dotnet/pull/1996))
- Build Windows and Tizen targets for `Sentry.Maui` ([#2005](https://github.com/getsentry/sentry-dotnet/pull/2005))
- Add Custom Measurements API ([#2013](https://github.com/getsentry/sentry-dotnet/pull/2013))
- Add `ISpan.GetTransaction` convenience method ([#2014](https://github.com/getsentry/sentry-dotnet/pull/2014))

### Fixes
Expand Down
67 changes: 67 additions & 0 deletions src/Sentry/IHasMeasurements.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Sentry.Protocol;

namespace Sentry
{
/// <summary>
/// Interface for transactions that can keep track of measurements.
/// </summary>
/// <remarks>
/// Ideally, this would just be implemented as part of <see cref="ITransactionData"/>.
/// However, adding a property to a public interface is a breaking change. We can do that in a future major version.
/// </remarks>
internal interface IHasMeasurements
{
/// <summary>
/// The measurements that have been set on the transaction.
/// </summary>
IReadOnlyDictionary<string, Measurement> Measurements { get; }

/// <summary>
/// Sets a measurement on the transaction.
/// </summary>
/// <param name="name">The name of the measurement.</param>
/// <param name="measurement">The measurement.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
void SetMeasurement(string name, Measurement measurement);
}

/// <summary>
/// Extensions for <see cref="IHasMeasurements"/>
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class MeasurementExtensions
{
/// <summary>
/// Sets a measurement on the transaction.
/// </summary>
/// <param name="transaction">The transaction.</param>
/// <param name="name">The name of the measurement.</param>
/// <param name="value">The value of the measurement.</param>
/// <param name="unit">The optional unit of the measurement.</param>
public static void SetMeasurement(this ITransactionData transaction, string name, int value,
MeasurementUnit unit = default) =>
(transaction as IHasMeasurements)?.SetMeasurement(name, new Measurement(value, unit));

/// <inheritdoc cref="SetMeasurement(Sentry.ITransactionData,string,int,Sentry.MeasurementUnit)" />
public static void SetMeasurement(this ITransactionData transaction, string name, long value,
MeasurementUnit unit = default) =>
(transaction as IHasMeasurements)?.SetMeasurement(name, new Measurement(value, unit));

/// <inheritdoc cref="SetMeasurement(Sentry.ITransactionData,string,int,Sentry.MeasurementUnit)" />
#if !__MOBILE__
// ulong parameter is not CLS compliant
[CLSCompliant(false)]
#endif
public static void SetMeasurement(this ITransactionData transaction, string name, ulong value,
MeasurementUnit unit = default) =>
(transaction as IHasMeasurements)?.SetMeasurement(name, new Measurement(value, unit));

/// <inheritdoc cref="SetMeasurement(Sentry.ITransactionData,string,int,Sentry.MeasurementUnit)" />
public static void SetMeasurement(this ITransactionData transaction, string name, double value,
MeasurementUnit unit = default) =>
(transaction as IHasMeasurements)?.SetMeasurement(name, new Measurement(value, unit));
}
}
100 changes: 88 additions & 12 deletions src/Sentry/Internal/Extensions/JsonExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,26 @@ public static void Deconstruct(this JsonProperty jsonProperty, out string name,
return result;
}

public static Dictionary<string, TValue>? GetDictionaryOrNull<TValue>(
this JsonElement json,
Func<JsonElement, TValue> factory)
where TValue : IJsonSerializable?
{
if (json.ValueKind != JsonValueKind.Object)
{
return null;
}

var result = new Dictionary<string, TValue>();

foreach (var (name, value) in json.EnumerateObject())
{
result[name] = factory(value);
}

return result;
}

public static Dictionary<string, string?>? GetStringDictionaryOrNull(this JsonElement json)
{
if (json.ValueKind != JsonValueKind.Object)
Expand Down Expand Up @@ -189,6 +209,37 @@ public static void WriteDictionaryValue(
}
}

public static void WriteDictionaryValue<TValue>(
this Utf8JsonWriter writer,
IEnumerable<KeyValuePair<string, TValue>>? dic,
IDiagnosticLogger? logger,
bool includeNullValues = true)
where TValue : IJsonSerializable?
{
if (dic is not null)
{
writer.WriteStartObject();

foreach (var (key, value) in dic)
{
if (value is not null)
{
writer.WriteSerializable(key, value, logger);
}
else if (includeNullValues)
{
writer.WriteNull(key);
}
}

writer.WriteEndObject();
}
else
{
writer.WriteNullValue();
}
}

public static void WriteStringDictionaryValue(
this Utf8JsonWriter writer,
IEnumerable<KeyValuePair<string, string?>>? dic)
Expand Down Expand Up @@ -220,6 +271,17 @@ public static void WriteDictionary(
writer.WriteDictionaryValue(dic, logger);
}

public static void WriteDictionary<TValue>(
this Utf8JsonWriter writer,
string propertyName,
IEnumerable<KeyValuePair<string, TValue>>? dic,
IDiagnosticLogger? logger)
where TValue : IJsonSerializable?
{
writer.WritePropertyName(propertyName);
writer.WriteDictionaryValue(dic, logger);
}

public static void WriteStringDictionary(
this Utf8JsonWriter writer,
string propertyName,
Expand Down Expand Up @@ -552,10 +614,24 @@ public static void WriteDictionaryIfNotEmpty(
IEnumerable<KeyValuePair<string, object?>>? dic,
IDiagnosticLogger? logger)
{
var asDictionary = dic as IReadOnlyDictionary<string, object?> ?? dic?.ToDictionary();
if (asDictionary is not null && asDictionary.Count > 0)
var dictionary = dic as IReadOnlyDictionary<string, object?> ?? dic?.ToDictionary();
if (dictionary is not null && dictionary.Count > 0)
{
writer.WriteDictionary(propertyName, dictionary, logger);
}
}

public static void WriteDictionaryIfNotEmpty<TValue>(
this Utf8JsonWriter writer,
string propertyName,
IEnumerable<KeyValuePair<string, TValue>>? dic,
IDiagnosticLogger? logger)
where TValue : IJsonSerializable?
{
var dictionary = dic as IReadOnlyDictionary<string, TValue> ?? dic?.ToDictionary();
if (dictionary is not null && dictionary.Count > 0)
{
writer.WriteDictionary(propertyName, asDictionary, logger);
writer.WriteDictionary(propertyName, dictionary, logger);
}
}

Expand All @@ -564,10 +640,10 @@ public static void WriteStringDictionaryIfNotEmpty(
string propertyName,
IEnumerable<KeyValuePair<string, string?>>? dic)
{
var asDictionary = dic as IReadOnlyDictionary<string, string?> ?? dic?.ToDictionary();
if (asDictionary is not null && asDictionary.Count > 0)
var dictionary = dic as IReadOnlyDictionary<string, string?> ?? dic?.ToDictionary();
if (dictionary is not null && dictionary.Count > 0)
{
writer.WriteStringDictionary(propertyName, asDictionary);
writer.WriteStringDictionary(propertyName, dictionary);
}
}

Expand All @@ -577,10 +653,10 @@ public static void WriteArrayIfNotEmpty(
IEnumerable<object?>? arr,
IDiagnosticLogger? logger)
{
var asList = arr as IReadOnlyList<object?> ?? arr?.ToArray();
if (asList is not null && asList.Count > 0)
var list = arr as IReadOnlyList<object?> ?? arr?.ToArray();
if (list is not null && list.Count > 0)
{
writer.WriteArray(propertyName, asList, logger);
writer.WriteArray(propertyName, list, logger);
}
}

Expand All @@ -589,10 +665,10 @@ public static void WriteStringArrayIfNotEmpty(
string propertyName,
IEnumerable<string?>? arr)
{
var asList = arr as IReadOnlyList<string?> ?? arr?.ToArray();
if (asList is not null && asList.Count > 0)
var list = arr as IReadOnlyList<string?> ?? arr?.ToArray();
if (list is not null && list.Count > 0)
{
writer.WriteStringArray(propertyName, asList);
writer.WriteStringArray(propertyName, list);
}
}

Expand Down
28 changes: 28 additions & 0 deletions src/Sentry/Internal/Polyfills.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,34 @@ public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
source.Reverse().Skip(count).Reverse();
}
}

namespace System
{
internal static class HashCode
{
public static int Combine<T1, T2>(T1 value1, T2 value2)
{
unchecked
{
var hashCode = value1 != null ? value1.GetHashCode() : 0;
hashCode = (hashCode * 397) ^ (value2 != null ? value2.GetHashCode() : 0);
return hashCode;
}
}

public static int Combine<T1, T2, T3>(T1 value1, T2 value2, T3 value3)
{
unchecked
{
var hashCode = value1 != null ? value1.GetHashCode() : 0;
hashCode = (hashCode * 397) ^ (value2 != null ? value2.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (value3 != null ? value3.GetHashCode() : 0);
return hashCode;
}
}
}
}

#endif

#if NET461
Expand Down
57 changes: 57 additions & 0 deletions src/Sentry/MeasurementUnit.Duration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
namespace Sentry
{
public readonly partial struct MeasurementUnit
{
/// <summary>
/// A time duration unit
/// </summary>
/// <seealso href="https://getsentry.github.io/relay/relay_metrics/enum.DurationUnit.html"/>
public enum Duration
{
/// <summary>
/// Nanosecond unit (10^-9 seconds)
/// </summary>
Nanosecond,

/// <summary>
/// Microsecond unit (10^-6 seconds)
/// </summary>
Microsecond,

/// <summary>
/// Millisecond unit (10^-3 seconds)
/// </summary>
Millisecond,

/// <summary>
/// Second unit
/// </summary>
Second,

/// <summary>
/// Minute unit (60 seconds)
/// </summary>
Minute,

/// <summary>
/// Hour unit (3,600 seconds)
/// </summary>
Hour,

/// <summary>
/// Day unit (86,400 seconds)
/// </summary>
Day,

/// <summary>
/// Week unit (604,800 seconds)
/// </summary>
Week
}

/// <summary>
/// Implicitly casts a <see cref="MeasurementUnit.Duration"/> to a <see cref="MeasurementUnit"/>.
/// </summary>
public static implicit operator MeasurementUnit(Duration unit) => new(unit);
}
}
31 changes: 31 additions & 0 deletions src/Sentry/MeasurementUnit.Fraction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.ComponentModel;

namespace Sentry
{
public readonly partial struct MeasurementUnit
{
/// <summary>
/// A fraction unit
/// </summary>
/// <seealso href="https://getsentry.github.io/relay/relay_metrics/enum.FractionUnit.html"/>
public enum Fraction
{
/// <summary>
/// Floating point fraction of 1.
/// A ratio of 1.0 equals 100%.
/// </summary>
Ratio,

/// <summary>
/// Ratio expressed as a fraction of 100.
/// 100% equals a ratio of 1.0.
/// </summary>
Percent
}

/// <summary>
/// Implicitly casts a <see cref="MeasurementUnit.Fraction"/> to a <see cref="MeasurementUnit"/>.
/// </summary>
public static implicit operator MeasurementUnit(Fraction unit) => new(unit);
}
}
Loading