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.
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 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
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.
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.
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
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,
Schließlich ist es eine Referenzseite.
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