diff --git a/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/Diagnostics/InvalidTagPropertyReturnTypeDiagnostic.cs b/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/Diagnostics/InvalidTagPropertyReturnTypeDiagnostic.cs index 670554f0f5b9..699eb17b3d22 100644 --- a/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/Diagnostics/InvalidTagPropertyReturnTypeDiagnostic.cs +++ b/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/Diagnostics/InvalidTagPropertyReturnTypeDiagnostic.cs @@ -11,7 +11,7 @@ namespace Datadog.Trace.SourceGenerators.TagsListGenerator.Diagnostics internal static class InvalidTagPropertyReturnTypeDiagnostic { internal const string Id = "TL3"; - private const string Message = "A tag property must return a string"; + private const string Message = "A tag property must return a string or a nullable int"; private const string Title = "Invalid return type"; public static Diagnostic Create(SyntaxNode? currentNode) => diff --git a/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/Sources.cs b/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/Sources.cs index fd8edbceafe8..a231e7095833 100644 --- a/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/Sources.cs +++ b/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/Sources.cs @@ -134,8 +134,18 @@ partial class ") sb.Append('"') .Append(property.TagValue) .Append(@""" => ") - .Append(property.PropertyName) - .Append( + .Append(property.PropertyName); + + switch (property.PropertyType) + { + case TagListGenerator.PropertyType.NullableInt: + sb.Append(" is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(") + .Append(property.PropertyName) + .Append(".Value)"); + break; + } + + sb.Append( @", "); } @@ -163,12 +173,42 @@ public override void SetTag(string key, string? value) .Append(property.TagValue) .Append( @""": - ") - .Append(property.PropertyName) - .Append( - @" = value; + "); + + if (property.PropertyType is TagListGenerator.PropertyType.NullableInt) + { + // Invalid values (null and anything that isn't a valid integer) remove the tag + sb.Append("if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsed") + .Append(property.PropertyName) + .Append( + @")) + { + ") + .Append(property.PropertyName) + .Append(@" = parsed") + .Append(property.PropertyName) + .Append( + @"; + } + else + { + ") + .Append(property.PropertyName) + .Append( + @" = null; + } + + break; + "); + } + else + { + sb.Append(property.PropertyName) + .Append( + @" = value; break; "); + } } var haveReadOnlyTags = false; @@ -209,20 +249,41 @@ public override void EnumerateTags(ref TProcessor processor) "); foreach (var property in tagList.TagProperties) { - sb.Append(@"if (") - .Append(property.PropertyName) - .Append(@" is not null) + switch (property.PropertyType) + { + case TagListGenerator.PropertyType.NullableInt: + sb.Append(@"if (") + .Append(property.PropertyName) + .Append(@" is not null) + { + processor.Process(new TagItem(""") + .Append(property.TagValue) + .Append(@""", ") + .Append(property.PropertyName) + .Append(@".Value, ") + .Append(property.PropertyName) + .Append(@"Bytes)); + } + + "); + break; + default: + sb.Append(@"if (") + .Append(property.PropertyName) + .Append(@" is not null) { processor.Process(new TagItem(""") - .Append(property.TagValue) - .Append(@""", ") - .Append(property.PropertyName) - .Append(@", ") - .Append(property.PropertyName) - .Append(@"Bytes)); + .Append(property.TagValue) + .Append(@""", ") + .Append(property.PropertyName) + .Append(@", ") + .Append(property.PropertyName) + .Append(@"Bytes)); } "); + break; + } } sb.Append( @@ -234,22 +295,45 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) "); foreach (var property in tagList.TagProperties) { - sb.Append(@"if (") - .Append(property.PropertyName) - .Append( - @" is not null) + switch (property.PropertyType) + { + case TagListGenerator.PropertyType.NullableInt: + sb.Append(@"if (") + .Append(property.PropertyName) + .Append( + @" is not null) { sb.Append(""") - .Append(property.TagValue) - .Append(@" (tag):"") + .Append(property.TagValue) + .Append(@" (tag):"") .Append(") - .Append(property.PropertyName) - .Append( - @") + .Append(property.PropertyName) + .Append( + @".Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) + .Append(','); + } + + "); + break; + default: + sb.Append(@"if (") + .Append(property.PropertyName) + .Append( + @" is not null) + { + sb.Append(""") + .Append(property.TagValue) + .Append(@" (tag):"") + .Append(") + .Append(property.PropertyName) + .Append( + @") .Append(','); } "); + break; + } } sb.Append(@"base.WriteAdditionalTags(sb); diff --git a/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/TagListGenerator.cs b/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/TagListGenerator.cs index 30a69dfe092e..21d42310de58 100644 --- a/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/TagListGenerator.cs +++ b/tracer/src/Datadog.Trace.SourceGenerators/TagsListGenerator/TagListGenerator.cs @@ -24,6 +24,13 @@ public class TagListGenerator : IIncrementalGenerator private const string TagAttributeFullName = "Datadog.Trace.SourceGenerators.TagAttribute"; private const string MetricAttributeFullName = "Datadog.Trace.SourceGenerators.MetricAttribute"; + internal enum PropertyType + { + String, + NullableInt, + NullableDouble, + } + /// public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -187,11 +194,28 @@ private static IEnumerable GetTagLists(ImmutableArray tagP } } - var hasRequiredReturnType = - isTag - ? propertySymbol.Type.Name == "String" - : propertySymbol.Type is INamedTypeSymbol { Name: "Nullable", TypeArguments: { Length: 1 } typeArgs } - && typeArgs[0].Name == "Double"; + PropertyType propertyType = PropertyType.String; + bool hasRequiredReturnType = false; + if (isTag) + { + if (propertySymbol.Type.Name == "String") + { + propertyType = PropertyType.String; + hasRequiredReturnType = true; + } + else if (propertySymbol.Type is INamedTypeSymbol { Name: "Nullable", TypeArguments: { Length: 1 } intTypeArgs } + && intTypeArgs[0].Name == "Int32") + { + propertyType = PropertyType.NullableInt; + hasRequiredReturnType = true; + } + } + else if (propertySymbol.Type is INamedTypeSymbol { Name: "Nullable", TypeArguments: { Length: 1 } doubleTypeArgs } + && doubleTypeArgs[0].Name == "Double") + { + propertyType = PropertyType.NullableDouble; + hasRequiredReturnType = true; + } if (!hasRequiredReturnType) { @@ -218,7 +242,8 @@ private static IEnumerable GetTagLists(ImmutableArray tagP isReadOnly: propertySymbol!.IsReadOnly, propertyName: propertySymbol.Name, tagValue: key!, - isTag: isTag); + isTag: isTag, + propertyType: propertyType); return new Result<(PropertyTag PropertyTag, bool IsValid)>((tag, true), errors); } @@ -280,8 +305,9 @@ internal readonly record struct PropertyTag public readonly string PropertyName; public readonly string TagValue; public readonly bool IsTag; + public readonly PropertyType PropertyType; - public PropertyTag(string nameSpace, string className, bool isReadOnly, string propertyName, string tagValue, bool isTag) + public PropertyTag(string nameSpace, string className, bool isReadOnly, string propertyName, string tagValue, bool isTag, PropertyType propertyType) { IsReadOnly = isReadOnly; PropertyName = propertyName; @@ -289,6 +315,7 @@ public PropertyTag(string nameSpace, string className, bool isReadOnly, string p IsTag = isTag; Namespace = nameSpace; ClassName = className; + PropertyType = propertyType; } } } diff --git a/tracer/src/Datadog.Trace/Agent/MessagePack/SpanMessagePackFormatter.cs b/tracer/src/Datadog.Trace/Agent/MessagePack/SpanMessagePackFormatter.cs index ab3fd2af6ad0..2a8ce9207562 100644 --- a/tracer/src/Datadog.Trace/Agent/MessagePack/SpanMessagePackFormatter.cs +++ b/tracer/src/Datadog.Trace/Agent/MessagePack/SpanMessagePackFormatter.cs @@ -1005,7 +1005,7 @@ private byte[] GetAppSecRulesetVersion(string version) } } - internal struct TagWriter : IItemProcessor, IItemProcessor, IItemProcessor + internal struct TagWriter : IItemProcessor, IItemProcessor, IItemProcessor, IItemProcessor { private readonly SpanMessagePackFormatter _formatter; private readonly ITagProcessor[] _tagProcessors; @@ -1039,6 +1039,24 @@ public void Process(TagItem item) Count++; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Process(TagItem item) + { + // int-backed tags are serialized as strings; IntStringCache keeps this allocation-free + var value = IntStringCache.ToInvariantString(item.Value); + + if (item.SerializedKey.IsEmpty) + { + _formatter.WriteTag(ref Bytes, ref Offset, item.Key, value, _tagProcessors); + } + else + { + _formatter.WriteTag(ref Bytes, ref Offset, item.SerializedKey, value, _tagProcessors); + } + + Count++; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Process(TagItem item) { diff --git a/tracer/src/Datadog.Trace/Agent/NullStatsAggregator.cs b/tracer/src/Datadog.Trace/Agent/NullStatsAggregator.cs index 7c41432ecbb0..a1647a80ad2b 100644 --- a/tracer/src/Datadog.Trace/Agent/NullStatsAggregator.cs +++ b/tracer/src/Datadog.Trace/Agent/NullStatsAggregator.cs @@ -30,18 +30,12 @@ public Task DisposeAsync() public StatsAggregationKey BuildKey(Span span) { - var rawHttpStatusCode = span.GetTag(Tags.HttpStatusCode); - if (rawHttpStatusCode is null || !int.TryParse(rawHttpStatusCode, out var httpStatusCode)) - { - httpStatusCode = 0; - } - return new StatsAggregationKey( span.ResourceName, span.ServiceName, span.OperationName, span.Type, - httpStatusCode, + span.GetHttpStatusCode() ?? 0, isSyntheticsRequest: span.Context.Origin?.StartsWith("synthetics") == true, spanKind: string.Empty, isError: false, diff --git a/tracer/src/Datadog.Trace/Agent/StatsAggregator.cs b/tracer/src/Datadog.Trace/Agent/StatsAggregator.cs index c94ac39f1331..92375d8b0499 100644 --- a/tracer/src/Datadog.Trace/Agent/StatsAggregator.cs +++ b/tracer/src/Datadog.Trace/Agent/StatsAggregator.cs @@ -361,13 +361,6 @@ public StatsAggregationKey BuildKey(Span span) [TestingAndPrivateOnly] internal StatsAggregationKey BuildKey(Span span, List peerTagKeys, out PeerTagResults peerTagResults, out AdditionalTagResults additionalTagResults) { - var rawHttpStatusCode = span.GetTag(Tags.HttpStatusCode); - - if (rawHttpStatusCode == null || !int.TryParse(rawHttpStatusCode, out var httpStatusCode)) - { - httpStatusCode = 0; - } - // Check gRPC status code tags in priority order per CSS v1.2.0 spec. // Stored as string to match the Go agent's wire format (GRPCStatusCode is a string field). // This preserves the distinction between "0" (gRPC OK) and "" (no gRPC status). @@ -459,7 +452,7 @@ internal StatsAggregationKey BuildKey(Span span, List peerTagKeys, o span.ServiceName, span.OperationName, span.Type, - httpStatusCode, + span.GetHttpStatusCode() ?? 0, span.Context.Origin?.StartsWith("synthetics") == true, spanKind, _isOtlp ? span.Error : false, diff --git a/tracer/src/Datadog.Trace/Agent/TraceSamplers/TraceFilter.cs b/tracer/src/Datadog.Trace/Agent/TraceSamplers/TraceFilter.cs index a4ebb5d0ae96..14147410a47c 100644 --- a/tracer/src/Datadog.Trace/Agent/TraceSamplers/TraceFilter.cs +++ b/tracer/src/Datadog.Trace/Agent/TraceSamplers/TraceFilter.cs @@ -11,6 +11,7 @@ using System.Text.RegularExpressions; using Datadog.Trace.Agent.DiscoveryService; using Datadog.Trace.Tagging; +using Datadog.Trace.Util; namespace Datadog.Trace.Agent.TraceSamplers; @@ -237,7 +238,7 @@ public RegexTagFilter(Regex keyPattern, Regex? valuePattern) } } - private struct RegexTagFilterProcessor : IItemProcessor + private struct RegexTagFilterProcessor : IItemProcessor, IItemProcessor { private readonly RegexTagFilter _filter; public bool Matched; @@ -261,5 +262,21 @@ public void Process(TagItem item) } } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Process(TagItem item) + { + if (!Matched) + { + if (_filter.KeyPattern.IsMatch(item.Key)) + { + // Key-only filter: any matching key is sufficient + // Key:Value filter: value must also match + // Regex.IsMatch(ReadOnlySpan) is .NET 7+, so we have to hand it a string; + // IntStringCache keeps that allocation-free for the values we actually see. + Matched = _filter.ValuePattern is null || _filter.ValuePattern.IsMatch(IntStringCache.ToInvariantString(item.Value)); + } + } + } } } diff --git a/tracer/src/Datadog.Trace/AppSec/ApiSec/ApiSecurity.cs b/tracer/src/Datadog.Trace/AppSec/ApiSec/ApiSecurity.cs index e92a90224f47..df09a0b88eea 100644 --- a/tracer/src/Datadog.Trace/AppSec/ApiSec/ApiSecurity.cs +++ b/tracer/src/Datadog.Trace/AppSec/ApiSec/ApiSecurity.cs @@ -36,7 +36,7 @@ public ApiSecurity(SecuritySettings securitySettings, int maxRouteSize = 4096) _endpointsCollectionMessageLimit = securitySettings.ApiSecurityEndpointCollectionMessageLimit; } - public bool ShouldAnalyzeSchema(bool lastWafCall, Span localRootSpan, IDictionary args, string? statusCode, IDictionary? routeValues) + public bool ShouldAnalyzeSchema(bool lastWafCall, Span localRootSpan, IDictionary args, int? statusCode, IDictionary? routeValues) { try { @@ -46,7 +46,7 @@ public bool ShouldAnalyzeSchema(bool lastWafCall, Span localRootSpan, IDictionar { var httpRouteTag = localRootSpan.GetTag(Tags.AspNetCoreEndpoint) ?? localRootSpan.GetTag(Tags.HttpRoute); var httpMethod = localRootSpan.GetTag(Tags.HttpMethod); - statusCode ??= localRootSpan.GetTag(Tags.HttpStatusCode); + statusCode ??= localRootSpan.GetHttpStatusCode(); if (httpRouteTag == null || httpMethod == null || statusCode == null) { Log.Debug("Unsupported groupkey for api security {Route}, {Method}, {Status}", httpRouteTag, httpMethod, statusCode); @@ -125,5 +125,5 @@ public bool ShouldAnalyzeSchema(bool lastWafCall, Span localRootSpan, IDictionar public int GetEndpointsCollectionMessageLimit() => _endpointsCollectionMessageLimit; [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int CombineHashes(string httpRouteTag, string httpMethod, string statusCode) => HashCode.Combine(httpRouteTag.GetHashCode(), httpMethod.GetHashCode(), statusCode.GetHashCode()); + internal static int CombineHashes(string httpRouteTag, string httpMethod, int? statusCode) => HashCode.Combine(httpRouteTag.GetHashCode(), httpMethod.GetHashCode(), statusCode?.GetHashCode()); } diff --git a/tracer/src/Datadog.Trace/AppSec/Coordinator/SecurityCoordinator.cs b/tracer/src/Datadog.Trace/AppSec/Coordinator/SecurityCoordinator.cs index 8c92ee1bd834..b8010c860878 100644 --- a/tracer/src/Datadog.Trace/AppSec/Coordinator/SecurityCoordinator.cs +++ b/tracer/src/Datadog.Trace/AppSec/Coordinator/SecurityCoordinator.cs @@ -66,7 +66,7 @@ internal readonly partial struct SecurityCoordinator args[AddressesConstants.UserSessionId] = sessionId!; } - _security.ApiSecurity.ShouldAnalyzeSchema(lastWafCall, _localRootSpan, args, _httpTransport.StatusCode?.ToString(), _httpTransport.RouteData); + _security.ApiSecurity.ShouldAnalyzeSchema(lastWafCall, _localRootSpan, args, _httpTransport.StatusCode, _httpTransport.RouteData); // run the WAF and execute the results result = runWithEphemeral diff --git a/tracer/src/Datadog.Trace/Ci/Agent/MessagePack/SpanMessagePackFormatter.cs b/tracer/src/Datadog.Trace/Ci/Agent/MessagePack/SpanMessagePackFormatter.cs index 73c4716c9e54..537725cd4223 100644 --- a/tracer/src/Datadog.Trace/Ci/Agent/MessagePack/SpanMessagePackFormatter.cs +++ b/tracer/src/Datadog.Trace/Ci/Agent/MessagePack/SpanMessagePackFormatter.cs @@ -412,7 +412,7 @@ public Span Deserialize(byte[] bytes, int offset, IFormatterResolver formatterRe throw new NotImplementedException(); } - internal struct TagWriter : IItemProcessor, IItemProcessor + internal struct TagWriter : IItemProcessor, IItemProcessor, IItemProcessor { private readonly SpanMessagePackFormatter _formatter; private readonly ITagProcessor[]? _tagProcessors; @@ -446,6 +446,24 @@ public void Process(TagItem item) Count++; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Process(TagItem item) + { + // int-backed tags are serialized as strings; IntStringCache keeps this allocation-free + var value = IntStringCache.ToInvariantString(item.Value); + + if (item.SerializedKey.IsEmpty) + { + _formatter.WriteTag(ref Bytes, ref Offset, item.Key, value, _tagProcessors); + } + else + { + _formatter.WriteTag(ref Bytes, ref Offset, item.SerializedKey, value, _tagProcessors); + } + + Count++; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Process(TagItem item) { diff --git a/tracer/src/Datadog.Trace/ExtensionMethods/SpanExtensions.cs b/tracer/src/Datadog.Trace/ExtensionMethods/SpanExtensions.cs index 8cf43e26798e..86dd16973731 100644 --- a/tracer/src/Datadog.Trace/ExtensionMethods/SpanExtensions.cs +++ b/tracer/src/Datadog.Trace/ExtensionMethods/SpanExtensions.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using Datadog.Trace.Configuration; using Datadog.Trace.Headers; using Datadog.Trace.Logging; @@ -93,6 +92,24 @@ internal static bool HasHttpStatusCode(this Span span) } } + internal static int? GetHttpStatusCode(this Span span) + { + if (span.Tags is IHasStatusCode statusCodeTags) + { + return statusCodeTags.HttpStatusCode; + } + else + { + var rawHttpStatusCode = span.GetTag(Tags.HttpStatusCode); + if (rawHttpStatusCode == null || !int.TryParse(rawHttpStatusCode, out var httpStatusCode)) + { + return null; + } + + return httpStatusCode; + } + } + internal static void SetHttpStatusCode(this Span span, int statusCode, bool isServer, MutableSettings tracerSettings) { if (statusCode < 100 || statusCode >= 600) @@ -101,15 +118,13 @@ internal static void SetHttpStatusCode(this Span span, int statusCode, bool isSe return; } - string statusCodeString = ConvertStatusCodeToString(statusCode); - if (span.Tags is IHasStatusCode statusCodeTags) { - statusCodeTags.HttpStatusCode = statusCodeString; + statusCodeTags.HttpStatusCode = statusCode; } else { - span.SetTag(Tags.HttpStatusCode, statusCodeString); + span.SetTag(Tags.HttpStatusCode, IntStringCache.ToInvariantString(statusCode)); } // Check the customers http statuses that should be marked as errors @@ -121,7 +136,7 @@ internal static void SetHttpStatusCode(this Span span, int statusCode, bool isSe { if (string.IsNullOrEmpty(span.GetTag(Tags.ErrorType))) { - span.SetTag(Tags.ErrorType, statusCodeString); + span.SetTag(Tags.ErrorType, IntStringCache.ToInvariantString(statusCode)); } } else @@ -129,50 +144,10 @@ internal static void SetHttpStatusCode(this Span span, int statusCode, bool isSe // if an error message already exists (e.g. from a previous exception), don't replace it if (string.IsNullOrEmpty(span.GetTag(Tags.ErrorMsg))) { - span.SetTag(Tags.ErrorMsg, $"The HTTP response has status code {statusCodeString}."); + span.SetTag(Tags.ErrorMsg, $"The HTTP response has status code {IntStringCache.ToInvariantString(statusCode)}."); } } } } - - private static string ConvertStatusCodeToString(int statusCode) - { - if (statusCode == 200) - { - return "200"; - } - - if (statusCode == 302) - { - return "302"; - } - - if (statusCode == 401) - { - return "401"; - } - - if (statusCode == 403) - { - return "403"; - } - - if (statusCode == 404) - { - return "404"; - } - - if (statusCode == 500) - { - return "500"; - } - - if (statusCode == 503) - { - return "503"; - } - - return statusCode.ToString(); - } } } diff --git a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs index 23da23a5f5b7..6b5d2d3193a5 100644 --- a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs @@ -69,7 +69,7 @@ partial class AwsSdkTags "_dd.peer.service.source" => PeerServiceSource, "http.method" => HttpMethod, "http.url" => HttpUrl, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), _ => base.GetTag(key), }; } @@ -103,7 +103,15 @@ public override void SetTag(string key, string? value) HttpUrl = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "component": case "aws.agent": @@ -181,7 +189,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } base.EnumerateTags(ref processor); @@ -276,7 +284,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs index 230de196b693..1414cda04601 100644 --- a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs @@ -44,7 +44,7 @@ partial class HttpTags "http.method" => HttpMethod, "http.url" => HttpUrl, "http-client-handler-type" => HttpClientHandlerType, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "out.host" => Host, _ => base.GetTag(key), }; @@ -67,7 +67,15 @@ public override void SetTag(string key, string? value) HttpClientHandlerType = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "out.host": Host = value; @@ -110,7 +118,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (Host is not null) @@ -161,7 +169,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs index 973342f908fd..a80c57e07420 100644 --- a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs @@ -50,7 +50,7 @@ partial class InferredProxyTags "http.method" => HttpMethod, "http.url" => HttpUrl, "http.route" => HttpRoute, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "stage" => Stage, "region" => Region, _ => base.GetTag(key), @@ -74,7 +74,15 @@ public override void SetTag(string key, string? value) HttpRoute = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "stage": Stage = value; @@ -120,7 +128,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (Stage is not null) @@ -176,7 +184,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs index cb966321491c..0d0922839d45 100644 --- a/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net461/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs @@ -47,7 +47,7 @@ partial class WebTags "http.method" => HttpMethod, "http.request.headers.host" => HttpRequestHeadersHost, "http.url" => HttpUrl, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "network.client.ip" => NetworkClientIp, "http.client_ip" => HttpClientIp, _ => base.GetTag(key), @@ -71,7 +71,15 @@ public override void SetTag(string key, string? value) HttpUrl = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "network.client.ip": NetworkClientIp = value; @@ -117,7 +125,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (NetworkClientIp is not null) @@ -173,7 +181,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs index 23da23a5f5b7..6b5d2d3193a5 100644 --- a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs @@ -69,7 +69,7 @@ partial class AwsSdkTags "_dd.peer.service.source" => PeerServiceSource, "http.method" => HttpMethod, "http.url" => HttpUrl, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), _ => base.GetTag(key), }; } @@ -103,7 +103,15 @@ public override void SetTag(string key, string? value) HttpUrl = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "component": case "aws.agent": @@ -181,7 +189,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } base.EnumerateTags(ref processor); @@ -276,7 +284,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs index 230de196b693..1414cda04601 100644 --- a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs @@ -44,7 +44,7 @@ partial class HttpTags "http.method" => HttpMethod, "http.url" => HttpUrl, "http-client-handler-type" => HttpClientHandlerType, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "out.host" => Host, _ => base.GetTag(key), }; @@ -67,7 +67,15 @@ public override void SetTag(string key, string? value) HttpClientHandlerType = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "out.host": Host = value; @@ -110,7 +118,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (Host is not null) @@ -161,7 +169,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs index 973342f908fd..a80c57e07420 100644 --- a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs @@ -50,7 +50,7 @@ partial class InferredProxyTags "http.method" => HttpMethod, "http.url" => HttpUrl, "http.route" => HttpRoute, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "stage" => Stage, "region" => Region, _ => base.GetTag(key), @@ -74,7 +74,15 @@ public override void SetTag(string key, string? value) HttpRoute = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "stage": Stage = value; @@ -120,7 +128,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (Stage is not null) @@ -176,7 +184,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs index cb966321491c..0d0922839d45 100644 --- a/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/net6.0/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs @@ -47,7 +47,7 @@ partial class WebTags "http.method" => HttpMethod, "http.request.headers.host" => HttpRequestHeadersHost, "http.url" => HttpUrl, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "network.client.ip" => NetworkClientIp, "http.client_ip" => HttpClientIp, _ => base.GetTag(key), @@ -71,7 +71,15 @@ public override void SetTag(string key, string? value) HttpUrl = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "network.client.ip": NetworkClientIp = value; @@ -117,7 +125,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (NetworkClientIp is not null) @@ -173,7 +181,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs index 23da23a5f5b7..6b5d2d3193a5 100644 --- a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs @@ -69,7 +69,7 @@ partial class AwsSdkTags "_dd.peer.service.source" => PeerServiceSource, "http.method" => HttpMethod, "http.url" => HttpUrl, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), _ => base.GetTag(key), }; } @@ -103,7 +103,15 @@ public override void SetTag(string key, string? value) HttpUrl = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "component": case "aws.agent": @@ -181,7 +189,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } base.EnumerateTags(ref processor); @@ -276,7 +284,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs index 230de196b693..1414cda04601 100644 --- a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs @@ -44,7 +44,7 @@ partial class HttpTags "http.method" => HttpMethod, "http.url" => HttpUrl, "http-client-handler-type" => HttpClientHandlerType, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "out.host" => Host, _ => base.GetTag(key), }; @@ -67,7 +67,15 @@ public override void SetTag(string key, string? value) HttpClientHandlerType = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "out.host": Host = value; @@ -110,7 +118,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (Host is not null) @@ -161,7 +169,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs index 973342f908fd..a80c57e07420 100644 --- a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs @@ -50,7 +50,7 @@ partial class InferredProxyTags "http.method" => HttpMethod, "http.url" => HttpUrl, "http.route" => HttpRoute, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "stage" => Stage, "region" => Region, _ => base.GetTag(key), @@ -74,7 +74,15 @@ public override void SetTag(string key, string? value) HttpRoute = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "stage": Stage = value; @@ -120,7 +128,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (Stage is not null) @@ -176,7 +184,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs index cb966321491c..0d0922839d45 100644 --- a/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netcoreapp3.1/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs @@ -47,7 +47,7 @@ partial class WebTags "http.method" => HttpMethod, "http.request.headers.host" => HttpRequestHeadersHost, "http.url" => HttpUrl, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "network.client.ip" => NetworkClientIp, "http.client_ip" => HttpClientIp, _ => base.GetTag(key), @@ -71,7 +71,15 @@ public override void SetTag(string key, string? value) HttpUrl = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "network.client.ip": NetworkClientIp = value; @@ -117,7 +125,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (NetworkClientIp is not null) @@ -173,7 +181,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs index 23da23a5f5b7..6b5d2d3193a5 100644 --- a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/AwsSdkTags.g.cs @@ -69,7 +69,7 @@ partial class AwsSdkTags "_dd.peer.service.source" => PeerServiceSource, "http.method" => HttpMethod, "http.url" => HttpUrl, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), _ => base.GetTag(key), }; } @@ -103,7 +103,15 @@ public override void SetTag(string key, string? value) HttpUrl = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "component": case "aws.agent": @@ -181,7 +189,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } base.EnumerateTags(ref processor); @@ -276,7 +284,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs index 230de196b693..1414cda04601 100644 --- a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/HttpTags.g.cs @@ -44,7 +44,7 @@ partial class HttpTags "http.method" => HttpMethod, "http.url" => HttpUrl, "http-client-handler-type" => HttpClientHandlerType, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "out.host" => Host, _ => base.GetTag(key), }; @@ -67,7 +67,15 @@ public override void SetTag(string key, string? value) HttpClientHandlerType = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "out.host": Host = value; @@ -110,7 +118,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (Host is not null) @@ -161,7 +169,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs index 973342f908fd..a80c57e07420 100644 --- a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/InferredProxyTags.g.cs @@ -50,7 +50,7 @@ partial class InferredProxyTags "http.method" => HttpMethod, "http.url" => HttpUrl, "http.route" => HttpRoute, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "stage" => Stage, "region" => Region, _ => base.GetTag(key), @@ -74,7 +74,15 @@ public override void SetTag(string key, string? value) HttpRoute = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "stage": Stage = value; @@ -120,7 +128,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (Stage is not null) @@ -176,7 +184,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs index cb966321491c..0d0922839d45 100644 --- a/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs +++ b/tracer/src/Datadog.Trace/Generated/netstandard2.0/Datadog.Trace.SourceGenerators/TagListGenerator/WebTags.g.cs @@ -47,7 +47,7 @@ partial class WebTags "http.method" => HttpMethod, "http.request.headers.host" => HttpRequestHeadersHost, "http.url" => HttpUrl, - "http.status_code" => HttpStatusCode, + "http.status_code" => HttpStatusCode is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(HttpStatusCode.Value), "network.client.ip" => NetworkClientIp, "http.client_ip" => HttpClientIp, _ => base.GetTag(key), @@ -71,7 +71,15 @@ public override void SetTag(string key, string? value) HttpUrl = value; break; case "http.status_code": - HttpStatusCode = value; + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedHttpStatusCode)) + { + HttpStatusCode = parsedHttpStatusCode; + } + else + { + HttpStatusCode = null; + } + break; case "network.client.ip": NetworkClientIp = value; @@ -117,7 +125,7 @@ public override void EnumerateTags(ref TProcessor processor) if (HttpStatusCode is not null) { - processor.Process(new TagItem("http.status_code", HttpStatusCode, HttpStatusCodeBytes)); + processor.Process(new TagItem("http.status_code", HttpStatusCode.Value, HttpStatusCodeBytes)); } if (NetworkClientIp is not null) @@ -173,7 +181,7 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) if (HttpStatusCode is not null) { sb.Append("http.status_code (tag):") - .Append(HttpStatusCode) + .Append(HttpStatusCode.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) .Append(','); } diff --git a/tracer/src/Datadog.Trace/OpenTelemetry/OtlpMapper.cs b/tracer/src/Datadog.Trace/OpenTelemetry/OtlpMapper.cs index d6cc0f566652..bca915285028 100644 --- a/tracer/src/Datadog.Trace/OpenTelemetry/OtlpMapper.cs +++ b/tracer/src/Datadog.Trace/OpenTelemetry/OtlpMapper.cs @@ -273,7 +273,7 @@ public static int EmitAttributesFromSpan(in SpanModel spanModel, int lim return droppedAttributesCount; } - internal struct TagWriter : IItemProcessor, IItemProcessor, IItemProcessor + internal struct TagWriter : IItemProcessor, IItemProcessor, IItemProcessor, IItemProcessor { private readonly KeyValueWriter _writeKeyValue; private readonly ITagProcessor[]? _tagProcessors; @@ -351,6 +351,32 @@ public void Process(TagItem item) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Process(TagItem item) + { + if (Count < _limit) + { + // We are using the original key since we're not serializing MessagePack + string key = item.Key; + int value = item.Value; + + if (_tagProcessors is not null) + { + for (var i = 0; i < _tagProcessors.Length; i++) + { + _tagProcessors[i]?.ProcessMeta(ref key, ref value); + } + } + + _writeKeyValue(ref State, new KeyValue(key, value)); + Count++; + } + else + { + DroppedCount++; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Process(TagItem item) { diff --git a/tracer/src/Datadog.Trace/Processors/ITagProcessor.cs b/tracer/src/Datadog.Trace/Processors/ITagProcessor.cs index 23d5e98409b0..706512f1991e 100644 --- a/tracer/src/Datadog.Trace/Processors/ITagProcessor.cs +++ b/tracer/src/Datadog.Trace/Processors/ITagProcessor.cs @@ -9,6 +9,8 @@ internal interface ITagProcessor { void ProcessMeta(ref string key, ref string value); + void ProcessMeta(ref string key, ref int value); + void ProcessMetric(ref string key, ref double value); } } diff --git a/tracer/src/Datadog.Trace/Processors/NormalizerTraceProcessor.cs b/tracer/src/Datadog.Trace/Processors/NormalizerTraceProcessor.cs index 363e2d7438da..a3ad4915b9e6 100644 --- a/tracer/src/Datadog.Trace/Processors/NormalizerTraceProcessor.cs +++ b/tracer/src/Datadog.Trace/Processors/NormalizerTraceProcessor.cs @@ -133,15 +133,15 @@ public Span Process(Span span) if (!TraceUtil.IsValidStatusCode(statusCodeTags.HttpStatusCode)) { Log.Debug("Fixing malformed trace. HTTP status code is invalid (reason:invalid_http_status_code), dropping invalid http.status_code={InvalidStatusCode}: {Span}", statusCodeTags.HttpStatusCode, span); - statusCodeTags.HttpStatusCode = string.Empty; + statusCodeTags.HttpStatusCode = null; } } else { - string httpStatusCode = span.GetTag(Tags.HttpStatusCode); - if (!string.IsNullOrEmpty(httpStatusCode) && !TraceUtil.IsValidStatusCode(httpStatusCode)) + var rawHttpStatusCode = span.GetTag(Tags.HttpStatusCode); + if (!string.IsNullOrEmpty(rawHttpStatusCode) && !TraceUtil.IsValidStatusCode(span.GetHttpStatusCode())) { - Log.Debug("Fixing malformed trace. HTTP status code is invalid (reason:invalid_http_status_code), dropping invalid http.status_code={InvalidStatusCode}: {Span}", httpStatusCode, span); + Log.Debug("Fixing malformed trace. HTTP status code is invalid (reason:invalid_http_status_code), dropping invalid http.status_code={InvalidStatusCode}: {Span}", rawHttpStatusCode, span); span.Tags.SetTag(Tags.HttpStatusCode, null); } } diff --git a/tracer/src/Datadog.Trace/Processors/TraceUtil.cs b/tracer/src/Datadog.Trace/Processors/TraceUtil.cs index 7f3a2b63e31b..e78f239b5bbe 100644 --- a/tracer/src/Datadog.Trace/Processors/TraceUtil.cs +++ b/tracer/src/Datadog.Trace/Processors/TraceUtil.cs @@ -110,14 +110,9 @@ static string TruncateUTF8Slow(string value, int limit) // https://github.com/DataDog/datadog-agent/blob/eac2327c5574da7f225f9ef0f89eaeb05ed10382/pkg/trace/agent/normalizer.go#L214-L219 [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsValidStatusCode(string statusCode) + public static bool IsValidStatusCode(int? code) { - if (int.TryParse(statusCode, out int code)) - { - return 100 <= code && code < 600; - } - - return false; + return code is not null && 100 <= code && code < 600; } // https://github.com/DataDog/datadog-agent/blob/eac2327c5574da7f225f9ef0f89eaeb05ed10382/pkg/trace/traceutil/normalize.go#L98-L209 diff --git a/tracer/src/Datadog.Trace/Processors/TruncatorTagsProcessor.cs b/tracer/src/Datadog.Trace/Processors/TruncatorTagsProcessor.cs index 345c32e83439..9f761838dab5 100644 --- a/tracer/src/Datadog.Trace/Processors/TruncatorTagsProcessor.cs +++ b/tracer/src/Datadog.Trace/Processors/TruncatorTagsProcessor.cs @@ -36,6 +36,16 @@ public void ProcessMeta(ref string key, ref string value) } } + // https://github.com/DataDog/datadog-agent/blob/eac2327c5574da7f225f9ef0f89eaeb05ed10382/pkg/trace/agent/truncator.go#L45-L53 + public void ProcessMeta(ref string key, ref int value) + { + if (TraceUtil.TruncateUTF8(ref key, MaxMetaKeyLen)) + { + key += "..."; + Log.Debug("span.truncate: truncating `Meta` key (max {MaxMetaKeyLen} chars): {Key}", MaxMetaKeyLen, key); + } + } + // https://github.com/DataDog/datadog-agent/blob/eac2327c5574da7f225f9ef0f89eaeb05ed10382/pkg/trace/agent/truncator.go#L45-L53 public void ProcessMetric(ref string key, ref double value) { diff --git a/tracer/src/Datadog.Trace/Tagging/AwsSdkTags.cs b/tracer/src/Datadog.Trace/Tagging/AwsSdkTags.cs index 6130d2ea35ae..f6504d87e2ad 100644 --- a/tracer/src/Datadog.Trace/Tagging/AwsSdkTags.cs +++ b/tracer/src/Datadog.Trace/Tagging/AwsSdkTags.cs @@ -52,6 +52,6 @@ internal abstract partial class AwsSdkTags : InstrumentationTags, IHasStatusCode public string? HttpUrl { get; set; } [Tag(Trace.Tags.HttpStatusCode)] - public string? HttpStatusCode { get; set; } + public int? HttpStatusCode { get; set; } } } diff --git a/tracer/src/Datadog.Trace/Tagging/HttpTags.cs b/tracer/src/Datadog.Trace/Tagging/HttpTags.cs index 416a360b5682..b1e6ed6e2e78 100644 --- a/tracer/src/Datadog.Trace/Tagging/HttpTags.cs +++ b/tracer/src/Datadog.Trace/Tagging/HttpTags.cs @@ -28,7 +28,7 @@ internal partial class HttpTags : InstrumentationTags, IHasStatusCode public string HttpClientHandlerType { get; set; } [Tag(Trace.Tags.HttpStatusCode)] - public string HttpStatusCode { get; set; } + public int? HttpStatusCode { get; set; } [Tag(Trace.Tags.OutHost)] public string Host { get; set; } diff --git a/tracer/src/Datadog.Trace/Tagging/IHasStatusCode.cs b/tracer/src/Datadog.Trace/Tagging/IHasStatusCode.cs index 088f2c2325db..2712e617e30d 100644 --- a/tracer/src/Datadog.Trace/Tagging/IHasStatusCode.cs +++ b/tracer/src/Datadog.Trace/Tagging/IHasStatusCode.cs @@ -7,6 +7,6 @@ namespace Datadog.Trace.Tagging { internal interface IHasStatusCode { - string HttpStatusCode { get; set; } + int? HttpStatusCode { get; set; } } } diff --git a/tracer/src/Datadog.Trace/Tagging/ITags.cs b/tracer/src/Datadog.Trace/Tagging/ITags.cs index 50f9454a18c8..8d10346d2643 100644 --- a/tracer/src/Datadog.Trace/Tagging/ITags.cs +++ b/tracer/src/Datadog.Trace/Tagging/ITags.cs @@ -12,7 +12,7 @@ internal interface ITags void SetTag(string key, string value); void EnumerateTags(ref TProcessor processor) - where TProcessor : struct, IItemProcessor; + where TProcessor : struct, IItemProcessor, IItemProcessor; double? GetMetric(string key); diff --git a/tracer/src/Datadog.Trace/Tagging/InferredProxyTags.cs b/tracer/src/Datadog.Trace/Tagging/InferredProxyTags.cs index 813f4e67e676..3b9973e06b1d 100644 --- a/tracer/src/Datadog.Trace/Tagging/InferredProxyTags.cs +++ b/tracer/src/Datadog.Trace/Tagging/InferredProxyTags.cs @@ -27,7 +27,7 @@ internal sealed partial class InferredProxyTags : InstrumentationTags, IHasStatu public string? HttpRoute { get; set; } [Tag(Trace.Tags.HttpStatusCode)] - public string? HttpStatusCode { get; set; } + public int? HttpStatusCode { get; set; } [Tag(Trace.Tags.ProxyStage)] public string? Stage { get; set; } diff --git a/tracer/src/Datadog.Trace/Tagging/TagsList.cs b/tracer/src/Datadog.Trace/Tagging/TagsList.cs index 1081efc20eeb..a26fe7209916 100644 --- a/tracer/src/Datadog.Trace/Tagging/TagsList.cs +++ b/tracer/src/Datadog.Trace/Tagging/TagsList.cs @@ -209,7 +209,7 @@ private static void SetTagNoLock(List> tags, KeyVal } public virtual void EnumerateTags(ref TProcessor processor) - where TProcessor : struct, IItemProcessor + where TProcessor : struct, IItemProcessor, IItemProcessor { var tags = Volatile.Read(ref _tags); if (tags is not null) diff --git a/tracer/src/Datadog.Trace/Tagging/WebTags.cs b/tracer/src/Datadog.Trace/Tagging/WebTags.cs index 80f60e34f35e..727e4f76e033 100644 --- a/tracer/src/Datadog.Trace/Tagging/WebTags.cs +++ b/tracer/src/Datadog.Trace/Tagging/WebTags.cs @@ -25,7 +25,7 @@ internal partial class WebTags : InstrumentationTags, IHasStatusCode public string HttpUrl { get; set; } [Tag(Trace.Tags.HttpStatusCode)] - public string HttpStatusCode { get; set; } + public int? HttpStatusCode { get; set; } [Tag(Trace.Tags.NetworkClientIp)] public string NetworkClientIp { get; set; } diff --git a/tracer/src/Datadog.Trace/Util/IntStringCache.cs b/tracer/src/Datadog.Trace/Util/IntStringCache.cs new file mode 100644 index 000000000000..0ada2d0f0342 --- /dev/null +++ b/tracer/src/Datadog.Trace/Util/IntStringCache.cs @@ -0,0 +1,45 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System.Globalization; + +namespace Datadog.Trace.Util +{ + /// + /// Caches the invariant decimal representation of small non-negative integers. + /// + /// + /// Tags backed by an int value have to be formatted + /// as a string every time a span is serialized, matched against a regex trace filter, or read + /// back through GetTag. Those values come from a small, bounded set, so caching them + /// keeps those paths allocation-free after the first occurrence of each value. + /// + internal static class IntStringCache + { + // Comfortably covers the HTTP status code range (100-599) with room for other small int tags. + private const int MaxCachedExclusive = 1024; + + private static readonly string?[] Cache = new string?[MaxCachedExclusive]; + + /// + /// Gets the invariant decimal representation of , using a cached + /// string when is small and non-negative. + /// + /// The value to format. + /// The invariant decimal representation of . + public static string ToInvariantString(int value) + { + if ((uint)value >= MaxCachedExclusive) + { + return value.ToString(CultureInfo.InvariantCulture); + } + + // The race here is benign: two threads can only ever produce equal, immutable strings. + return Cache[value] ??= value.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/HttpMessageHandlerTests.cs b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/HttpMessageHandlerTests.cs index 5c95766dda23..9e97cf94ec38 100644 --- a/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/HttpMessageHandlerTests.cs +++ b/tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/HttpMessageHandlerTests.cs @@ -106,7 +106,7 @@ public async Task HttpClient_SubmitsTraces( foreach (var span in spans) { - if (span.Tags[Tags.HttpStatusCode] == "502") + if (span.GetHttpStatusCode() == "502") { span.Error.Should().Be(1); } diff --git a/tracer/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.SingleSpan.cs b/tracer/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.SingleSpan.cs index ef6377432f21..9a89941447a4 100644 --- a/tracer/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.SingleSpan.cs +++ b/tracer/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.SingleSpan.cs @@ -20,6 +20,7 @@ using Datadog.Trace.Debugger.SpanCodeOrigin; using Datadog.Trace.DiagnosticListeners; using Datadog.Trace.DogStatsd; +using Datadog.Trace.ExtensionMethods; using Datadog.Trace.Iast.Settings; using Datadog.Trace.Sampling; using Datadog.Trace.TestHelpers; @@ -283,7 +284,7 @@ private static async Task AssertSingleSpanDiagnosticObserverSubmitsSpans( parentSpan.Type.Should().Be(SpanTypes.Web); parentSpan.ResourceName.Should().Be(resourceName); AssertTagHasValue(parentSpan, Tags.SpanKind, SpanKinds.Server); - AssertTagHasValue(parentSpan, Tags.HttpStatusCode, statusCode.ToString()); + parentSpan.GetHttpStatusCode().Should().Be(statusCode); parentSpan.Error.Should().Be(isError); if (expectedSpanTags is not null) diff --git a/tracer/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs b/tracer/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs index 7d74ad59d6ad..0a9db03dc7da 100644 --- a/tracer/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs +++ b/tracer/test/Datadog.Trace.IntegrationTests/DiagnosticListeners/AspNetCoreDiagnosticObserverTests.cs @@ -20,6 +20,7 @@ using Datadog.Trace.Debugger.SpanCodeOrigin; using Datadog.Trace.DiagnosticListeners; using Datadog.Trace.DogStatsd; +using Datadog.Trace.ExtensionMethods; using Datadog.Trace.Iast.Settings; using Datadog.Trace.Sampling; using Datadog.Trace.TestHelpers; @@ -481,7 +482,7 @@ private static async Task AssertDiagnosticObserverSubmitsSpans( parentSpan.Type.Should().Be(SpanTypes.Web); parentSpan.ResourceName.Should().Be(resourceName); AssertTagHasValue(parentSpan, Tags.SpanKind, SpanKinds.Server); - AssertTagHasValue(parentSpan, Tags.HttpStatusCode, statusCode.ToString()); + parentSpan.GetHttpStatusCode().Should().Be(statusCode); parentSpan.Error.Should().Be(isError); if (expectedParentSpanTags is not null) diff --git a/tracer/test/Datadog.Trace.IntegrationTests/StatsTests.cs b/tracer/test/Datadog.Trace.IntegrationTests/StatsTests.cs index ef5428e0a42d..7d411447b7f6 100644 --- a/tracer/test/Datadog.Trace.IntegrationTests/StatsTests.cs +++ b/tracer/test/Datadog.Trace.IntegrationTests/StatsTests.cs @@ -156,7 +156,7 @@ public async Task SendsStatsWithProcessing_Normalizer() durationStartBuckets.Single().Hits.Should().Be(3); durationStartBuckets.Single().Duration.Should().Be(beforeY2KDuration.ToNanoseconds()); - var durationStartSpans = spans.Where(s => s.Name == "default_operation" && s.Resource == "default-resource" && s.Service == "default-service" && s.GetTag(Tags.Origin) != "synthetics" && s.Type == "default-type" && s.GetTag(Tags.HttpStatusCode) == "200"); + var durationStartSpans = spans.Where(s => s.Name == "default_operation" && s.Resource == "default-resource" && s.Service == "default-service" && s.GetTag(Tags.Origin) != "synthetics" && s.Type == "default-type" && s.GetHttpStatusCode() == "200"); durationStartSpans.Should().HaveCount(3); durationStartSpans.Sum(s => s.Duration).Should().Be(beforeY2KDuration.ToNanoseconds()); @@ -603,7 +603,7 @@ void AssertStats(MockClientStatsPayload stats, Span span, long totalDuration) group.Errors.Should().Be(2); group.ErrorSummary.Should().NotBeEmpty(); group.Hits.Should().Be(tracesCount); - group.HttpStatusCode.Should().Be(int.Parse(span.GetTag(Tags.HttpStatusCode))); + group.HttpStatusCode.Should().Be(span.GetHttpStatusCode()); group.Name.Should().Be(span.OperationName); group.OkSummary.Should().NotBeEmpty(); group.Synthetics.Should().Be(false); diff --git a/tracer/test/Datadog.Trace.Security.Unit.Tests/ApiSec/ApiSecurityTests.cs b/tracer/test/Datadog.Trace.Security.Unit.Tests/ApiSec/ApiSecurityTests.cs index e20b02d94e74..378e8b2895fd 100644 --- a/tracer/test/Datadog.Trace.Security.Unit.Tests/ApiSec/ApiSecurityTests.cs +++ b/tracer/test/Datadog.Trace.Security.Unit.Tests/ApiSec/ApiSecurityTests.cs @@ -50,8 +50,8 @@ public async Task ApiSecurityTest(bool enable, bool apmTracingEnabled, bool last tc.SetSamplingPriority(samplingPriority); var span = new Span(new SpanContext(SpanContext.None, tc, "Test"), DateTimeOffset.Now); span.SetTag(Tags.HttpRoute, route); - var statusCode = "200"; - span.SetTag(Tags.HttpStatusCode, statusCode); + var statusCode = 200; + span.SetTag(Tags.HttpStatusCode, statusCode.ToString()); // TODO: We may need to test that setting http.response.status_code succeeds too span.SetTag(Tags.HttpMethod, "GET"); var res = apiSec.ShouldAnalyzeSchema(lastCall, span, dic, statusCode, new Dictionary()); if (res) @@ -86,7 +86,7 @@ public async Task ApiSecurityTestMaxRoutes() var route = $"route{i}"; var method = $"GET{i}"; - var statusCode = i.ToString(); + var statusCode = i; var resHash = ApiSecurity.CombineHashes(route, method, statusCode); queue.Enqueue(resHash); var dic = new Dictionary(); @@ -95,7 +95,7 @@ public async Task ApiSecurityTestMaxRoutes() var span = new Span(new SpanContext(SpanContext.None, tc, "Test"), dt); span.SetTag(Tags.HttpRoute, route); - span.SetTag(Tags.HttpStatusCode, statusCode); + span.SetTag(Tags.HttpStatusCode, statusCode.ToString()); span.SetTag(Tags.HttpMethod, method); var res = apiSec.ShouldAnalyzeSchema(true, span, dic, statusCode, new Dictionary()); res.Should().BeTrue(); diff --git a/tracer/test/Datadog.Trace.SourceGenerators.Tests/TagsListGeneratorTests.cs b/tracer/test/Datadog.Trace.SourceGenerators.Tests/TagsListGeneratorTests.cs index 1975fe501cc5..d2a38b49564b 100644 --- a/tracer/test/Datadog.Trace.SourceGenerators.Tests/TagsListGeneratorTests.cs +++ b/tracer/test/Datadog.Trace.SourceGenerators.Tests/TagsListGeneratorTests.cs @@ -88,6 +88,202 @@ protected override void WriteAdditionalTags(System.Text.StringBuilder sb) Assert.Empty(diagnostics); } + [Fact] + public void CanGenerateTagsListWithNullableIntTag() + { + // language=csharp + const string input = + """ + using Datadog.Trace.SourceGenerators; + namespace MyTests.TestListNameSpace + { + public class TestList + { + [Tag("TestPort")] + public int? Id { get; set; } + } + } + """; + // language=csharp + const string expected = Constants.FileHeader + + """ + using Datadog.Trace.Processors; + using Datadog.Trace.Tagging; + using System; + + namespace MyTests.TestListNameSpace + { + partial class TestList + { + // IdBytes = MessagePack.Serialize("TestPort"); + private static ReadOnlySpan IdBytes => [168, 84, 101, 115, 116, 80, 111, 114, 116]; + + public override string? GetTag(string key) + { + return key switch + { + "TestPort" => Id is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(Id.Value), + _ => base.GetTag(key), + }; + } + + public override void SetTag(string key, string? value) + { + switch(key) + { + case "TestPort": + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedId)) + { + Id = parsedId; + } + else + { + Id = null; + } + + break; + default: + base.SetTag(key, value); + break; + } + } + + public override void EnumerateTags(ref TProcessor processor) + { + if (Id is not null) + { + processor.Process(new TagItem("TestPort", Id.Value, IdBytes)); + } + + base.EnumerateTags(ref processor); + } + + protected override void WriteAdditionalTags(System.Text.StringBuilder sb) + { + if (Id is not null) + { + sb.Append("TestPort (tag):") + .Append(Id.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) + .Append(','); + } + + base.WriteAdditionalTags(sb); + } + } + } + + """; + + var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input); + Assert.Equal(expected, output); + Assert.Empty(diagnostics); + } + + [Fact] + public void CanGenerateTagsListWithMixedStringAndIntTags() + { + const string input = @"using Datadog.Trace.SourceGenerators; +namespace MyTests.TestListNameSpace +{ + public class TestList + { + [Tag(""NameTag"")] + public string Name { get; set; } + + [Tag(""PortTag"")] + public int? Port { get; set; } + } +}"; + const string expected = Constants.FileHeader + @"using Datadog.Trace.Processors; +using Datadog.Trace.Tagging; +using System; + +namespace MyTests.TestListNameSpace +{ + partial class TestList + { + // NameBytes = MessagePack.Serialize(""NameTag""); + private static ReadOnlySpan NameBytes => [167, 78, 97, 109, 101, 84, 97, 103]; + + // PortBytes = MessagePack.Serialize(""PortTag""); + private static ReadOnlySpan PortBytes => [167, 80, 111, 114, 116, 84, 97, 103]; + + public override string? GetTag(string key) + { + return key switch + { + ""NameTag"" => Name, + ""PortTag"" => Port is null ? null : Datadog.Trace.Util.IntStringCache.ToInvariantString(Port.Value), + _ => base.GetTag(key), + }; + } + + public override void SetTag(string key, string? value) + { + switch(key) + { + case ""NameTag"": + Name = value; + break; + case ""PortTag"": + if (int.TryParse(value, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out var parsedPort)) + { + Port = parsedPort; + } + else + { + Port = null; + } + + break; + default: + base.SetTag(key, value); + break; + } + } + + public override void EnumerateTags(ref TProcessor processor) + { + if (Name is not null) + { + processor.Process(new TagItem(""NameTag"", Name, NameBytes)); + } + + if (Port is not null) + { + processor.Process(new TagItem(""PortTag"", Port.Value, PortBytes)); + } + + base.EnumerateTags(ref processor); + } + + protected override void WriteAdditionalTags(System.Text.StringBuilder sb) + { + if (Name is not null) + { + sb.Append(""NameTag (tag):"") + .Append(Name) + .Append(','); + } + + if (Port is not null) + { + sb.Append(""PortTag (tag):"") + .Append(Port.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)) + .Append(','); + } + + base.WriteAdditionalTags(sb); + } + } +} +"; + + var (diagnostics, output) = TestHelpers.GetGeneratedOutput(input); + Assert.Equal(expected, output); + Assert.Empty(diagnostics); + } + [Fact] public void CanGenerateTagsListWithMetric() { @@ -681,6 +877,7 @@ public class TestList [Theory] [InlineData("double")] [InlineData("double?")] + [InlineData("int")] // only int? is supported [InlineData("SomeOtherType")] public void CantUseWrongTypeForTagProperty(string returnType) { diff --git a/tracer/test/Datadog.Trace.TestHelpers/MockSpanExtensions.cs b/tracer/test/Datadog.Trace.TestHelpers/MockSpanExtensions.cs new file mode 100644 index 000000000000..283f1a10ae22 --- /dev/null +++ b/tracer/test/Datadog.Trace.TestHelpers/MockSpanExtensions.cs @@ -0,0 +1,25 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +using System.Globalization; + +namespace Datadog.Trace.TestHelpers +{ + public static class MockSpanExtensions + { + /// + /// Gets the HTTP status code recorded on the span, or null if there isn't one. + /// + /// + /// This is the counterpart to SpanExtensions.GetHttpStatusCode. + /// Tests should go through it rather than reading http.status_code directly, so that + /// adding OTel semantic conventions only has to be handled here. + /// + /// The span to read the status code from. + /// The HTTP status code, or null if the span doesn't have one. + public static string GetHttpStatusCode(this MockSpan span) + => span.GetTag(Tags.HttpStatusCode); + } +} diff --git a/tracer/test/Datadog.Trace.Tests/Agent/MessagePack/SpanMessagePackFormatterTests.cs b/tracer/test/Datadog.Trace.Tests/Agent/MessagePack/SpanMessagePackFormatterTests.cs index 0ccc199bb79b..8da189d3f930 100644 --- a/tracer/test/Datadog.Trace.Tests/Agent/MessagePack/SpanMessagePackFormatterTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Agent/MessagePack/SpanMessagePackFormatterTests.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -88,7 +89,7 @@ public void SerializeSpans() actual.Error.Should().Be(expected.Error ? (byte)0x1 : (byte)0x0); actual.ParentId.Should().Be(expected.Context.ParentId); - var tagsProcessor = new TagsProcessor(actual.Tags); + var tagsProcessor = new TagsProcessor(actual.Tags); expected.Tags.EnumerateTags(ref tagsProcessor); // runtime-id and language are added during serialization @@ -111,7 +112,7 @@ public void SerializeSpans() } } - var metricsProcessor = new TagsProcessor(actual.Metrics); + var metricsProcessor = new MetricsProcessor(actual.Metrics); expected.Tags.EnumerateMetrics(ref metricsProcessor); // process-id and _dd.top_level are added during serialization @@ -727,21 +728,46 @@ public async Task ApmDisabled_WritesApmEnabledZero_OnAllSpans() } } - private readonly struct TagsProcessor : IItemProcessor + private readonly struct TagsProcessor : IItemProcessor, IItemProcessor { - private readonly Dictionary _expectedTags; + private readonly Dictionary _expectedTags; - public TagsProcessor(IEnumerable> expectedTags) + public TagsProcessor(IEnumerable> expectedTags) { _expectedTags = expectedTags.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } - public IEnumerable> Remaining => _expectedTags; + public IEnumerable> Remaining => _expectedTags; - public void Process(TagItem item) + public void Process(TagItem item) + => Assert(item.Key, item.Value); + + // int-backed tags are serialized as strings, so compare against the string representation + public void Process(TagItem item) + => Assert(item.Key, item.Value.ToString(CultureInfo.InvariantCulture)); + + private void Assert(string key, string value) + { + _expectedTags.Should().Contain(new KeyValuePair(key, value)); + _expectedTags.Remove(key); + } + } + + private readonly struct MetricsProcessor : IItemProcessor + { + private readonly Dictionary _expectedMetrics; + + public MetricsProcessor(IEnumerable> expectedMetrics) + { + _expectedMetrics = expectedMetrics.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + public IEnumerable> Remaining => _expectedMetrics; + + public void Process(TagItem item) { - _expectedTags.Should().Contain(new KeyValuePair(item.Key, item.Value)); - _expectedTags.Remove(item.Key); + _expectedMetrics.Should().Contain(new KeyValuePair(item.Key, item.Value)); + _expectedMetrics.Remove(item.Key); } } } diff --git a/tracer/test/Datadog.Trace.Tests/OpenTelemetrySpecialTagRemapperTests.cs b/tracer/test/Datadog.Trace.Tests/OpenTelemetrySpecialTagRemapperTests.cs index 682b1e6c6855..f5f2482fa21e 100644 --- a/tracer/test/Datadog.Trace.Tests/OpenTelemetrySpecialTagRemapperTests.cs +++ b/tracer/test/Datadog.Trace.Tests/OpenTelemetrySpecialTagRemapperTests.cs @@ -10,6 +10,7 @@ using Datadog.Trace.Activity.DuckTypes; using Datadog.Trace.Agent; using Datadog.Trace.Configuration; +using Datadog.Trace.ExtensionMethods; using Datadog.Trace.Sampling; using Datadog.Trace.Tagging; using Datadog.Trace.TestHelpers.TestTracer; @@ -204,7 +205,7 @@ public void HttpStatusCode_WithIntegerValue_IsNotRemapped_WhenOtelSemanticsEnabl using var scope = new Scope(parent: null, span, new AsyncLocalScopeManager(), finishOnClose: true); OtlpHelpers.UpdateSpanFromActivity(activityMock.Object, span, openTelemetrySemanticsEnabled: true); - span.GetTag(Tags.HttpStatusCode).Should().BeNull(); + span.GetHttpStatusCode().Should().BeNull(); span.GetMetric(key).Should().Be((double)statusCode); } @@ -223,7 +224,7 @@ public void HttpResponseStatusCode_WithIntegerValue_IsNotRemapped_WhenOtelSemant using var scope = new Scope(parent: null, span, new AsyncLocalScopeManager(), finishOnClose: true); OtlpHelpers.UpdateSpanFromActivity(activityMock.Object, span, openTelemetrySemanticsEnabled: true); - span.GetTag(Tags.HttpStatusCode).Should().BeNull(); + span.GetHttpStatusCode().Should().BeNull(); span.GetMetric(key).Should().Be((double)statusCode); } } diff --git a/tracer/test/Datadog.Trace.Tests/Tagging/TagsListTests.cs b/tracer/test/Datadog.Trace.Tests/Tagging/TagsListTests.cs index 614693ddcd6f..5c96c1489479 100644 --- a/tracer/test/Datadog.Trace.Tests/Tagging/TagsListTests.cs +++ b/tracer/test/Datadog.Trace.Tests/Tagging/TagsListTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Reflection; using System.Threading; @@ -101,6 +102,48 @@ public void SetTags_WithOnlyNullValues_DoesNotInitializeBackingTagsList() GetBackingTagsList(tags).Should().BeNull(); } + [Theory] + [InlineData(typeof(HttpTags))] + [InlineData(typeof(HttpV1Tags))] + [InlineData(typeof(WebTags))] + [InlineData(typeof(AspNetCoreTags))] + [InlineData(typeof(AwsSqsTags))] + [InlineData(typeof(InferredProxyTags))] + public void SetTag_WithNullValue_RemovesIntBackedTag(Type tagsType) + { + var tags = (TagsList)Activator.CreateInstance(tagsType); + + tags.SetTag(Tags.HttpStatusCode, "200"); + + ((IHasStatusCode)tags).HttpStatusCode.Should().Be(200); + tags.GetTag(Tags.HttpStatusCode).Should().Be("200"); + + tags.SetTag(Tags.HttpStatusCode, null); + + ((IHasStatusCode)tags).HttpStatusCode.Should().BeNull(); + tags.GetTag(Tags.HttpStatusCode).Should().BeNull(); + GetTagsSnapshot(tags).Select(x => x.Key).Should().NotContain(Tags.HttpStatusCode); + } + + [Theory] + [InlineData(typeof(HttpTags))] + [InlineData(typeof(HttpV1Tags))] + [InlineData(typeof(WebTags))] + [InlineData(typeof(AspNetCoreTags))] + [InlineData(typeof(AwsSqsTags))] + [InlineData(typeof(InferredProxyTags))] + public void SetTag_WithUnparseableValue_RemovesIntBackedTag(Type tagsType) + { + var tags = (TagsList)Activator.CreateInstance(tagsType); + + tags.SetTag(Tags.HttpStatusCode, "200"); + tags.SetTag(Tags.HttpStatusCode, "not-an-int"); + + ((IHasStatusCode)tags).HttpStatusCode.Should().BeNull(); + tags.GetTag(Tags.HttpStatusCode).Should().BeNull(); + GetTagsSnapshot(tags).Select(x => x.Key).Should().NotContain(Tags.HttpStatusCode); + } + [Fact] public void GetTag_GetMetric_ReturnUpdatedValues() { @@ -437,7 +480,7 @@ private static object GetBackingTagsList(TagsList tags) return field.GetValue(tags); } - private readonly struct TagCollectorProcessor : IItemProcessor + private readonly struct TagCollectorProcessor : IItemProcessor, IItemProcessor { private readonly List> _items; @@ -450,6 +493,11 @@ public void Process(TagItem item) { _items.Add(new(item.Key, item.Value)); } + + public void Process(TagItem item) + { + _items.Add(new(item.Key, item.Value.ToString(CultureInfo.InvariantCulture))); + } } } }