From c72c7dbe9a75215e8166a5e352a4d7a3f0d41c31 Mon Sep 17 00:00:00 2001 From: jianchenma Date: Tue, 26 Jan 2021 16:06:20 +0800 Subject: [PATCH] improve tracing feature --- .example/net/gtrace/1.http/client/main.go | 2 +- .example/net/gtrace/1.http/server/main.go | 2 +- .example/net/gtrace/2.http+db/main.go | 42 +++++------ .example/net/gtrace/3.http+redis/main.go | 2 +- .../gtrace/4.http+db+redis+log/client/main.go | 71 +++++++++++++++++++ .../{ => server}/config.toml | 0 .../4.http+db+redis+log/{ => server}/main.go | 63 ++++++++++++++-- database/gdb/gdb_core.go | 51 ------------- database/gdb/gdb_core_tracing.go | 65 +++++++++++++++++ database/gredis/gredis_conn.go | 2 +- net/ghttp/internal/client/client_tracing.go | 2 +- .../internal/client/client_tracing_tracer.go | 11 +-- net/gtrace/gtrace.go | 10 +++ 13 files changed, 235 insertions(+), 88 deletions(-) create mode 100644 .example/net/gtrace/4.http+db+redis+log/client/main.go rename .example/net/gtrace/4.http+db+redis+log/{ => server}/config.toml (100%) rename .example/net/gtrace/4.http+db+redis+log/{ => server}/main.go (50%) create mode 100644 database/gdb/gdb_core_tracing.go diff --git a/.example/net/gtrace/1.http/client/main.go b/.example/net/gtrace/1.http/client/main.go index cae43e329..d10eed73d 100644 --- a/.example/net/gtrace/1.http/client/main.go +++ b/.example/net/gtrace/1.http/client/main.go @@ -13,7 +13,7 @@ import ( const ( JaegerEndpoint = "http://localhost:14268/api/traces" - ServiceName = "TracingHttpClient" + ServiceName = "tracing-http-client" ) // initTracer creates a new trace provider instance and registers it as global trace provider. diff --git a/.example/net/gtrace/1.http/server/main.go b/.example/net/gtrace/1.http/server/main.go index 1be9b6fc8..7c6a2687c 100644 --- a/.example/net/gtrace/1.http/server/main.go +++ b/.example/net/gtrace/1.http/server/main.go @@ -11,7 +11,7 @@ import ( const ( JaegerEndpoint = "http://localhost:14268/api/traces" - ServiceName = "TracingHttpServer" + ServiceName = "tracing-http-server" ) // initTracer creates a new trace provider instance and registers it as global trace provider. diff --git a/.example/net/gtrace/2.http+db/main.go b/.example/net/gtrace/2.http+db/main.go index 744971f1f..c85061135 100644 --- a/.example/net/gtrace/2.http+db/main.go +++ b/.example/net/gtrace/2.http+db/main.go @@ -10,6 +10,27 @@ import ( type tracingApi struct{} +const ( + JaegerEndpoint = "http://localhost:14268/api/traces" + ServiceName = "tracing-demo-db" +) + +// initTracer creates a new trace provider instance and registers it as global trace provider. +func initTracer() func() { + // Create and install Jaeger export pipeline. + flush, err := jaeger.InstallNewPipeline( + jaeger.WithCollectorEndpoint(JaegerEndpoint), + jaeger.WithProcess(jaeger.Process{ + ServiceName: ServiceName, + }), + jaeger.WithSDK(&sdkTrace.Config{DefaultSampler: sdkTrace.AlwaysSample()}), + ) + if err != nil { + g.Log().Fatal(err) + } + return flush +} + func (api *tracingApi) Insert(r *ghttp.Request) { result, err := g.Table("user").Ctx(r.Context()).Insert(g.Map{ "name": r.GetString("name"), @@ -29,27 +50,6 @@ func (api *tracingApi) Query(r *ghttp.Request) { r.Response.Write("user:", one) } -const ( - JaegerEndpoint = "http://localhost:14268/api/traces" - ServiceName = "TracingHttpServerWithDB" -) - -// initTracer creates a new trace provider instance and registers it as global trace provider. -func initTracer() func() { - // Create and install Jaeger export pipeline. - flush, err := jaeger.InstallNewPipeline( - jaeger.WithCollectorEndpoint(JaegerEndpoint), - jaeger.WithProcess(jaeger.Process{ - ServiceName: ServiceName, - }), - jaeger.WithSDK(&sdkTrace.Config{DefaultSampler: sdkTrace.AlwaysSample()}), - ) - if err != nil { - g.Log().Fatal(err) - } - return flush -} - func main() { flush := initTracer() defer flush() diff --git a/.example/net/gtrace/3.http+redis/main.go b/.example/net/gtrace/3.http+redis/main.go index 9de300397..35788087f 100644 --- a/.example/net/gtrace/3.http+redis/main.go +++ b/.example/net/gtrace/3.http+redis/main.go @@ -12,7 +12,7 @@ type tracingApi struct{} const ( JaegerEndpoint = "http://localhost:14268/api/traces" - ServiceName = "TracingHttpServerWithRedis" + ServiceName = "tracing-demo-redis" ) func (api *tracingApi) Set(r *ghttp.Request) { diff --git a/.example/net/gtrace/4.http+db+redis+log/client/main.go b/.example/net/gtrace/4.http+db+redis+log/client/main.go new file mode 100644 index 000000000..019509cda --- /dev/null +++ b/.example/net/gtrace/4.http+db+redis+log/client/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "context" + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/net/gtrace" + "go.opentelemetry.io/otel/exporters/trace/jaeger" + sdkTrace "go.opentelemetry.io/otel/sdk/trace" +) + +const ( + JaegerEndpoint = "http://localhost:14268/api/traces" + ServiceName = "tracing-http-client" +) + +// initTracer creates a new trace provider instance and registers it as global trace provider. +func initTracer() func() { + // Create and install Jaeger export pipeline. + flush, err := jaeger.InstallNewPipeline( + jaeger.WithCollectorEndpoint(JaegerEndpoint), + jaeger.WithProcess(jaeger.Process{ + ServiceName: ServiceName, + }), + jaeger.WithSDK(&sdkTrace.Config{DefaultSampler: sdkTrace.AlwaysSample()}), + ) + if err != nil { + g.Log().Fatal(err) + } + return flush +} + +func main() { + flush := initTracer() + defer flush() + + ctx, span := gtrace.Tracer().Start(context.Background(), "root") + defer span.End() + + client := g.Client().Use(ghttp.MiddlewareClientTracing) + //client := g.Client() + // Add user info. + idStr := client.Ctx(ctx).PostContent( + "http://127.0.0.1:8199/user/insert", + g.Map{ + "name": "john", + }, + ) + if idStr == "" { + g.Log().Ctx(ctx).Fatal("retrieve empty id string") + } + g.Log().Ctx(ctx).Print("insert:", idStr) + + // Query user info. + userJson := client.Ctx(ctx).GetContent( + "http://127.0.0.1:8199/user/query", + g.Map{ + "id": idStr, + }, + ) + g.Log().Ctx(ctx).Print("query:", idStr, userJson) + + // Delete user info. + deleteResult := client.Ctx(ctx).PostContent( + "http://127.0.0.1:8199/user/delete", + g.Map{ + "id": idStr, + }, + ) + g.Log().Ctx(ctx).Print("delete:", idStr, deleteResult) +} diff --git a/.example/net/gtrace/4.http+db+redis+log/config.toml b/.example/net/gtrace/4.http+db+redis+log/server/config.toml similarity index 100% rename from .example/net/gtrace/4.http+db+redis+log/config.toml rename to .example/net/gtrace/4.http+db+redis+log/server/config.toml diff --git a/.example/net/gtrace/4.http+db+redis+log/main.go b/.example/net/gtrace/4.http+db+redis+log/server/main.go similarity index 50% rename from .example/net/gtrace/4.http+db+redis+log/main.go rename to .example/net/gtrace/4.http+db+redis+log/server/main.go index 26fa71ae5..4b244fb4c 100644 --- a/.example/net/gtrace/4.http+db+redis+log/main.go +++ b/.example/net/gtrace/4.http+db+redis+log/server/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "github.com/gogf/gcache-adapter/adapter" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/frame/g" @@ -14,7 +15,7 @@ type tracingApi struct{} const ( JaegerEndpoint = "http://localhost:14268/api/traces" - ServiceName = "TracingHttpServerWithDBRedisLog" + ServiceName = "tracing-http-server" ) // initTracer creates a new trace provider instance and registers it as global trace provider. @@ -33,26 +34,76 @@ func initTracer() func() { return flush } +type userApiInsert struct { + Name string `v:"required#Please input user name."` +} + +// Insert is a route handler for inserting user info into dtabase. func (api *tracingApi) Insert(r *ghttp.Request) { + var ( + dataReq *userApiInsert + ) + if err := r.Parse(&dataReq); err != nil { + r.Response.WriteExit(gerror.Current(err)) + } result, err := g.Table("user").Ctx(r.Context()).Insert(g.Map{ - "name": r.GetString("name"), + "name": dataReq.Name, }) if err != nil { r.Response.WriteExit(gerror.Current(err)) } id, _ := result.LastInsertId() - r.Response.Write("id:", id) + r.Response.Write(id) } +type userApiQuery struct { + Id int `v:"min:1#User id is required for querying."` +} + +// Query is a route handler for querying user info. It firstly retrieves the info from redis, +// if there's nothing in the redis, it then does db select. func (api *tracingApi) Query(r *ghttp.Request) { + var ( + dataReq *userApiQuery + ) + if err := r.Parse(&dataReq); err != nil { + r.Response.WriteExit(gerror.Current(err)) + } one, err := g.Table("user"). Ctx(r.Context()). - Cache(5 * time.Second). - FindOne(r.GetInt("id")) + Cache(5*time.Second, api.userCacheKey(dataReq.Id)). + FindOne(dataReq.Id) if err != nil { r.Response.WriteExit(gerror.Current(err)) } - r.Response.Write("user:", one) + r.Response.WriteJson(one) +} + +type userApiDelete struct { + Id int `v:"min:1#User id is required for deleting."` +} + +// Delete is a route handler for deleting specified user info. +func (api *tracingApi) Delete(r *ghttp.Request) { + var ( + dataReq *userApiDelete + ) + if err := r.Parse(&dataReq); err != nil { + r.Response.WriteExit(gerror.Current(err)) + } + _, err := g.Table("user"). + Ctx(r.Context()). + Cache(-1, api.userCacheKey(dataReq.Id)). + WherePri(dataReq.Id). + Delete() + if err != nil { + r.Response.WriteExit(gerror.Current(err)) + } + r.Response.Write("ok") +} + +func (api *tracingApi) userCacheKey(id int) string { + return fmt.Sprintf(`userInfo:%d`, id) } func main() { diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index 797750602..08c21c245 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -11,13 +11,8 @@ import ( "context" "database/sql" "fmt" - "github.com/gogf/gf" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/text/gstr" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/label" - "go.opentelemetry.io/otel/trace" "reflect" "strings" @@ -146,52 +141,6 @@ func (c *Core) DoQuery(link Link, sql string, args ...interface{}) (rows *sql.Ro return nil, err } -func (c *Core) addSqlToTracing(ctx context.Context, sql *Sql) { - if ctx == nil { - return - } - spanCtx := trace.SpanContextFromContext(ctx) - if traceId := spanCtx.TraceID; !traceId.IsValid() { - return - } - - tr := otel.GetTracerProvider().Tracer( - "github.com/gogf/gf/database/gdb", - trace.WithInstrumentationVersion(fmt.Sprintf(`%s`, gf.VERSION)), - ) - ctx, span := tr.Start(ctx, sql.Type) - defer span.End() - if sql.Error != nil { - span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, sql.Error)) - } - labels := make([]label.KeyValue, 0) - labels = append(labels, label.String("db.type", c.DB.GetConfig().Type)) - if c.DB.GetConfig().Host != "" { - labels = append(labels, label.String("db.host", c.DB.GetConfig().Host)) - } - if c.DB.GetConfig().Port != "" { - labels = append(labels, label.String("db.port", c.DB.GetConfig().Port)) - } - if c.DB.GetConfig().Name != "" { - labels = append(labels, label.String("db.name", c.DB.GetConfig().Name)) - } - if c.DB.GetConfig().User != "" { - labels = append(labels, label.String("db.user", c.DB.GetConfig().User)) - } - if filteredLinkInfo := c.DB.FilteredLinkInfo(); filteredLinkInfo != "" { - labels = append(labels, label.String("db.link", c.DB.FilteredLinkInfo())) - } - if group := c.DB.GetGroup(); group != "" { - labels = append(labels, label.String("db.group", group)) - } - span.SetAttributes(labels...) - span.AddEvent("db.execution", trace.WithAttributes( - label.String(`db.execution.sql`, sql.Format), - label.String(`db.execution.cost`, fmt.Sprintf(`%d ms`, sql.End-sql.Start)), - label.String(`db.execution.type`, sql.Type), - )) -} - // Exec commits one query SQL to underlying driver and returns the execution result. // It is most commonly used for data inserting and updating. func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err error) { diff --git a/database/gdb/gdb_core_tracing.go b/database/gdb/gdb_core_tracing.go new file mode 100644 index 000000000..b012f8159 --- /dev/null +++ b/database/gdb/gdb_core_tracing.go @@ -0,0 +1,65 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. +// + +package gdb + +import ( + "context" + "fmt" + "github.com/gogf/gf" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/label" + "go.opentelemetry.io/otel/trace" +) + +// addSqlToTracing adds sql information to tracer if it's enabled. +func (c *Core) addSqlToTracing(ctx context.Context, sql *Sql) { + if ctx == nil { + return + } + spanCtx := trace.SpanContextFromContext(ctx) + if traceId := spanCtx.TraceID; !traceId.IsValid() { + return + } + + tr := otel.GetTracerProvider().Tracer( + "github.com/gogf/gf/database/gdb", + trace.WithInstrumentationVersion(fmt.Sprintf(`%s`, gf.VERSION)), + ) + ctx, span := tr.Start(ctx, sql.Type) + defer span.End() + if sql.Error != nil { + span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, sql.Error)) + } + labels := make([]label.KeyValue, 0) + labels = append(labels, label.String("db.type", c.DB.GetConfig().Type)) + if c.DB.GetConfig().Host != "" { + labels = append(labels, label.String("db.host", c.DB.GetConfig().Host)) + } + if c.DB.GetConfig().Port != "" { + labels = append(labels, label.String("db.port", c.DB.GetConfig().Port)) + } + if c.DB.GetConfig().Name != "" { + labels = append(labels, label.String("db.name", c.DB.GetConfig().Name)) + } + if c.DB.GetConfig().User != "" { + labels = append(labels, label.String("db.user", c.DB.GetConfig().User)) + } + if filteredLinkInfo := c.DB.FilteredLinkInfo(); filteredLinkInfo != "" { + labels = append(labels, label.String("db.link", c.DB.FilteredLinkInfo())) + } + if group := c.DB.GetGroup(); group != "" { + labels = append(labels, label.String("db.group", group)) + } + span.SetAttributes(labels...) + span.AddEvent("db.execution", trace.WithAttributes( + label.String(`db.execution.sql`, sql.Format), + label.String(`db.execution.cost`, fmt.Sprintf(`%d ms`, sql.End-sql.Start)), + label.String(`db.execution.type`, sql.Type), + )) +} diff --git a/database/gredis/gredis_conn.go b/database/gredis/gredis_conn.go index 3f5540359..bd852ec7b 100644 --- a/database/gredis/gredis_conn.go +++ b/database/gredis/gredis_conn.go @@ -80,7 +80,7 @@ func (c *Conn) do(timeout time.Duration, commandName string, args ...interface{} if ctx == nil { ctx = context.Background() } - _, span := tr.Start(ctx, commandName) + _, span := tr.Start(ctx, "Redis."+commandName) defer span.End() if err != nil { span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err)) diff --git a/net/ghttp/internal/client/client_tracing.go b/net/ghttp/internal/client/client_tracing.go index 546447fa8..ffe35141d 100644 --- a/net/ghttp/internal/client/client_tracing.go +++ b/net/ghttp/internal/client/client_tracing.go @@ -52,7 +52,7 @@ func MiddlewareTracing(c *Client, r *http.Request) (response *Response, err erro if err != nil { span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err)) } - if response == nil { + if response == nil || response.Response == nil { return } var resBodyContent string diff --git a/net/ghttp/internal/client/client_tracing_tracer.go b/net/ghttp/internal/client/client_tracing_tracer.go index fa2f6366e..bf833c069 100644 --- a/net/ghttp/internal/client/client_tracing_tracer.go +++ b/net/ghttp/internal/client/client_tracing_tracer.go @@ -38,6 +38,11 @@ func newClientTrace(ctx context.Context, span trace.Span, request *http.Request) request: request, headers: make(map[string]interface{}), } + if ct.request.ContentLength <= tracingMaxContentLogSize { + reqBodyContent, _ := ioutil.ReadAll(ct.request.Body) + ct.requestBody = reqBodyContent + ct.request.Body = utils.NewReadCloser(reqBodyContent, false) + } return &httptrace.ClientTrace{ GetConn: ct.getConn, GotConn: ct.gotConn, @@ -131,11 +136,7 @@ func (ct *clientTracer) wroteHeaderField(k string, v []string) { } func (ct *clientTracer) wroteHeaders() { - if ct.request.ContentLength <= tracingMaxContentLogSize { - reqBodyContent, _ := ioutil.ReadAll(ct.request.Body) - ct.requestBody = reqBodyContent - ct.request.Body = utils.NewReadCloser(reqBodyContent, false) - } + } func (ct *clientTracer) wroteRequest(info httptrace.WroteRequestInfo) { diff --git a/net/gtrace/gtrace.go b/net/gtrace/gtrace.go index 232951004..ea30b0e9e 100644 --- a/net/gtrace/gtrace.go +++ b/net/gtrace/gtrace.go @@ -25,6 +25,16 @@ func Tracer(name ...string) trace.Tracer { return otel.Tracer(tracerName) } +// GetTraceId retrieves and returns TraceId from context. +func GetTraceId(ctx context.Context) string { + return trace.SpanContextFromContext(ctx).TraceID.String() +} + +// GetSpanId retrieves and returns SpanId from context. +func GetSpanId(ctx context.Context) string { + return trace.SpanContextFromContext(ctx).SpanID.String() +} + // SetBaggageValue is a convenient function for adding one key-value pair to baggage. // Note that it uses label.Any to set the key-value pair. func SetBaggageValue(ctx context.Context, key string, value interface{}) context.Context {