HTTP+DB+Redis+Logging

Let’s look at a relatively complete example that includes several commonly used core components for tracing. The example code is available at: https://github.com/gogf/gf/tree/master/example/trace/http_with_db

Client

  1. package main
  2. import (
  3. "github.com/gogf/gf/contrib/trace/otlphttp/v2"
  4. "github.com/gogf/gf/v2/database/gdb"
  5. "github.com/gogf/gf/v2/frame/g"
  6. "github.com/gogf/gf/v2/net/ghttp"
  7. "github.com/gogf/gf/v2/net/gtrace"
  8. "github.com/gogf/gf/v2/os/gctx"
  9. )
  10. const (
  11. serviceName = "otlp-http-client"
  12. endpoint = "tracing-analysis-dc-hz.aliyuncs.com"
  13. path = "adapt_******_******/api/otlp/traces" )
  14. func main() {
  15. var ctx = gctx.New()
  16. shutdown, err := otlphttp.Init(serviceName, endpoint, path)
  17. if err != nil {
  18. g.Log().Fatal(ctx, err)
  19. }
  20. defer shutdown()
  21. StartRequests()
  22. }
  23. func StartRequests() {
  24. ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests")
  25. defer span.End()
  26. var (
  27. err error
  28. client = g.Client()
  29. )
  30. // Add user info.
  31. var insertRes = struct {
  32. ghttp.DefaultHandlerResponse
  33. Data struct{ Id int64 } `json:"data"`
  34. }{}
  35. err = client.PostVar(ctx, "http://127.0.0.1:8199/user/insert", g.Map{
  36. "name": "john",
  37. }).Scan(&insertRes)
  38. if err != nil {
  39. panic(err)
  40. }
  41. g.Log().Info(ctx, "insert result:", insertRes)
  42. if insertRes.Data.Id == 0 {
  43. g.Log().Error(ctx, "retrieve empty id string")
  44. return
  45. }
  46. // Query user info.
  47. var queryRes = struct {
  48. ghttp.DefaultHandlerResponse
  49. Data struct{ User gdb.Record } `json:"data"`
  50. }{}
  51. err = client.GetVar(ctx, "http://127.0.0.1:8199/user/query", g.Map{
  52. "id": insertRes.Data.Id,
  53. }).Scan(&queryRes)
  54. if err != nil {
  55. panic(err)
  56. }
  57. g.Log().Info(ctx, "query result:", queryRes)
  58. // Delete user info.
  59. var deleteRes = struct {
  60. ghttp.DefaultHandlerResponse
  61. }{}
  62. err = client.PostVar(ctx, "http://127.0.0.1:8199/user/delete", g.Map{
  63. "id": insertRes.Data.Id,
  64. }).Scan(&deleteRes)
  65. if err != nil {
  66. panic(err)
  67. }
  68. g.Log().Info(ctx, "delete result:", deleteRes)
  69. }

Brief explanation of the client code:

  1. First, the client needs to initialize Jaeger via the jaeger.Init method.
  2. In this example, we send 3 requests to the server via HTTP client:
    1. /user/insert is used to add a new user and returns the user’s ID upon success.
    2. /user/query is used to query users using the user ID returned from the previous interface.
    3. /user/delete is used to delete users using the user ID returned from the previous interface.

Server

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "time"
  6. "github.com/gogf/gf/contrib/trace/otlphttp/v2"
  7. "github.com/gogf/gf/v2/database/gdb"
  8. "github.com/gogf/gf/v2/frame/g"
  9. "github.com/gogf/gf/v2/net/ghttp"
  10. "github.com/gogf/gf/v2/os/gcache"
  11. "github.com/gogf/gf/v2/os/gctx"
  12. )
  13. type cTrace struct{}
  14. const (
  15. serviceName = "otlp-http-client"
  16. endpoint = "tracing-analysis-dc-hz.aliyuncs.com"
  17. path = "adapt_******_******/api/otlp/traces" )
  18. func main() {
  19. var ctx = gctx.New()
  20. shutdown, err := otlphttp.Init(serviceName, endpoint, path)
  21. if err != nil {
  22. g.Log().Fatal(ctx, err)
  23. }
  24. defer shutdown()
  25. // Set ORM cache adapter with redis.
  26. g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis()))
  27. // Start HTTP server.
  28. s := g.Server()
  29. s.Use(ghttp.MiddlewareHandlerResponse)
  30. s.Group("/", func(group *ghttp.RouterGroup) {
  31. group.ALL("/user", new(cTrace))
  32. })
  33. s.SetPort(8199)
  34. s.Run()
  35. }
  36. type InsertReq struct {
  37. Name string `v:"required#Please input user name."`
  38. }
  39. type InsertRes struct {
  40. Id int64
  41. }
  42. // Insert is a route handler for inserting user info into database.
  43. func (c *cTrace) Insert(ctx context.Context, req *InsertReq) (res *InsertRes, err error) {
  44. result, err := g.Model("user").Ctx(ctx).Insert(req)
  45. if err != nil {
  46. return nil, err
  47. }
  48. id, _ := result.LastInsertId()
  49. res = &InsertRes{
  50. Id: id,
  51. }
  52. return
  53. }
  54. type QueryReq struct {
  55. Id int `v:"min:1#User id is required for querying"`
  56. }
  57. type QueryRes struct {
  58. User gdb.Record
  59. }
  60. // Query is a route handler for querying user info. It firstly retrieves the info from redis,
  61. // if there's nothing in the redis, it then does db select.
  62. func (c *cTrace) Query(ctx context.Context, req *QueryReq) (res *QueryRes, err error) {
  63. one, err := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
  64. Duration: 5 * time.Second,
  65. Name: c.userCacheKey(req.Id),
  66. Force: false,
  67. }).WherePri(req.Id).One()
  68. if err != nil {
  69. return nil, err
  70. }
  71. res = &QueryRes{
  72. User: one,
  73. }
  74. return
  75. }
  76. type DeleteReq struct {
  77. Id int `v:"min:1#User id is required for deleting."`
  78. }
  79. type DeleteRes struct{}
  80. // Delete is a route handler for deleting specified user info.
  81. func (c *cTrace) Delete(ctx context.Context, req *DeleteReq) (res *DeleteRes, err error) {
  82. _, err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
  83. Duration: -1,
  84. Name: c.userCacheKey(req.Id),
  85. Force: false,
  86. }).WherePri(req.Id).Delete()
  87. if err != nil {
  88. return nil, err
  89. }
  90. return
  91. }
  92. func (c *cTrace) userCacheKey(id int) string {
  93. return fmt.Sprintf(`userInfo:%d`, id)
  94. }

