introduce

This article shows you how to add API log interceptors/middleware to gRPC microservices.

What is a log interceptor/middleware?

The log interceptor logs every API request.

We will use RK-boot to start the gRPC service.

Please visit the following address for the full tutorial:

  • rkdev.info/cn
  • Rkdocs.net lilify. app/cn (standby)

The installation

go get github.com/rookie-ninja/rk-boot
Copy the code

Quick start

Rk-boot integrates the following two open source libraries by default.

  • Uber-go/Zap serves as the underlying log library.
  • Logrus scrolls as a log.

1. Create the boot. Yaml

To verify, we also started commonService. CommonService contains a set of common apis. Details: CommonService

GRPC enables grPC-gateway by default to provide Restful API services. During validation, we can send Restful requests directly.

---
grpc:
  - name: greeter                   # Name of grpc entry
    port: 8080                      # Port of grpc entry
    enabled: true                   # Enable grpc entry
    commonService:
      enabled: true                 # Enable common service for testing
    interceptors:
      loggingZap:
        enabled: true               # Enable logging interceptor
Copy the code

2. Create a main. Go

package main

import (
   "context"
   "github.com/rookie-ninja/rk-boot"
)

// Application entrance.
func main() {
   // Create a new boot instance.
   boot := rkboot.NewBoot()

   // Bootstrap
   boot.Bootstrap(context.Background())

   // Wait for shutdown sig
   boot.WaitForShutdownSig(context.Background())
}
Copy the code

3. Folder structure

$tree. └── Go. ├─ go. ├─ goCopy the code

3. Verify

$ go run main.go
Copy the code
  • Send the request
$ curl -X GET localhost:8080/rk/v1/healthy
{"healthy":true}
Copy the code
  • Verify the log

The default output is stdout (console).

The following log format is from RK-Query, or the user can choose JSON format, which we’ll cover later.

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- endTime = 2021-07-09 T23:44:09. 81483 + 08:00 StartTime = 2021-07-09 T23:44:09. 814784 + 08:00 elapsedNano = 46065 timezone = CST ids={"eventId":"67d64dab-f3ea-4b77-93d0-6782caf4cfee"} app={"appName":"rk-demo","appVersion":"master-f414049","entryName":"greeter","entryType":"GrpcEntry"} Env = {" arch ", "amd64", "az" : "*", "domain" : "*", "hostname" : "lark. Local", "localIP" : "10.8.0.2", "OS" : "Darwin", "realm" : "*", "region ":" * "} payloads={"grpcMethod":"Healthy","grpcService":"rk.api.v1.RkCommonService","grpcType":"unaryServer","gwMethod":"","gwPat h":"","gwScheme":"","gwUserAgent":""} error={} counters={} pairs={"healthy":"true"} timing={} remoteAddr=localhost:58205  operation=/rk.api.v1.RkCommonService/Healthy resCode=OK eventStatus=Ended EOECopy the code

Changing the Log Format

We can modify the log format by modifying boot.yaml. Currently, two formats are supported: JSON and Console. The default format is Console.

By changing the value of eventLoggerEncoding to JSON, we can convert the log output to JSON format.

grpc:
  - name: greeter                     # Name of grpc entry
    port: 8080                        # Port of grpc entry
    enabled: true                     # Enable grpc entry
    commonService:
      enabled: true                   # Enable common service for testing
    interceptors:
      loggingZap:
        enabled: true                 # Enable logging interceptor
        zapLoggerEncoding: "json"     # Override to json format, option: json or console
        eventLoggerEncoding: "json"   # Override to json format, option: json or console
Copy the code

Changing a Log Path

You can specify the output path by modifying the value of eventLoggerOutputPaths.

Logs are cut and compressed after 1GB by default.

grpc:
  - name: greeter                     # Name of grpc entry
    port: 8080                        # Port of grpc entry
    enabled: true                     # Enable grpc entry
    commonService:
      enabled: true                   # Enable common service for testing
    interceptors:
      loggingZap:
        enabled: true                 # Enable logging interceptor
        zapLoggerOutputPaths: ["logs/app.log"]        # Override output paths, option: json or console
        eventLoggerOutputPaths: ["logs/event.log"]    # Override output paths, option: json or console
Copy the code

concept

After verifying the log interceptor, let’s talk about the log interceptor provided by RK-boot in detail.

There are two concepts we need to understand in advance.

  • EventLogger
  • ZapLogger

ZapLogger

Users can obtain the ZapLogger instance of the RPC call and write logs. Each ZapLogger instance of the RPC call contains the current RequestId.

2021-07-09T23:52:13.667+0800    INFO    boot/grpc_entry.go:694  Bootstrapping grpcEntry.        {"eventId": "9bc192fb-567c-45d4-8775-7a097b0dab04", "entryName": "greeter", "entryType": "GrpcEntry", "grpcPort": 8080, "commonServiceEnabled": true, "tlsEnabled": false, "gwEnabled": true, "reflectionEnabled": false, "swEnabled": false, "tvEnabled": false, "promEnabled": false, "gwClientTlsEnabled": false, "gwServerTlsEnabled": false}
Copy the code

