diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e202b4e15..99c8d7c897 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/src/Sentry/IHasMeasurements.cs b/src/Sentry/IHasMeasurements.cs
new file mode 100644
index 0000000000..ec5fab8003
--- /dev/null
+++ b/src/Sentry/IHasMeasurements.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Sentry.Protocol;
+
+namespace Sentry
+{
+ ///
+ /// Interface for transactions that can keep track of measurements.
+ ///
+ ///
+ /// Ideally, this would just be implemented as part of .
+ /// However, adding a property to a public interface is a breaking change. We can do that in a future major version.
+ ///
+ internal interface IHasMeasurements
+ {
+ ///
+ /// The measurements that have been set on the transaction.
+ ///
+ IReadOnlyDictionary Measurements { get; }
+
+ ///
+ /// Sets a measurement on the transaction.
+ ///
+ /// The name of the measurement.
+ /// The measurement.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ void SetMeasurement(string name, Measurement measurement);
+ }
+
+ ///
+ /// Extensions for
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static class MeasurementExtensions
+ {
+ ///
+ /// Sets a measurement on the transaction.
+ ///
+ /// The transaction.
+ /// The name of the measurement.
+ /// The value of the measurement.
+ /// The optional unit of the measurement.
+ public static void SetMeasurement(this ITransactionData transaction, string name, int value,
+ MeasurementUnit unit = default) =>
+ (transaction as IHasMeasurements)?.SetMeasurement(name, new Measurement(value, unit));
+
+ ///
+ public static void SetMeasurement(this ITransactionData transaction, string name, long value,
+ MeasurementUnit unit = default) =>
+ (transaction as IHasMeasurements)?.SetMeasurement(name, new Measurement(value, unit));
+
+ ///
+#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));
+
+ ///
+ public static void SetMeasurement(this ITransactionData transaction, string name, double value,
+ MeasurementUnit unit = default) =>
+ (transaction as IHasMeasurements)?.SetMeasurement(name, new Measurement(value, unit));
+ }
+}
diff --git a/src/Sentry/Internal/Extensions/JsonExtensions.cs b/src/Sentry/Internal/Extensions/JsonExtensions.cs
index b3e811f933..26e32545c3 100644
--- a/src/Sentry/Internal/Extensions/JsonExtensions.cs
+++ b/src/Sentry/Internal/Extensions/JsonExtensions.cs
@@ -52,6 +52,26 @@ public static void Deconstruct(this JsonProperty jsonProperty, out string name,
return result;
}
+ public static Dictionary? GetDictionaryOrNull(
+ this JsonElement json,
+ Func factory)
+ where TValue : IJsonSerializable?
+ {
+ if (json.ValueKind != JsonValueKind.Object)
+ {
+ return null;
+ }
+
+ var result = new Dictionary();
+
+ foreach (var (name, value) in json.EnumerateObject())
+ {
+ result[name] = factory(value);
+ }
+
+ return result;
+ }
+
public static Dictionary? GetStringDictionaryOrNull(this JsonElement json)
{
if (json.ValueKind != JsonValueKind.Object)
@@ -189,6 +209,37 @@ public static void WriteDictionaryValue(
}
}
+ public static void WriteDictionaryValue(
+ this Utf8JsonWriter writer,
+ IEnumerable>? 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>? dic)
@@ -220,6 +271,17 @@ public static void WriteDictionary(
writer.WriteDictionaryValue(dic, logger);
}
+ public static void WriteDictionary(
+ this Utf8JsonWriter writer,
+ string propertyName,
+ IEnumerable>? dic,
+ IDiagnosticLogger? logger)
+ where TValue : IJsonSerializable?
+ {
+ writer.WritePropertyName(propertyName);
+ writer.WriteDictionaryValue(dic, logger);
+ }
+
public static void WriteStringDictionary(
this Utf8JsonWriter writer,
string propertyName,
@@ -552,10 +614,24 @@ public static void WriteDictionaryIfNotEmpty(
IEnumerable>? dic,
IDiagnosticLogger? logger)
{
- var asDictionary = dic as IReadOnlyDictionary ?? dic?.ToDictionary();
- if (asDictionary is not null && asDictionary.Count > 0)
+ var dictionary = dic as IReadOnlyDictionary ?? dic?.ToDictionary();
+ if (dictionary is not null && dictionary.Count > 0)
+ {
+ writer.WriteDictionary(propertyName, dictionary, logger);
+ }
+ }
+
+ public static void WriteDictionaryIfNotEmpty(
+ this Utf8JsonWriter writer,
+ string propertyName,
+ IEnumerable>? dic,
+ IDiagnosticLogger? logger)
+ where TValue : IJsonSerializable?
+ {
+ var dictionary = dic as IReadOnlyDictionary ?? dic?.ToDictionary();
+ if (dictionary is not null && dictionary.Count > 0)
{
- writer.WriteDictionary(propertyName, asDictionary, logger);
+ writer.WriteDictionary(propertyName, dictionary, logger);
}
}
@@ -564,10 +640,10 @@ public static void WriteStringDictionaryIfNotEmpty(
string propertyName,
IEnumerable>? dic)
{
- var asDictionary = dic as IReadOnlyDictionary ?? dic?.ToDictionary();
- if (asDictionary is not null && asDictionary.Count > 0)
+ var dictionary = dic as IReadOnlyDictionary ?? dic?.ToDictionary();
+ if (dictionary is not null && dictionary.Count > 0)
{
- writer.WriteStringDictionary(propertyName, asDictionary);
+ writer.WriteStringDictionary(propertyName, dictionary);
}
}
@@ -577,10 +653,10 @@ public static void WriteArrayIfNotEmpty(
IEnumerable