introduce
Through a complete example, add API logging middleware to the micro service based on Gin framework.
What is log interceptor / Middleware?
The log interceptor logs every API request.
We will use rk-boot To start the microservices of the Gin framework.
Please visit the following address for a complete tutorial: https://rkdocs.netlify.app/cn
install
go get github.com/rookie-ninja/rk-boot
Quick start
rk-boot By default, the following two open source libraries are integrated.
- uber-go/zap As the underlying log repository.
- logrus Scroll as log.
1. Create boot.yaml
The boot.yaml file describes the original information of Gin framework startup. Rk boot starts Gin by reading boot.yaml.
For verification, we also started commonService. commonService contains a series of general API s.
Details: CommonService
--- gin: - name: greeter # Required, name of gin entry port: 8080 # Required, port of gin entry enabled: true # Required, enable gin entry commonService: enabled: true # Optional, enable common service interceptors: loggingZap: enabled: true # Optional, enable logging interceptor
2. Create 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()) }
3. Folder structure
$ tree . ├── boot.yaml ├── go.mod ├── go.sum └── main.go 0 directories, 4 files
4. Start main.go
$ go run main.go
5. Verification
We send the / rk / V1 / health request that comes with CommonService.
$ curl -X GET localhost:8080/rk/v1/healthy {"healthy":true}
EventLog will be output to stdout by default.
The following log format is from rk-query , users can also choose JSON format, which will be introduced later.
------------------------------------------------------------------------ endTime=2021-11-03T16:07:16.163624+08:00 startTime=2021-11-03T16:07:16.16355+08:00 elapsedNano=73445 timezone=CST ids={"eventId":"a9cc6ca1-cb54-4495-a83c-b296a8162149"} app={"appName":"rk-demo","appVersion":"master-2c9c6fd","entryName":"greeter","entryType":"GinEntry"} env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"} payloads={"apiMethod":"GET","apiPath":"/rk/v1/healthy","apiProtocol":"HTTP/1.1","apiQuery":"","userAgent":"curl/7.64.1"} error={} counters={} pairs={} timing={} remoteAddr=localhost:50722 operation=/rk/v1/healthy resCode=200 eventStatus=Ended EOE
Modify log format
We can modify the log format by modifying boot.yaml.
At present, it supports json and console formats, and the default is console.
By changing the value of eventLoggerEncoding to JSON, we can output the log in JSON format.
gin: - name: greeter # Required, name of gin entry port: 8080 # Required, port of gin entry enabled: true # Required, enable gin entry commonService: enabled: true # Optional, enable common service interceptors: loggingZap: enabled: true # Optional, enable logging interceptor zapLoggerEncoding: "json" # Override to json format, option: json or console eventLoggerEncoding: "json" # Override to json format, option: json or console
{ "endTime":"2021-11-03T16:09:27.712+0800", "startTime":"2021-11-03T16:09:27.712+0800", "elapsedNano":81181, "timezone":"CST", "ids":{ "eventId":"8a9ae674-d1dc-4f43-a5ad-4e30412cb69e" }, "app":{ "appName":"rk-demo", "appVersion":"master-2c9c6fd", "entryName":"greeter", "entryType":"GinEntry" }, "env":{ "arch":"amd64", "az":"*", "domain":"*", "hostname":"lark.local", "localIP":"10.8.0.2", "os":"darwin", "realm":"*", "region":"*" }, "payloads":{ "apiMethod":"GET", "apiPath":"/rk/v1/healthy", "apiProtocol":"HTTP/1.1", "apiQuery":"", "userAgent":"curl/7.64.1" }, "error":{}, "counters":{}, "pairs":{}, "timing":{}, "remoteAddr":"localhost:59140", "operation":"/rk/v1/healthy", "eventStatus":"Ended", "resCode":"200" }
Modify log path
You can specify the output path by modifying the value of eventLoggerOutputPaths.
By default, the log is after 1GB, cut and compressed.
--- gin: - name: greeter # Required, name of gin entry port: 8080 # Required, port of gin entry enabled: true # Required, enable gin entry commonService: enabled: true # Optional, enable common service interceptors: loggingZap: enabled: true # Optional, enable logging interceptor zapLoggerOutputPaths: ["logs/app.log"] # Override output paths eventLoggerOutputPaths: ["logs/event.log"] # Override output paths
. ├── boot.yaml ├── go.mod ├── go.sum ├── logs │ └── event.log └── main.go
concept
After verifying the log interceptor, let's talk about the functions of the log interceptor provided by rk boot.
We need to understand two concepts in advance.
- EventLogger
- ZapLogger
ZapLogger
It is used to record the error / detailed log. The user can obtain the ZapLogger instance of this RPC call and write the log. Each RPC ZapLogger instance contains the current RequestId.
2021-11-03T16:13:36.362+0800 INFO boot/gin_entry.go:759 Bootstrapping GinEntry. {"eventId": "68d0c4c0-c631-4a9c-a518-2576ed6175a4", "entryName": "greeter", "entryType": "GinEntry", "port": 8080, "interceptorsCount": 2, "swEnabled": false, "tlsEnabled": false, "commonServiceEnabled": true, "tvEnabled": false}
EventLogger
The RK initiator treats each RPC request as an Event and uses the Event type in rk query to log.
field | details |
---|---|
endTime | End time |
startTime | start time |
elapsedNano | Event time overhead (Nanoseconds) |
timezone | time zone |
ids | Contains eventId, requestId and traceId. If as like as two peas, the original data interceptor is activated, or event.SetRequest() is invoked by the user, the new RequestId will be used, and eventId will be exactly the same as requestId. If the call chain interceptor is started, the traceId will be recorded. |
app | contain appName, appVersion, entryName, entryType. |
env | Contains the fields arch, az, domain, hostname, localIP, os, realm, region. realm, region, az, domain. These fields are from the system environment variables (real, REGION, AZ, DOMAIN). "*" means that the environment variable is empty. |
payloads | Contains RPC related information. |
error | Contains errors. |
counters | Operate through event.SetCounter(). |
pairs | Operate through event.AddPair(). |
timing | Operate through event.StartTimer() and event.EndTimer(). |
remoteAddr | RPC remote address. |
operation | RPC name. |
resCode | RPC return code. |
eventStatus | Ended or InProgress |
------------------------------------------------------------------------ endTime=2021-11-03T16:07:16.163624+08:00 startTime=2021-11-03T16:07:16.16355+08:00 elapsedNano=73445 timezone=CST ids={"eventId":"a9cc6ca1-cb54-4495-a83c-b296a8162149"} app={"appName":"rk-demo","appVersion":"master-2c9c6fd","entryName":"greeter","entryType":"GinEntry"} env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"} payloads={"apiMethod":"GET","apiPath":"/rk/v1/healthy","apiProtocol":"HTTP/1.1","apiQuery":"","userAgent":"curl/7.64.1"} error={} counters={} pairs={} timing={} remoteAddr=localhost:50722 operation=/rk/v1/healthy resCode=200 eventStatus=Ended EOE
Log interceptor options
name | describe | type | Default value |
---|---|---|---|
gin.interceptors.loggingZap.enabled | Start log interceptor | boolean | false |
gin.interceptors.loggingZap.zapLoggerEncoding | Log format: json or console | string | console |
gin.interceptors.loggingZap.zapLoggerOutputPaths | log file path | []string | stdout |
gin.interceptors.loggingZap.eventLoggerEncoding | Log format: json or console | string | console |
gin.interceptors.loggingZap.eventLoggerOutputPaths | log file path | []string | stdout |
Get RPC log instance
Every time an RPC request comes in, the interceptor will inject the RequestId (when the original data interceptor is started) into the log instance.
In other words, each RPC request will have a new Logger instance. Let's see how to log ZapLogger for an RPC request.
Obtain the log instance of this request through the rkginctx.GetLogger(ctx) method.
func Greeter(ctx *gin.Context) { rkginctx.GetLogger(ctx).Info("Request received") ctx.JSON(http.StatusOK, &GreeterResponse{ Message: fmt.Sprintf("Hello %s!", ctx.Query("name")), }) }
The log is printed!
2021-11-03T16:16:20.041+0800 INFO basic/main.go:43 Request received {"requestId": "f1c15b4e-d4e2-4da9-a9dd-55aa31fc35ac"}
Modify Event
The log interceptor will create an Event instance for each RPC request.
Users can add pairs, counters and errors.
Get the Event instance of this RPC through rkginctx.GetEvent(ctx).
func Greeter(ctx *gin.Context) { event := rkginctx.GetEvent(ctx) event.AddPair("key", "value") ctx.JSON(http.StatusOK, &GreeterResponse{ Message: fmt.Sprintf("Hello %s!", ctx.Query("name")), }) }
pairs={"key":"value"} is added to the Event!
------------------------------------------------------------------------ endTime=2021-11-03T16:17:42.892857+08:00 startTime=2021-11-03T16:17:42.892762+08:00 elapsedNano=94494 timezone=CST ids={"eventId":"1ef6ceab-f6fd-4995-9272-b0ce60476e57","requestId":"1ef6ceab-f6fd-4995-9272-b0ce60476e57"} app={"appName":"rk-demo","appVersion":"master-2c9c6fd","entryName":"greeter","entryType":"GinEntry"} env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"} payloads={"apiMethod":"GET","apiPath":"/v1/greeter","apiProtocol":"HTTP/1.1","apiQuery":"","userAgent":"curl/7.64.1"} error={} counters={} pairs={"key":"value"} timing={} remoteAddr=localhost:57814 operation=/v1/greeter resCode=200 eventStatus=Ended EOE