EventLogger

The RK initiator treats each RPC request as an Event and logs it using the Event type in RK-Query.

field details
endTime The end of time
startTime The start time
elapsedNano Event time overhead (Nanoseconds)
timezone The time zone
ids Contains eventId, requestId, and traceId. If the original data interceptor is started, or if event.setrequest () is called by the user, the new RequestId will be used and eventId will be exactly the same as RequestId. If the invocation chain interceptor is started, traceId will be logged.
app containsappName, appVersion, entryType entryName.
env Realm contains arch, AZ, Domain, hostname, localIP, OS, realm, region. Realm, region, az, domain fields. These fields come from system environment variables (REALM, REGION, AZ, DOMAIN). “*” means the environment variable is empty.
payloads Contains RPC-related information.
error Contains errors.
counters Do this via event.setCounter ().
pairs Operate via event.addpair ().
timing Operate via event.startTimer () and event.endTimer ().
remoteAddr RPC remote address.
operation RPC name.
resCode RPC return code.
eventStatus Ended 或者 InProgress
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- endTime = 2021-07-09 T23:44:09. 81483 + 08:00 StartTime = 2021-07-09 T23:44:09. 814784 + 08:00 elapsedNano = 46065 timezone = CST ids={"eventId":"67d64dab-f3ea-4b77-93d0-6782caf4cfee"} app={"appName":"rk-demo","appVersion":"master-f414049","entryName":"greeter","entryType":"GrpcEntry"} Env = {" arch ", "amd64", "az" : "*", "domain" : "*", "hostname" : "lark. Local", "localIP" : "10.8.0.2", "OS" : "Darwin", "realm" : "*", "region ":" * "} payloads={"grpcMethod":"Healthy","grpcService":"rk.api.v1.RkCommonService","grpcType":"unaryServer","gwMethod":"","gwPat h":"","gwScheme":"","gwUserAgent":""} error={} counters={} pairs={"healthy":"true"} timing={} remoteAddr=localhost:58205  operation=/rk.api.v1.RkCommonService/Healthy resCode=OK eventStatus=Ended EOECopy the code

Log interceptor option

The name describe type The default value
grpc.interceptors.loggingZap.enabled Start the log interceptor boolean false
grpc.interceptors.loggingZap.zapLoggerEncoding Log format: JSON or Console string console
grpc.interceptors.loggingZap.zapLoggerOutputPaths Log File Path []string stdout
grpc.interceptors.loggingZap.eventLoggerEncoding Log format: JSON or Console string console
grpc.interceptors.loggingZap.eventLoggerOutputPaths Log File Path []string false

Obtain the RPC log instance

Every time an RPC request comes in, the interceptor injects the RequestId (when the raw data interceptor is enabled) into the log instance.

In other words, for every RPC request, there is a new Logger instance. Let’s look at how to log a ZapLogger for an RPC request.

The rkgrpCCtx.getLogger (CTX) method is used to obtain the log instance of this request.

func (server *GreeterServer) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) { rkgrpcctx.GetLogger(ctx).Info("Received request") return &greeter.GreeterResponse{ Message: fmt.Sprintf("Hello %s!" , request.Name), }, nil }Copy the code

The log is printed!

2021-07-09T23:50:39.318+0800    INFO    basic/main.go:36        Received request        {"requestId": "c33698f2-3071-48d4-9d92-b1aa311e6c06"}
Copy the code

6. Modify the Event

The log interceptor creates an Event instance for each RPC request.

Users can add pairs, counters, and errors.

Run the rkgrpCCtx.getevent (CTX) command to obtain the Event instance of the RPC.

func (server *GreeterServer) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) { event := rkgrpcctx.GetEvent(ctx) event.AddPair("key", "value") return &greeter.GreeterResponse{ Message: fmt.Sprintf("Hello %s!" , request.Name), }, nil }Copy the code

Pairs ={“key”:”value”}!

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- endTime = 2021-07-09 T23:52:39. 103351 + 08:00 StartTime = 2021-07-09 T23:52:39. 10332 + 08:00 elapsedNano = 31154 timezone = CST ids={"eventId":"92001951-80c1-4dda-8f14-f920834f5c61","requestId":"92001951-80c1-4dda-8f14-f920834f5c61"} app={"appName":"rk-demo","appVersion":"master-f414049","entryName":"greeter","entryType":"GrpcEntry"} Env = {" arch ", "amd64", "az" : "*", "domain" : "*", "hostname" : "lark. Local", "localIP" : "10.8.0.2", "OS" : "Darwin", "realm" : "*", "region ":" * "} payloads={"grpcMethod":"Greeter","grpcService":"api.v1.Greeter","grpcType":"unaryServer","gwMethod":"","gwPath":"","gwSc heme":"","gwUserAgent":""} error={} counters={} pairs={"key":"value"} timing={} remoteAddr=localhost:58269 operation=/api.v1.Greeter/Greeter resCode=OK eventStatus=Ended EOECopy the code