fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

grpc: port internal error interceptors from sourcegraph/sourcegraph (#639)

+1876 -7
+13 -2
cmd/zoekt-sourcegraph-indexserver/main.go
··· 36 36 "github.com/prometheus/client_golang/prometheus" 37 37 "github.com/prometheus/client_golang/prometheus/promauto" 38 38 sglog "github.com/sourcegraph/log" 39 + "github.com/sourcegraph/zoekt/grpc/internalerrs" 39 40 "go.uber.org/automaxprocs/maxprocs" 40 41 "golang.org/x/net/trace" 41 42 "golang.org/x/sys/unix" ··· 1370 1371 WithShouldUseGRPC(conf.useGRPC), 1371 1372 } 1372 1373 1374 + logger := sglog.Scoped("zoektConfigurationGRPCClient", "") 1375 + 1373 1376 gRPCConnectionOptions := []grpc.DialOption{ 1374 1377 grpc.WithTransportCredentials(insecure.NewCredentials()), 1375 - grpc.WithChainStreamInterceptor(internalActorStreamInterceptor()), 1376 - grpc.WithChainUnaryInterceptor(internalActorUnaryInterceptor()), 1378 + grpc.WithChainStreamInterceptor( 1379 + internalActorStreamInterceptor(), 1380 + internalerrs.LoggingStreamClientInterceptor(logger), 1381 + internalerrs.PrometheusStreamClientInterceptor, 1382 + ), 1383 + grpc.WithChainUnaryInterceptor( 1384 + internalActorUnaryInterceptor(), 1385 + internalerrs.LoggingUnaryClientInterceptor(logger), 1386 + internalerrs.PrometheusUnaryClientInterceptor, 1387 + ), 1377 1388 grpc.WithDefaultServiceConfig(defaultGRPCServiceConfigurationJSON), 1378 1389 } 1379 1390
+12 -3
cmd/zoekt-webserver/main.go
··· 38 38 "time" 39 39 40 40 "github.com/sourcegraph/mountinfo" 41 + "github.com/sourcegraph/zoekt/grpc/internalerrs" 42 + zoektgrpc "github.com/sourcegraph/zoekt/grpc/server" 41 43 "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 42 44 "golang.org/x/net/http2" 43 45 "golang.org/x/net/http2/h2c" ··· 46 48 "github.com/sourcegraph/zoekt" 47 49 "github.com/sourcegraph/zoekt/build" 48 50 "github.com/sourcegraph/zoekt/debugserver" 49 - zoektgrpc "github.com/sourcegraph/zoekt/grpc" 50 51 v1 "github.com/sourcegraph/zoekt/grpc/v1" 51 52 "github.com/sourcegraph/zoekt/internal/profiler" 52 53 "github.com/sourcegraph/zoekt/internal/tracer" ··· 288 289 log.Println("watchdog disabled") 289 290 } 290 291 292 + logger := sglog.Scoped("ZoektWebserverGRPCServer", "The Zoekt Webserver GRPC Server") 293 + 291 294 grpcServer := grpc.NewServer( 292 - grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()), 293 - grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()), 295 + grpc.ChainStreamInterceptor( 296 + otelgrpc.StreamServerInterceptor(), 297 + internalerrs.LoggingStreamServerInterceptor(logger), 298 + ), 299 + grpc.ChainUnaryInterceptor( 300 + otelgrpc.UnaryServerInterceptor(), 301 + internalerrs.LoggingUnaryServerInterceptor(logger), 302 + ), 294 303 ) 295 304 v1.RegisterWebserverServiceServer(grpcServer, zoektgrpc.NewServer(web.NewTraceAwareSearcher(s.Searcher))) 296 305
+14
grpc/grpcutil/util.go
··· 1 + package grpcutil 2 + 3 + import "strings" 4 + 5 + // SplitMethodName splits a full gRPC method name (e.g. "/package.service/method") in to its individual components (service, method) 6 + // 7 + // Copied from github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/reporter.go 8 + func SplitMethodName(fullMethod string) (string, string) { 9 + fullMethod = strings.TrimPrefix(fullMethod, "/") // remove leading slash 10 + if i := strings.Index(fullMethod, "/"); i >= 0 { 11 + return fullMethod[:i], fullMethod[i+1:] 12 + } 13 + return "unknown", "unknown" 14 + }
+59
grpc/grpcutil/util_test.go
··· 1 + package grpcutil 2 + 3 + import ( 4 + "testing" 5 + 6 + "github.com/google/go-cmp/cmp" 7 + ) 8 + 9 + func TestSplitMethodName(t *testing.T) { 10 + testCases := []struct { 11 + name string 12 + 13 + fullMethod string 14 + wantService string 15 + wantMethod string 16 + }{ 17 + { 18 + name: "full method with service and method", 19 + 20 + fullMethod: "/package.service/method", 21 + wantService: "package.service", 22 + wantMethod: "method", 23 + }, 24 + { 25 + name: "method without leading slash", 26 + 27 + fullMethod: "package.service/method", 28 + wantService: "package.service", 29 + wantMethod: "method", 30 + }, 31 + { 32 + name: "service without method", 33 + 34 + fullMethod: "/package.service/", 35 + wantService: "package.service", 36 + wantMethod: "", 37 + }, 38 + { 39 + name: "empty input", 40 + 41 + fullMethod: "", 42 + wantService: "unknown", 43 + wantMethod: "unknown", 44 + }, 45 + } 46 + 47 + for _, tc := range testCases { 48 + t.Run(tc.name, func(t *testing.T) { 49 + service, method := SplitMethodName(tc.fullMethod) 50 + if diff := cmp.Diff(service, tc.wantService); diff != "" { 51 + t.Errorf("splitMethodName(%q) service (-want +got):\n%s", tc.fullMethod, diff) 52 + } 53 + 54 + if diff := cmp.Diff(method, tc.wantMethod); diff != "" { 55 + t.Errorf("splitMethodName(%q) method (-want +got):\n%s", tc.fullMethod, diff) 56 + } 57 + }) 58 + } 59 + }
+272
grpc/internalerrs/common.go
··· 1 + package internalerrs 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "fmt" 7 + "os" 8 + "strconv" 9 + "strings" 10 + "sync" 11 + "sync/atomic" 12 + "unicode/utf8" 13 + 14 + "github.com/dustin/go-humanize" 15 + "google.golang.org/protobuf/proto" 16 + "google.golang.org/protobuf/reflect/protopath" 17 + "google.golang.org/protobuf/reflect/protorange" 18 + 19 + "google.golang.org/grpc" 20 + "google.golang.org/grpc/codes" 21 + "google.golang.org/grpc/status" 22 + ) 23 + 24 + // callBackClientStream is a grpc.ClientStream that calls a function after SendMsg and RecvMsg. 25 + type callBackClientStream struct { 26 + grpc.ClientStream 27 + 28 + postMessageSend func(message any, err error) 29 + postMessageReceive func(message any, err error) 30 + } 31 + 32 + func (c *callBackClientStream) SendMsg(m any) error { 33 + err := c.ClientStream.SendMsg(m) 34 + if c.postMessageSend != nil { 35 + c.postMessageSend(m, err) 36 + } 37 + 38 + return err 39 + } 40 + 41 + func (c *callBackClientStream) RecvMsg(m any) error { 42 + err := c.ClientStream.RecvMsg(m) 43 + if c.postMessageReceive != nil { 44 + c.postMessageReceive(m, err) 45 + } 46 + 47 + return err 48 + } 49 + 50 + var _ grpc.ClientStream = &callBackClientStream{} 51 + 52 + // requestSavingClientStream is a grpc.ClientStream that saves the initial request sent to the server. 53 + type requestSavingClientStream struct { 54 + grpc.ClientStream 55 + 56 + initialRequest atomic.Pointer[proto.Message] 57 + saveRequestOnce sync.Once 58 + } 59 + 60 + func (c *requestSavingClientStream) SendMsg(m any) error { 61 + c.saveRequestOnce.Do(func() { 62 + message, ok := m.(proto.Message) 63 + if !ok { 64 + return 65 + } 66 + 67 + c.initialRequest.Store(&message) 68 + }) 69 + 70 + return c.ClientStream.SendMsg(m) 71 + } 72 + 73 + // InitialRequest returns the initial request sent by the client on the stream. 74 + func (c *requestSavingClientStream) InitialRequest() *proto.Message { 75 + return c.initialRequest.Load() 76 + } 77 + 78 + var _ grpc.ClientStream = &requestSavingClientStream{} 79 + 80 + // requestSavingServerStream is a grpc.ServerStream that saves the initial request sent by the client. 81 + type requestSavingServerStream struct { 82 + grpc.ServerStream 83 + 84 + initialRequest atomic.Pointer[proto.Message] 85 + saveRequestOnce sync.Once 86 + } 87 + 88 + func (s *requestSavingServerStream) RecvMsg(m any) error { 89 + s.saveRequestOnce.Do(func() { 90 + message, ok := m.(proto.Message) 91 + if !ok { 92 + return 93 + } 94 + 95 + s.initialRequest.Store(&message) 96 + }) 97 + 98 + return s.ServerStream.RecvMsg(m) 99 + } 100 + 101 + // InitialRequest returns the initial request sent by the client on the stream. 102 + func (s *requestSavingServerStream) InitialRequest() *proto.Message { 103 + return s.initialRequest.Load() 104 + } 105 + 106 + var _ grpc.ServerStream = &requestSavingServerStream{} 107 + 108 + // callBackServerStream is a grpc.ServerStream that calls a function after SendMsg and RecvMsg. 109 + type callBackServerStream struct { 110 + grpc.ServerStream 111 + 112 + postMessageSend func(message any, err error) 113 + postMessageReceive func(message any, err error) 114 + } 115 + 116 + func (c *callBackServerStream) SendMsg(m any) error { 117 + err := c.ServerStream.SendMsg(m) 118 + 119 + if c.postMessageSend != nil { 120 + c.postMessageSend(m, err) 121 + } 122 + 123 + return err 124 + } 125 + 126 + func (c *callBackServerStream) RecvMsg(m any) error { 127 + err := c.ServerStream.RecvMsg(m) 128 + 129 + if c.postMessageReceive != nil { 130 + c.postMessageReceive(m, err) 131 + } 132 + 133 + return err 134 + } 135 + 136 + var _ grpc.ServerStream = &callBackServerStream{} 137 + 138 + // probablyInternalGRPCError checks if a gRPC status likely represents an error that comes from 139 + // the go-grpc library. 140 + // 141 + // Note: this is a heuristic and may not be 100% accurate. 142 + // From a cursory glance at the go-grpc source code, it seems most errors are prefixed with "grpc:". This may break in the future, but 143 + // it's better than nothing. 144 + // Some other ad-hoc errors that we traced back to the go-grpc library are also checked for. 145 + func probablyInternalGRPCError(s *status.Status, checkers []internalGRPCErrorChecker) bool { 146 + if s.Code() == codes.OK { 147 + return false 148 + } 149 + 150 + for _, checker := range checkers { 151 + if checker(s) { 152 + return true 153 + } 154 + } 155 + 156 + return false 157 + } 158 + 159 + // internalGRPCErrorChecker is a function that checks if a gRPC status likely represents an error that comes from 160 + // the go-grpc library. 161 + type internalGRPCErrorChecker func(*status.Status) bool 162 + 163 + // allCheckers is a list of functions that check if a gRPC status likely represents an 164 + // error that comes from the go-grpc library. 165 + var allCheckers = []internalGRPCErrorChecker{ 166 + gRPCPrefixChecker, 167 + gRPCResourceExhaustedChecker, 168 + gRPCUnexpectedContentTypeChecker, 169 + } 170 + 171 + // gRPCPrefixChecker checks if a gRPC status likely represents an error that comes from the go-grpc library, by checking if the error message 172 + // is prefixed with "grpc: ". 173 + func gRPCPrefixChecker(s *status.Status) bool { 174 + return s.Code() != codes.OK && strings.HasPrefix(s.Message(), "grpc: ") 175 + } 176 + 177 + // gRPCResourceExhaustedChecker checks if a gRPC status likely represents an error that comes from the go-grpc library, by checking if the error message 178 + // is prefixed with "trying to send message larger than max". 179 + func gRPCResourceExhaustedChecker(s *status.Status) bool { 180 + // Observed from https://github.com/grpc/grpc-go/blob/756119c7de49e91b6f3b9d693b9850e1598938eb/stream.go#L884 181 + return s.Code() == codes.ResourceExhausted && strings.HasPrefix(s.Message(), "trying to send message larger than max (") 182 + } 183 + 184 + // gRPCUnexpectedContentTypeChecker checks if a gRPC status likely represents an error that comes from the go-grpc library, by checking if the error message 185 + // is prefixed with "transport: received unexpected content-type". 186 + func gRPCUnexpectedContentTypeChecker(s *status.Status) bool { 187 + // Observed from https://github.com/grpc/grpc-go/blob/2997e84fd8d18ddb000ac6736129b48b3c9773ec/internal/transport/http2_client.go#L1415-L1417 188 + return s.Code() != codes.OK && strings.Contains(s.Message(), "transport: received unexpected content-type") 189 + } 190 + 191 + // findNonUTF8StringFields returns a list of field names that contain invalid UTF-8 strings 192 + // in the given proto message. 193 + // 194 + // Example: ["author", "attachments[1].key_value_attachment.data["key2"]`] 195 + func findNonUTF8StringFields(m proto.Message) ([]string, error) { 196 + if m == nil { 197 + return nil, nil 198 + } 199 + 200 + var fields []string 201 + err := protorange.Range(m.ProtoReflect(), func(p protopath.Values) error { 202 + last := p.Index(-1) 203 + s, ok := last.Value.Interface().(string) 204 + if ok && !utf8.ValidString(s) { 205 + fieldName := p.Path[1:].String() 206 + fields = append(fields, strings.TrimPrefix(fieldName, ".")) 207 + } 208 + 209 + return nil 210 + }) 211 + 212 + if err != nil { 213 + return nil, fmt.Errorf("iterating over proto message: %w", err) 214 + } 215 + 216 + return fields, nil 217 + } 218 + 219 + // massageIntoStatusErr converts an error into a status.Status if possible. 220 + func massageIntoStatusErr(err error) (s *status.Status, ok bool) { 221 + if err == nil { 222 + return nil, false 223 + } 224 + 225 + if s, ok := status.FromError(err); ok { 226 + return s, true 227 + } 228 + 229 + if errors.Is(err, context.Canceled) { 230 + return status.New(codes.Canceled, context.Canceled.Error()), true 231 + 232 + } 233 + 234 + if errors.Is(err, context.DeadlineExceeded) { 235 + return status.New(codes.DeadlineExceeded, context.DeadlineExceeded.Error()), true 236 + } 237 + 238 + return nil, false 239 + } 240 + 241 + func envMustGetBool(key string, defaultValue bool) bool { 242 + rawValue, ok := os.LookupEnv(key) 243 + if !ok { 244 + return defaultValue 245 + } 246 + 247 + value, err := strconv.ParseBool(rawValue) 248 + if err != nil { 249 + panic(fmt.Sprintf("Failed to parse enviroment variable %q as valid boolean. Got %q. Err: %s", key, rawValue, err)) 250 + } 251 + 252 + return value 253 + } 254 + 255 + func envMustGetBytes(key string, defaultByteSize string) uint64 { 256 + defaultByteSizeValue, err := humanize.ParseBytes(defaultByteSize) 257 + if err != nil { 258 + panic(fmt.Sprintf("Failed to parse default byte size %q as valid byte size. Err: %s", defaultByteSize, err)) 259 + } 260 + 261 + rawValue, ok := os.LookupEnv(key) 262 + if !ok { 263 + return defaultByteSizeValue 264 + } 265 + 266 + value, err := humanize.ParseBytes(rawValue) 267 + if err != nil { 268 + panic(fmt.Sprintf("Failed to parse enviroment variable %q as valid byte size. Got %q. Err: %s", key, rawValue, err)) 269 + } 270 + 271 + return value 272 + }
+491
grpc/internalerrs/common_test.go
··· 1 + package internalerrs 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "fmt" 7 + "sort" 8 + "strings" 9 + "testing" 10 + 11 + "github.com/google/go-cmp/cmp/cmpopts" 12 + "google.golang.org/protobuf/proto" 13 + "google.golang.org/protobuf/types/known/timestamppb" 14 + 15 + newspb "github.com/sourcegraph/zoekt/grpc/testprotos/news/v1" 16 + 17 + "github.com/google/go-cmp/cmp" 18 + "google.golang.org/grpc" 19 + "google.golang.org/grpc/codes" 20 + "google.golang.org/grpc/status" 21 + ) 22 + 23 + func TestCallBackClientStream(t *testing.T) { 24 + t.Run("SendMsg calls postMessageSend with message and error", func(t *testing.T) { 25 + sentinelMessage := struct{}{} 26 + sentinelErr := errors.New("send error") 27 + 28 + var called bool 29 + stream := callBackClientStream{ 30 + ClientStream: &mockClientStream{ 31 + sendErr: sentinelErr, 32 + }, 33 + postMessageSend: func(message any, err error) { 34 + called = true 35 + 36 + if diff := cmp.Diff(message, sentinelMessage); diff != "" { 37 + t.Errorf("postMessageSend called with unexpected message (-want +got):\n%s", diff) 38 + } 39 + if !errors.Is(err, sentinelErr) { 40 + t.Errorf("got %v, want %v", err, sentinelErr) 41 + } 42 + }, 43 + } 44 + 45 + sendErr := stream.SendMsg(sentinelMessage) 46 + if !called { 47 + t.Error("postMessageSend not called") 48 + } 49 + 50 + if !errors.Is(sendErr, sentinelErr) { 51 + t.Errorf("got %v, want %v", sendErr, sentinelErr) 52 + } 53 + }) 54 + 55 + t.Run("RecvMsg calls postMessageReceive with message and error", func(t *testing.T) { 56 + sentinelMessage := struct{}{} 57 + sentinelErr := errors.New("receive error") 58 + 59 + var called bool 60 + stream := callBackClientStream{ 61 + ClientStream: &mockClientStream{ 62 + recvErr: sentinelErr, 63 + }, 64 + postMessageReceive: func(message any, err error) { 65 + called = true 66 + 67 + if diff := cmp.Diff(message, sentinelMessage); diff != "" { 68 + t.Errorf("postMessageReceive called with unexpected message (-want +got):\n%s", diff) 69 + } 70 + if !errors.Is(err, sentinelErr) { 71 + t.Errorf("got %v, want %v", err, sentinelErr) 72 + } 73 + }, 74 + } 75 + 76 + receiveErr := stream.RecvMsg(sentinelMessage) 77 + if !called { 78 + t.Error("postMessageReceive not called") 79 + } 80 + 81 + if !errors.Is(receiveErr, sentinelErr) { 82 + t.Errorf("got %v, want %v", receiveErr, sentinelErr) 83 + } 84 + }) 85 + } 86 + 87 + func TestRequestSavingClientStream_InitialRequest(t *testing.T) { 88 + // Setup: create a mock ClientStream that returns a sentinel error on SendMsg 89 + sentinelErr := errors.New("send error") 90 + mockClientStream := &mockClientStream{ 91 + sendErr: sentinelErr, 92 + } 93 + 94 + // Setup: create a requestSavingClientStream with the mock ClientStream 95 + stream := &requestSavingClientStream{ 96 + ClientStream: mockClientStream, 97 + } 98 + 99 + // Setup: create a sample proto.Message for the request 100 + request := &newspb.BinaryAttachment{ 101 + Name: "sample_request", 102 + Data: []byte("sample data"), 103 + } 104 + 105 + // Test: call SendMsg with the request 106 + err := stream.SendMsg(request) 107 + 108 + // Check: assert SendMsg propagates the error 109 + if !errors.Is(err, sentinelErr) { 110 + t.Errorf("got %v, want %v", err, sentinelErr) 111 + } 112 + 113 + // Check: assert InitialRequest returns the request 114 + if diff := cmp.Diff(request, *stream.InitialRequest(), cmpopts.IgnoreUnexported(newspb.BinaryAttachment{})); diff != "" { 115 + t.Fatalf("InitialRequest() (-want +got):\n%s", diff) 116 + } 117 + } 118 + 119 + // mockClientStream is a grpc.ClientStream that returns a given error on SendMsg and RecvMsg. 120 + type mockClientStream struct { 121 + grpc.ClientStream 122 + sendErr error 123 + recvErr error 124 + } 125 + 126 + func (s *mockClientStream) SendMsg(any) error { 127 + return s.sendErr 128 + } 129 + 130 + func (s *mockClientStream) RecvMsg(any) error { 131 + return s.recvErr 132 + } 133 + 134 + func TestCallBackServerStream(t *testing.T) { 135 + t.Run("SendMsg calls postMessageSend with message and error", func(t *testing.T) { 136 + sentinelMessage := struct{}{} 137 + sentinelErr := errors.New("send error") 138 + 139 + var called bool 140 + stream := callBackServerStream{ 141 + ServerStream: &mockServerStream{ 142 + sendErr: sentinelErr, 143 + }, 144 + postMessageSend: func(message any, err error) { 145 + called = true 146 + 147 + if diff := cmp.Diff(message, sentinelMessage); diff != "" { 148 + t.Errorf("postMessageSend called with unexpected message (-want +got):\n%s", diff) 149 + } 150 + if !errors.Is(err, sentinelErr) { 151 + t.Errorf("got %v, want %v", err, sentinelErr) 152 + } 153 + }, 154 + } 155 + 156 + sendErr := stream.SendMsg(sentinelMessage) 157 + if !called { 158 + t.Error("postMessageSend not called") 159 + } 160 + 161 + if !errors.Is(sendErr, sentinelErr) { 162 + t.Errorf("got %v, want %v", sendErr, sentinelErr) 163 + } 164 + }) 165 + 166 + t.Run("RecvMsg calls postMessageReceive with message and error", func(t *testing.T) { 167 + sentinelMessage := struct{}{} 168 + sentinelErr := errors.New("receive error") 169 + 170 + var called bool 171 + stream := callBackServerStream{ 172 + ServerStream: &mockServerStream{ 173 + recvErr: sentinelErr, 174 + }, 175 + postMessageReceive: func(message any, err error) { 176 + called = true 177 + 178 + if diff := cmp.Diff(message, sentinelMessage); diff != "" { 179 + t.Errorf("postMessageReceive called with unexpected message (-want +got):\n%s", diff) 180 + } 181 + if !errors.Is(err, sentinelErr) { 182 + t.Errorf("got %v, want %v", err, sentinelErr) 183 + } 184 + }, 185 + } 186 + 187 + receiveErr := stream.RecvMsg(sentinelMessage) 188 + if !called { 189 + t.Error("postMessageReceive not called") 190 + } 191 + 192 + if !errors.Is(receiveErr, sentinelErr) { 193 + t.Errorf("got %v, want %v", receiveErr, sentinelErr) 194 + } 195 + }) 196 + } 197 + 198 + func TestRequestSavingServerStream_InitialRequest(t *testing.T) { 199 + // Setup: create a mock ServerStream that returns a sentinel error on SendMsg 200 + sentinelErr := errors.New("receive error") 201 + mockServerStream := &mockServerStream{ 202 + recvErr: sentinelErr, 203 + } 204 + 205 + // Setup: create a requestSavingServerStream with the mock ServerStream 206 + stream := &requestSavingServerStream{ 207 + ServerStream: mockServerStream, 208 + } 209 + 210 + // Setup: create a sample proto.Message for the request 211 + request := &newspb.BinaryAttachment{ 212 + Name: "sample_request", 213 + Data: []byte("sample data"), 214 + } 215 + 216 + // Test: call RecvMsg with the request 217 + err := stream.RecvMsg(request) 218 + 219 + // Check: assert RecvMsg propagates the error 220 + if !errors.Is(err, sentinelErr) { 221 + t.Errorf("got %v, want %v", err, sentinelErr) 222 + } 223 + 224 + // Check: assert InitialRequest returns the request 225 + if diff := cmp.Diff(request, *stream.InitialRequest(), cmpopts.IgnoreUnexported(newspb.BinaryAttachment{})); diff != "" { 226 + t.Fatalf("InitialRequest() (-want +got):\n%s", diff) 227 + } 228 + } 229 + 230 + // mockServerStream is a grpc.ServerStream that returns a given error on SendMsg and RecvMsg. 231 + type mockServerStream struct { 232 + grpc.ServerStream 233 + sendErr error 234 + recvErr error 235 + } 236 + 237 + func (s *mockServerStream) SendMsg(any) error { 238 + return s.sendErr 239 + } 240 + 241 + func (s *mockServerStream) RecvMsg(any) error { 242 + return s.recvErr 243 + } 244 + 245 + func TestProbablyInternalGRPCError(t *testing.T) { 246 + checker := func(s *status.Status) bool { 247 + return strings.HasPrefix(s.Message(), "custom error") 248 + } 249 + 250 + testCases := []struct { 251 + status *status.Status 252 + checkers []internalGRPCErrorChecker 253 + wantResult bool 254 + }{ 255 + { 256 + status: status.New(codes.OK, ""), 257 + checkers: []internalGRPCErrorChecker{func(*status.Status) bool { return true }}, 258 + wantResult: false, 259 + }, 260 + { 261 + status: status.New(codes.Internal, "custom error message"), 262 + checkers: []internalGRPCErrorChecker{checker}, 263 + wantResult: true, 264 + }, 265 + { 266 + status: status.New(codes.Internal, "some other error"), 267 + checkers: []internalGRPCErrorChecker{checker}, 268 + wantResult: false, 269 + }, 270 + } 271 + 272 + for _, tc := range testCases { 273 + gotResult := probablyInternalGRPCError(tc.status, tc.checkers) 274 + if gotResult != tc.wantResult { 275 + t.Errorf("probablyInternalGRPCError(%v, %v) = %v, want %v", tc.status, tc.checkers, gotResult, tc.wantResult) 276 + } 277 + } 278 + } 279 + 280 + func TestGRPCResourceExhaustedChecker(t *testing.T) { 281 + testCases := []struct { 282 + status *status.Status 283 + expectPass bool 284 + }{ 285 + { 286 + status: status.New(codes.ResourceExhausted, "trying to send message larger than max (1024 vs 2)"), 287 + expectPass: true, 288 + }, 289 + { 290 + status: status.New(codes.ResourceExhausted, "some other error"), 291 + expectPass: false, 292 + }, 293 + { 294 + status: status.New(codes.OK, "trying to send message larger than max (1024 vs 5)"), 295 + expectPass: false, 296 + }, 297 + } 298 + 299 + for _, tc := range testCases { 300 + actual := gRPCResourceExhaustedChecker(tc.status) 301 + if actual != tc.expectPass { 302 + t.Errorf("gRPCResourceExhaustedChecker(%v) got %t, want %t", tc.status, actual, tc.expectPass) 303 + } 304 + } 305 + } 306 + 307 + func TestGRPCPrefixChecker(t *testing.T) { 308 + tests := []struct { 309 + status *status.Status 310 + want bool 311 + }{ 312 + { 313 + status: status.New(codes.OK, "not a grpc error"), 314 + want: false, 315 + }, 316 + { 317 + status: status.New(codes.Internal, "grpc: internal server error"), 318 + want: true, 319 + }, 320 + { 321 + status: status.New(codes.Unavailable, "some other error"), 322 + want: false, 323 + }, 324 + } 325 + for _, test := range tests { 326 + got := gRPCPrefixChecker(test.status) 327 + if got != test.want { 328 + t.Errorf("gRPCPrefixChecker(%v) = %v, want %v", test.status, got, test.want) 329 + } 330 + } 331 + } 332 + 333 + func TestGRPCUnexpectedContentTypeChecker(t *testing.T) { 334 + tests := []struct { 335 + name string 336 + status *status.Status 337 + want bool 338 + }{ 339 + { 340 + name: "gRPC error with OK status", 341 + status: status.New(codes.OK, "transport: received unexpected content-type"), 342 + want: false, 343 + }, 344 + { 345 + name: "gRPC error without unexpected content-type message", 346 + status: status.New(codes.Internal, "some random error"), 347 + want: false, 348 + }, 349 + { 350 + name: "gRPC error with unexpected content-type message", 351 + status: status.Newf(codes.Internal, "transport: received unexpected content-type %q", "application/octet-stream"), 352 + want: true, 353 + }, 354 + { 355 + name: "gRPC error with unexpected content-type message as part of chain", 356 + status: status.Newf(codes.Unknown, "transport: malformed grpc-status %q; transport: received unexpected content-type %q", "random-status", "application/octet-stream"), 357 + want: true, 358 + }, 359 + } 360 + 361 + for _, tt := range tests { 362 + t.Run(tt.name, func(t *testing.T) { 363 + if got := gRPCUnexpectedContentTypeChecker(tt.status); got != tt.want { 364 + t.Errorf("gRPCUnexpectedContentTypeChecker() = %v, want %v", got, tt.want) 365 + } 366 + }) 367 + } 368 + } 369 + 370 + func TestFindNonUTF8StringFields(t *testing.T) { 371 + // Create instances of the BinaryAttachment and KeyValueAttachment messages 372 + invalidBinaryAttachment := &newspb.BinaryAttachment{ 373 + Name: "inval\x80id_binary", 374 + Data: []byte("sample data"), 375 + } 376 + 377 + invalidKeyValueAttachment := &newspb.KeyValueAttachment{ 378 + Name: "inval\x80id_key_value", 379 + Data: map[string]string{ 380 + "key1": "value1", 381 + "key2": "inval\x80id_value", 382 + }, 383 + } 384 + 385 + // Create a sample Article message with invalid UTF-8 strings 386 + article := &newspb.Article{ 387 + Author: "inval\x80id_author", 388 + Date: &timestamppb.Timestamp{Seconds: 1234567890}, 389 + Title: "valid_title", 390 + Content: "valid_content", 391 + Status: newspb.Article_STATUS_PUBLISHED, 392 + Attachments: []*newspb.Attachment{ 393 + {Contents: &newspb.Attachment_BinaryAttachment{BinaryAttachment: invalidBinaryAttachment}}, 394 + {Contents: &newspb.Attachment_KeyValueAttachment{KeyValueAttachment: invalidKeyValueAttachment}}, 395 + }, 396 + } 397 + 398 + tests := []struct { 399 + name string 400 + message proto.Message 401 + expectedPaths []string 402 + }{ 403 + { 404 + name: "Article with invalid UTF-8 strings", 405 + message: article, 406 + expectedPaths: []string{ 407 + "author", 408 + "attachments[0].binary_attachment.name", 409 + "attachments[1].key_value_attachment.name", 410 + `attachments[1].key_value_attachment.data["key2"]`, 411 + }, 412 + }, 413 + { 414 + name: "nil message", 415 + message: nil, 416 + expectedPaths: []string{}, 417 + }, 418 + } 419 + 420 + for _, tt := range tests { 421 + t.Run(tt.name, func(t *testing.T) { 422 + invalidFields, err := findNonUTF8StringFields(tt.message) 423 + if err != nil { 424 + t.Fatalf("unexpected error: %v", err) 425 + } 426 + 427 + sort.Strings(invalidFields) 428 + sort.Strings(tt.expectedPaths) 429 + 430 + if diff := cmp.Diff(tt.expectedPaths, invalidFields, cmpopts.EquateEmpty()); diff != "" { 431 + t.Fatalf("unexpected invalid fields (-want +got):\n%s", diff) 432 + } 433 + }) 434 + } 435 + } 436 + 437 + func TestMassageIntoStatusErr(t *testing.T) { 438 + testCases := []struct { 439 + description string 440 + input error 441 + expected *status.Status 442 + expectedOk bool 443 + }{ 444 + { 445 + description: "nil error", 446 + input: nil, 447 + expected: nil, 448 + expectedOk: false, 449 + }, 450 + { 451 + description: "status error", 452 + input: status.Errorf(codes.InvalidArgument, "invalid argument"), 453 + expected: status.New(codes.InvalidArgument, "invalid argument"), 454 + expectedOk: true, 455 + }, 456 + { 457 + description: "context.Canceled error", 458 + input: context.Canceled, 459 + expected: status.New(codes.Canceled, "context canceled"), 460 + expectedOk: true, 461 + }, 462 + { 463 + description: "context.DeadlineExceeded error", 464 + input: context.DeadlineExceeded, 465 + expected: status.New(codes.DeadlineExceeded, "context deadline exceeded"), 466 + expectedOk: true, 467 + }, 468 + { 469 + description: "non-status error", 470 + input: errors.New("non-status error"), 471 + expected: nil, 472 + expectedOk: false, 473 + }, 474 + } 475 + 476 + for _, tc := range testCases { 477 + t.Run(tc.description, func(t *testing.T) { 478 + result, ok := massageIntoStatusErr(tc.input) 479 + if ok != tc.expectedOk { 480 + t.Errorf("Expected ok to be %v, but got %v", tc.expectedOk, ok) 481 + } 482 + 483 + expectedStatusString := fmt.Sprintf("%s", tc.expected) 484 + actualStatusString := fmt.Sprintf("%s", result) 485 + 486 + if diff := cmp.Diff(expectedStatusString, actualStatusString); diff != "" { 487 + t.Fatalf("Unexpected status string (-want +got):\n%s", diff) 488 + } 489 + }) 490 + } 491 + }
+308
grpc/internalerrs/logging.go
··· 1 + package internalerrs 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + "io" 8 + "strings" 9 + 10 + "github.com/dustin/go-humanize" 11 + "github.com/sourcegraph/zoekt/grpc/grpcutil" 12 + 13 + "google.golang.org/grpc/codes" 14 + "google.golang.org/protobuf/proto" 15 + 16 + "github.com/sourcegraph/log" 17 + "google.golang.org/grpc" 18 + "google.golang.org/grpc/status" 19 + ) 20 + 21 + var ( 22 + logScope = "gRPC.internal.error.reporter" 23 + logDescription = "logs gRPC errors that appear to come from the go-grpc implementation" 24 + 25 + envLoggingEnabled = envMustGetBool("GRPC_INTERNAL_ERROR_LOGGING_ENABLED", true) // "Enables logging of gRPC internal errors" 26 + envLogStackTracesEnabled = envMustGetBool("GRPC_INTERNAL_ERROR_LOGGING_LOG_STACK_TRACES", false) // "Enables including stack traces in logs of gRPC internal errors" 27 + 28 + envLogMessagesEnabled = envMustGetBool("GRPC_INTERNAL_ERROR_LOGGING_LOG_PROTOBUF_MESSAGES_ENABLED", false) // "Enables inclusion of raw protobuf messages in the gRPC internal error logs" 29 + envLogMessagesHandleMaxMessageSizeBytes = envMustGetBytes("GRPC_INTERNAL_ERROR_LOGGING_LOG_PROTOBUF_MESSAGES_HANDLING_MAX_MESSAGE_SIZE_BYTES", "100MB") // "Maximum size of protobuf messages that can be included in gRPC internal error logs. The purpose of this is to avoid excessive allocations. 0 bytes mean no limit.") 30 + envLogMessagesMaxJSONSizeBytes = envMustGetBytes("GRPC_INTERNAL_ERROR_LOGGING_LOG_PROTOBUF_MESSAGES_JSON_TRUNCATION_SIZE_BYTES", "1KB") // "Maximum size of the JSON representation of protobuf messages to log. JSON representations larger than this value will be truncated. 0 bytes disables truncation.") 31 + ) 32 + 33 + // LoggingUnaryClientInterceptor returns a grpc.UnaryClientInterceptor that logs 34 + // errors that appear to come from the go-grpc implementation. 35 + func LoggingUnaryClientInterceptor(l log.Logger) grpc.UnaryClientInterceptor { 36 + if !envLoggingEnabled { 37 + // Just return the default invoker if logging is disabled. 38 + return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { 39 + return invoker(ctx, method, req, reply, cc, opts...) 40 + } 41 + } 42 + 43 + logger := l.Scoped(logScope, logDescription) 44 + logger = logger.Scoped("unaryMethod", "errors that originated from a unary method") 45 + 46 + return func(ctx context.Context, fullMethod string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { 47 + err := invoker(ctx, fullMethod, req, reply, cc, opts...) 48 + if err != nil { 49 + serviceName, methodName := grpcutil.SplitMethodName(fullMethod) 50 + 51 + var initialRequest proto.Message 52 + if m, ok := req.(proto.Message); ok { 53 + initialRequest = m 54 + } 55 + 56 + doLog(logger, serviceName, methodName, &initialRequest, req, err) 57 + } 58 + 59 + return err 60 + } 61 + } 62 + 63 + // LoggingStreamClientInterceptor returns a grpc.StreamClientInterceptor that logs 64 + // errors that appear to come from the go-grpc implementation. 65 + func LoggingStreamClientInterceptor(l log.Logger) grpc.StreamClientInterceptor { 66 + if !envLoggingEnabled { 67 + // Just return the default streamer if logging is disabled. 68 + return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { 69 + return streamer(ctx, desc, cc, method, opts...) 70 + } 71 + } 72 + 73 + logger := l.Scoped(logScope, logDescription) 74 + logger = logger.Scoped("streamingMethod", "errors that originated from a streaming method") 75 + 76 + return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, fullMethod string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { 77 + serviceName, methodName := grpcutil.SplitMethodName(fullMethod) 78 + 79 + stream, err := streamer(ctx, desc, cc, fullMethod, opts...) 80 + if err != nil { 81 + // Note: This is a bit hacky, we provide nil initial and payload messages here since the message isn't available 82 + // until after the stream is created. 83 + // 84 + // This is fine since the error is already available, and the non-utf8 string check is robust against nil messages. 85 + logger := logger.Scoped("postInit", "errors that occurred after stream initialization, but before the first message was sent") 86 + doLog(logger, serviceName, methodName, nil, nil, err) 87 + return nil, err 88 + } 89 + 90 + stream = newLoggingClientStream(stream, logger, serviceName, methodName) 91 + return stream, nil 92 + } 93 + } 94 + 95 + // LoggingUnaryServerInterceptor returns a grpc.UnaryServerInterceptor that logs 96 + // errors that appear to come from the go-grpc implementation. 97 + func LoggingUnaryServerInterceptor(l log.Logger) grpc.UnaryServerInterceptor { 98 + if !envLoggingEnabled { 99 + // Just return the default handler if logging is disabled. 100 + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { 101 + return handler(ctx, req) 102 + } 103 + } 104 + 105 + logger := l.Scoped(logScope, logDescription) 106 + logger = logger.Scoped("unaryMethod", "errors that originated from a unary method") 107 + 108 + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { 109 + response, err := handler(ctx, req) 110 + if err != nil { 111 + serviceName, methodName := grpcutil.SplitMethodName(info.FullMethod) 112 + 113 + var initialRequest proto.Message 114 + if m, ok := req.(proto.Message); ok { 115 + initialRequest = m 116 + } 117 + 118 + doLog(logger, serviceName, methodName, &initialRequest, response, err) 119 + } 120 + 121 + return response, err 122 + } 123 + } 124 + 125 + // LoggingStreamServerInterceptor returns a grpc.StreamServerInterceptor that logs 126 + // errors that appear to come from the go-grpc implementation. 127 + func LoggingStreamServerInterceptor(l log.Logger) grpc.StreamServerInterceptor { 128 + if !envLoggingEnabled { 129 + // Just return the default handler if logging is disabled. 130 + return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 131 + return handler(srv, ss) 132 + } 133 + } 134 + 135 + logger := l.Scoped(logScope, logDescription) 136 + logger = logger.Scoped("streamingMethod", "errors that originated from a streaming method") 137 + 138 + return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 139 + serviceName, methodName := grpcutil.SplitMethodName(info.FullMethod) 140 + 141 + stream := newLoggingServerStream(ss, logger, serviceName, methodName) 142 + return handler(srv, stream) 143 + } 144 + } 145 + 146 + func newLoggingServerStream(s grpc.ServerStream, logger log.Logger, serviceName, methodName string) grpc.ServerStream { 147 + sendLogger := logger.Scoped("postMessageSend", "errors that occurred after sending a message") 148 + receiveLogger := logger.Scoped("postMessageReceive", "errors that occurred after receiving a message") 149 + 150 + requestSaver := requestSavingServerStream{ServerStream: s} 151 + 152 + return &callBackServerStream{ 153 + ServerStream: &requestSaver, 154 + 155 + postMessageSend: func(m any, err error) { 156 + if err != nil { 157 + doLog(sendLogger, serviceName, methodName, requestSaver.InitialRequest(), m, err) 158 + } 159 + }, 160 + 161 + postMessageReceive: func(m any, err error) { 162 + if err != nil && err != io.EOF { // EOF is expected at the end of a stream, so no need to log an error 163 + doLog(receiveLogger, serviceName, methodName, requestSaver.InitialRequest(), m, err) 164 + } 165 + }, 166 + } 167 + } 168 + 169 + func newLoggingClientStream(s grpc.ClientStream, logger log.Logger, serviceName, methodName string) grpc.ClientStream { 170 + sendLogger := logger.Scoped("postMessageSend", "errors that occurred after sending a message") 171 + receiveLogger := logger.Scoped("postMessageReceive", "errors that occurred after receiving a message") 172 + 173 + requestSaver := requestSavingClientStream{ClientStream: s} 174 + 175 + return &callBackClientStream{ 176 + ClientStream: &requestSaver, 177 + 178 + postMessageSend: func(m any, err error) { 179 + if err != nil { 180 + doLog(sendLogger, serviceName, methodName, requestSaver.InitialRequest(), m, err) 181 + } 182 + }, 183 + 184 + postMessageReceive: func(m any, err error) { 185 + if err != nil && err != io.EOF { // EOF is expected at the end of a stream, so no need to log an error 186 + doLog(receiveLogger, serviceName, methodName, requestSaver.InitialRequest(), m, err) 187 + } 188 + }, 189 + } 190 + } 191 + 192 + func doLog(logger log.Logger, serviceName, methodName string, initialRequest *proto.Message, payload any, err error) { 193 + if err == nil { 194 + return 195 + } 196 + 197 + s, ok := massageIntoStatusErr(err) 198 + if !ok { 199 + // If the error isn't a grpc error, we don't know how to handle it. 200 + // Just return. 201 + return 202 + } 203 + 204 + if !probablyInternalGRPCError(s, allCheckers) { 205 + return 206 + } 207 + 208 + allFields := []log.Field{ 209 + log.String("grpcService", serviceName), 210 + log.String("grpcMethod", methodName), 211 + log.String("grpcCode", s.Code().String()), 212 + } 213 + 214 + if envLogStackTracesEnabled { 215 + allFields = append(allFields, log.String("errWithStack", fmt.Sprintf("%+v", err))) 216 + } 217 + 218 + // Log the initial request message 219 + if envLogMessagesEnabled { 220 + fs := messageJSONFields(initialRequest, "initialRequestJSON", envLogMessagesHandleMaxMessageSizeBytes, envLogMessagesMaxJSONSizeBytes) 221 + allFields = append(allFields, fs...) 222 + } 223 + 224 + if isNonUTF8StringError(s) { 225 + m, ok := payload.(proto.Message) 226 + if ok { 227 + allFields = append(allFields, nonUTF8StringLogFields(m)...) 228 + 229 + if envLogMessagesEnabled { // Log the latest message as well for non-utf8 errors 230 + fs := messageJSONFields(&m, "messageJSON", envLogMessagesHandleMaxMessageSizeBytes, envLogMessagesMaxJSONSizeBytes) 231 + allFields = append(allFields, fs...) 232 + } 233 + } 234 + } 235 + 236 + logger.Error(s.Message(), allFields...) 237 + } 238 + 239 + // messageJSONFields converts a protobuf message to a JSON string and returns it as a log field using the provided "key". 240 + // The resulting JSON string is truncated to maxJSONSizeBytes. 241 + // 242 + // If the size of the original protobuf message exceeds maxMessageSizeBytes or any serialization errors are encountered, log fields 243 + // describing the error are returned instead. 244 + func messageJSONFields(m *proto.Message, key string, maxMessageSizeBytes, maxJSONSizeBytes uint64) []log.Field { 245 + if m == nil || *m == nil { 246 + return nil 247 + } 248 + 249 + if maxMessageSizeBytes > 0 { 250 + size := uint64(proto.Size(*m)) 251 + if size > maxMessageSizeBytes { 252 + err := fmt.Errorf( 253 + "failed to marshal protobuf message (key: %q) to string: message too large (size %q, limit %q)", 254 + key, 255 + humanize.Bytes(size), humanize.Bytes(maxMessageSizeBytes), 256 + ) 257 + 258 + return []log.Field{log.Error(err)} 259 + } 260 + } 261 + 262 + // Note: we can't use the protojson library here since it doesn't support messages with non-UTF8 strings. 263 + bs, err := json.Marshal(*m) 264 + if err != nil { 265 + err := fmt.Errorf("failed to marshal protobuf message (key: %q) to string: %w", key, err) 266 + return []log.Field{log.Error(err)} 267 + } 268 + 269 + s := truncate(string(bs), maxJSONSizeBytes) 270 + return []log.Field{log.String(key, s)} 271 + } 272 + 273 + // truncate shortens the string be to at most maxBytes bytes, appending a message indicating that the string was truncated if necessary. 274 + // 275 + // If maxBytes is 0, then the string is not truncated. 276 + func truncate(s string, maxBytes uint64) string { 277 + if maxBytes <= 0 { 278 + return s 279 + } 280 + 281 + bytesToTruncate := len(s) - int(maxBytes) 282 + if bytesToTruncate > 0 { 283 + s = s[:maxBytes] 284 + s = fmt.Sprintf("%s...(truncated %d bytes)", s, bytesToTruncate) 285 + } 286 + 287 + return s 288 + } 289 + 290 + func isNonUTF8StringError(s *status.Status) bool { 291 + if s.Code() != codes.Internal { 292 + return false 293 + } 294 + 295 + return strings.Contains(s.Message(), "string field contains invalid UTF-8") 296 + } 297 + 298 + // nonUTF8StringLogFields checks a protobuf message for fields that contain non-utf8 strings, and returns them as log fields. 299 + func nonUTF8StringLogFields(m proto.Message) []log.Field { 300 + fs, err := findNonUTF8StringFields(m) 301 + if err != nil { 302 + err := fmt.Errorf("failed to find non-UTF8 string fields in protobuf message: %w", err) 303 + return []log.Field{log.Error(err)} 304 + 305 + } 306 + 307 + return []log.Field{log.Strings("nonUTF8StringFields", fs)} 308 + }
+115
grpc/internalerrs/prometheus.go
··· 1 + package internalerrs 2 + 3 + import ( 4 + "context" 5 + "io" 6 + "sync" 7 + 8 + "github.com/prometheus/client_golang/prometheus" 9 + "github.com/prometheus/client_golang/prometheus/promauto" 10 + "github.com/sourcegraph/zoekt/grpc/grpcutil" 11 + "google.golang.org/grpc" 12 + "google.golang.org/grpc/codes" 13 + ) 14 + 15 + var metricGRPCMethodStatus = promauto.NewCounterVec(prometheus.CounterOpts{ 16 + Name: "grpc_method_status", 17 + Help: "Counts the number of gRPC methods that return a given status code, and whether a possible error is an go-grpc internal error.", 18 + }, 19 + []string{ 20 + "grpc_service", // e.g. "gitserver.v1.GitserverService" 21 + "grpc_method", // e.g. "Exec" 22 + "grpc_code", // e.g. "NotFound" 23 + "is_internal_error", // e.g. "true" 24 + }, 25 + ) 26 + 27 + // PrometheusUnaryClientInterceptor returns a grpc.UnaryClientInterceptor that observes the result of 28 + // the RPC and records it as a Prometheus metric ("src_grpc_method_status"). 29 + func PrometheusUnaryClientInterceptor(ctx context.Context, fullMethod string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { 30 + serviceName, methodName := grpcutil.SplitMethodName(fullMethod) 31 + 32 + err := invoker(ctx, fullMethod, req, reply, cc, opts...) 33 + doObservation(serviceName, methodName, err) 34 + return err 35 + } 36 + 37 + // PrometheusStreamClientInterceptor returns a grpc.StreamClientInterceptor that observes the result of 38 + // the RPC and records it as a Prometheus metric ("src_grpc_method_status"). 39 + // 40 + // If any errors are encountered during the stream, the first error is recorded. Otherwise, the 41 + // final status of the stream is recorded. 42 + func PrometheusStreamClientInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, fullMethod string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { 43 + serviceName, methodName := grpcutil.SplitMethodName(fullMethod) 44 + 45 + s, err := streamer(ctx, desc, cc, fullMethod, opts...) 46 + if err != nil { 47 + doObservation(serviceName, methodName, err) // method failed to be invoked at all, record it 48 + return nil, err 49 + } 50 + 51 + return newPrometheusServerStream(s, serviceName, methodName), err 52 + } 53 + 54 + // newPrometheusServerStream wraps a grpc.ClientStream to observe the first error 55 + // encountered during the stream, if any. 56 + func newPrometheusServerStream(s grpc.ClientStream, serviceName, methodName string) grpc.ClientStream { 57 + // Design note: We only want a single observation for each RPC call: it either succeeds or fails 58 + // with a single error. This ensures we do not double-count RPCs in Prometheus metrics. 59 + // 60 + // For unary calls this is straightforward, but for streaming RPCs we need to make a compromise. We only 61 + // observe the first error (either sending or receiving) that occurs during the stream, instead of every 62 + // error that occurs during the stream's lifespan. While this approach swallows some errors, it keeps the 63 + // Prometheus metric count clean and non-duplicated. The logging interceptor handles surfacing all errors 64 + // that are encountered during a stream. 65 + var observeOnce sync.Once 66 + 67 + return &callBackClientStream{ 68 + ClientStream: s, 69 + postMessageSend: func(_ any, err error) { 70 + if err != nil { 71 + observeOnce.Do(func() { 72 + doObservation(serviceName, methodName, err) 73 + }) 74 + } 75 + }, 76 + postMessageReceive: func(_ any, err error) { 77 + if err != nil { 78 + if err == io.EOF { 79 + // EOF signals end of stream, not an error. We handle this by setting err to nil, because 80 + // we want to treat the stream as successfully completed. 81 + err = nil 82 + } 83 + 84 + observeOnce.Do(func() { 85 + doObservation(serviceName, methodName, err) 86 + }) 87 + } 88 + }, 89 + } 90 + 91 + } 92 + 93 + func doObservation(serviceName, methodName string, rpcErr error) { 94 + if rpcErr == nil { 95 + // No error occurred, so we record a successful call. 96 + metricGRPCMethodStatus.WithLabelValues(serviceName, methodName, codes.OK.String(), "false").Inc() 97 + return 98 + } 99 + 100 + s, ok := massageIntoStatusErr(rpcErr) 101 + if !ok { 102 + // An error occurred, but it was not an error that has a status.Status implementation. We record this as an unknown error. 103 + metricGRPCMethodStatus.WithLabelValues(serviceName, methodName, codes.Unknown.String(), "false").Inc() 104 + return 105 + } 106 + 107 + if !probablyInternalGRPCError(s, allCheckers) { 108 + // An error occurred, but it was not an internal gRPC error. We record this as a non-internal error. 109 + metricGRPCMethodStatus.WithLabelValues(serviceName, methodName, s.Code().String(), "false").Inc() 110 + return 111 + } 112 + 113 + // An error occurred, and it looks like an internal gRPC error. We record this as an internal error. 114 + metricGRPCMethodStatus.WithLabelValues(serviceName, methodName, s.Code().String(), "true").Inc() 115 + }
+1 -1
grpc/server.go grpc/server/server.go
··· 1 - package grpc 1 + package server 2 2 3 3 import ( 4 4 "context"
+1 -1
grpc/server_test.go grpc/server/server_test.go
··· 1 - package grpc 1 + package server 2 2 3 3 import ( 4 4 "context"
+7
grpc/testprotos/news/v1/buf.gen.yaml
··· 1 + # Configuration file for https://buf.build/, which we use for Protobuf code generation. 2 + version: v1 3 + plugins: 4 + - plugin: buf.build/protocolbuffers/go:v1.29.1 5 + out: . 6 + opt: 7 + - paths=source_relative
+537
grpc/testprotos/news/v1/news.pb.go
··· 1 + // Copyright 2020 The Go Authors. All rights reserved. 2 + // Use of this source code is governed by a BSD-style 3 + // license that can be found in the LICENSE file. 4 + // 5 + // Note (@Sourcegraph): This file was copied / adapted from 6 + // https://github.com/protocolbuffers/protobuf-go/blob/v1.30.0/internal/testprotos/news/news.proto to aid our testing. 7 + 8 + // Code generated by protoc-gen-go. DO NOT EDIT. 9 + // versions: 10 + // protoc-gen-go v1.29.1 11 + // protoc (unknown) 12 + // source: news.proto 13 + 14 + package v1 15 + 16 + import ( 17 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 18 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 19 + timestamppb "google.golang.org/protobuf/types/known/timestamppb" 20 + reflect "reflect" 21 + sync "sync" 22 + ) 23 + 24 + const ( 25 + // Verify that this generated code is sufficiently up-to-date. 26 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 27 + // Verify that runtime/protoimpl is sufficiently up-to-date. 28 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 29 + ) 30 + 31 + type Article_Status int32 32 + 33 + const ( 34 + Article_STATUS_DRAFT_UNSPECIFIED Article_Status = 0 35 + Article_STATUS_PUBLISHED Article_Status = 1 36 + Article_STATUS_REVOKED Article_Status = 2 37 + ) 38 + 39 + // Enum value maps for Article_Status. 40 + var ( 41 + Article_Status_name = map[int32]string{ 42 + 0: "STATUS_DRAFT_UNSPECIFIED", 43 + 1: "STATUS_PUBLISHED", 44 + 2: "STATUS_REVOKED", 45 + } 46 + Article_Status_value = map[string]int32{ 47 + "STATUS_DRAFT_UNSPECIFIED": 0, 48 + "STATUS_PUBLISHED": 1, 49 + "STATUS_REVOKED": 2, 50 + } 51 + ) 52 + 53 + func (x Article_Status) Enum() *Article_Status { 54 + p := new(Article_Status) 55 + *p = x 56 + return p 57 + } 58 + 59 + func (x Article_Status) String() string { 60 + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) 61 + } 62 + 63 + func (Article_Status) Descriptor() protoreflect.EnumDescriptor { 64 + return file_news_proto_enumTypes[0].Descriptor() 65 + } 66 + 67 + func (Article_Status) Type() protoreflect.EnumType { 68 + return &file_news_proto_enumTypes[0] 69 + } 70 + 71 + func (x Article_Status) Number() protoreflect.EnumNumber { 72 + return protoreflect.EnumNumber(x) 73 + } 74 + 75 + // Deprecated: Use Article_Status.Descriptor instead. 76 + func (Article_Status) EnumDescriptor() ([]byte, []int) { 77 + return file_news_proto_rawDescGZIP(), []int{0, 0} 78 + } 79 + 80 + type Article struct { 81 + state protoimpl.MessageState 82 + sizeCache protoimpl.SizeCache 83 + unknownFields protoimpl.UnknownFields 84 + 85 + Author string `protobuf:"bytes,1,opt,name=author,proto3" json:"author,omitempty"` 86 + Date *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=date,proto3" json:"date,omitempty"` 87 + Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"` 88 + Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"` 89 + Status Article_Status `protobuf:"varint,8,opt,name=status,proto3,enum=grpc.testprotos.news.v1.Article_Status" json:"status,omitempty"` 90 + Attachments []*Attachment `protobuf:"bytes,7,rep,name=attachments,proto3" json:"attachments,omitempty"` 91 + } 92 + 93 + func (x *Article) Reset() { 94 + *x = Article{} 95 + if protoimpl.UnsafeEnabled { 96 + mi := &file_news_proto_msgTypes[0] 97 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 98 + ms.StoreMessageInfo(mi) 99 + } 100 + } 101 + 102 + func (x *Article) String() string { 103 + return protoimpl.X.MessageStringOf(x) 104 + } 105 + 106 + func (*Article) ProtoMessage() {} 107 + 108 + func (x *Article) ProtoReflect() protoreflect.Message { 109 + mi := &file_news_proto_msgTypes[0] 110 + if protoimpl.UnsafeEnabled && x != nil { 111 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 112 + if ms.LoadMessageInfo() == nil { 113 + ms.StoreMessageInfo(mi) 114 + } 115 + return ms 116 + } 117 + return mi.MessageOf(x) 118 + } 119 + 120 + // Deprecated: Use Article.ProtoReflect.Descriptor instead. 121 + func (*Article) Descriptor() ([]byte, []int) { 122 + return file_news_proto_rawDescGZIP(), []int{0} 123 + } 124 + 125 + func (x *Article) GetAuthor() string { 126 + if x != nil { 127 + return x.Author 128 + } 129 + return "" 130 + } 131 + 132 + func (x *Article) GetDate() *timestamppb.Timestamp { 133 + if x != nil { 134 + return x.Date 135 + } 136 + return nil 137 + } 138 + 139 + func (x *Article) GetTitle() string { 140 + if x != nil { 141 + return x.Title 142 + } 143 + return "" 144 + } 145 + 146 + func (x *Article) GetContent() string { 147 + if x != nil { 148 + return x.Content 149 + } 150 + return "" 151 + } 152 + 153 + func (x *Article) GetStatus() Article_Status { 154 + if x != nil { 155 + return x.Status 156 + } 157 + return Article_STATUS_DRAFT_UNSPECIFIED 158 + } 159 + 160 + func (x *Article) GetAttachments() []*Attachment { 161 + if x != nil { 162 + return x.Attachments 163 + } 164 + return nil 165 + } 166 + 167 + type Attachment struct { 168 + state protoimpl.MessageState 169 + sizeCache protoimpl.SizeCache 170 + unknownFields protoimpl.UnknownFields 171 + 172 + // Types that are assignable to Contents: 173 + // 174 + // *Attachment_BinaryAttachment 175 + // *Attachment_KeyValueAttachment 176 + Contents isAttachment_Contents `protobuf_oneof:"contents"` 177 + } 178 + 179 + func (x *Attachment) Reset() { 180 + *x = Attachment{} 181 + if protoimpl.UnsafeEnabled { 182 + mi := &file_news_proto_msgTypes[1] 183 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 184 + ms.StoreMessageInfo(mi) 185 + } 186 + } 187 + 188 + func (x *Attachment) String() string { 189 + return protoimpl.X.MessageStringOf(x) 190 + } 191 + 192 + func (*Attachment) ProtoMessage() {} 193 + 194 + func (x *Attachment) ProtoReflect() protoreflect.Message { 195 + mi := &file_news_proto_msgTypes[1] 196 + if protoimpl.UnsafeEnabled && x != nil { 197 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 198 + if ms.LoadMessageInfo() == nil { 199 + ms.StoreMessageInfo(mi) 200 + } 201 + return ms 202 + } 203 + return mi.MessageOf(x) 204 + } 205 + 206 + // Deprecated: Use Attachment.ProtoReflect.Descriptor instead. 207 + func (*Attachment) Descriptor() ([]byte, []int) { 208 + return file_news_proto_rawDescGZIP(), []int{1} 209 + } 210 + 211 + func (m *Attachment) GetContents() isAttachment_Contents { 212 + if m != nil { 213 + return m.Contents 214 + } 215 + return nil 216 + } 217 + 218 + func (x *Attachment) GetBinaryAttachment() *BinaryAttachment { 219 + if x, ok := x.GetContents().(*Attachment_BinaryAttachment); ok { 220 + return x.BinaryAttachment 221 + } 222 + return nil 223 + } 224 + 225 + func (x *Attachment) GetKeyValueAttachment() *KeyValueAttachment { 226 + if x, ok := x.GetContents().(*Attachment_KeyValueAttachment); ok { 227 + return x.KeyValueAttachment 228 + } 229 + return nil 230 + } 231 + 232 + type isAttachment_Contents interface { 233 + isAttachment_Contents() 234 + } 235 + 236 + type Attachment_BinaryAttachment struct { 237 + BinaryAttachment *BinaryAttachment `protobuf:"bytes,1,opt,name=binary_attachment,json=binaryAttachment,proto3,oneof"` 238 + } 239 + 240 + type Attachment_KeyValueAttachment struct { 241 + KeyValueAttachment *KeyValueAttachment `protobuf:"bytes,2,opt,name=key_value_attachment,json=keyValueAttachment,proto3,oneof"` 242 + } 243 + 244 + func (*Attachment_BinaryAttachment) isAttachment_Contents() {} 245 + 246 + func (*Attachment_KeyValueAttachment) isAttachment_Contents() {} 247 + 248 + type BinaryAttachment struct { 249 + state protoimpl.MessageState 250 + sizeCache protoimpl.SizeCache 251 + unknownFields protoimpl.UnknownFields 252 + 253 + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 254 + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` 255 + } 256 + 257 + func (x *BinaryAttachment) Reset() { 258 + *x = BinaryAttachment{} 259 + if protoimpl.UnsafeEnabled { 260 + mi := &file_news_proto_msgTypes[2] 261 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 262 + ms.StoreMessageInfo(mi) 263 + } 264 + } 265 + 266 + func (x *BinaryAttachment) String() string { 267 + return protoimpl.X.MessageStringOf(x) 268 + } 269 + 270 + func (*BinaryAttachment) ProtoMessage() {} 271 + 272 + func (x *BinaryAttachment) ProtoReflect() protoreflect.Message { 273 + mi := &file_news_proto_msgTypes[2] 274 + if protoimpl.UnsafeEnabled && x != nil { 275 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 276 + if ms.LoadMessageInfo() == nil { 277 + ms.StoreMessageInfo(mi) 278 + } 279 + return ms 280 + } 281 + return mi.MessageOf(x) 282 + } 283 + 284 + // Deprecated: Use BinaryAttachment.ProtoReflect.Descriptor instead. 285 + func (*BinaryAttachment) Descriptor() ([]byte, []int) { 286 + return file_news_proto_rawDescGZIP(), []int{2} 287 + } 288 + 289 + func (x *BinaryAttachment) GetName() string { 290 + if x != nil { 291 + return x.Name 292 + } 293 + return "" 294 + } 295 + 296 + func (x *BinaryAttachment) GetData() []byte { 297 + if x != nil { 298 + return x.Data 299 + } 300 + return nil 301 + } 302 + 303 + type KeyValueAttachment struct { 304 + state protoimpl.MessageState 305 + sizeCache protoimpl.SizeCache 306 + unknownFields protoimpl.UnknownFields 307 + 308 + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 309 + Data map[string]string `protobuf:"bytes,2,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 310 + } 311 + 312 + func (x *KeyValueAttachment) Reset() { 313 + *x = KeyValueAttachment{} 314 + if protoimpl.UnsafeEnabled { 315 + mi := &file_news_proto_msgTypes[3] 316 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 317 + ms.StoreMessageInfo(mi) 318 + } 319 + } 320 + 321 + func (x *KeyValueAttachment) String() string { 322 + return protoimpl.X.MessageStringOf(x) 323 + } 324 + 325 + func (*KeyValueAttachment) ProtoMessage() {} 326 + 327 + func (x *KeyValueAttachment) ProtoReflect() protoreflect.Message { 328 + mi := &file_news_proto_msgTypes[3] 329 + if protoimpl.UnsafeEnabled && x != nil { 330 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 331 + if ms.LoadMessageInfo() == nil { 332 + ms.StoreMessageInfo(mi) 333 + } 334 + return ms 335 + } 336 + return mi.MessageOf(x) 337 + } 338 + 339 + // Deprecated: Use KeyValueAttachment.ProtoReflect.Descriptor instead. 340 + func (*KeyValueAttachment) Descriptor() ([]byte, []int) { 341 + return file_news_proto_rawDescGZIP(), []int{3} 342 + } 343 + 344 + func (x *KeyValueAttachment) GetName() string { 345 + if x != nil { 346 + return x.Name 347 + } 348 + return "" 349 + } 350 + 351 + func (x *KeyValueAttachment) GetData() map[string]string { 352 + if x != nil { 353 + return x.Data 354 + } 355 + return nil 356 + } 357 + 358 + var File_news_proto protoreflect.FileDescriptor 359 + 360 + var file_news_proto_rawDesc = []byte{ 361 + 0x0a, 0x0a, 0x6e, 0x65, 0x77, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x67, 0x72, 362 + 0x70, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x6e, 0x65, 363 + 0x77, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 364 + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 365 + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdb, 0x02, 0x0a, 0x07, 0x41, 0x72, 0x74, 0x69, 0x63, 366 + 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 367 + 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x2e, 0x0a, 0x04, 0x64, 0x61, 368 + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 369 + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 370 + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, 371 + 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 372 + 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 373 + 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x74, 374 + 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x67, 0x72, 0x70, 375 + 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x6e, 0x65, 0x77, 376 + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x63, 0x6c, 0x65, 0x2e, 0x53, 0x74, 0x61, 377 + 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x45, 0x0a, 0x0b, 0x61, 378 + 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 379 + 0x32, 0x23, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 380 + 0x6f, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, 381 + 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 382 + 0x74, 0x73, 0x22, 0x50, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x18, 383 + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x44, 0x52, 0x41, 0x46, 0x54, 0x5f, 0x55, 0x4e, 0x53, 384 + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, 385 + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0x01, 386 + 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, 387 + 0x45, 0x44, 0x10, 0x02, 0x22, 0xd3, 0x01, 0x0a, 0x0a, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 388 + 0x65, 0x6e, 0x74, 0x12, 0x58, 0x0a, 0x11, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x74, 389 + 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 390 + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 391 + 0x2e, 0x6e, 0x65, 0x77, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x41, 392 + 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x62, 0x69, 0x6e, 393 + 0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x5f, 0x0a, 394 + 0x14, 0x6b, 0x65, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 395 + 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x72, 396 + 0x70, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x6e, 0x65, 397 + 0x77, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x41, 0x74, 398 + 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x6b, 0x65, 0x79, 0x56, 399 + 0x61, 0x6c, 0x75, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x0a, 400 + 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x0a, 0x10, 0x42, 0x69, 401 + 0x6e, 0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 402 + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 403 + 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 404 + 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xac, 0x01, 0x0a, 0x12, 0x4b, 0x65, 0x79, 0x56, 0x61, 405 + 0x6c, 0x75, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 406 + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 407 + 0x65, 0x12, 0x49, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 408 + 0x35, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 409 + 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 410 + 0x75, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 411 + 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 412 + 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 413 + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 414 + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 415 + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 416 + 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, 417 + 0x7a, 0x6f, 0x65, 0x6b, 0x74, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 418 + 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x6e, 0x65, 0x77, 0x73, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 419 + 0x72, 0x6f, 0x74, 0x6f, 0x33, 420 + } 421 + 422 + var ( 423 + file_news_proto_rawDescOnce sync.Once 424 + file_news_proto_rawDescData = file_news_proto_rawDesc 425 + ) 426 + 427 + func file_news_proto_rawDescGZIP() []byte { 428 + file_news_proto_rawDescOnce.Do(func() { 429 + file_news_proto_rawDescData = protoimpl.X.CompressGZIP(file_news_proto_rawDescData) 430 + }) 431 + return file_news_proto_rawDescData 432 + } 433 + 434 + var file_news_proto_enumTypes = make([]protoimpl.EnumInfo, 1) 435 + var file_news_proto_msgTypes = make([]protoimpl.MessageInfo, 5) 436 + var file_news_proto_goTypes = []interface{}{ 437 + (Article_Status)(0), // 0: grpc.testprotos.news.v1.Article.Status 438 + (*Article)(nil), // 1: grpc.testprotos.news.v1.Article 439 + (*Attachment)(nil), // 2: grpc.testprotos.news.v1.Attachment 440 + (*BinaryAttachment)(nil), // 3: grpc.testprotos.news.v1.BinaryAttachment 441 + (*KeyValueAttachment)(nil), // 4: grpc.testprotos.news.v1.KeyValueAttachment 442 + nil, // 5: grpc.testprotos.news.v1.KeyValueAttachment.DataEntry 443 + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp 444 + } 445 + var file_news_proto_depIdxs = []int32{ 446 + 6, // 0: grpc.testprotos.news.v1.Article.date:type_name -> google.protobuf.Timestamp 447 + 0, // 1: grpc.testprotos.news.v1.Article.status:type_name -> grpc.testprotos.news.v1.Article.Status 448 + 2, // 2: grpc.testprotos.news.v1.Article.attachments:type_name -> grpc.testprotos.news.v1.Attachment 449 + 3, // 3: grpc.testprotos.news.v1.Attachment.binary_attachment:type_name -> grpc.testprotos.news.v1.BinaryAttachment 450 + 4, // 4: grpc.testprotos.news.v1.Attachment.key_value_attachment:type_name -> grpc.testprotos.news.v1.KeyValueAttachment 451 + 5, // 5: grpc.testprotos.news.v1.KeyValueAttachment.data:type_name -> grpc.testprotos.news.v1.KeyValueAttachment.DataEntry 452 + 6, // [6:6] is the sub-list for method output_type 453 + 6, // [6:6] is the sub-list for method input_type 454 + 6, // [6:6] is the sub-list for extension type_name 455 + 6, // [6:6] is the sub-list for extension extendee 456 + 0, // [0:6] is the sub-list for field type_name 457 + } 458 + 459 + func init() { file_news_proto_init() } 460 + func file_news_proto_init() { 461 + if File_news_proto != nil { 462 + return 463 + } 464 + if !protoimpl.UnsafeEnabled { 465 + file_news_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 466 + switch v := v.(*Article); i { 467 + case 0: 468 + return &v.state 469 + case 1: 470 + return &v.sizeCache 471 + case 2: 472 + return &v.unknownFields 473 + default: 474 + return nil 475 + } 476 + } 477 + file_news_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 478 + switch v := v.(*Attachment); i { 479 + case 0: 480 + return &v.state 481 + case 1: 482 + return &v.sizeCache 483 + case 2: 484 + return &v.unknownFields 485 + default: 486 + return nil 487 + } 488 + } 489 + file_news_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 490 + switch v := v.(*BinaryAttachment); i { 491 + case 0: 492 + return &v.state 493 + case 1: 494 + return &v.sizeCache 495 + case 2: 496 + return &v.unknownFields 497 + default: 498 + return nil 499 + } 500 + } 501 + file_news_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 502 + switch v := v.(*KeyValueAttachment); i { 503 + case 0: 504 + return &v.state 505 + case 1: 506 + return &v.sizeCache 507 + case 2: 508 + return &v.unknownFields 509 + default: 510 + return nil 511 + } 512 + } 513 + } 514 + file_news_proto_msgTypes[1].OneofWrappers = []interface{}{ 515 + (*Attachment_BinaryAttachment)(nil), 516 + (*Attachment_KeyValueAttachment)(nil), 517 + } 518 + type x struct{} 519 + out := protoimpl.TypeBuilder{ 520 + File: protoimpl.DescBuilder{ 521 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 522 + RawDescriptor: file_news_proto_rawDesc, 523 + NumEnums: 1, 524 + NumMessages: 5, 525 + NumExtensions: 0, 526 + NumServices: 0, 527 + }, 528 + GoTypes: file_news_proto_goTypes, 529 + DependencyIndexes: file_news_proto_depIdxs, 530 + EnumInfos: file_news_proto_enumTypes, 531 + MessageInfos: file_news_proto_msgTypes, 532 + }.Build() 533 + File_news_proto = out.File 534 + file_news_proto_rawDesc = nil 535 + file_news_proto_goTypes = nil 536 + file_news_proto_depIdxs = nil 537 + }
+46
grpc/testprotos/news/v1/news.proto
··· 1 + // Copyright 2020 The Go Authors. All rights reserved. 2 + // Use of this source code is governed by a BSD-style 3 + // license that can be found in the LICENSE file. 4 + // 5 + // Note (@Sourcegraph): This file was copied / adapted from 6 + // https://github.com/protocolbuffers/protobuf-go/blob/v1.30.0/internal/testprotos/news/news.proto to aid our testing. 7 + 8 + syntax = "proto3"; 9 + 10 + package grpc.testprotos.news.v1; 11 + 12 + import "google/protobuf/timestamp.proto"; 13 + 14 + option go_package = "github.com/sourcegraph/zoekt/grpc/testprotos/news/v1"; 15 + 16 + message Article { 17 + enum Status { 18 + STATUS_DRAFT_UNSPECIFIED = 0; 19 + STATUS_PUBLISHED = 1; 20 + STATUS_REVOKED = 2; 21 + } 22 + 23 + string author = 1; 24 + google.protobuf.Timestamp date = 2; 25 + string title = 3; 26 + string content = 4; 27 + Status status = 8; 28 + repeated Attachment attachments = 7; 29 + } 30 + 31 + message Attachment { 32 + oneof contents { 33 + BinaryAttachment binary_attachment = 1; 34 + KeyValueAttachment key_value_attachment = 2; 35 + } 36 + } 37 + 38 + message BinaryAttachment { 39 + string name = 1; 40 + bytes data = 2; 41 + } 42 + 43 + message KeyValueAttachment { 44 + string name = 1; 45 + map<string, string> data = 2; 46 + }