diff --git a/internal/gitserver/v1/BUILD.bazel b/internal/gitserver/v1/BUILD.bazel index 67adeb0bfa8..0cc2fd705b9 100644 --- a/internal/gitserver/v1/BUILD.bazel +++ b/internal/gitserver/v1/BUILD.bazel @@ -1,3 +1,4 @@ +load("//dev:go_defs.bzl", "go_test") load("@io_bazel_rules_go//go:def.bzl", "go_library") load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") load("@rules_buf//buf:defs.bzl", "buf_lint_test") diff --git a/internal/grpc/internalerrs/BUILD.bazel b/internal/grpc/internalerrs/BUILD.bazel index 71fc0a27ff2..35fa041b0e3 100644 --- a/internal/grpc/internalerrs/BUILD.bazel +++ b/internal/grpc/internalerrs/BUILD.bazel @@ -12,12 +12,16 @@ go_library( visibility = ["//:__subpackages__"], deps = [ "//internal/env", + "//lib/errors", "@com_github_prometheus_client_golang//prometheus", "@com_github_prometheus_client_golang//prometheus/promauto", "@com_github_sourcegraph_log//:log", "@org_golang_google_grpc//:go_default_library", "@org_golang_google_grpc//codes", "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//proto", + "@org_golang_google_protobuf//reflect/protopath", + "@org_golang_google_protobuf//reflect/protorange", ], ) @@ -26,9 +30,13 @@ go_test( srcs = ["common_test.go"], embed = [":internalerrs"], deps = [ + "//internal/grpc/testprotos/news/v1:news", "@com_github_google_go_cmp//cmp", + "@com_github_google_go_cmp//cmp/cmpopts", "@org_golang_google_grpc//:go_default_library", "@org_golang_google_grpc//codes", "@org_golang_google_grpc//status", + "@org_golang_google_protobuf//proto", + "@org_golang_google_protobuf//types/known/timestamppb", ], ) diff --git a/internal/grpc/internalerrs/common.go b/internal/grpc/internalerrs/common.go index 511f6cdff57..eb9a96e8aec 100644 --- a/internal/grpc/internalerrs/common.go +++ b/internal/grpc/internalerrs/common.go @@ -2,6 +2,12 @@ package internalerrs import ( "strings" + "unicode/utf8" + + "github.com/sourcegraph/sourcegraph/lib/errors" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protopath" + "google.golang.org/protobuf/reflect/protorange" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -12,20 +18,20 @@ import ( type callBackClientStream struct { grpc.ClientStream - postMessageSend func(error) - postMessageReceive func(error) + postMessageSend func(message any, err error) + postMessageReceive func(message any, err error) } -func (c *callBackClientStream) SendMsg(m interface{}) error { +func (c *callBackClientStream) SendMsg(m any) error { err := c.ClientStream.SendMsg(m) - c.postMessageSend(err) + c.postMessageSend(m, err) return err } -func (c *callBackClientStream) RecvMsg(m interface{}) error { +func (c *callBackClientStream) RecvMsg(m any) error { err := c.ClientStream.RecvMsg(m) - c.postMessageReceive(err) + c.postMessageReceive(m, err) return err } @@ -87,3 +93,31 @@ func splitMethodName(fullMethod string) (string, string) { } return "unknown", "unknown" } + +// findNonUTF8StringFields returns a list of field names that contain invalid UTF-8 strings +// in the given proto message. +// +// Example: ["author", "attachments[1].key_value_attachment.data["key2"]`] +func findNonUTF8StringFields(m proto.Message) ([]string, error) { + if m == nil { + return nil, nil + } + + var fields []string + err := protorange.Range(m.ProtoReflect(), func(p protopath.Values) error { + last := p.Index(-1) + s, ok := last.Value.Interface().(string) + if ok && !utf8.ValidString(s) { + fieldName := p.Path[1:].String() + fields = append(fields, strings.TrimPrefix(fieldName, ".")) + } + + return nil + }) + + if err != nil { + return nil, errors.Wrap(err, "iterating over proto message") + } + + return fields, nil +} diff --git a/internal/grpc/internalerrs/common_test.go b/internal/grpc/internalerrs/common_test.go index 5496f8b4eae..5beef28d55f 100644 --- a/internal/grpc/internalerrs/common_test.go +++ b/internal/grpc/internalerrs/common_test.go @@ -2,9 +2,15 @@ package internalerrs import ( "errors" + "sort" "strings" "testing" + "github.com/google/go-cmp/cmp/cmpopts" + newspb "github.com/sourcegraph/sourcegraph/internal/grpc/testprotos/news/v1" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" + "github.com/google/go-cmp/cmp" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -12,7 +18,8 @@ import ( ) func TestCallBackClientStream(t *testing.T) { - t.Run("SendMsg calls postMessageSend with error", func(t *testing.T) { + t.Run("SendMsg calls postMessageSend with message and error", func(t *testing.T) { + sentinelMessage := struct{}{} sentinelErr := errors.New("send error") var called bool @@ -20,16 +27,19 @@ func TestCallBackClientStream(t *testing.T) { ClientStream: &mockClientStream{ sendErr: sentinelErr, }, - postMessageSend: func(err error) { + postMessageSend: func(message any, err error) { called = true + if diff := cmp.Diff(message, sentinelMessage); diff != "" { + t.Errorf("postMessageSend called with unexpected message (-want +got):\n%s", diff) + } if !errors.Is(err, sentinelErr) { t.Errorf("got %v, want %v", err, sentinelErr) } }, } - sendErr := stream.SendMsg(nil) + sendErr := stream.SendMsg(sentinelMessage) if !called { t.Error("postMessageSend not called") } @@ -39,7 +49,8 @@ func TestCallBackClientStream(t *testing.T) { } }) - t.Run("RecvMsg calls postMessageReceive with error", func(t *testing.T) { + t.Run("RecvMsg calls postMessageReceive with message and error", func(t *testing.T) { + sentinelMessage := struct{}{} sentinelErr := errors.New("receive error") var called bool @@ -47,16 +58,19 @@ func TestCallBackClientStream(t *testing.T) { ClientStream: &mockClientStream{ recvErr: sentinelErr, }, - postMessageReceive: func(err error) { + postMessageReceive: func(message any, err error) { called = true + if diff := cmp.Diff(message, sentinelMessage); diff != "" { + t.Errorf("postMessageReceive called with unexpected message (-want +got):\n%s", diff) + } if !errors.Is(err, sentinelErr) { t.Errorf("got %v, want %v", err, sentinelErr) } }, } - receiveErr := stream.RecvMsg(nil) + receiveErr := stream.RecvMsg(sentinelMessage) if !called { t.Error("postMessageReceive not called") } @@ -74,11 +88,11 @@ type mockClientStream struct { recvErr error } -func (s *mockClientStream) SendMsg(m interface{}) error { +func (s *mockClientStream) SendMsg(any) error { return s.sendErr } -func (s *mockClientStream) RecvMsg(m interface{}) error { +func (s *mockClientStream) RecvMsg(any) error { return s.recvErr } @@ -221,3 +235,70 @@ func TestSplitMethodName(t *testing.T) { }) } } + +func TestFindNonUTF8StringFields(t *testing.T) { + // Create instances of the BinaryAttachment and KeyValueAttachment messages + invalidBinaryAttachment := &newspb.BinaryAttachment{ + Name: "inval\x80id_binary", + Data: []byte("sample data"), + } + + invalidKeyValueAttachment := &newspb.KeyValueAttachment{ + Name: "inval\x80id_key_value", + Data: map[string]string{ + "key1": "value1", + "key2": "inval\x80id_value", + }, + } + + // Create a sample Article message with invalid UTF-8 strings + article := &newspb.Article{ + Author: "inval\x80id_author", + Date: ×tamppb.Timestamp{Seconds: 1234567890}, + Title: "valid_title", + Content: "valid_content", + Status: newspb.Article_STATUS_PUBLISHED, + Attachments: []*newspb.Attachment{ + {Contents: &newspb.Attachment_BinaryAttachment{BinaryAttachment: invalidBinaryAttachment}}, + {Contents: &newspb.Attachment_KeyValueAttachment{KeyValueAttachment: invalidKeyValueAttachment}}, + }, + } + + tests := []struct { + name string + message proto.Message + expectedPaths []string + }{ + { + name: "Article with invalid UTF-8 strings", + message: article, + expectedPaths: []string{ + "author", + "attachments[0].binary_attachment.name", + "attachments[1].key_value_attachment.name", + `attachments[1].key_value_attachment.data["key2"]`, + }, + }, + { + name: "nil message", + message: nil, + expectedPaths: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + invalidFields, err := findNonUTF8StringFields(tt.message) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + sort.Strings(invalidFields) + sort.Strings(tt.expectedPaths) + + if diff := cmp.Diff(tt.expectedPaths, invalidFields, cmpopts.EquateEmpty()); diff != "" { + t.Fatalf("unexpected invalid fields (-want +got):\n%s", diff) + } + }) + } +} diff --git a/internal/grpc/internalerrs/logging.go b/internal/grpc/internalerrs/logging.go index d3f377e3543..7789d5e8989 100644 --- a/internal/grpc/internalerrs/logging.go +++ b/internal/grpc/internalerrs/logging.go @@ -2,8 +2,14 @@ package internalerrs import ( "context" + "encoding/json" "fmt" "io" + "strings" + + "github.com/sourcegraph/sourcegraph/lib/errors" + "google.golang.org/grpc/codes" + "google.golang.org/protobuf/proto" "github.com/sourcegraph/log" "google.golang.org/grpc" @@ -18,6 +24,9 @@ var ( envLoggingEnabled = env.MustGetBool("SRC_GRPC_INTERNAL_ERROR_LOGGING_ENABLED", true, "Enables logging of gRPC internal errors") envLogStackTracesEnabled = env.MustGetBool("SRC_GRPC_INTERNAL_ERROR_LOGGING_LOG_STACK_TRACES", false, "Enables including stack traces in logs of gRPC internal errors") + + envLogNonUTF8ProtobufMessages = env.MustGetBool("SRC_GRPC_INTERNAL_ERROR_LOGGING_LOG_NON_UTF8_PROTOBUF_MESSAGES_ENABLED", false, "Enables logging of non-UTF-8 protobuf messages") + envLogNonUTF8ProtobufMessagesMaxSize = env.MustGetInt("SRC_GRPC_INTERNAL_ERROR_LOGGING_LOG_NON_UTF8_PROTOBUF_MESSAGES_MAX_SIZE_BYTES", 1024, "Maximum size of non-UTF-8 protobuf messages to log before truncation, in bytes. Negative values disable truncation.") ) // LoggingUnaryClientInterceptor returns a grpc.UnaryClientInterceptor that logs @@ -35,7 +44,7 @@ func LoggingUnaryClientInterceptor(l log.Logger) grpc.UnaryClientInterceptor { err := invoker(ctx, fullMethod, req, reply, cc, opts...) if err != nil { serviceName, methodName := splitMethodName(fullMethod) - doLog(logger, serviceName, methodName, err) + doLog(logger, serviceName, methodName, req, err) } return err @@ -59,7 +68,11 @@ func LoggingStreamClientInterceptor(l log.Logger) grpc.StreamClientInterceptor { stream, err := streamer(ctx, desc, cc, fullMethod, opts...) if err != nil { - doLog(logger, serviceName, methodName, err) + // Note: This is a bit hacky, we provide a nil message here since the message isn't available + // until after the stream is created. + // + // This is fine since the error is already available, and the non-utf8 string check is robust against nil messages. + doLog(logger, serviceName, methodName, nil, err) return nil, err } @@ -71,20 +84,22 @@ func LoggingStreamClientInterceptor(l log.Logger) grpc.StreamClientInterceptor { func newLoggingClientStream(s grpc.ClientStream, logger log.Logger, serviceName, methodName string) *callBackClientStream { return &callBackClientStream{ ClientStream: s, - postMessageSend: func(err error) { + + postMessageSend: func(m any, err error) { if err != nil { - doLog(logger, serviceName, methodName, err) + doLog(logger, serviceName, methodName, m, err) } }, - postMessageReceive: func(err error) { + + postMessageReceive: func(m any, err error) { if err != nil && err != io.EOF { // EOF is expected at the end of a stream, so no need to log an error - doLog(logger, serviceName, methodName, err) + doLog(logger, serviceName, methodName, m, err) } }, } } -func doLog(logger log.Logger, serviceName, methodName string, err error) { +func doLog(logger log.Logger, serviceName, methodName string, payload any, err error) { if err == nil { return } @@ -98,15 +113,77 @@ func doLog(logger log.Logger, serviceName, methodName string, err error) { return } - fields := []log.Field{ + allFields := []log.Field{ log.String("grpcService", serviceName), log.String("grpcMethod", methodName), log.String("grpcCode", s.Code().String()), } if envLogStackTracesEnabled { - fields = append(fields, log.String("errWithStack", fmt.Sprintf("%+v", err))) + allFields = append(allFields, log.String("errWithStack", fmt.Sprintf("%+v", err))) } - logger.Error(s.Message(), fields...) + if isNonUTF8StringError(s) { + if m, ok := payload.(proto.Message); ok { + allFields = append( + allFields, + additionalNonUTF8StringDebugFields(m, envLogNonUTF8ProtobufMessages, envLogNonUTF8ProtobufMessagesMaxSize)..., + ) + } + } + + logger.Error(s.Message(), allFields...) +} + +// additionalNonUTF8StringDebugFields returns additional log fields that should be included when logging a non-UTF8 string error. +// +// By default, this includes the names of all fields that contain non-UTF8 strings. +// If shouldLogMessageJSON is true, then the JSON representation of the message is also included. +// The maxMessageSizeLogBytes parameter controls the maximum size of the message that will be logged, after which it will be truncated. Negative values disable truncation. +func additionalNonUTF8StringDebugFields(message proto.Message, shouldLogMessageJSON bool, maxMessageLogSizeBytes int) []log.Field { + var allFields []log.Field + + // Add the names of all protobuf fields that contain non-UTF-8 strings to the log. + + badFields, err := findNonUTF8StringFields(message) + if err != nil { + allFields = append(allFields, log.Error(errors.Wrapf(err, "failed to find non-UTF8 string allFields"))) + return allFields + } + + allFields = append(allFields, log.Strings("nonUTF8StringFields", badFields)) + + // Add the JSON representation of the message to the log. + + if !shouldLogMessageJSON { + return allFields + } + + // Note: we can't use the protojson library here since it doesn't support messages with non-UTF8 strings. + jsonBytes, err := json.Marshal(message) + if err != nil { + allFields = append(allFields, log.Error(errors.Wrapf(err, "failed to marshal protobuf message to bytes"))) + return allFields + } + + if maxMessageLogSizeBytes < 0 { // If truncation is disabled, set the max size to the full message size. + maxMessageLogSizeBytes = len(jsonBytes) + } + + bytesToTruncate := len(jsonBytes) - maxMessageLogSizeBytes + if bytesToTruncate > 0 { + jsonBytes = jsonBytes[:maxMessageLogSizeBytes] + jsonBytes = append(jsonBytes, []byte(fmt.Sprintf("...(truncated %d bytes)", bytesToTruncate))...) + } + + allFields = append(allFields, log.String("messageJSON", string(jsonBytes))) + return allFields +} + +func isNonUTF8StringError(s *status.Status) bool { + if s.Code() != codes.Internal { + return false + } + + return strings.Contains(s.Message(), "string field contains invalid UTF-8") } diff --git a/internal/grpc/internalerrs/prometheus.go b/internal/grpc/internalerrs/prometheus.go index 801fdefef8c..5dc0fbe27f2 100644 --- a/internal/grpc/internalerrs/prometheus.go +++ b/internal/grpc/internalerrs/prometheus.go @@ -66,14 +66,14 @@ func newPrometheusServerStream(s grpc.ClientStream, serviceName, methodName stri return &callBackClientStream{ ClientStream: s, - postMessageSend: func(err error) { + postMessageSend: func(_ any, err error) { if err != nil { observeOnce.Do(func() { doObservation(serviceName, methodName, err) }) } }, - postMessageReceive: func(err error) { + postMessageReceive: func(_ any, err error) { if err != nil { if err == io.EOF { // EOF signals end of stream, not an error. We handle this by setting err to nil, because diff --git a/internal/grpc/testprotos/news/v1/BUILD.bazel b/internal/grpc/testprotos/news/v1/BUILD.bazel new file mode 100644 index 00000000000..511735c2ee8 --- /dev/null +++ b/internal/grpc/testprotos/news/v1/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") + +proto_library( + name = "news_proto", + srcs = ["news.proto"], + visibility = ["//:__subpackages__"], + deps = ["@com_google_protobuf//:timestamp_proto"], +) + +go_proto_library( + name = "news_go_proto", + importpath = "github.com/sourcegraph/sourcegraph/internal/grpc/testprotos/news", + proto = ":news_proto", + visibility = ["//:__subpackages__"], +) + +go_library( + name = "news", + embed = [":v1_go_proto"], + importpath = "github.com/sourcegraph/sourcegraph/internal/grpc/testprotos/news/v1", + visibility = ["//:__subpackages__"], +) + +go_proto_library( + name = "v1_go_proto", + importpath = "github.com/sourcegraph/sourcegraph/internal/grpc/testprotos/news/v1", + proto = ":news_proto", + visibility = ["//:__subpackages__"], +) diff --git a/internal/grpc/testprotos/news/v1/buf.gen.yaml b/internal/grpc/testprotos/news/v1/buf.gen.yaml new file mode 100644 index 00000000000..0f755cc35a1 --- /dev/null +++ b/internal/grpc/testprotos/news/v1/buf.gen.yaml @@ -0,0 +1,7 @@ +# Configuration file for https://buf.build/, which we use for Protobuf code generation. +version: v1 +plugins: + - plugin: buf.build/protocolbuffers/go:v1.29.1 + out: . + opt: + - paths=source_relative diff --git a/internal/grpc/testprotos/news/v1/news.pb.go b/internal/grpc/testprotos/news/v1/news.pb.go new file mode 100644 index 00000000000..b4ffc6d82b8 --- /dev/null +++ b/internal/grpc/testprotos/news/v1/news.pb.go @@ -0,0 +1,538 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Note (@Sourcegraph): This file was copied / adapted from +// https://github.com/protocolbuffers/protobuf-go/blob/v1.30.0/internal/testprotos/news/news.proto to aid our testing. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.29.1 +// protoc (unknown) +// source: news.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Article_Status int32 + +const ( + Article_STATUS_DRAFT_UNSPECIFIED Article_Status = 0 + Article_STATUS_PUBLISHED Article_Status = 1 + Article_STATUS_REVOKED Article_Status = 2 +) + +// Enum value maps for Article_Status. +var ( + Article_Status_name = map[int32]string{ + 0: "STATUS_DRAFT_UNSPECIFIED", + 1: "STATUS_PUBLISHED", + 2: "STATUS_REVOKED", + } + Article_Status_value = map[string]int32{ + "STATUS_DRAFT_UNSPECIFIED": 0, + "STATUS_PUBLISHED": 1, + "STATUS_REVOKED": 2, + } +) + +func (x Article_Status) Enum() *Article_Status { + p := new(Article_Status) + *p = x + return p +} + +func (x Article_Status) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Article_Status) Descriptor() protoreflect.EnumDescriptor { + return file_news_proto_enumTypes[0].Descriptor() +} + +func (Article_Status) Type() protoreflect.EnumType { + return &file_news_proto_enumTypes[0] +} + +func (x Article_Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Article_Status.Descriptor instead. +func (Article_Status) EnumDescriptor() ([]byte, []int) { + return file_news_proto_rawDescGZIP(), []int{0, 0} +} + +type Article struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Author string `protobuf:"bytes,1,opt,name=author,proto3" json:"author,omitempty"` + Date *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=date,proto3" json:"date,omitempty"` + Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"` + Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content,omitempty"` + Status Article_Status `protobuf:"varint,8,opt,name=status,proto3,enum=grpc.testprotos.news.v1.Article_Status" json:"status,omitempty"` + Attachments []*Attachment `protobuf:"bytes,7,rep,name=attachments,proto3" json:"attachments,omitempty"` +} + +func (x *Article) Reset() { + *x = Article{} + if protoimpl.UnsafeEnabled { + mi := &file_news_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Article) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Article) ProtoMessage() {} + +func (x *Article) ProtoReflect() protoreflect.Message { + mi := &file_news_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Article.ProtoReflect.Descriptor instead. +func (*Article) Descriptor() ([]byte, []int) { + return file_news_proto_rawDescGZIP(), []int{0} +} + +func (x *Article) GetAuthor() string { + if x != nil { + return x.Author + } + return "" +} + +func (x *Article) GetDate() *timestamppb.Timestamp { + if x != nil { + return x.Date + } + return nil +} + +func (x *Article) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *Article) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *Article) GetStatus() Article_Status { + if x != nil { + return x.Status + } + return Article_STATUS_DRAFT_UNSPECIFIED +} + +func (x *Article) GetAttachments() []*Attachment { + if x != nil { + return x.Attachments + } + return nil +} + +type Attachment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Contents: + // + // *Attachment_BinaryAttachment + // *Attachment_KeyValueAttachment + Contents isAttachment_Contents `protobuf_oneof:"contents"` +} + +func (x *Attachment) Reset() { + *x = Attachment{} + if protoimpl.UnsafeEnabled { + mi := &file_news_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Attachment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Attachment) ProtoMessage() {} + +func (x *Attachment) ProtoReflect() protoreflect.Message { + mi := &file_news_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Attachment.ProtoReflect.Descriptor instead. +func (*Attachment) Descriptor() ([]byte, []int) { + return file_news_proto_rawDescGZIP(), []int{1} +} + +func (m *Attachment) GetContents() isAttachment_Contents { + if m != nil { + return m.Contents + } + return nil +} + +func (x *Attachment) GetBinaryAttachment() *BinaryAttachment { + if x, ok := x.GetContents().(*Attachment_BinaryAttachment); ok { + return x.BinaryAttachment + } + return nil +} + +func (x *Attachment) GetKeyValueAttachment() *KeyValueAttachment { + if x, ok := x.GetContents().(*Attachment_KeyValueAttachment); ok { + return x.KeyValueAttachment + } + return nil +} + +type isAttachment_Contents interface { + isAttachment_Contents() +} + +type Attachment_BinaryAttachment struct { + BinaryAttachment *BinaryAttachment `protobuf:"bytes,1,opt,name=binary_attachment,json=binaryAttachment,proto3,oneof"` +} + +type Attachment_KeyValueAttachment struct { + KeyValueAttachment *KeyValueAttachment `protobuf:"bytes,2,opt,name=key_value_attachment,json=keyValueAttachment,proto3,oneof"` +} + +func (*Attachment_BinaryAttachment) isAttachment_Contents() {} + +func (*Attachment_KeyValueAttachment) isAttachment_Contents() {} + +type BinaryAttachment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *BinaryAttachment) Reset() { + *x = BinaryAttachment{} + if protoimpl.UnsafeEnabled { + mi := &file_news_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BinaryAttachment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BinaryAttachment) ProtoMessage() {} + +func (x *BinaryAttachment) ProtoReflect() protoreflect.Message { + mi := &file_news_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BinaryAttachment.ProtoReflect.Descriptor instead. +func (*BinaryAttachment) Descriptor() ([]byte, []int) { + return file_news_proto_rawDescGZIP(), []int{2} +} + +func (x *BinaryAttachment) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *BinaryAttachment) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type KeyValueAttachment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + 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"` +} + +func (x *KeyValueAttachment) Reset() { + *x = KeyValueAttachment{} + if protoimpl.UnsafeEnabled { + mi := &file_news_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KeyValueAttachment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeyValueAttachment) ProtoMessage() {} + +func (x *KeyValueAttachment) ProtoReflect() protoreflect.Message { + mi := &file_news_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeyValueAttachment.ProtoReflect.Descriptor instead. +func (*KeyValueAttachment) Descriptor() ([]byte, []int) { + return file_news_proto_rawDescGZIP(), []int{3} +} + +func (x *KeyValueAttachment) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *KeyValueAttachment) GetData() map[string]string { + if x != nil { + return x.Data + } + return nil +} + +var File_news_proto protoreflect.FileDescriptor + +var file_news_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x6e, 0x65, 0x77, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x17, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x6e, 0x65, + 0x77, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdb, 0x02, 0x0a, 0x07, 0x41, 0x72, 0x74, 0x69, 0x63, + 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x2e, 0x0a, 0x04, 0x64, 0x61, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x6e, 0x65, 0x77, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x72, 0x74, 0x69, 0x63, 0x6c, 0x65, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x45, 0x0a, 0x0b, 0x61, + 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x23, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x22, 0x50, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x18, + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x44, 0x52, 0x41, 0x46, 0x54, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x45, 0x44, 0x10, 0x01, + 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x52, 0x45, 0x56, 0x4f, 0x4b, + 0x45, 0x44, 0x10, 0x02, 0x22, 0xd3, 0x01, 0x0a, 0x0a, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, + 0x65, 0x6e, 0x74, 0x12, 0x58, 0x0a, 0x11, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x74, + 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, + 0x2e, 0x6e, 0x65, 0x77, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x41, + 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x62, 0x69, 0x6e, + 0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x5f, 0x0a, + 0x14, 0x6b, 0x65, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2e, 0x6e, 0x65, + 0x77, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x41, 0x74, + 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x6b, 0x65, 0x79, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x0a, + 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x3a, 0x0a, 0x10, 0x42, 0x69, + 0x6e, 0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xac, 0x01, 0x0a, 0x12, 0x4b, 0x65, 0x79, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x49, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x35, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, + 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, + 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x45, 0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x6e, 0x65, 0x77, 0x73, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_news_proto_rawDescOnce sync.Once + file_news_proto_rawDescData = file_news_proto_rawDesc +) + +func file_news_proto_rawDescGZIP() []byte { + file_news_proto_rawDescOnce.Do(func() { + file_news_proto_rawDescData = protoimpl.X.CompressGZIP(file_news_proto_rawDescData) + }) + return file_news_proto_rawDescData +} + +var file_news_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_news_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_news_proto_goTypes = []interface{}{ + (Article_Status)(0), // 0: grpc.testprotos.news.v1.Article.Status + (*Article)(nil), // 1: grpc.testprotos.news.v1.Article + (*Attachment)(nil), // 2: grpc.testprotos.news.v1.Attachment + (*BinaryAttachment)(nil), // 3: grpc.testprotos.news.v1.BinaryAttachment + (*KeyValueAttachment)(nil), // 4: grpc.testprotos.news.v1.KeyValueAttachment + nil, // 5: grpc.testprotos.news.v1.KeyValueAttachment.DataEntry + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp +} +var file_news_proto_depIdxs = []int32{ + 6, // 0: grpc.testprotos.news.v1.Article.date:type_name -> google.protobuf.Timestamp + 0, // 1: grpc.testprotos.news.v1.Article.status:type_name -> grpc.testprotos.news.v1.Article.Status + 2, // 2: grpc.testprotos.news.v1.Article.attachments:type_name -> grpc.testprotos.news.v1.Attachment + 3, // 3: grpc.testprotos.news.v1.Attachment.binary_attachment:type_name -> grpc.testprotos.news.v1.BinaryAttachment + 4, // 4: grpc.testprotos.news.v1.Attachment.key_value_attachment:type_name -> grpc.testprotos.news.v1.KeyValueAttachment + 5, // 5: grpc.testprotos.news.v1.KeyValueAttachment.data:type_name -> grpc.testprotos.news.v1.KeyValueAttachment.DataEntry + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_news_proto_init() } +func file_news_proto_init() { + if File_news_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_news_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Article); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_news_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Attachment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_news_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BinaryAttachment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_news_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeyValueAttachment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_news_proto_msgTypes[1].OneofWrappers = []interface{}{ + (*Attachment_BinaryAttachment)(nil), + (*Attachment_KeyValueAttachment)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_news_proto_rawDesc, + NumEnums: 1, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_news_proto_goTypes, + DependencyIndexes: file_news_proto_depIdxs, + EnumInfos: file_news_proto_enumTypes, + MessageInfos: file_news_proto_msgTypes, + }.Build() + File_news_proto = out.File + file_news_proto_rawDesc = nil + file_news_proto_goTypes = nil + file_news_proto_depIdxs = nil +} diff --git a/internal/grpc/testprotos/news/v1/news.proto b/internal/grpc/testprotos/news/v1/news.proto new file mode 100644 index 00000000000..36bdf0c9a96 --- /dev/null +++ b/internal/grpc/testprotos/news/v1/news.proto @@ -0,0 +1,46 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Note (@Sourcegraph): This file was copied / adapted from +// https://github.com/protocolbuffers/protobuf-go/blob/v1.30.0/internal/testprotos/news/news.proto to aid our testing. + +syntax = "proto3"; + +package grpc.testprotos.news.v1; + +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/sourcegraph/sourcegraph/internal/grpc/testprotos/news/v1"; + +message Article { + enum Status { + STATUS_DRAFT_UNSPECIFIED = 0; + STATUS_PUBLISHED = 1; + STATUS_REVOKED = 2; + } + + string author = 1; + google.protobuf.Timestamp date = 2; + string title = 3; + string content = 4; + Status status = 8; + repeated Attachment attachments = 7; +} + +message Attachment { + oneof contents { + BinaryAttachment binary_attachment = 1; + KeyValueAttachment key_value_attachment = 2; + } +} + +message BinaryAttachment { + string name = 1; + bytes data = 2; +} + +message KeyValueAttachment { + string name = 1; + map data = 2; +} diff --git a/sg.config.yaml b/sg.config.yaml index 83bcfac13c3..ea70262c5d5 100644 --- a/sg.config.yaml +++ b/sg.config.yaml @@ -124,6 +124,10 @@ env: # OTEL_EXPORTER_OTLP_ENDPOINT: http://127.0.0.1:4318 # OTEL_EXPORTER_OTLP_PROTOCOL: http/json + # Enable full protobuf message logging when a non-utf8 string error occurs + SRC_GRPC_INTERNAL_ERROR_LOGGING_LOG_NON_UTF8_PROTOBUF_MESSAGES_ENABLED: "true" + SRC_GRPC_INTERNAL_ERROR_LOGGING_LOG_NON_UTF8_PROTOBUF_MESSAGES_MAX_SIZE_BYTES: 1024 + commands: server: description: Run an all-in-one sourcegraph/server image