Skip to content

Commit 4035f3a

Browse files
authored
feat(trace): integrate semantics registry for gRPC status code lookup (#48481)
### What does this PR do? Replaces the ad-hoc gRPC status code key scanning in `getGRPCStatusCode` with `semantics.LookupString` via `DDSpanAccessor`. Adds the two missing string-typed fallbacks (`rpc.grpc.status.code`, `grpc.status.code`) to `mappings.json`. Note: the fallbacks use interleaved ordering (string then int64 per key). For V1 spans this gives key-first resolution — first key found wins regardless of storage type, matching the original `GetAttributeAsString` loop. For V0 spans it gives per-key meta-before-metrics, which differs from the original "any meta key beats any metrics key" only in the contrived cross-key case (not a realistic scenario). ### Motivation Centralize attribute fallback logic in the semantics registry. ### Describe how you validated your changes All existing `TestGetGRPCStatusCode` cases pass unchanged. `TestGetGRPCStatusCodeV1` covers integer, numeric string, enum name, `StatusCode.`-prefixed, fallback keys, and cross-key precedence. Co-authored-by: inigo.lopezdeheredia <inigo.lopezdeheredia@datadoghq.com>
1 parent 5949a8c commit 4035f3a

3 files changed

Lines changed: 106 additions & 60 deletions

File tree

pkg/trace/semantics/mappings.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,14 @@
143143
"rpc.grpc.status_code": {
144144
"canonical": "rpc.grpc.status_code",
145145
"fallbacks": [
146-
{"name": "rpc.grpc.status_code", "provider": "otel", "type": "int64"},
147-
{"name": "grpc.code", "provider": "datadog", "type": "int64"},
148-
{"name": "rpc.grpc.status.code", "provider": "otel", "type": "int64"},
149-
{"name": "grpc.status.code", "provider": "datadog", "type": "int64"},
150-
{"name": "rpc.grpc.status_code", "provider": "otel", "type": "string"},
151-
{"name": "grpc.code", "provider": "datadog", "type": "string"}
146+
{"name": "rpc.grpc.status_code", "provider": "otel", "type": "string"},
147+
{"name": "rpc.grpc.status_code", "provider": "otel", "type": "int64"},
148+
{"name": "grpc.code", "provider": "datadog", "type": "string"},
149+
{"name": "grpc.code", "provider": "datadog", "type": "int64"},
150+
{"name": "rpc.grpc.status.code", "provider": "otel", "type": "string"},
151+
{"name": "rpc.grpc.status.code", "provider": "otel", "type": "int64"},
152+
{"name": "grpc.status.code", "provider": "datadog", "type": "string"},
153+
{"name": "grpc.status.code", "provider": "datadog", "type": "int64"}
152154
]
153155
},
154156
"span.kind": {

pkg/trace/stats/aggregation.go

Lines changed: 26 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -193,65 +193,37 @@ var grpcStatusMap = map[string]string{
193193
"DATALOSS": "15",
194194
}
195195

196-
func getGRPCStatusCode(meta map[string]string, metrics map[string]float64) string {
197-
// List of possible keys to check in order
198-
statusCodeFields := []string{"rpc.grpc.status_code", "grpc.code", "rpc.grpc.status.code", "grpc.status.code"}
199-
200-
for _, key := range statusCodeFields {
201-
if strC, exists := meta[key]; exists && strC != "" {
202-
c, err := strconv.ParseUint(strC, 10, 32)
203-
if err == nil {
204-
return strconv.FormatUint(c, 10)
205-
}
206-
strC = strings.TrimPrefix(strC, "StatusCode.") // Some tracers send status code values prefixed by "StatusCode."
207-
strCUpper := strings.ToUpper(strC)
208-
if statusCode, exists := grpcStatusMap[strCUpper]; exists {
209-
return statusCode
210-
}
211-
212-
// If not integer or canceled or multi-word, check for valid gRPC status string
213-
if codeNum, found := code.Code_value[strCUpper]; found {
214-
return strconv.Itoa(int(codeNum))
215-
}
216-
217-
return ""
218-
}
196+
// parseGRPCStatusString converts a raw gRPC status string (numeric, enum name, or
197+
// "StatusCode."-prefixed) to its canonical numeric string form, or "" if unrecognized.
198+
func parseGRPCStatusString(strC string) string {
199+
if c, err := strconv.ParseUint(strC, 10, 32); err == nil {
200+
return strconv.FormatUint(c, 10)
219201
}
220-
221-
for _, key := range statusCodeFields { // Check if gRPC status code is stored in metrics
222-
if code, ok := metrics[key]; ok {
223-
return strconv.FormatUint(uint64(code), 10)
224-
}
202+
strC = strings.TrimPrefix(strC, "StatusCode.") // Some tracers send status code values prefixed by "StatusCode."
203+
strCUpper := strings.ToUpper(strC)
204+
if statusCode, exists := grpcStatusMap[strCUpper]; exists {
205+
return statusCode
206+
}
207+
if codeNum, found := code.Code_value[strCUpper]; found {
208+
return strconv.Itoa(int(codeNum))
225209
}
226-
227210
return ""
228211
}
229212

230-
func getGRPCStatusCodeV1(s *idx.InternalSpan) string {
231-
// List of possible keys to check in order
232-
statusCodeFields := []string{"rpc.grpc.status_code", "grpc.code", "rpc.grpc.status.code", "grpc.status.code"}
233-
234-
for _, key := range statusCodeFields {
235-
// TODO: could optimize this to use the Attribute directly to avoid the string conversion sometimes
236-
if strC, exists := s.GetAttributeAsString(key); exists && strC != "" {
237-
c, err := strconv.ParseUint(strC, 10, 32)
238-
if err == nil {
239-
return strconv.FormatUint(c, 10)
240-
}
241-
strC = strings.TrimPrefix(strC, "StatusCode.") // Some tracers send status code values prefixed by "StatusCode."
242-
strCUpper := strings.ToUpper(strC)
243-
if statusCode, exists := grpcStatusMap[strCUpper]; exists {
244-
return statusCode
245-
}
246-
247-
// If not integer or canceled or multi-word, check for valid gRPC status string
248-
if codeNum, found := code.Code_value[strCUpper]; found {
249-
return strconv.Itoa(int(codeNum))
250-
}
251-
252-
return ""
253-
}
213+
func getGRPCStatusCode(meta map[string]string, metrics map[string]float64) string {
214+
a := semantics.NewDDSpanAccessor(meta, metrics)
215+
strC := semantics.LookupString(ddRegistry, a, semantics.ConceptGRPCStatusCode)
216+
if strC == "" {
217+
return ""
254218
}
219+
return parseGRPCStatusString(strC)
220+
}
255221

256-
return ""
222+
func getGRPCStatusCodeV1(s *idx.InternalSpan) string {
223+
a := semantics.NewDDSpanAccessorV1(s)
224+
strC := semantics.LookupString(ddRegistry, a, semantics.ConceptGRPCStatusCode)
225+
if strC == "" {
226+
return ""
227+
}
228+
return parseGRPCStatusString(strC)
257229
}

pkg/trace/stats/aggregation_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,83 @@ func TestGetGRPCStatusCode(t *testing.T) {
170170
},
171171
"3",
172172
},
173+
// Earlier key wins regardless of whether the value comes from meta or metrics.
174+
{
175+
&pb.Span{
176+
Meta: map[string]string{"grpc.code": "7"},
177+
Metrics: map[string]float64{"rpc.grpc.status_code": 10},
178+
},
179+
"10",
180+
},
173181
} {
174182
assert.Equal(t, tt.out, getGRPCStatusCode(tt.in.Meta, tt.in.Metrics))
175183
}
176184
}
177185

186+
func TestGetGRPCStatusCodeV1(t *testing.T) {
187+
for _, tt := range []struct {
188+
name string
189+
in *idx.InternalSpan
190+
out string
191+
}{
192+
{"empty", newTestInternalSpanV1(), ""},
193+
{"int value", func() *idx.InternalSpan {
194+
s := newTestInternalSpanV1()
195+
s.SetAttributeFromString("rpc.grpc.status_code", "10")
196+
return s
197+
}(), "10"},
198+
{"numeric string value", func() *idx.InternalSpan {
199+
s := newTestInternalSpanV1()
200+
s.SetStringAttribute("rpc.grpc.status.code", "15")
201+
return s
202+
}(), "15"},
203+
{"enum name", func() *idx.InternalSpan {
204+
s := newTestInternalSpanV1()
205+
s.SetStringAttribute("rpc.grpc.status_code", "aborted")
206+
return s
207+
}(), "10"},
208+
{"StatusCode prefix", func() *idx.InternalSpan {
209+
s := newTestInternalSpanV1()
210+
s.SetStringAttribute("grpc.status.code", "StatusCode.ABORTED")
211+
return s
212+
}(), "10"},
213+
{"CANCELLED", func() *idx.InternalSpan {
214+
s := newTestInternalSpanV1()
215+
s.SetStringAttribute("rpc.grpc.status.code", "CANCELLED")
216+
return s
217+
}(), "1"},
218+
{"Canceled", func() *idx.InternalSpan {
219+
s := newTestInternalSpanV1()
220+
s.SetStringAttribute("rpc.grpc.status.code", "Canceled")
221+
return s
222+
}(), "1"},
223+
{"InvalidArgument", func() *idx.InternalSpan {
224+
s := newTestInternalSpanV1()
225+
s.SetStringAttribute("rpc.grpc.status_code", "InvalidArgument")
226+
return s
227+
}(), "3"},
228+
{"invalid unrecognized string", func() *idx.InternalSpan {
229+
s := newTestInternalSpanV1()
230+
s.SetStringAttribute("grpc.status.code", "StatusCodee.ABORTED")
231+
return s
232+
}(), ""},
233+
{"fallback key", func() *idx.InternalSpan {
234+
s := newTestInternalSpanV1()
235+
s.SetAttributeFromString("grpc.code", "7")
236+
return s
237+
}(), "7"},
238+
// Earlier key wins regardless of attribute storage type.
239+
{"earlier key beats later key regardless of type", func() *idx.InternalSpan {
240+
s := newTestInternalSpanV1()
241+
s.SetAttributeFromString("rpc.grpc.status_code", "10")
242+
s.SetStringAttribute("grpc.code", "7")
243+
return s
244+
}(), "10"},
245+
} {
246+
assert.Equal(t, tt.out, getGRPCStatusCodeV1(tt.in), tt.name)
247+
}
248+
}
249+
178250
func TestNewAggregation(t *testing.T) {
179251
peerSvcOnlyHash := uint64(3430395298086625290)
180252
peerTagsHash := uint64(9894752672193411515)

0 commit comments

Comments
 (0)