diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c68f52af..13023d774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http - Support Go `1.23.1`. ([#1051](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1051)) - Log version information in the CLI. ([#1077](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1077)) - Support `google.golang.org/grpc` `1.66.1`. ([#1078](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1078)) +- Add gRPC status code attribute for client spans (`rpc.grpc.status_code`). ([#1044](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1044)) +- Support `google.golang.org/grpc` `1.68.0-dev`. ([#1044](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1044)) +- Support `go.opentelemetry.io/otel@v1.30.0`. ([#1044](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1044)) ### Changed diff --git a/internal/pkg/inject/offset_results.json b/internal/pkg/inject/offset_results.json index bb28bfce4..8301f8469 100644 --- a/internal/pkg/inject/offset_results.json +++ b/internal/pkg/inject/offset_results.json @@ -938,7 +938,8 @@ "1.26.0", "1.27.0", "1.28.0", - "1.29.0" + "1.29.0", + "1.30.0" ] } ] @@ -1033,7 +1034,8 @@ "1.26.0", "1.27.0", "1.28.0", - "1.29.0" + "1.29.0", + "1.30.0" ] } ] @@ -1128,7 +1130,8 @@ "1.26.0", "1.27.0", "1.28.0", - "1.29.0" + "1.29.0", + "1.30.0" ] } ] @@ -1228,7 +1231,8 @@ "1.26.0", "1.27.0", "1.28.0", - "1.29.0" + "1.29.0", + "1.30.0" ] } ] @@ -1531,6 +1535,12 @@ { "field": "StreamID", "offsets": [ + { + "offset": null, + "versions": [ + "0.8.0" + ] + }, { "offset": 8, "versions": [ @@ -1541,7 +1551,6 @@ "0.5.0", "0.6.0", "0.7.0", - "0.8.0", "0.9.0", "0.10.0", "0.11.0", @@ -1575,6 +1584,12 @@ { "field": "Fields", "offsets": [ + { + "offset": null, + "versions": [ + "0.8.0" + ] + }, { "offset": 8, "versions": [ @@ -1585,7 +1600,6 @@ "0.5.0", "0.6.0", "0.7.0", - "0.8.0", "0.9.0", "0.10.0", "0.11.0", @@ -1620,6 +1634,219 @@ { "module": "google.golang.org/grpc", "packages": [ + { + "package": "google.golang.org/genproto/googleapis/rpc/status", + "structs": [ + { + "struct": "Status", + "fields": [ + { + "field": "Code", + "offsets": [ + { + "offset": null, + "versions": [ + "1.0.0", + "1.0.1-GA", + "1.0.2", + "1.0.3", + "1.0.4", + "1.0.5", + "1.2.0", + "1.2.1", + "1.3.0", + "1.4.0", + "1.4.1", + "1.4.2", + "1.5.0", + "1.5.1", + "1.5.2", + "1.6.0", + "1.7.0", + "1.7.1", + "1.7.2", + "1.7.3", + "1.7.4", + "1.7.5" + ] + }, + { + "offset": 0, + "versions": [ + "1.15.0", + "1.16.0", + "1.17.0", + "1.18.0", + "1.18.1", + "1.19.0", + "1.19.1", + "1.20.0", + "1.20.1", + "1.21.0", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + "1.22.0", + "1.22.1", + "1.22.2", + "1.22.3", + "1.23.0", + "1.23.1", + "1.24.0", + "1.25.0", + "1.25.1", + "1.26.0", + "1.27.0-pre", + "1.27.0", + "1.27.1", + "1.28.0-pre", + "1.28.0", + "1.28.1", + "1.29.0-dev", + "1.29.0", + "1.29.1", + "1.30.0-dev", + "1.30.0-dev.1", + "1.30.0", + "1.30.1", + "1.31.0-dev", + "1.31.0", + "1.31.1", + "1.32.0-dev", + "1.32.0", + "1.33.0-dev", + "1.33.0", + "1.33.1", + "1.34.0-dev" + ] + }, + { + "offset": 40, + "versions": [ + "1.8.0", + "1.8.2", + "1.9.0", + "1.9.1", + "1.9.2", + "1.10.0", + "1.10.1", + "1.11.0", + "1.11.1", + "1.11.2", + "1.11.3", + "1.12.0", + "1.12.1", + "1.12.2", + "1.13.0", + "1.14.0", + "1.33.2", + "1.33.3", + "1.34.0", + "1.34.1", + "1.34.2", + "1.35.0-dev", + "1.35.0", + "1.35.1", + "1.36.0-dev", + "1.36.0", + "1.36.1", + "1.37.0-dev", + "1.37.0", + "1.37.1", + "1.38.0-dev", + "1.38.0", + "1.38.1", + "1.39.0-dev", + "1.39.0", + "1.39.1", + "1.40.0-dev", + "1.40.0", + "1.40.1", + "1.41.0-dev", + "1.41.0", + "1.41.1", + "1.42.0-dev", + "1.42.0", + "1.43.0-dev", + "1.43.0", + "1.44.0-dev", + "1.44.0", + "1.45.0-dev", + "1.45.0", + "1.46.0-dev", + "1.46.0", + "1.46.1", + "1.46.2", + "1.47.0-dev", + "1.47.0", + "1.48.0-dev", + "1.48.0", + "1.49.0-dev", + "1.49.0", + "1.50.0-dev", + "1.50.0", + "1.50.1", + "1.51.0-dev", + "1.51.0", + "1.52.0-dev", + "1.52.0", + "1.52.1", + "1.52.3", + "1.53.0-dev", + "1.53.0", + "1.54.0", + "1.54.1", + "1.55.0-dev", + "1.55.0", + "1.55.1", + "1.56.0-dev", + "1.56.0", + "1.56.1", + "1.56.2", + "1.56.3", + "1.57.0-dev", + "1.57.0", + "1.57.1", + "1.57.2", + "1.58.0-dev", + "1.58.0", + "1.58.1", + "1.58.2", + "1.58.3", + "1.59.0-dev", + "1.59.0", + "1.60.0-dev", + "1.60.0", + "1.60.1", + "1.61.0-dev", + "1.61.0", + "1.61.1", + "1.61.2", + "1.62.0", + "1.62.1", + "1.62.2", + "1.63.0", + "1.63.1", + "1.63.2", + "1.63.3", + "1.64.0", + "1.64.1", + "1.65.0-dev", + "1.65.0", + "1.66.0-dev", + "1.66.0", + "1.66.1", + "1.67.0-dev", + "1.68.0-dev" + ] + } + ] + } + ] + } + ] + }, { "package": "google.golang.org/grpc", "structs": [ @@ -1818,7 +2045,419 @@ "1.66.0-dev", "1.66.0", "1.66.1", - "1.67.0-dev" + "1.67.0-dev", + "1.68.0-dev" + ] + } + ] + } + ] + } + ] + }, + { + "package": "google.golang.org/grpc/internal/status", + "structs": [ + { + "struct": "Error", + "fields": [ + { + "field": "s", + "offsets": [ + { + "offset": null, + "versions": [ + "1.0.0", + "1.0.1-GA", + "1.0.2", + "1.0.3", + "1.0.4", + "1.0.5", + "1.2.0", + "1.2.1", + "1.3.0", + "1.4.0", + "1.4.1", + "1.4.2", + "1.5.0", + "1.5.1", + "1.5.2", + "1.6.0", + "1.7.0", + "1.7.1", + "1.7.2", + "1.7.3", + "1.7.4", + "1.7.5", + "1.8.0", + "1.8.2", + "1.9.0", + "1.9.1", + "1.9.2", + "1.10.0", + "1.10.1", + "1.11.0", + "1.11.1", + "1.11.2", + "1.11.3", + "1.12.0", + "1.12.1", + "1.12.2", + "1.13.0", + "1.14.0", + "1.15.0", + "1.16.0", + "1.17.0", + "1.18.0", + "1.18.1", + "1.19.0", + "1.19.1", + "1.20.0", + "1.20.1", + "1.21.0", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + "1.22.0", + "1.22.1", + "1.22.2", + "1.22.3", + "1.23.0", + "1.23.1", + "1.24.0", + "1.25.0", + "1.25.1", + "1.26.0", + "1.27.0-pre", + "1.27.0", + "1.27.1", + "1.28.0-pre", + "1.28.0", + "1.28.1", + "1.29.0-dev", + "1.29.0", + "1.29.1", + "1.30.0-dev", + "1.30.0-dev.1", + "1.30.0", + "1.30.1", + "1.31.0-dev", + "1.31.0", + "1.31.1", + "1.32.0-dev", + "1.32.0", + "1.33.0-dev", + "1.33.0", + "1.33.1", + "1.33.2", + "1.33.3", + "1.34.0-dev", + "1.34.0", + "1.34.1", + "1.34.2", + "1.35.0-dev", + "1.35.0", + "1.35.1", + "1.36.0-dev", + "1.36.0", + "1.36.1", + "1.37.0-dev", + "1.37.0", + "1.37.1", + "1.38.0-dev", + "1.38.0", + "1.38.1", + "1.39.0-dev", + "1.39.0", + "1.39.1", + "1.40.0-dev" + ] + }, + { + "offset": 0, + "versions": [ + "1.40.0", + "1.40.1", + "1.41.0-dev", + "1.41.0", + "1.41.1", + "1.42.0-dev", + "1.42.0", + "1.43.0-dev", + "1.43.0", + "1.44.0-dev", + "1.44.0", + "1.45.0-dev", + "1.45.0", + "1.46.0-dev", + "1.46.0", + "1.46.1", + "1.46.2", + "1.47.0-dev", + "1.47.0", + "1.48.0-dev", + "1.48.0", + "1.49.0-dev", + "1.49.0", + "1.50.0-dev", + "1.50.0", + "1.50.1", + "1.51.0-dev", + "1.51.0", + "1.52.0-dev", + "1.52.0", + "1.52.1", + "1.52.3", + "1.53.0-dev", + "1.53.0", + "1.54.0", + "1.54.1", + "1.55.0-dev", + "1.55.0", + "1.55.1", + "1.56.0-dev", + "1.56.0", + "1.56.1", + "1.56.2", + "1.56.3", + "1.57.0-dev", + "1.57.0", + "1.57.1", + "1.57.2", + "1.58.0-dev", + "1.58.0", + "1.58.1", + "1.58.2", + "1.58.3", + "1.59.0-dev", + "1.59.0", + "1.60.0-dev", + "1.60.0", + "1.60.1", + "1.61.0-dev", + "1.61.0", + "1.61.1", + "1.61.2", + "1.62.0", + "1.62.1", + "1.62.2", + "1.63.0", + "1.63.1", + "1.63.2", + "1.63.3", + "1.64.0", + "1.64.1", + "1.65.0-dev", + "1.65.0", + "1.66.0-dev", + "1.66.0", + "1.66.1", + "1.67.0-dev", + "1.68.0-dev" + ] + } + ] + } + ] + }, + { + "struct": "Status", + "fields": [ + { + "field": "s", + "offsets": [ + { + "offset": null, + "versions": [ + "1.0.0", + "1.0.1-GA", + "1.0.2", + "1.0.3", + "1.0.4", + "1.0.5", + "1.2.0", + "1.2.1", + "1.3.0", + "1.4.0", + "1.4.1", + "1.4.2", + "1.5.0", + "1.5.1", + "1.5.2", + "1.6.0", + "1.7.0", + "1.7.1", + "1.7.2", + "1.7.3", + "1.7.4", + "1.7.5", + "1.8.0", + "1.8.2", + "1.9.0", + "1.9.1", + "1.9.2", + "1.10.0", + "1.10.1", + "1.11.0", + "1.11.1", + "1.11.2", + "1.11.3", + "1.12.0", + "1.12.1", + "1.12.2", + "1.13.0", + "1.14.0", + "1.15.0", + "1.16.0", + "1.17.0", + "1.18.0", + "1.18.1", + "1.19.0", + "1.19.1", + "1.20.0", + "1.20.1", + "1.21.0", + "1.21.1", + "1.21.2", + "1.21.3", + "1.21.4", + "1.22.0", + "1.22.1", + "1.22.2", + "1.22.3", + "1.23.0", + "1.23.1", + "1.24.0", + "1.25.0", + "1.25.1", + "1.26.0", + "1.27.0-pre", + "1.27.0", + "1.27.1", + "1.28.0-pre", + "1.28.0", + "1.28.1", + "1.29.0-dev" + ] + }, + { + "offset": 0, + "versions": [ + "1.29.0", + "1.29.1", + "1.30.0-dev", + "1.30.0-dev.1", + "1.30.0", + "1.30.1", + "1.31.0-dev", + "1.31.0", + "1.31.1", + "1.32.0-dev", + "1.32.0", + "1.33.0-dev", + "1.33.0", + "1.33.1", + "1.33.2", + "1.33.3", + "1.34.0-dev", + "1.34.0", + "1.34.1", + "1.34.2", + "1.35.0-dev", + "1.35.0", + "1.35.1", + "1.36.0-dev", + "1.36.0", + "1.36.1", + "1.37.0-dev", + "1.37.0", + "1.37.1", + "1.38.0-dev", + "1.38.0", + "1.38.1", + "1.39.0-dev", + "1.39.0", + "1.39.1", + "1.40.0-dev", + "1.40.0", + "1.40.1", + "1.41.0-dev", + "1.41.0", + "1.41.1", + "1.42.0-dev", + "1.42.0", + "1.43.0-dev", + "1.43.0", + "1.44.0-dev", + "1.44.0", + "1.45.0-dev", + "1.45.0", + "1.46.0-dev", + "1.46.0", + "1.46.1", + "1.46.2", + "1.47.0-dev", + "1.47.0", + "1.48.0-dev", + "1.48.0", + "1.49.0-dev", + "1.49.0", + "1.50.0-dev", + "1.50.0", + "1.50.1", + "1.51.0-dev", + "1.51.0", + "1.52.0-dev", + "1.52.0", + "1.52.1", + "1.52.3", + "1.53.0-dev", + "1.53.0", + "1.54.0", + "1.54.1", + "1.55.0-dev", + "1.55.0", + "1.55.1", + "1.56.0-dev", + "1.56.0", + "1.56.1", + "1.56.2", + "1.56.3", + "1.57.0-dev", + "1.57.0", + "1.57.1", + "1.57.2", + "1.58.0-dev", + "1.58.0", + "1.58.1", + "1.58.2", + "1.58.3", + "1.59.0-dev", + "1.59.0", + "1.60.0-dev", + "1.60.0", + "1.60.1", + "1.61.0-dev", + "1.61.0", + "1.61.1", + "1.61.2", + "1.62.0", + "1.62.1", + "1.62.2", + "1.63.0", + "1.63.1", + "1.63.2", + "1.63.3", + "1.64.0", + "1.64.1", + "1.65.0-dev", + "1.65.0", + "1.66.0-dev", + "1.66.0", + "1.66.1", + "1.67.0-dev", + "1.68.0-dev" ] } ] @@ -2035,7 +2674,8 @@ "versions": [ "1.66.0", "1.66.1", - "1.67.0-dev" + "1.67.0-dev", + "1.68.0-dev" ] } ] @@ -2232,7 +2872,8 @@ "1.66.0-dev", "1.66.0", "1.66.1", - "1.67.0-dev" + "1.67.0-dev", + "1.68.0-dev" ] } ] @@ -2444,7 +3085,8 @@ "versions": [ "1.66.0", "1.66.1", - "1.67.0-dev" + "1.67.0-dev", + "1.68.0-dev" ] } ] @@ -2646,7 +3288,8 @@ "1.66.0-dev", "1.66.0", "1.66.1", - "1.67.0-dev" + "1.67.0-dev", + "1.68.0-dev" ] } ] @@ -2843,7 +3486,8 @@ "1.66.0-dev", "1.66.0", "1.66.1", - "1.67.0-dev" + "1.67.0-dev", + "1.68.0-dev" ] } ] @@ -3070,7 +3714,8 @@ "1.66.0-dev", "1.66.0", "1.66.1", - "1.67.0-dev" + "1.67.0-dev", + "1.68.0-dev" ] } ] diff --git a/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf/probe.bpf.c b/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf/probe.bpf.c index 0d7468e42..40bd01031 100644 --- a/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf/probe.bpf.c +++ b/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf/probe.bpf.c @@ -18,6 +18,7 @@ struct grpc_request_t BASE_SPAN_PROPERTIES char method[MAX_SIZE]; char target[MAX_SIZE]; + u32 status_code; }; struct hpack_header_field @@ -48,6 +49,9 @@ volatile const u64 clientconn_target_ptr_pos; volatile const u64 httpclient_nextid_pos; volatile const u64 headerFrame_streamid_pos; volatile const u64 headerFrame_hf_pos; +volatile const u64 error_status_pos; +volatile const u64 status_s_pos; +volatile const u64 status_code_pos; // This instrumentation attaches uprobe to the following function: // func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...CallOption) error @@ -105,7 +109,52 @@ int uprobe_ClientConn_Invoke(struct pt_regs *ctx) return 0; } -UPROBE_RETURN(ClientConn_Invoke, struct grpc_request_t, grpc_events, events, 3, 0, true) +// This instrumentation attaches uprobe to the following function: +// func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...CallOption) error +SEC("uprobe/ClientConn_Invoke") +int uprobe_ClientConn_Invoke_Returns(struct pt_regs *ctx) { + struct go_iface go_context = {0}; + get_Go_context(ctx, 3, 0, true, &go_context); + void *key = get_consistent_key(ctx, go_context.data); + struct grpc_request_t *grpc_span = bpf_map_lookup_elem(&grpc_events, &key); + if (grpc_span == NULL) { + bpf_printk("event is NULL in ret probe"); + return 0; + } + + // Getting the returned response (error) + // The status code is embedded 3 layers deep: + // Invoke() error + // the `error` interface concrete type here is a gRPC `internal.Error` struct + // type Error struct { + // s *Status + // } + // The `Error` struct embeds a `Status` proto object + // type Status struct { + // s *Status + // } + // The `Status` proto object contains a `Code` int32 field, which is what we want + void *resp_ptr = get_argument(ctx, 2); + if(resp_ptr == 0) { + // err == nil + goto done; + } + void *status_ptr = 0; + // get `s` (Status pointer field) from Error struct + bpf_probe_read_user(&status_ptr, sizeof(status_ptr), (void *)(resp_ptr+error_status_pos)); + // get `s` field from Status object pointer + void *s_ptr = 0; + bpf_probe_read_user(&s_ptr, sizeof(s_ptr), (void *)(status_ptr + status_s_pos)); + // Get status code from Status.s pointer + bpf_probe_read_user(&grpc_span->status_code, sizeof(grpc_span->status_code), (void *)(s_ptr + status_code_pos)); + +done: + grpc_span->end_time = bpf_ktime_get_ns(); + output_span_event(ctx, grpc_span, sizeof(*grpc_span), &grpc_span->sc); + stop_tracking_span(&grpc_span->sc, &grpc_span->psc); + bpf_map_delete_elem(&grpc_events, &key); + return 0; +} // func (l *loopyWriter) headerHandler(h *headerFrame) error SEC("uprobe/loopyWriter_headerHandler") diff --git a/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf_arm64_bpfel.go b/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf_arm64_bpfel.go index 397671c99..6ecadb4ef 100644 --- a/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf_arm64_bpfel.go +++ b/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf_arm64_bpfel.go @@ -13,13 +13,13 @@ import ( ) type bpfGrpcRequestT struct { - StartTime uint64 - EndTime uint64 - Sc bpfSpanContext - Psc bpfSpanContext - Method [50]int8 - Target [50]int8 - _ [4]byte + StartTime uint64 + EndTime uint64 + Sc bpfSpanContext + Psc bpfSpanContext + Method [50]int8 + Target [50]int8 + StatusCode uint32 } type bpfSliceArrayBuff struct{ Buff [1024]uint8 } diff --git a/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf_x86_bpfel.go b/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf_x86_bpfel.go index 10329e809..6edd1c2e5 100644 --- a/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf_x86_bpfel.go +++ b/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/bpf_x86_bpfel.go @@ -13,13 +13,13 @@ import ( ) type bpfGrpcRequestT struct { - StartTime uint64 - EndTime uint64 - Sc bpfSpanContext - Psc bpfSpanContext - Method [50]int8 - Target [50]int8 - _ [4]byte + StartTime uint64 + EndTime uint64 + Sc bpfSpanContext + Psc bpfSpanContext + Method [50]int8 + Target [50]int8 + StatusCode uint32 } type bpfSliceArrayBuff struct{ Buff [1024]uint8 } diff --git a/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/probe.go b/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/probe.go index 6bb734ac7..f1011d6a3 100644 --- a/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/probe.go +++ b/internal/pkg/instrumentation/bpf/google.golang.org/grpc/client/probe.go @@ -11,6 +11,7 @@ import ( "github.com/cilium/ebpf" "github.com/go-logr/logr" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" "go.opentelemetry.io/otel/trace" "golang.org/x/sys/unix" @@ -56,6 +57,18 @@ func New(logger logr.Logger) probe.Probe { Key: "headerFrame_streamid_pos", Val: structfield.NewID("google.golang.org/grpc", "google.golang.org/grpc/internal/transport", "headerFrame", "streamID"), }, + probe.StructFieldConst{ + Key: "error_status_pos", + Val: structfield.NewID("google.golang.org/grpc", "google.golang.org/grpc/internal/status", "Error", "s"), + }, + probe.StructFieldConst{ + Key: "status_s_pos", + Val: structfield.NewID("google.golang.org/grpc", "google.golang.org/grpc/internal/status", "Status", "s"), + }, + probe.StructFieldConst{ + Key: "status_code_pos", + Val: structfield.NewID("google.golang.org/grpc", "google.golang.org/genproto/googleapis/rpc/status", "Status", "Code"), + }, }, Uprobes: []probe.Uprobe{ { @@ -88,8 +101,9 @@ func verifyAndLoadBpf() (*ebpf.CollectionSpec, error) { // event represents an event in the gRPC client during a gRPC request. type event struct { context.BaseSpanProperties - Method [50]byte - Target [50]byte + Method [50]byte + Target [50]byte + StatusCode int32 } // According to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/rpc.md @@ -110,6 +124,8 @@ func convertEvent(e *event) []*probe.SpanEvent { semconv.RPCServiceKey.String(method), semconv.ServerAddress(target)) + attrs = append(attrs, semconv.RPCGRPCStatusCodeKey.Int(int(e.StatusCode))) + sc := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: e.SpanContext.TraceID, SpanID: e.SpanContext.SpanID, @@ -129,15 +145,19 @@ func convertEvent(e *event) []*probe.SpanEvent { pscPtr = nil } - return []*probe.SpanEvent{ - { - SpanName: method, - StartTime: utils.BootRelativeTime(e.StartTime), - EndTime: utils.BootRelativeTime(e.EndTime), - Attributes: attrs, - SpanContext: &sc, - ParentSpanContext: pscPtr, - TracerSchema: semconv.SchemaURL, - }, + event := &probe.SpanEvent{ + SpanName: method, + StartTime: utils.BootRelativeTime(e.StartTime), + EndTime: utils.BootRelativeTime(e.EndTime), + Attributes: attrs, + SpanContext: &sc, + ParentSpanContext: pscPtr, + TracerSchema: semconv.SchemaURL, } + + if e.StatusCode > 0 { + event.Status = probe.Status{Code: codes.Error} + } + + return []*probe.SpanEvent{event} } diff --git a/internal/test/e2e/grpc/main.go b/internal/test/e2e/grpc/main.go index 9999cf650..3540139a3 100644 --- a/internal/test/e2e/grpc/main.go +++ b/internal/test/e2e/grpc/main.go @@ -71,6 +71,13 @@ func main() { s.GracefulStop() <-done + // try making a request after the server has stopped to generate an error status + _, err = c.SayHello(ctx, &pb.HelloRequest{Name: "world"}) + if err == nil { + log.Fatalf("expected an error but none was returned") + } + log.Printf("received expected error: %+v", err) + // Give time for auto-instrumentation to do the dew. time.Sleep(5 * time.Second) } diff --git a/internal/test/e2e/grpc/traces.json b/internal/test/e2e/grpc/traces.json index b786811b5..20baa8664 100644 --- a/internal/test/e2e/grpc/traces.json +++ b/internal/test/e2e/grpc/traces.json @@ -104,6 +104,12 @@ "value": { "stringValue": "localhost" } + }, + { + "key": "rpc.grpc.status_code", + "value": { + "intValue": "0" + } } ], "flags": 256, @@ -113,6 +119,49 @@ "spanId": "xxxxx", "status": {}, "traceId": "xxxxx" + }, + { + "attributes": [ + { + "key": "network.peer.port", + "value": { + "intValue": "xxxxx" + } + }, + { + "key": "rpc.system", + "value": { + "stringValue": "grpc" + } + }, + { + "key": "rpc.service", + "value": { + "stringValue": "/helloworld.Greeter/SayHello" + } + }, + { + "key": "server.address", + "value": { + "stringValue": "localhost" + } + }, + { + "key": "rpc.grpc.status_code", + "value": { + "intValue": "14" + } + } + ], + "flags": 256, + "kind": 3, + "name": "/helloworld.Greeter/SayHello", + "parentSpanId": "", + "spanId": "xxxxx", + "status": { + "code": 2 + }, + "traceId": "xxxxx" } ] } diff --git a/internal/test/e2e/grpc/verify.bats b/internal/test/e2e/grpc/verify.bats index 56432d81c..d4514520b 100644 --- a/internal/test/e2e/grpc/verify.bats +++ b/internal/test/e2e/grpc/verify.bats @@ -40,14 +40,16 @@ SCOPE="go.opentelemetry.io/auto/google.golang.org/grpc" } @test "client, server :: spans have same trace ID" { - client_trace_id=$(client_spans_from_scope_named ${SCOPE} | jq ".traceId") + # only check the first client span (the 2nd is an error) + client_trace_id=$(client_spans_from_scope_named ${SCOPE} | jq ".traceId" | jq -Rn '[inputs]' | jq -r .[0]) server_trace_id=$(server_spans_from_scope_named ${SCOPE} | jq ".traceId") assert_equal "$server_trace_id" "$client_trace_id" } @test "client, server :: server span has client span as parent" { server_parent_span_id=$(server_spans_from_scope_named ${SCOPE} | jq ".parentSpanId") - client_span_id=$(client_spans_from_scope_named ${SCOPE} | jq ".spanId") + # only check the first client span (the 2nd is an error) + client_span_id=$(client_spans_from_scope_named ${SCOPE} | jq ".spanId"| jq -Rn '[inputs]' | jq -r .[0]) assert_equal "$client_span_id" "$server_parent_span_id" } @@ -57,26 +59,36 @@ SCOPE="go.opentelemetry.io/auto/google.golang.org/grpc" } @test "client :: emits a span name 'SayHello'" { - result=$(client_span_names_for ${SCOPE}) + result=$(client_span_names_for ${SCOPE} | uniq) assert_equal "$result" '"/helloworld.Greeter/SayHello"' } @test "client :: includes rpc.system attribute" { - result=$(client_span_attributes_for ${SCOPE} | jq "select(.key == \"rpc.system\").value.stringValue") + result=$(client_span_attributes_for ${SCOPE} | jq "select(.key == \"rpc.system\").value.stringValue" | uniq) assert_equal "$result" '"grpc"' } @test "client :: includes rpc.service attribute" { - result=$(client_span_attributes_for ${SCOPE} | jq "select(.key == \"rpc.service\").value.stringValue") + result=$(client_span_attributes_for ${SCOPE} | jq "select(.key == \"rpc.service\").value.stringValue" | uniq) assert_equal "$result" '"/helloworld.Greeter/SayHello"' } @test "client :: trace ID present and valid in all spans" { - trace_id=$(client_spans_from_scope_named ${SCOPE} | jq ".traceId") + trace_id=$(client_spans_from_scope_named ${SCOPE} | jq ".traceId" | jq -Rn '[inputs]' | jq -r .[0]) + assert_regex "$trace_id" ${MATCH_A_TRACE_ID} + trace_id=$(client_spans_from_scope_named ${SCOPE} | jq ".traceId" | jq -Rn '[inputs]' | jq -r .[1]) assert_regex "$trace_id" ${MATCH_A_TRACE_ID} } @test "client :: span ID present and valid in all spans" { - span_id=$(client_spans_from_scope_named ${SCOPE} | jq ".spanId") + span_id=$(client_spans_from_scope_named ${SCOPE} | jq ".spanId" | jq -Rn '[inputs]' | jq -r .[0]) + assert_regex "$span_id" ${MATCH_A_SPAN_ID} + span_id=$(client_spans_from_scope_named ${SCOPE} | jq ".spanId" | jq -Rn '[inputs]' | jq -r .[1]) assert_regex "$span_id" ${MATCH_A_SPAN_ID} } + +@test "client :: error code is present for unsuccessful span" { + # gRPC error code 14 - Unavailable + result=$(client_span_attributes_for ${SCOPE} | jq 'select(.key == "rpc.grpc.status_code" and .value.intValue == "14")') + assert_not_empty "$result" +} diff --git a/internal/tools/inspect/cmd/offsetgen/main.go b/internal/tools/inspect/cmd/offsetgen/main.go index 94f2ceefb..fd8de960a 100644 --- a/internal/tools/inspect/cmd/offsetgen/main.go +++ b/internal/tools/inspect/cmd/offsetgen/main.go @@ -142,6 +142,9 @@ func manifests() ([]inspect.Manifest, error) { structfield.NewID("google.golang.org/grpc", "google.golang.org/grpc/internal/transport", "http2Client", "nextID"), structfield.NewID("google.golang.org/grpc", "google.golang.org/grpc/internal/transport", "headerFrame", "streamID"), structfield.NewID("google.golang.org/grpc", "google.golang.org/grpc/internal/transport", "headerFrame", "hf"), + structfield.NewID("google.golang.org/grpc", "google.golang.org/grpc/internal/status", "Error", "s"), + structfield.NewID("google.golang.org/grpc", "google.golang.org/grpc/internal/status", "Status", "s"), + structfield.NewID("google.golang.org/grpc", "google.golang.org/genproto/googleapis/rpc/status", "Status", "Code"), }, }, {