diff --git a/src/Activities/Internal/ActivityMetricsSender.cs b/src/Activities/Internal/ActivityMetricsSender.cs index 578c121b9..de97d8896 100644 --- a/src/Activities/Internal/ActivityMetricsSender.cs +++ b/src/Activities/Internal/ActivityMetricsSender.cs @@ -23,6 +23,10 @@ internal sealed class ActivityMetricsSender : IActivitiesEventSender, IDisposabl private readonly HashSet m_customTagObjectsDimension; private readonly bool m_isSetParentNameAsDimensionEnabled; + // Caps metric tag value length to reduce cardinality risk + // from externally controlled baggage or tag values + private const int MaxMetricTagValueLength = 256; + public ActivityMetricsSender( IExecutionContext executionContext, IHostEnvironment hostEnvironment, @@ -68,7 +72,7 @@ public void SendActivityMetric(Activity activity) string? baggageItem = activity.GetBaggageItem(dimension); if (!string.IsNullOrWhiteSpace(baggageItem)) { - tagList.Add(dimension, baggageItem); + tagList.Add(dimension, TruncateSafe(baggageItem, MaxMetricTagValueLength)); } } @@ -77,7 +81,16 @@ public void SendActivityMetric(Activity activity) object? tagItem = activity.GetTagItem(dimension); if (tagItem != null) { - tagList.Add(dimension, tagItem); + // Only truncate string values — preserve original + // type for numeric/bool to avoid breaking exporters + if (tagItem is string stringValue) + { + tagList.Add(dimension, TruncateSafe(stringValue, MaxMetricTagValueLength)); + } + else + { + tagList.Add(dimension, tagItem); + } } } @@ -93,6 +106,19 @@ public void SendActivityMetric(Activity activity) histogram.Record(durationMs, tagList); } + // Truncates string to maxLength while avoiding + // splitting UTF-16 surrogate pairs + private static string TruncateSafe(string value, int maxLength) + { + if (value.Length <= maxLength) + return value; + + if (char.IsHighSurrogate(value[maxLength - 1])) + return value[..(maxLength - 1)]; + + return value[..maxLength]; + } + public void Dispose() => m_meter.Dispose(); } }