3
* Copyright 2018 gRPC authors.
5
* Licensed under the Apache License, Version 2.0 (the "License");
6
* you may not use this file except in compliance with the License.
7
* You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
27
"github.com/golang/protobuf/proto"
28
"github.com/golang/protobuf/ptypes"
29
pb "google.golang.org/grpc/binarylog/grpc_binarylog_v1"
30
"google.golang.org/grpc/metadata"
31
"google.golang.org/grpc/status"
34
type callIDGenerator struct {
38
func (g *callIDGenerator) next() uint64 {
39
id := atomic.AddUint64(&g.id, 1)
43
// reset is for testing only, and doesn't need to be thread safe.
44
func (g *callIDGenerator) reset() {
48
var idGen callIDGenerator
50
// MethodLogger is the sub-logger for each method.
51
type MethodLogger struct {
52
headerMaxLen, messageMaxLen uint64
55
idWithinCallGen *callIDGenerator
57
sink Sink // TODO(blog): make this plugable.
60
func newMethodLogger(h, m uint64) *MethodLogger {
66
idWithinCallGen: &callIDGenerator{},
68
sink: DefaultSink, // TODO(blog): make it plugable.
72
// Log creates a proto binary log entry, and logs it to the sink.
73
func (ml *MethodLogger) Log(c LogEntryConfig) {
75
timestamp, _ := ptypes.TimestampProto(time.Now())
76
m.Timestamp = timestamp
78
m.SequenceIdWithinCall = ml.idWithinCallGen.next()
80
switch pay := m.Payload.(type) {
81
case *pb.GrpcLogEntry_ClientHeader:
82
m.PayloadTruncated = ml.truncateMetadata(pay.ClientHeader.GetMetadata())
83
case *pb.GrpcLogEntry_ServerHeader:
84
m.PayloadTruncated = ml.truncateMetadata(pay.ServerHeader.GetMetadata())
85
case *pb.GrpcLogEntry_Message:
86
m.PayloadTruncated = ml.truncateMessage(pay.Message)
92
func (ml *MethodLogger) truncateMetadata(mdPb *pb.Metadata) (truncated bool) {
93
if ml.headerMaxLen == maxUInt {
97
bytesLimit = ml.headerMaxLen
100
// At the end of the loop, index will be the first entry where the total
101
// size is greater than the limit:
103
// len(entry[:index]) <= ml.hdr && len(entry[:index+1]) > ml.hdr.
104
for ; index < len(mdPb.Entry); index++ {
105
entry := mdPb.Entry[index]
106
if entry.Key == "grpc-trace-bin" {
107
// "grpc-trace-bin" is a special key. It's kept in the log entry,
108
// but not counted towards the size limit.
111
currentEntryLen := uint64(len(entry.Value))
112
if currentEntryLen > bytesLimit {
115
bytesLimit -= currentEntryLen
117
truncated = index < len(mdPb.Entry)
118
mdPb.Entry = mdPb.Entry[:index]
122
func (ml *MethodLogger) truncateMessage(msgPb *pb.Message) (truncated bool) {
123
if ml.messageMaxLen == maxUInt {
126
if ml.messageMaxLen >= uint64(len(msgPb.Data)) {
129
msgPb.Data = msgPb.Data[:ml.messageMaxLen]
133
// LogEntryConfig represents the configuration for binary log entry.
134
type LogEntryConfig interface {
135
toProto() *pb.GrpcLogEntry
138
// ClientHeader configs the binary log entry to be a ClientHeader entry.
139
type ClientHeader struct {
144
Timeout time.Duration
145
// PeerAddr is required only when it's on server side.
149
func (c *ClientHeader) toProto() *pb.GrpcLogEntry {
150
// This function doesn't need to set all the fields (e.g. seq ID). The Log
151
// function will set the fields when necessary.
152
clientHeader := &pb.ClientHeader{
153
Metadata: mdToMetadataProto(c.Header),
154
MethodName: c.MethodName,
155
Authority: c.Authority,
158
clientHeader.Timeout = ptypes.DurationProto(c.Timeout)
160
ret := &pb.GrpcLogEntry{
161
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_HEADER,
162
Payload: &pb.GrpcLogEntry_ClientHeader{
163
ClientHeader: clientHeader,
167
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
169
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
171
if c.PeerAddr != nil {
172
ret.Peer = addrToProto(c.PeerAddr)
177
// ServerHeader configs the binary log entry to be a ServerHeader entry.
178
type ServerHeader struct {
181
// PeerAddr is required only when it's on client side.
185
func (c *ServerHeader) toProto() *pb.GrpcLogEntry {
186
ret := &pb.GrpcLogEntry{
187
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_HEADER,
188
Payload: &pb.GrpcLogEntry_ServerHeader{
189
ServerHeader: &pb.ServerHeader{
190
Metadata: mdToMetadataProto(c.Header),
195
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
197
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
199
if c.PeerAddr != nil {
200
ret.Peer = addrToProto(c.PeerAddr)
205
// ClientMessage configs the binary log entry to be a ClientMessage entry.
206
type ClientMessage struct {
208
// Message can be a proto.Message or []byte. Other messages formats are not
213
func (c *ClientMessage) toProto() *pb.GrpcLogEntry {
218
if m, ok := c.Message.(proto.Message); ok {
219
data, err = proto.Marshal(m)
221
grpclogLogger.Infof("binarylogging: failed to marshal proto message: %v", err)
223
} else if b, ok := c.Message.([]byte); ok {
226
grpclogLogger.Infof("binarylogging: message to log is neither proto.message nor []byte")
228
ret := &pb.GrpcLogEntry{
229
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_MESSAGE,
230
Payload: &pb.GrpcLogEntry_Message{
231
Message: &pb.Message{
232
Length: uint32(len(data)),
238
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
240
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
245
// ServerMessage configs the binary log entry to be a ServerMessage entry.
246
type ServerMessage struct {
248
// Message can be a proto.Message or []byte. Other messages formats are not
253
func (c *ServerMessage) toProto() *pb.GrpcLogEntry {
258
if m, ok := c.Message.(proto.Message); ok {
259
data, err = proto.Marshal(m)
261
grpclogLogger.Infof("binarylogging: failed to marshal proto message: %v", err)
263
} else if b, ok := c.Message.([]byte); ok {
266
grpclogLogger.Infof("binarylogging: message to log is neither proto.message nor []byte")
268
ret := &pb.GrpcLogEntry{
269
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_MESSAGE,
270
Payload: &pb.GrpcLogEntry_Message{
271
Message: &pb.Message{
272
Length: uint32(len(data)),
278
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
280
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
285
// ClientHalfClose configs the binary log entry to be a ClientHalfClose entry.
286
type ClientHalfClose struct {
290
func (c *ClientHalfClose) toProto() *pb.GrpcLogEntry {
291
ret := &pb.GrpcLogEntry{
292
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_HALF_CLOSE,
293
Payload: nil, // No payload here.
296
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
298
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
303
// ServerTrailer configs the binary log entry to be a ServerTrailer entry.
304
type ServerTrailer struct {
307
// Err is the status error.
309
// PeerAddr is required only when it's on client side and the RPC is trailer
314
func (c *ServerTrailer) toProto() *pb.GrpcLogEntry {
315
st, ok := status.FromError(c.Err)
317
grpclogLogger.Info("binarylogging: error in trailer is not a status error")
323
stProto := st.Proto()
324
if stProto != nil && len(stProto.Details) != 0 {
325
detailsBytes, err = proto.Marshal(stProto)
327
grpclogLogger.Infof("binarylogging: failed to marshal status proto: %v", err)
330
ret := &pb.GrpcLogEntry{
331
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_TRAILER,
332
Payload: &pb.GrpcLogEntry_Trailer{
333
Trailer: &pb.Trailer{
334
Metadata: mdToMetadataProto(c.Trailer),
335
StatusCode: uint32(st.Code()),
336
StatusMessage: st.Message(),
337
StatusDetails: detailsBytes,
342
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
344
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
346
if c.PeerAddr != nil {
347
ret.Peer = addrToProto(c.PeerAddr)
352
// Cancel configs the binary log entry to be a Cancel entry.
357
func (c *Cancel) toProto() *pb.GrpcLogEntry {
358
ret := &pb.GrpcLogEntry{
359
Type: pb.GrpcLogEntry_EVENT_TYPE_CANCEL,
363
ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
365
ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
370
// metadataKeyOmit returns whether the metadata entry with this key should be
372
func metadataKeyOmit(key string) bool {
374
case "lb-token", ":path", ":authority", "content-encoding", "content-type", "user-agent", "te":
376
case "grpc-trace-bin": // grpc-trace-bin is special because it's visiable to users.
379
return strings.HasPrefix(key, "grpc-")
382
func mdToMetadataProto(md metadata.MD) *pb.Metadata {
383
ret := &pb.Metadata{}
384
for k, vv := range md {
385
if metadataKeyOmit(k) {
388
for _, v := range vv {
389
ret.Entry = append(ret.Entry,
400
func addrToProto(addr net.Addr) *pb.Address {
402
switch a := addr.(type) {
404
if a.IP.To4() != nil {
405
ret.Type = pb.Address_TYPE_IPV4
406
} else if a.IP.To16() != nil {
407
ret.Type = pb.Address_TYPE_IPV6
409
ret.Type = pb.Address_TYPE_UNKNOWN
410
// Do not set address and port fields.
413
ret.Address = a.IP.String()
414
ret.IpPort = uint32(a.Port)
416
ret.Type = pb.Address_TYPE_UNIX
417
ret.Address = a.String()
419
ret.Type = pb.Address_TYPE_UNKNOWN