diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b216bafc..e71e1ce7b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Metric unit names are now sanitized correctly. This was preventing some built in metrics from showing in the Sentry dashboard ([#3151](https://github.com/getsentry/sentry-dotnet/pull/3151)) + ## 4.1.1 ### Fixes diff --git a/src/Sentry/MetricHelper.cs b/src/Sentry/MetricHelper.cs index 4705501764..75431ec1e0 100644 --- a/src/Sentry/MetricHelper.cs +++ b/src/Sentry/MetricHelper.cs @@ -8,6 +8,8 @@ internal static partial class MetricHelper private const int RollupInSeconds = 10; private const string InvalidKeyCharactersPattern = @"[^a-zA-Z0-9_/.-]+"; private const string InvalidValueCharactersPattern = @"[^\w\d_:/@\.\{\}\[\]$-]+"; + // See https://docs.sysdig.com/en/docs/sysdig-monitor/integrations/working-with-integrations/custom-integrations/integrate-statsd-metrics/#characters-allowed-for-statsd-metric-names + private const string InvalidMetricUnitCharactersPattern = @"[^a-zA-Z0-9_/.]+"; #if NET6_0_OR_GREATER private static readonly DateTimeOffset UnixEpoch = DateTimeOffset.UnixEpoch; @@ -48,10 +50,18 @@ internal static DateTimeOffset GetCutoff() => DateTimeOffset.UtcNow [GeneratedRegex(InvalidValueCharactersPattern, RegexOptions.Compiled)] private static partial Regex InvalidValueCharacters(); internal static string SanitizeValue(string input) => InvalidValueCharacters().Replace(input, "_"); + + [GeneratedRegex(InvalidMetricUnitCharactersPattern, RegexOptions.Compiled)] + private static partial Regex InvalidMetricUnitCharacters(); + internal static string SanitizeMetricUnit(string input) => InvalidMetricUnitCharacters().Replace(input, "_"); #else private static readonly Regex InvalidKeyCharacters = new(InvalidKeyCharactersPattern, RegexOptions.Compiled); internal static string SanitizeKey(string input) => InvalidKeyCharacters.Replace(input, "_"); + private static readonly Regex InvalidValueCharacters = new(InvalidValueCharactersPattern, RegexOptions.Compiled); internal static string SanitizeValue(string input) => InvalidValueCharacters.Replace(input, "_"); + + private static readonly Regex InvalidMetricUnitCharacters = new(InvalidMetricUnitCharactersPattern, RegexOptions.Compiled); + internal static string SanitizeMetricUnit(string input) => InvalidMetricUnitCharacters.Replace(input, "_"); #endif } diff --git a/src/Sentry/Protocol/Metrics/Metric.cs b/src/Sentry/Protocol/Metrics/Metric.cs index 3899da616d..302aacc171 100644 --- a/src/Sentry/Protocol/Metrics/Metric.cs +++ b/src/Sentry/Protocol/Metrics/Metric.cs @@ -108,9 +108,10 @@ public async Task SerializeAsync(Stream stream, IDiagnosticLogger? logger, Cance var metricName = MetricHelper.SanitizeKey(Key); await Write($"{metricName}@").ConfigureAwait(false); var unit = Unit ?? MeasurementUnit.None; + var sanitizedUnit = MetricHelper.SanitizeMetricUnit(unit.ToString()); // We don't need ConfigureAwait(false) here as ConfigureAwait on metricName above avoids capturing the ExecutionContext. #pragma warning disable CA2007 - await Write(unit.ToString()); + await Write(sanitizedUnit); foreach (var value in SerializedStatsdValues()) { diff --git a/test/Sentry.Tests/MetricHelperTests.cs b/test/Sentry.Tests/MetricHelperTests.cs index edae77af0d..ba2f69073e 100644 --- a/test/Sentry.Tests/MetricHelperTests.cs +++ b/test/Sentry.Tests/MetricHelperTests.cs @@ -47,4 +47,17 @@ public void SanitizeValue_ShouldReplaceInvalidCharactersWithUnderscore(string in // Assert result.Should().Be(expected); } + + [Theory] + [InlineData("Test123_.", "Test123_.")] // Valid characters + [InlineData("test{value}", "test_value_")] + [InlineData("test-value", "test_value")] + public void SanitizeMetricUnit_ShouldReplaceInvalidCharactersWithUnderscore(string input, string expected) + { + // Act + var result = MetricHelper.SanitizeMetricUnit(input); + + // Assert + result.Should().Be(expected); + } }