github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/internal/binarylog/method_logger.go (about)

     1  /*
     2   *
     3   * Copyright 2018 gRPC authors.
     4   *
     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
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    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.
    16   *
    17   */
    18  
    19  package binarylog
    20  
    21  import (
    22  	"net"
    23  	"strings"
    24  	"sync/atomic"
    25  	"time"
    26  
    27  	"github.com/golang/protobuf/proto"
    28  	"github.com/golang/protobuf/ptypes"
    29  	pb "github.com/hxx258456/ccgo/grpc/binarylog/grpc_binarylog_v1"
    30  	"github.com/hxx258456/ccgo/grpc/metadata"
    31  	"github.com/hxx258456/ccgo/grpc/status"
    32  )
    33  
    34  type callIDGenerator struct {
    35  	id uint64
    36  }
    37  
    38  func (g *callIDGenerator) next() uint64 {
    39  	id := atomic.AddUint64(&g.id, 1)
    40  	return id
    41  }
    42  
    43  // reset is for testing only, and doesn't need to be thread safe.
    44  func (g *callIDGenerator) reset() {
    45  	g.id = 0
    46  }
    47  
    48  var idGen callIDGenerator
    49  
    50  // MethodLogger is the sub-logger for each method.
    51  type MethodLogger struct {
    52  	headerMaxLen, messageMaxLen uint64
    53  
    54  	callID          uint64
    55  	idWithinCallGen *callIDGenerator
    56  
    57  	sink Sink // TODO(blog): make this plugable.
    58  }
    59  
    60  func newMethodLogger(h, m uint64) *MethodLogger {
    61  	return &MethodLogger{
    62  		headerMaxLen:  h,
    63  		messageMaxLen: m,
    64  
    65  		callID:          idGen.next(),
    66  		idWithinCallGen: &callIDGenerator{},
    67  
    68  		sink: DefaultSink, // TODO(blog): make it plugable.
    69  	}
    70  }
    71  
    72  // Log creates a proto binary log entry, and logs it to the sink.
    73  func (ml *MethodLogger) Log(c LogEntryConfig) {
    74  	m := c.toProto()
    75  	timestamp, _ := ptypes.TimestampProto(time.Now())
    76  	m.Timestamp = timestamp
    77  	m.CallId = ml.callID
    78  	m.SequenceIdWithinCall = ml.idWithinCallGen.next()
    79  
    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)
    87  	}
    88  
    89  	ml.sink.Write(m)
    90  }
    91  
    92  func (ml *MethodLogger) truncateMetadata(mdPb *pb.Metadata) (truncated bool) {
    93  	if ml.headerMaxLen == maxUInt {
    94  		return false
    95  	}
    96  	var (
    97  		bytesLimit = ml.headerMaxLen
    98  		index      int
    99  	)
   100  	// At the end of the loop, index will be the first entry where the total
   101  	// size is greater than the limit:
   102  	//
   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.
   109  			continue
   110  		}
   111  		currentEntryLen := uint64(len(entry.Value))
   112  		if currentEntryLen > bytesLimit {
   113  			break
   114  		}
   115  		bytesLimit -= currentEntryLen
   116  	}
   117  	truncated = index < len(mdPb.Entry)
   118  	mdPb.Entry = mdPb.Entry[:index]
   119  	return truncated
   120  }
   121  
   122  func (ml *MethodLogger) truncateMessage(msgPb *pb.Message) (truncated bool) {
   123  	if ml.messageMaxLen == maxUInt {
   124  		return false
   125  	}
   126  	if ml.messageMaxLen >= uint64(len(msgPb.Data)) {
   127  		return false
   128  	}
   129  	msgPb.Data = msgPb.Data[:ml.messageMaxLen]
   130  	return true
   131  }
   132  
   133  // LogEntryConfig represents the configuration for binary log entry.
   134  type LogEntryConfig interface {
   135  	toProto() *pb.GrpcLogEntry
   136  }
   137  
   138  // ClientHeader configs the binary log entry to be a ClientHeader entry.
   139  type ClientHeader struct {
   140  	OnClientSide bool
   141  	Header       metadata.MD
   142  	MethodName   string
   143  	Authority    string
   144  	Timeout      time.Duration
   145  	// PeerAddr is required only when it's on server side.
   146  	PeerAddr net.Addr
   147  }
   148  
   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,
   156  	}
   157  	if c.Timeout > 0 {
   158  		clientHeader.Timeout = ptypes.DurationProto(c.Timeout)
   159  	}
   160  	ret := &pb.GrpcLogEntry{
   161  		Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_HEADER,
   162  		Payload: &pb.GrpcLogEntry_ClientHeader{
   163  			ClientHeader: clientHeader,
   164  		},
   165  	}
   166  	if c.OnClientSide {
   167  		ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
   168  	} else {
   169  		ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
   170  	}
   171  	if c.PeerAddr != nil {
   172  		ret.Peer = addrToProto(c.PeerAddr)
   173  	}
   174  	return ret
   175  }
   176  
   177  // ServerHeader configs the binary log entry to be a ServerHeader entry.
   178  type ServerHeader struct {
   179  	OnClientSide bool
   180  	Header       metadata.MD
   181  	// PeerAddr is required only when it's on client side.
   182  	PeerAddr net.Addr
   183  }
   184  
   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),
   191  			},
   192  		},
   193  	}
   194  	if c.OnClientSide {
   195  		ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
   196  	} else {
   197  		ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
   198  	}
   199  	if c.PeerAddr != nil {
   200  		ret.Peer = addrToProto(c.PeerAddr)
   201  	}
   202  	return ret
   203  }
   204  
   205  // ClientMessage configs the binary log entry to be a ClientMessage entry.
   206  type ClientMessage struct {
   207  	OnClientSide bool
   208  	// Message can be a proto.Message or []byte. Other messages formats are not
   209  	// supported.
   210  	Message interface{}
   211  }
   212  
   213  func (c *ClientMessage) toProto() *pb.GrpcLogEntry {
   214  	var (
   215  		data []byte
   216  		err  error
   217  	)
   218  	if m, ok := c.Message.(proto.Message); ok {
   219  		data, err = proto.Marshal(m)
   220  		if err != nil {
   221  			grpclogLogger.Infof("binarylogging: failed to marshal proto message: %v", err)
   222  		}
   223  	} else if b, ok := c.Message.([]byte); ok {
   224  		data = b
   225  	} else {
   226  		grpclogLogger.Infof("binarylogging: message to log is neither proto.message nor []byte")
   227  	}
   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)),
   233  				Data:   data,
   234  			},
   235  		},
   236  	}
   237  	if c.OnClientSide {
   238  		ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
   239  	} else {
   240  		ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
   241  	}
   242  	return ret
   243  }
   244  
   245  // ServerMessage configs the binary log entry to be a ServerMessage entry.
   246  type ServerMessage struct {
   247  	OnClientSide bool
   248  	// Message can be a proto.Message or []byte. Other messages formats are not
   249  	// supported.
   250  	Message interface{}
   251  }
   252  
   253  func (c *ServerMessage) toProto() *pb.GrpcLogEntry {
   254  	var (
   255  		data []byte
   256  		err  error
   257  	)
   258  	if m, ok := c.Message.(proto.Message); ok {
   259  		data, err = proto.Marshal(m)
   260  		if err != nil {
   261  			grpclogLogger.Infof("binarylogging: failed to marshal proto message: %v", err)
   262  		}
   263  	} else if b, ok := c.Message.([]byte); ok {
   264  		data = b
   265  	} else {
   266  		grpclogLogger.Infof("binarylogging: message to log is neither proto.message nor []byte")
   267  	}
   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)),
   273  				Data:   data,
   274  			},
   275  		},
   276  	}
   277  	if c.OnClientSide {
   278  		ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
   279  	} else {
   280  		ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
   281  	}
   282  	return ret
   283  }
   284  
   285  // ClientHalfClose configs the binary log entry to be a ClientHalfClose entry.
   286  type ClientHalfClose struct {
   287  	OnClientSide bool
   288  }
   289  
   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.
   294  	}
   295  	if c.OnClientSide {
   296  		ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
   297  	} else {
   298  		ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
   299  	}
   300  	return ret
   301  }
   302  
   303  // ServerTrailer configs the binary log entry to be a ServerTrailer entry.
   304  type ServerTrailer struct {
   305  	OnClientSide bool
   306  	Trailer      metadata.MD
   307  	// Err is the status error.
   308  	Err error
   309  	// PeerAddr is required only when it's on client side and the RPC is trailer
   310  	// only.
   311  	PeerAddr net.Addr
   312  }
   313  
   314  func (c *ServerTrailer) toProto() *pb.GrpcLogEntry {
   315  	st, ok := status.FromError(c.Err)
   316  	if !ok {
   317  		grpclogLogger.Info("binarylogging: error in trailer is not a status error")
   318  	}
   319  	var (
   320  		detailsBytes []byte
   321  		err          error
   322  	)
   323  	stProto := st.Proto()
   324  	if stProto != nil && len(stProto.Details) != 0 {
   325  		detailsBytes, err = proto.Marshal(stProto)
   326  		if err != nil {
   327  			grpclogLogger.Infof("binarylogging: failed to marshal status proto: %v", err)
   328  		}
   329  	}
   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,
   338  			},
   339  		},
   340  	}
   341  	if c.OnClientSide {
   342  		ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
   343  	} else {
   344  		ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
   345  	}
   346  	if c.PeerAddr != nil {
   347  		ret.Peer = addrToProto(c.PeerAddr)
   348  	}
   349  	return ret
   350  }
   351  
   352  // Cancel configs the binary log entry to be a Cancel entry.
   353  type Cancel struct {
   354  	OnClientSide bool
   355  }
   356  
   357  func (c *Cancel) toProto() *pb.GrpcLogEntry {
   358  	ret := &pb.GrpcLogEntry{
   359  		Type:    pb.GrpcLogEntry_EVENT_TYPE_CANCEL,
   360  		Payload: nil,
   361  	}
   362  	if c.OnClientSide {
   363  		ret.Logger = pb.GrpcLogEntry_LOGGER_CLIENT
   364  	} else {
   365  		ret.Logger = pb.GrpcLogEntry_LOGGER_SERVER
   366  	}
   367  	return ret
   368  }
   369  
   370  // metadataKeyOmit returns whether the metadata entry with this key should be
   371  // omitted.
   372  func metadataKeyOmit(key string) bool {
   373  	switch key {
   374  	case "lb-token", ":path", ":authority", "content-encoding", "content-type", "user-agent", "te":
   375  		return true
   376  	case "grpc-trace-bin": // grpc-trace-bin is special because it's visiable to users.
   377  		return false
   378  	}
   379  	return strings.HasPrefix(key, "grpc-")
   380  }
   381  
   382  func mdToMetadataProto(md metadata.MD) *pb.Metadata {
   383  	ret := &pb.Metadata{}
   384  	for k, vv := range md {
   385  		if metadataKeyOmit(k) {
   386  			continue
   387  		}
   388  		for _, v := range vv {
   389  			ret.Entry = append(ret.Entry,
   390  				&pb.MetadataEntry{
   391  					Key:   k,
   392  					Value: []byte(v),
   393  				},
   394  			)
   395  		}
   396  	}
   397  	return ret
   398  }
   399  
   400  func addrToProto(addr net.Addr) *pb.Address {
   401  	ret := &pb.Address{}
   402  	switch a := addr.(type) {
   403  	case *net.TCPAddr:
   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
   408  		} else {
   409  			ret.Type = pb.Address_TYPE_UNKNOWN
   410  			// Do not set address and port fields.
   411  			break
   412  		}
   413  		ret.Address = a.IP.String()
   414  		ret.IpPort = uint32(a.Port)
   415  	case *net.UnixAddr:
   416  		ret.Type = pb.Address_TYPE_UNIX
   417  		ret.Address = a.String()
   418  	default:
   419  		ret.Type = pb.Address_TYPE_UNKNOWN
   420  	}
   421  	return ret
   422  }