Single Process Example

Single process tracing refers to the call chain relationship between methods within the process. This scenario does not involve distributed tracing, making it relatively simple. Let’s use this example as our introductory case. Example code address: https://github.com/gogf/gf/tree/master/example/trace/inprocess

Root Span

root span is the first span object in the trace. In the single process scenario here, it often needs to be created manually. Subsequently, spans created within methods will act as its child spans.

Tracing - Basic Example - 图1tip

In inter-service communication scenarios of distributed architectures, it is often unnecessary for developers to manually create a root span, as it is automatically created by interceptors of client/server requests.

Create a tracer and generate a root span:

  1. func main() {
  2. const (
  3. serviceName = "otlp-http-client"
  4. endpoint = "tracing-analysis-dc-hz.aliyuncs.com"
  5. path = "adapt_******_******/api/otlp/traces"
  6. )
  7. var ctx = gctx.New()
  8. shutdown, err := otlphttp.Init(serviceName, endpoint, path)
  9. if err != nil {
  10. g.Log().Fatal(ctx, err)
  11. }
  12. defer shutdown()
  13. ctx, span := gtrace.NewSpan(ctx, "main")
  14. defer span.End()
  15. // Trace 1.
  16. user1 := GetUser(ctx, 1)
  17. g.Dump(user1)
  18. // Trace 2.
  19. user100 := GetUser(ctx, 100)
  20. g.Dump(user100)
  21. }

The above code creates a root span and passes this span through context to the GetUser method, so that the trace chain can continue within the GetUser method.

Span Creation Between Methods

  1. // GetUser retrieves and returns hard coded user data for demonstration.
  2. func GetUser(ctx context.Context, id int) g.Map {
  3. ctx, span := gtrace.NewSpan(ctx, "GetUser")
  4. defer span.End()
  5. m := g.Map{}
  6. gutil.MapMerge(
  7. m,
  8. GetInfo(ctx, id),
  9. GetDetail(ctx, id),
  10. GetScores(ctx, id),
  11. )
  12. return m
  13. }
  14. // GetInfo retrieves and returns hard coded user info for demonstration.
  15. func GetInfo(ctx context.Context, id int) g.Map {
  16. ctx, span := gtrace.NewSpan(ctx, "GetInfo")
  17. defer span.End()
  18. if id == 100 {
  19. return g.Map{
  20. "id": 100,
  21. "name": "john",
  22. "gender": 1,
  23. }
  24. }
  25. return nil
  26. }
  27. // GetDetail retrieves and returns hard coded user detail for demonstration.
  28. func GetDetail(ctx context.Context, id int) g.Map {
  29. ctx, span := gtrace.NewSpan(ctx, "GetDetail")
  30. defer span.End()
  31. if id == 100 {
  32. return g.Map{
  33. "site": "https://goframe.org",
  34. "email": "john@goframe.org",
  35. }
  36. }
  37. return nil
  38. }
  39. // GetScores retrieves and returns hard coded user scores for demonstration.
  40. func GetScores(ctx context.Context, id int) g.Map {
  41. ctx, span := gtrace.NewSpan(ctx, "GetScores")
  42. defer span.End()
  43. if id == 100 {
  44. return g.Map{
  45. "math": 100,
  46. "english": 60,
  47. "chinese": 50,
  48. }
  49. }
  50. return nil
  51. }

This example code shows multi-level method trace information transmission, meaning that the ctx context variable is passed as the first parameter of the method. Within the method, we use fixed syntax to create/start a Span:

  1. ctx, span := gtrace.NewSpan(ctx, "xxx")
  2. defer span.End()

By calling span.End using defer, we end a Span, which records the lifecycle (start and end) information of the Span. This information will be displayed in the tracing system. For the second parameter spanName of the gtrace.NewSpan method, we directly give the method name so that it is more recognizable in the trace display.

Result Viewing

After executing the above program, the terminal outputs:

Tracing - Basic Example - 图2

Open Jaeger UI: http://localhost:16686/search, and you can see the trace results: Tracing - Basic Example - 图3

Tracing - Basic Example - 图4

Clicking on the details allows you to view specific information, including the invocation order and relationship of spans, the execution timeline, and recorded Attributes and Events information, which significantly helps us locate system anomalies and identify performance bottlenecks.

Tracing - Basic Example - 图5

The tracing-inprocess is the name of our tracer, which is usually the service name. Since we only have one process and one tracer, there is only one service name displayed here. The main is the name of the root span we created, and other spans were created based on this root span. Since we called the GetUser method twice in the program, it shows two calls to the GetUser method. Each GetUser call internally invokes the three methods GetInfo, GetDetail, and GetScores, and the hierarchical relationship of method calls is displayed very clearly, along with the duration of each method call.

The Tags and Process information recorded in each span corresponds to the Attributes and Events information in OpenTelemetry, which we will introduce in more detail in subsequent chapters.