1. The significance of structured logs

1.1 Log Formatting

Logs are mainly used to track the running information of the service. As a back-end siege lion, you generally have an idea. At ordinary times, you hope that the log is as little as possible, and when something goes wrong, you always complain about it.

Therefore, logs are important, and it is also important to format logs for subsequent analysis. For example, by formatting error codes into logs, you can query the health status of the collected log analysis interface and report the time spent on the interface. In this way, you can monitor operations with high latency and query associated logs for analysis.

So as long as you format the output, it’s easy to have a lot of applications.

1.2 Features the log library should have

  • There are two main aspects of high performance: the time spent for each operation and the memory allocated for each operation. As a log library, both indicators should be very low.

  • Log level filtering Can print logs of different levels by adjusting log levels.

  • Sampling rate You can set the sampling rate to prevent the log volume from soaring when service requests increase, affecting service performance.

  • Automatic file cutting Automatically divides files into a certain size, files them regularly, and saves a certain number of files.

Based on the above points, Uber’s open source log library zAP is selected.

2. Integrate it into the GRPC

2.1 train of thought

GRPC defines the GRpCLOG package and defines the interface of LoggerV2. Therefore, as long as the interface of LoggerV2 is implemented through ZAP and the object is set to grpCLOG package through SetLoggerV2(L LoggerV2) interface. In this case, GRPC uses ZAP to output logs, and upper-layer applications can use GRPCLOG to print service logs.

2.2 implementation

See grpc-wrapper for the complete code and an example use

typeStruct {logger *zap.Logger} struct {zap *zap. Func NewZapLogger(Logger * zap.logger) *ZapLogger {func NewZapLogger(logger * zap.logger) *ZapLogger {return&ZapLogger{ logger: logger, } } func (zl *ZapLogger) Info(args ... interface{}) { zl.logger.Sugar().Info(args...) } func (zl *ZapLogger) Infoln(args ... interface{}) { zl.logger.Sugar().Info(args...) } func (zl *ZapLogger) Infof(format string, args ... interface{}) { zl.logger.Sugar().Infof(format, args...) } func (zl *ZapLogger) Warning(args ... interface{}) { zl.logger.Sugar().Warn(args...) } func (zl *ZapLogger) Warningln(args ... interface{}) { zl.logger.Sugar().Warn(args...) } func (zl *ZapLogger) Warningf(format string, args ... interface{}) { zl.logger.Sugar().Warnf(format, args...) } func (zl *ZapLogger) Error(args ... interface{}) { zl.logger.Sugar().Error(args...) } func (zl *ZapLogger) Errorln(args ... interface{}) { zl.logger.Sugar().Error(args...) } func (zl *ZapLogger) Errorf(format string, args ... interface{}) { zl.logger.Sugar().Errorf(format, args...) } func (zl *ZapLogger) Fatal(args ... interface{}) { zl.logger.Sugar().Fatal(args...) } func (zl *ZapLogger) Fatalln(args ... interface{}) { zl.logger.Sugar().Fatal(args...) } // Fatalf logs to fatal level func (zl *ZapLogger) Fatalf(format string, args ... interface{}) { zl.logger.Sugar().Fatalf(format, args...) } // V reports whether verbosity level l is at least the requested verbose level. func (zl *ZapLogger) V(v int) bool {return false
}

Copy the code

3. End result

As you can see, the log is output in JSON format, showing the number of lines of code when the log is printed, and tracing the call stack when an error occurs.

reference

grpc

zap.

grpc-wrapper