github.com/pinpoint-apm/pinpoint-go-agent@v1.4.1-0.20240110120318-a50c2eb18c8c/doc/instrument.md (about)

     1  # Custom Instrumentation
     2  
     3  Pinpoint Go Agent enables you to monitor Go applications using Pinpoint.
     4  Go applications must be instrumented manually at the source code level,
     5  because Go is a compiled language and does not have a virtual machine like Java.
     6  
     7  There are two ways to instrument your applications:
     8  
     9  * Using plugin packages.
    10  * Custom instrumentation with the Pinpoint Go Agent API.
    11  
    12  Pinpoint Go Agent provides plugins packages to help developers trace the popular frameworks and toolkits.
    13  These packages help you to make instruments with simple source code modifications.
    14  For more information on plugin packages, refer [Plugin User Guide](plugin_guide.md).
    15  
    16  ## Overview
    17  
    18  In Pinpoint, a transaction consists of a group of Spans.
    19  Each span represents a trace of a single logical node where the transaction has gone through.
    20  A span records important function invocations and their related data(arguments, return value, etc.)
    21  before encapsulating them as SpanEvents in a call stack like representation.
    22  The span itself and each of its SpanEvents represents a function invocation.
    23  
    24  Find out more about the concept of Pinpoint at the links below:
    25  
    26  * https://pinpoint-apm.gitbook.io/pinpoint/want-a-quick-tour/techdetail
    27  * https://pinpoint-apm.gitbook.io/pinpoint/documents/plugin-dev-guide
    28  
    29  ## Span
    30  
    31  Span represents a top-level operation in your application, such as an HTTP or RPC request.
    32  To report a span, You can call Agent interface.
    33  
    34  * **Agent.NewSpanTracer()** returns a span Tracer indicating the start of a transaction.
    35    A span is sampled according to a given sampling policy, and trace data is not collected if not sampled.
    36  
    37  * **Agent.NewSpanTracerWithReader()** returns a span Tracer that continues a transaction passed from the previous node.
    38    A span is sampled according to a given sampling policy, and trace data is not collected if not sampled.
    39    Distributed tracing headers are extracted from the reader. If it is empty, new transaction is started.
    40  
    41  The point at which the span can be created is the http or grpc server request handler.
    42  The following is an example of creating a span from http server request handler:
    43  
    44  ``` go
    45  func doHandle(w http.ResponseWriter, r *http.Request) {
    46      tracer = pinpoint.GetAgent().NewSpanTracerWithReader("HTTP Server", r.URL.Path, r.Header)
    47      defer tracer.EndSpan()
    48  
    49      span := tracer.Span()
    50      span.SetEndPoint(r.Host)
    51  }
    52  ```
    53  
    54  You can instrument a single call stack of application and makes the result a single span using Tracer interface.
    55  **Tracer.EndSpan()** must be called to complete a span and deliver it to the collector.
    56  
    57  The SpanRecorder and Annotation interface allow trace data to be recorded in a Span.
    58  
    59  ## SpanEvent
    60  
    61  SpanEvent represents an operation within a span, such as a database query, a request to another service, or function call.
    62  To report a span, You can call **Tracer.NewSpanEvent()**.
    63  **Tracer.EndSpanEvent()** must be called to complete a span event.
    64  
    65  The SpanEventRecorder and Annotation interface allow trace data to be recorded in a SpanEvent.
    66  
    67  ``` go
    68  func doHandle(w http.ResponseWriter, r *http.Request) {
    69      tracer := pinpoint.GetAgent().NewSpanTracerWithReader("HTTP Server", r.URL.Path, r.Header)
    70      defer tracer.EndSpan()
    71  
    72      span := tracer.Span()
    73      span.SetEndPoint(r.Host)
    74      defer tracer.NewSpanEvent("doHandle").EndSpanEvent()
    75      
    76      func() {
    77          defer tracer.NewSpanEvent("func_1").EndSpanEvent()
    78  
    79          func() {
    80              defer tracer.NewSpanEvent("func_2").EndSpanEvent()
    81              time.Sleep(100 * time.Millisecond)
    82          }()
    83          time.Sleep(1 * time.Second)
    84      }()
    85  }
    86  ```
    87  
    88  The screenshot below is a pinpoint web screen showing the results of the above example.
    89  
    90  ![span](span.png) 
    91  
    92  ## Distributed tracing context
    93  
    94  If the request came from another node traced by a Pinpoint Agent, then the transaction will already have a transaction context.
    95  Most of these data are sent from the previous node, usually packed in the request message. 
    96  Pinpoint Go Agent provides two functions below to read and write these data.
    97  
    98  * **Tracer.Extract**(reader DistributedTracingContextReader) extracts distributed tracing headers from the reader.
    99  * **Tracer.Inject**(writer DistributedTracingContextWriter) injects distributed tracing headers to the writer.
   100  
   101  Using **Agent.NewSpanTracerWithReader()**, you can create a span that continues the transaction started from previous node.
   102  (Tracer.Extract() function is used internally to read the transaction context.)
   103  
   104  If you request to another service and the next node is traceable, the transaction context must be propagated to the next node.
   105  Tracer.Inject() is provided for this action.
   106  The following is an example of instrumenting a http request to other node:
   107  
   108  ``` go
   109  func externalRequest(tracer pinpoint.Tracer) int {
   110      req, err := http.NewRequest("GET", "http://localhost:9000/async_wrapper", nil)
   111      client := &http.Client{}
   112  
   113      tracer.NewSpanEvent("externalRequest")
   114      defer tracer.EndSpanEvent()
   115  
   116      se := tracer.SpanEvent()
   117      se.SetEndPoint(req.Host)
   118      se.SetDestination(req.Host)
   119      se.SetServiceType(pinpoint.ServiceTypeGoHttpClient)
   120      se.Annotations().AppendString(pinpoint.AnnotationHttpUrl, req.URL.String())
   121      tracer.Inject(req.Header)
   122  
   123      resp, err := client.Do(req)
   124      defer resp.Body.Close()
   125  
   126      tracer.SpanEvent().SetError(err)
   127      return resp.StatusCode
   128  }
   129  ```
   130  
   131  [Full Example](/example/custom.go)
   132  
   133  The screenshot below is a pinpoint web screen showing the results of the above example.
   134  It can be seen that the call stack of the [next node](/example/async.go) that receives the http request is displayed as one transaction.
   135  
   136  ![span](inject.png) 
   137  
   138  ## Context propagation
   139  In many Go APIs, the first argument to functions and methods is often context.Context. 
   140  Context provides a means of other request-scoped values across API boundaries and between processes. 
   141  It is often used when a library interacts — directly or transitively — with remote servers, such as databases, APIs, and the like.
   142  For information on the go context package, visit https://golang.org/pkg/context.
   143  
   144  Pinpoint Go Agent also uses Context to propagate the Tracer across API boundaries.
   145  Pinpoint Go Agent provides a function that adds a tracer to the Context,
   146  and a function that imports a tracer from the Context, respectively.
   147  
   148  * **NewContext()** adds a tracer to the Context. 
   149  * **FromContext()** imports a tracer from the Context.
   150  
   151  The following is an example of propagating Tracer to the sql driver using Context:
   152  
   153  ``` go
   154  func tableCount(w http.ResponseWriter, r *http.Request) {
   155      tracer := pinpoint.FromContext(r.Context())
   156  
   157      db, err := sql.Open("mysql-pinpoint", "root:p123@tcp(127.0.0.1:3306)/information_schema")
   158      defer db.Close()
   159  
   160      ctx := pinpoint.NewContext(context.Background(), tracer)
   161      row := db.QueryRowContext(ctx, "SELECT count(*) from tables")
   162      var count int
   163      row.Scan(&count)
   164  
   165      fmt.Println("number of tables in information_schema", count)
   166  }
   167  ```
   168  
   169  ## Instrument Goroutine
   170  
   171  The Pinpoint Tracer is designed to track a single call stack,
   172  so applications can be crashed if a tracer is shared on goroutines.
   173  The Tracer.NewGoroutineTracer() function should be called to create a new tracer that traces a goroutine,
   174  and then pass it to the goroutine.
   175  
   176  To pass the tracer to a goroutine, there is ways below:
   177  
   178  * function parameter
   179  * channel
   180  * context.Context
   181  
   182  The **Tracer.EndSpan()** function must be called at the end of the goroutine.
   183  
   184  ### Function parameter
   185  
   186  ``` go
   187  func outGoingRequest(ctx context.Context) {
   188      client := pphttp.WrapClient(nil)
   189  	
   190      request, _ := http.NewRequest("GET", "https://github.com/pinpoint-apm/pinpoint-go-agent", nil)
   191      request = request.WithContext(ctx)
   192  
   193      resp, err := client.Do(request)
   194      if nil != err {
   195          log.Println(err.Error())
   196          return
   197      }
   198      defer resp.Body.Close()
   199      log.Println(resp.Body)
   200  }
   201  
   202  func asyncWithTracer(w http.ResponseWriter, r *http.Request) {
   203      tracer := pinpoint.FromContext(r.Context())
   204      wg := &sync.WaitGroup{}
   205      wg.Add(1)
   206  
   207      go func(asyncTracer pinpoint.Tracer) {
   208          defer wg.Done()
   209  
   210          defer asyncTracer.EndSpan() // must be called
   211          defer asyncTracer.NewSpanEvent("asyncWithTracer_goroutine").EndSpanEvent()
   212  
   213          ctx := pinpoint.NewContext(context.Background(), asyncTracer)
   214          outGoingRequest(w, ctx)
   215      }(tracer.NewGoroutineTracer())
   216  
   217      wg.Wait()
   218  }
   219  ```
   220  
   221  ### Channel
   222  
   223  ``` go
   224  func asyncWithChan(w http.ResponseWriter, r *http.Request) {
   225      tracer := pinpoint.FromContext(r.Context())
   226      wg := &sync.WaitGroup{}
   227      wg.Add(1)
   228  
   229      ch := make(chan pinpoint.Tracer)
   230  
   231      go func() {
   232          defer wg.Done()
   233  
   234          asyncTracer := <-ch
   235          defer asyncTracer.EndSpan() // must be called
   236          defer asyncTracer.NewSpanEvent("asyncWithChan_goroutine").EndSpanEvent()
   237  
   238          ctx := pinpoint.NewContext(context.Background(), asyncTracer)
   239          outGoingRequest(w, ctx)
   240      }()
   241  
   242      ch <- tracer.NewGoroutineTracer()
   243      wg.Wait()
   244  }
   245  ```
   246  
   247  ### Context
   248  
   249  ``` go
   250  func asyncWithContext(w http.ResponseWriter, r *http.Request) {
   251      tracer := pinpoint.FromContext(r.Context())
   252      wg := &sync.WaitGroup{}
   253      wg.Add(1)
   254  
   255      go func(asyncCtx context.Context) {
   256          defer wg.Done()
   257  
   258          asyncTracer := pinpoint.FromContext(asyncCtx)
   259          defer asyncTracer.EndSpan() // must be called
   260          defer asyncTracer.NewSpanEvent("asyncWithContext_goroutine").EndSpanEvent()
   261  
   262          ctx := pinpoint.NewContext(context.Background(), asyncTracer)
   263          outGoingRequest(w, ctx)
   264      }(pinpoint.NewContext(context.Background(), tracer.NewGoroutineTracer()))
   265  
   266      wg.Wait()
   267  }
   268  ```
   269  
   270  ### Wrapper function
   271  **Tracer.WrapGoroutine()** function creates a tracer for the goroutine and passes it to the goroutine in context.
   272  You don't need to call Tracer.EndSpan() because wrapper call it when the goroutine function ends.
   273  Just call the wrapped function as goroutine.
   274  We recommend using this function.
   275  
   276  ``` go
   277  func asyncFunc(asyncCtx context.Context) {
   278      w := asyncCtx.Value("wr").(http.ResponseWriter)
   279      outGoingRequest(w, asyncCtx)
   280  }
   281  
   282  func asyncWithWrapper(w http.ResponseWriter, r *http.Request) {
   283      tracer := pinpoint.FromContext(r.Context())
   284      ctx := context.WithValue(context.Background(), "wr", w)
   285      f := tracer.WrapGoroutine("asyncFunc", asyncFunc, ctx)
   286      go f()
   287  }
   288  ```