[GO] Versuchen Sie, das strukturierte gRPC-Protokoll einfach und einfach mit grpc_zap zu implementieren

Einführung

Wir haben gRPC im Team übernommen und entwickeln weiter. Es gibt noch wenige Artikel auf Japanisch, insbesondere detaillierte Artikel zur Protokollimplementierung. Es war schwer zu finden und es dauerte einige Zeit, um Informationen zu sammeln. .. Daher möchte ich Informationen mit dem Team teilen und zusätzlich zu meinem Memorandum einen Artikel schreiben.

Dieses Mal, während Sie einen gRPC-Server in der Sprache Go entwickeln, Eine schnelle Protokollausgabe ist möglich und in die grpc-Middleware integriert Lassen Sie uns ein einfaches und einfaches strukturiertes Protokoll mit grpc_zap implementieren. Darüber hinaus geht dieser Artikel nicht auf die Gliederung und den Mechanismus von gRPC ein.

Was ist grpc_zap?

Uber kann den von OSS bereitgestellten "zap" -Logger verwenden und in gRPC integrieren Es ist ein Paket, das als einer der Interceptors in grpc-middleware integriert ist. Zap-strukturierte Protokolle sind einfach zu implementieren und können mit grpc_ctxtags kombiniert werden Sie können Felder frei hinzufügen.

Beispiel und Betriebsumgebung

Beispiel ist im GitHub-Repository verfügbar. Ich werde anhand dieses Beispiels erklären.

Die Betriebsumgebung wird unten bestätigt.

OS: macOS Catalina 10.15.4 @ 2.7GHz 2Core, 16GB
Docker Desktop: 2.3.0.5(48029), Engine:19.03.12

Bedarf

Betrachten wir dieses Mal einen gRPC-Server, der bei einer Anfrage Schülerinformationen abrufen kann. gRPC verwendet "Protocol Buffer (protobuf)" als IDL, die Servern und Clients gemeinsam ist. Da viele Implementierungen verwendet wurden, wird in diesem Artikel auch Protobuf verwendet.

proto/sample.proto


package sample;

service Student {
    //Holen Sie sich Schülerinformationen
    rpc Get (StudentRequest) returns (StudentResponse) {}
}

message StudentRequest {
    int32 id = 1;       //Studentenausweis, den Sie erhalten möchten
}

message StudentResponse {
    int32 id = 1;       //Studenten ID
    string name = 2;    //Name
    int32 age = 3;      //Alter
    School school = 4;  //Verbundene Schule
}

message School {
    int32 id = 1;       //Schulausweis
    string name = 2;    //Schulname
    string grade = 3;   //Schuljahr
}

Wenn Sie mit einem Studentenausweis anfordern, können Sie den Studenten und seine Schule / Klasse als Antwort erhalten. Ich gehe von einem einfachen gRPC-Server aus.

Implementierung

Erstens ist die Datei main.go.

main.go


package main

import (
	"log"
	"net"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/reflection"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"

	sv "github.com/y-harashima/grpc-sample/server"
	pb "github.com/y-harashima/grpc-sample/proto"
)

func main() {

	port, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatal(err)
	}
        
    //zap logger und option einstellungen
	zap, _ := zap.NewProduction()     // --- ①
	zap_opt := grpc_zap.WithLevels(          // --- ②
		func(c codes.Code) zapcore.Level {   
			var l zapcore.Level              
			switch c {                       
			case codes.OK:                   
				l = zapcore.InfoLevel        
				                             
			case codes.Internal:             
				l = zapcore.ErrorLevel       

			default:
				l = zapcore.DebugLevel
			}
			return l
		},
	)
    //Richten Sie Interceptor ein und initialisieren Sie den gRPC-Server
	grpc := grpc.NewServer(    // --- ③
		grpc_middleware.WithUnaryServerChain(    
			grpc_ctxtags.UnaryServerInterceptor(),   
			grpc_zap.UnaryServerInterceptor(zap, zap_opt),
		),
	)
	
	server := &sv.Server{}
	pb.RegisterStudentServer(grpc, server)
	reflection.Register(grpc)
	log.Println("Server process starting...")
	if err := grpc.Serve(port); err != nil {
		log.Fatal(err)
	}
}

