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 ```