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/grpclog"
31 "google.golang.org/grpc/metadata"
32 "google.golang.org/grpc/status"
35 type callIDGenerator struct {
39 func (g *callIDGenerator) next() uint64 {
40 id := atomic.AddUint64(&g.id, 1)
44 // reset is for testing only, and doesn't need to be thread safe.
45 func (g *callIDGenerator) reset() {
49 var idGen callIDGenerator
51 // MethodLogger is the sub-logger for each method.
52 type MethodLogger struct {
53 headerMaxLen, messageMaxLen uint64
56 idWithinCallGen *callIDGenerator
58 sink Sink // TODO(blog): make this plugable.
61 func newMethodLogger(h, m uint64) *MethodLogger {
67 idWithinCallGen: &callIDGenerator{},
69 sink: defaultSink, // TODO(blog): make it plugable.
73 // Log creates a proto binary log entry, and logs it to the sink.
74 func (ml *MethodLogger) Log(c LogEntryConfig) {
76 timestamp, _ := ptypes.TimestampProto(time.Now())
77 m.Timestamp = timestamp
79 m.SequenceIdWithinCall = ml.idWithinCallGen.next()
81 switch pay := m.Payload.(type) {
82 case *pb.GrpcLogEntry_ClientHeader:
83 m.PayloadTruncated = ml.truncateMetadata(pay.ClientHeader.GetMetadata())
84 case *pb.GrpcLogEntry_ServerHeader:
85 m.PayloadTruncated = ml.truncateMetadata(pay.ServerHeader.GetMetadata())
86 case *pb.GrpcLogEntry_Message:
87 m.PayloadTruncated = ml.truncateMessage(pay.Message)
93 func (ml *MethodLogger) truncateMetadata(mdPb *pb.Metadata) (truncated bool) {
94 if ml.headerMaxLen == maxUInt {
98 bytesLimit = ml.headerMaxLen
101 // At the end of the loop, index will be the first entry where the total
102 // size is greater than the limit:
104 // len(entry[:index]) <= ml.hdr && len(entry[:index+1]) > ml.hdr.
105 for ; index < len(mdPb.Entry); index++ {
106 entry := mdPb.Entry[index]
107 if entry.Key == "grpc-trace-bin" {
108 // "grpc-trace-bin" is a special key. It's kept in the log entry,
109 // but not counted towards the size limit.
112 currentEntryLen := uint64(len(entry.Value))
113 if currentEntryLen > bytesLimit {
116 bytesLimit -= currentEntryLen
118 truncated = index < len(mdPb.Entry)
119 mdPb.Entry = mdPb.Entry[:index]
123 func (ml *MethodLogger) truncateMessage(msgPb *pb.Message) (truncated bool) {
124 if ml.messageMaxLen == maxUInt {
127 if ml.messageMaxLen >= uint64(len(msgPb.Data)) {
130 msgPb.Data = msgPb.Data[:ml.messageMaxLen]
134 // LogEntryConfig represents the configuration for binary log entry.
135 type LogEntryConfig interface {
136 toProto() *pb.GrpcLogEntry
139 // ClientHeader configs the binary log entry to be a ClientHeader entry.
140 type ClientHeader struct {
145 Timeout time.Duration
146 // PeerAddr is required only when it's on server side.
150 func (c *ClientHeader) toProto() *pb.GrpcLogEntry {
151 // This function doesn't need to set all the fields (e.g. seq ID). The Log
152 // function will set the fields when necessary.
153 clientHeader := &pb.ClientHeader{
154 Metadata: mdToMetadataProto(c.Header),
155 MethodName: c.MethodName,
156 Authority: c.Authority,
159 clientHeader.Timeout = ptypes.DurationProto(c.Timeout)
161 ret := &pb.GrpcLogEntry{
162 Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_HEADER,
163 Payload: &pb.GrpcLogEntry_ClientHeader{
164 ClientHeader: clientHeader,
168 ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
170 ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
172 if c.PeerAddr != nil {
173 ret.Peer = addrToProto(c.PeerAddr)
178 // ServerHeader configs the binary log entry to be a ServerHeader entry.
179 type ServerHeader struct {
182 // PeerAddr is required only when it's on client side.
186 func (c *ServerHeader) toProto() *pb.GrpcLogEntry {
187 ret := &pb.GrpcLogEntry{
188 Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_HEADER,
189 Payload: &pb.GrpcLogEntry_ServerHeader{
190 ServerHeader: &pb.ServerHeader{
191 Metadata: mdToMetadataProto(c.Header),
196 ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
198 ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
200 if c.PeerAddr != nil {
201 ret.Peer = addrToProto(c.PeerAddr)
206 // ClientMessage configs the binary log entry to be a ClientMessage entry.
207 type ClientMessage struct {
209 // Message can be a proto.Message or []byte. Other messages formats are not
214 func (c *ClientMessage) toProto() *pb.GrpcLogEntry {
219 if m, ok := c.Message.(proto.Message); ok {
220 data, err = proto.Marshal(m)
222 grpclog.Infof("binarylogging: failed to marshal proto message: %v", err)
224 } else if b, ok := c.Message.([]byte); ok {
227 grpclog.Infof("binarylogging: message to log is neither proto.message nor []byte")
229 ret := &pb.GrpcLogEntry{
230 Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_MESSAGE,
231 Payload: &pb.GrpcLogEntry_Message{
232 Message: &pb.Message{
233 Length: uint32(len(data)),
239 ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
241 ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
246 // ServerMessage configs the binary log entry to be a ServerMessage entry.
247 type ServerMessage struct {
249 // Message can be a proto.Message or []byte. Other messages formats are not
254 func (c *ServerMessage) toProto() *pb.GrpcLogEntry {
259 if m, ok := c.Message.(proto.Message); ok {
260 data, err = proto.Marshal(m)
262 grpclog.Infof("binarylogging: failed to marshal proto message: %v", err)
264 } else if b, ok := c.Message.([]byte); ok {
267 grpclog.Infof("binarylogging: message to log is neither proto.message nor []byte")
269 ret := &pb.GrpcLogEntry{
270 Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_MESSAGE,
271 Payload: &pb.GrpcLogEntry_Message{
272 Message: &pb.Message{
273 Length: uint32(len(data)),
279 ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
281 ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
286 // ClientHalfClose configs the binary log entry to be a ClientHalfClose entry.
287 type ClientHalfClose struct {
291 func (c *ClientHalfClose) toProto() *pb.GrpcLogEntry {
292 ret := &pb.GrpcLogEntry{
293 Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_HALF_CLOSE,
294 Payload: nil, // No payload here.
297 ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
299 ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
304 // ServerTrailer configs the binary log entry to be a ServerTrailer entry.
305 type ServerTrailer struct {
308 // Err is the status error.
310 // PeerAddr is required only when it's on client side and the RPC is trailer
315 func (c *ServerTrailer) toProto() *pb.GrpcLogEntry {
316 st, ok := status.FromError(c.Err)
318 grpclog.Info("binarylogging: error in trailer is not a status error")
324 stProto := st.Proto()
325 if stProto != nil && len(stProto.Details) != 0 {
326 detailsBytes, err = proto.Marshal(stProto)
328 grpclog.Infof("binarylogging: failed to marshal status proto: %v", err)
331 ret := &pb.GrpcLogEntry{
332 Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_TRAILER,
333 Payload: &pb.GrpcLogEntry_Trailer{
334 Trailer: &pb.Trailer{
335 Metadata: mdToMetadataProto(c.Trailer),
336 StatusCode: uint32(st.Code()),
337 StatusMessage: st.Message(),
338 StatusDetails: detailsBytes,
343 ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
345 ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
347 if c.PeerAddr != nil {
348 ret.Peer = addrToProto(c.PeerAddr)
353 // Cancel configs the binary log entry to be a Cancel entry.
358 func (c *Cancel) toProto() *pb.GrpcLogEntry {
359 ret := &pb.GrpcLogEntry{
360 Type: pb.GrpcLogEntry_EVENT_TYPE_CANCEL,
364 ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
366 ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
371 // metadataKeyOmit returns whether the metadata entry with this key should be
373 func metadataKeyOmit(key string) bool {
375 case "lb-token", ":path", ":authority", "content-encoding", "content-type", "user-agent", "te":
377 case "grpc-trace-bin": // grpc-trace-bin is special because it's visiable to users.
380 if strings.HasPrefix(key, "grpc-") {
386 func mdToMetadataProto(md metadata.MD) *pb.Metadata {
387 ret := &pb.Metadata{}
388 for k, vv := range md {
389 if metadataKeyOmit(k) {
392 for _, v := range vv {
393 ret.Entry = append(ret.Entry,
404 func addrToProto(addr net.Addr) *pb.Address {
406 switch a := addr.(type) {
408 if a.IP.To4() != nil {
409 ret.Type = pb.Address_TYPE_IPV4
410 } else if a.IP.To16() != nil {
411 ret.Type = pb.Address_TYPE_IPV6
413 ret.Type = pb.Address_TYPE_UNKNOWN
414 // Do not set address and port fields.
417 ret.Address = a.IP.String()
418 ret.IpPort = uint32(a.Port)
420 ret.Type = pb.Address_TYPE_UNIX
421 ret.Address = a.String()
423 ret.Type = pb.Address_TYPE_UNKNOWN