Gin framework: add API logging Middleware

Keywords: Go Microservices gin

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.

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

Posted by allelopath on Wed, 03 Nov 2021 20:43:13 -0700