Brief explanation of the server code:

  1. First, the client also needs to initialize Jaeger via the jaeger.Init method.
  2. In this example, we use database and database cache features to demonstrate tracing for both ORM and Redis.
  3. At program startup, we set the adapter of the current database cache management to redis using the following method. For those interested in cache adapter introductions, please refer to the Caching - Interface section.
  1. g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis()))
  1. In ORM operations, the Ctx method needs to be used to pass the context variable to the component. The orm component automatically recognizes if the current context contains tracing information and enables tracing features if it does.
  2. In ORM operations, the Cache method is used here to cache query results to redis, and in delete operations, the Cache method is also used to clear the cache results in redis. For introductions to ORM cache management, please refer to ORM Model - Query Cache section.

View Results

Start the server:

Tracing HTTP - Data Ops - 图1

Start the client:

Tracing HTTP - Data Ops - 图2

View the tracing information in Jaeger:

Tracing HTTP - Data Ops - 图3

As you can see, this request generates a total of 14 spans, with 4 spans on the client side and 10 spans on the server side. Each span represents a trace node. However, we notice that 3 errors occurred. Let’s click for details to see the reasons.

Tracing HTTP - Data Ops - 图4

It seems that all redis operations reported errors. Let’s randomly click a related redis span to check the details:

Tracing HTTP - Data Ops - 图5

It turns out that the error was caused by the inability to connect to redis, causing all orm cache functionalities to fail. However, it didn’t affect the interface logic, and all queries went through the database. This error occurred because I forgot to start the local redis server. I’ll quickly start the local redis server and check the results again:

Tracing HTTP - Data Ops - 图6

Now let’s run the client code above again and check jaeger:

Tracing HTTP - Data Ops - 图7

Tracing HTTP - Data Ops - 图8

No errors this time.

The HTTP Client&Server, Logging components have been introduced before, so here we mainly focus on the tracing information of the orm and redis components.

ORM Tracing Information

Attributes/Tags

Let’s randomly click an ORM trace Span and look at the Attributes/Tags information:

Tracing HTTP - Data Ops - 图9

We can see that the span.kind here is internal, which is a method internal span type introduced before. Many Tags here have been introduced before, so we’ll mainly introduce database-related Tags:

Attribute/TagDescription
db.typeDatabase connection type, such as mysql, mssql, pgsql etc.
db.linkDatabase connection information. The password field is automatically hidden.
db.groupDatabase group name in the configuration file.

Events/Process

Tracing HTTP - Data Ops - 图10

Event/LogDescription
db.execution.sqlThe specific SQL statement executed. Since the ORM bottom layer is pre-processed, this statement is automatically concatenated for easier viewing and is for reference only.
db.execution.typeThe type of SQL statement executed, commonly DB.ExecContext and DB.QueryContext, representing write and read operations, respectively.
db.execution.costExecution time of the current SQL statement, measured in milliseconds (ms).

Redis Tracing Information

Attributes/Tags

Tracing HTTP - Data Ops - 图11

Attribute/TagDescription
redis.hostRedis connection address.
redis.portRedis connection port.
redis.dbRedis operation db.

Events/Process

Tracing HTTP - Data Ops - 图12

Event/LogDescription
redis.execution.commandRedis execution command.
redis.execution.argumentsRedis execution command arguments.
redis.execution.costExecution time of the Redis command, measured in milliseconds (ms).