Ich möchte eins nach dem anderen erklären.


zap, _ := zap.NewProduction()

Initialisieren Sie zunächst den zap Logger. Sie benötigen es, wenn Sie es in grpc_zap einbetten. Hier ist es "NewProduction ()", aber der Übersichtlichkeit halber bei der Ausgabe des Protokolls Es wird als strukturiertes Protokoll einschließlich der Protokollebene ausgegeben. (Es gibt auch eine Initialisierungsfunktion namens "NewDevelopment ()". Es scheint, dass die Protokollstufe nicht in JSON enthalten ist und hier ausgegeben wird.


    zap_opt := grpc_zap.WithLevels(
        func(c codes.Code) zapcore.Level {   
            var l zapcore.Level              
            switch c {                       
            case codes.OK:                   
                l = zapcore.InfoLevel        

            case codes.Internal:             
                l = zapcore.ErrorLevel       

            default:
                l = zapcore.DebugLevel
            }
            return l
        },
    )

grpc_zap legt die Protokollebene fest, die dem Statuscode entspricht Es ist relativ einfach als Option einzustellen. Wenn Sie die Protokollebene festlegen, die Sie wie im Implementierungsbeispiel an Codes.Code von grpc verteilen möchten, Geben Sie auf der entsprechenden Protokollebene einfach den Statuscode an, wenn Sie die Antwort implementieren Es wird ausgegeben.


	grpc := grpc.NewServer(
		grpc_middleware.WithUnaryServerChain(    
			grpc_ctxtags.UnaryServerInterceptor(),   
			grpc_zap.UnaryServerInterceptor(zap, zap_opt),
		),
	)

Integrieren Sie Interceptor bei der Initialisierung des gRPC-Servers. Wenn nur ein Interceptor integriert werden muss, verwenden Sie "WithUnaryServerChain" Es muss nicht zusammengesetzt werden, aber dieses Mal möchte ich dem strukturierten Protokoll beliebige Felder hinzufügen Verwenden Sie "WithUnaryServerChain", um "grpc_ctxtags" und "grpc_zap" zu integrieren.

Schauen wir uns als nächstes die Datei server.go an, die der Antwortteil ist.

server/server.go


package server

import (
	"context"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
	pb "github.com/y-harashima/grpc-sample/proto"
)

type Server struct{}

func (s *Server) Get(ctx context.Context, req *pb.StudentRequest) (*pb.StudentResponse, error) {
	if req.Id == 1 {
		res := &pb.StudentResponse{
			Id: 1,
			Name: "Taro",
			Age: 11,
			School: &pb.School{
				Id: 1,
				Name: "ABC school",
				Grade: "5th",
			},
		}
        //Definieren Sie die zu protokollierenden Felder
		log := map[string]interface{}{  // --- ②
			"name": res.Name, 
			"age": res.Age, 
			"school_name": res.School.Name,
			"school_grade": res.School.Grade,
		}
		grpc_ctxtags.Extract(ctx).Set("data", log)
		return res, nil
	} else {
		grpc_ctxtags.Extract(ctx).Set("request_id", req.Id) // --- ①
		return nil, status.Errorf(codes.Internal, "No students found.") // --- ③
	}
}

Wenn für die Verarbeitungseinheit die angeforderte ID 1 ist, werden die Taro-Informationen zurückgegeben. Es ist eine einfache Antwort. Die Reihenfolge der Erklärung entspricht nicht dem Code-Fluss, aber ich werde es Schritt für Schritt erklären.


		grpc_ctxtags.Extract(ctx).Set("request_id", req.Id)

Sie können dem Kontextprotokoll Felder mit grpc_ctxtags hinzufügen. Durch Hinzufügen von "Set (Schlüssel, Wert)" zu dem als Argument übergebenen Kontext Es kann auf die Ausgabe von grpc_zap gesetzt werden.


		log := map[string]interface{}{
			"name": res.Name, 
			"age": res.Age, 
			"school_name": res.School.Name,
			"school_grade": res.School.Grade,
		}
		grpc_ctxtags.Extract(ctx).Set("data", log)
		return res, nil

Da der einzustellende Wert vom Typ "interface {}" unterstützt wird, kann er auch von map unterstützt werden. Wenn der zu übergebende Wert "map [string] interface {}" ist, werden der Schlüsselname und der Wert entsprechend strukturiert Es ist auch möglich, zu verschachteln und auszugeben. Im Falle einer normalen Antwort durch Verarbeitung auf die gleiche Weise wie bei normalem gRPC Das integrierte grpc_zap als Interceptor gibt ein strukturiertes Protokoll aus. Es ist sehr einfach und unkompliziert, da Sie ein strukturiertes Protokoll ohne komplizierte Konfiguration erhalten können.


		return nil, status.Errorf(codes.Internal, "No students found.")

Selbst wenn Sie die Verarbeitung mit einem Fehler zurückgeben, implementieren Sie die Fehlerverarbeitung einfach so, wie sie ist Es kann als Antwortprotokoll ausgegeben werden, aber wenn Sie das gRPC-Paket "status" verwenden Der Fehler kann durch Angabe des Statuscodes behoben werden. Durch Kombination mit der in main.go definierten Option grpc_zap Es wird auf Protokollebene ausgegeben, die dem Statuscode entspricht. Im obigen Beispiel wird es als "Error Level" -Protokoll ausgegeben.

Prüfung

Lassen Sie uns einen Test nach der Implementierung durchführen. Im Beispiel wird main.go mit docker-compose ausgeführt.

grpc-sample/


docker-compose up -d --build

Der Betriebstest von gRPC wird von [gRPCurl](https://github.com/fullstorydev/grpcurl), [evans](https://github.com/ktr0731/evans) usw. durchgeführt. Dieses Mal werden wir gRPCurl verwenden.

shell


grpcurl -plaintext -d '{"id": 1}' localhost:50051 sample.Student.Get
{
  "id": 1,
  "name": "Taro",
  "age": 11,
  "school": {
    "id": 1,
    "name": "ABC school",
    "grade": "5th"
  }
} 

shell


grpcurl -plaintext -d '{"id": 2}' localhost:50051 sample.Student.Get
ERROR:
  Code: Internal
  Message: No students found.

Es wurde bestätigt, dass bei einer ID von 1 eine normale Verarbeitung erfolgt, andernfalls tritt ein Fehler auf. Im Fehlerfall wird es wie in code.Internal angegeben ausgegeben. Lassen Sie uns das Ausgabeprotokoll überprüfen.

docker-logs(OK)


{
  "level":"info",
  "ts":1602527196.8505046,
  "caller":"zap/options.go:203",
  "msg":"finished unary call with code OK",
  "grpc.start_time":"2020-10-12T18:26:36Z", 
  "system":"grpc",
  "span.kind":"server",
  "grpc.service":"sample.Student",
  "grpc.method":"Get",
  "peer.address":"192.168.208.1:54062",
  "data":{
    "age":11,
    "name":"Taro",
    "school_grade":"5th",
    "school_name":"ABC school"
  },
  "grpc.code":"OK",
  "grpc.time_ms":0.03400000184774399
}

docker-log(Error)


{
  "level":"error",
  "ts":1602651069.7882483,
  "caller":"zap/options.go:203",
  "msg":"finished unary call with code Internal",
  "grpc.start_time":"2020-10-14T04:51:09Z",
  "system":"grpc",
  "span.kind":"server",
  "grpc.service":"sample.Student",
  "grpc.method":"Get",
  "peer.address":"192.168.208.1:54066",
  "request_id":2,
  "error":"rpc error: code = Internal desc = No students found.",
  "grpc.code":"Internal",
  "grpc.time_ms":1.3320000171661377,
  "stacktrace":"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap.DefaultMessageProducer\n\t/go/pkg/mod/github.com/grpc-ecosystem/[email protected]/logging/zap/options.go:203\ngithub.com/grpc-ecosystem/go-grpc-middleware/logging/zap.UnaryServerInterceptor.func1\n\t/go/pkg/mod/github.com/grpc-ecosystem/[email protected]/logging/zap/server_interceptors.go:39\ngithub.com/grpc-ecosystem/go-grpc-middleware.ChainUnaryServer.func1.1.1\n\t/go/pkg/mod/github.com/grpc-ecosystem/[email protected]/chain.go:25\ngithub.com/grpc-ecosystem/go-grpc-middleware/tags.UnaryServerInterceptor.func1\n\t/go/pkg/mod/github.com/grpc-ecosystem/[email protected]/tags/interceptors.go:23\ngithub.com/grpc-ecosystem/go-grpc-middleware.ChainUnaryServer.func1.1.1\n\t/go/pkg/mod/github.com/grpc-ecosystem/[email protected]/chain.go:25\ngithub.com/grpc-ecosystem/go-grpc-middleware.ChainUnaryServer.func1\n\t/go/pkg/mod/github.com/grpc-ecosystem/[email protected]/chain.go:34\ngithub.com/y-harashima/grpc-sample/proto._Student_Get_Handler\n\t/app/proto/sample.pb.go:389\ngoogle.golang.org/grpc.(*Server).processUnaryRPC\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1210\ngoogle.golang.org/grpc.(*Server).handleStream\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:1533\ngoogle.golang.org/grpc.(*Server).serveStreams.func1.2\n\t/go/pkg/mod/google.golang.org/[email protected]/server.go:871"
}

(Das Obige ist zur besseren Lesbarkeit mit Zeilenumbrüchen und Einrückungen formatiert.)

Sie können sehen, dass das Protokoll im JSON-Format ausgegeben wird. Durch die Verwendung von grpc_ctxtags wurden außerdem die folgenden Felder hinzugefügt.

docker-logs(OK, Auszug)


  "data":{
    "age":11,
    "name":"Taro",
    "school_grade":"5th",
    "school_name":"ABC school"
  },

docker-logs(Fehler, Auszug)


  "request_id":2,

Referenzseite

Schließlich ist es eine Referenzseite.

Zusammenfassung

Durch die Verwendung von "grpc_zap" konnten wir "zap" Logger einfach und einfach in gRPC integrieren. Sie können auch Felder mit grpc_ctxtags hinzufügen und die Protokollebene mit Option festlegen. Ich denke, es ist relativ einfach zu implementieren und flexibel anzupassen. Ich denke, es ist einfach, ein Protokoll zu entwerfen, also verwenden Sie es bitte. Hinweis ・ Teilen wie "Es gibt auch eine solche Verwendung!" Ist willkommen.

Recommended Posts

Versuchen Sie, das strukturierte gRPC-Protokoll einfach und einfach mit grpc_zap zu implementieren
Versuchen wir es mit gRPC mit Go und Docker
Versuchen Sie, mit Python schnell und einfach auf die Twitter-API zuzugreifen
Versuchen Sie, RBM mit Chainer zu implementieren.
Versuchen Sie, XOR mit PyTorch zu implementieren
Versuchen Sie, Parfüm mit Go zu implementieren
Authentifizierungsprozess mit gRPC- und Firebase-Authentifizierung
Kommunizieren Sie mit gRPC zwischen Elixir und Python
Versuchen Sie, XOR mit der Keras Functional API zu implementieren
Probieren Sie ganz einfach die automatische Bilderzeugung mit DCGAN-Tensorfluss aus
Laden Sie einfach mp3 / mp4 mit Python und youtube-dl herunter!
Behandeln Sie strukturierte Protokolle mit GCP Cloud Logging
Versuchen Sie, Yuma mit Brainf * ck 512-Zeilen zu implementieren (Code mit Python generieren und ausführen)