github.com/newrelic/go-agent@v3.26.0+incompatible/examples/server/main.go (about) 1 // Copyright 2020 New Relic Corporation. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 4 // +build go1.7 5 6 package main 7 8 import ( 9 "errors" 10 "fmt" 11 "io" 12 "log" 13 "math/rand" 14 "net/http" 15 "os" 16 "sync" 17 "time" 18 19 newrelic "github.com/newrelic/go-agent" 20 ) 21 22 func index(w http.ResponseWriter, r *http.Request) { 23 io.WriteString(w, "hello world") 24 } 25 26 func versionHandler(w http.ResponseWriter, r *http.Request) { 27 io.WriteString(w, "New Relic Go Agent Version: "+newrelic.Version) 28 } 29 30 func noticeError(w http.ResponseWriter, r *http.Request) { 31 io.WriteString(w, "noticing an error") 32 33 if txn := newrelic.FromContext(r.Context()); txn != nil { 34 txn.NoticeError(errors.New("my error message")) 35 } 36 } 37 38 func noticeErrorWithAttributes(w http.ResponseWriter, r *http.Request) { 39 io.WriteString(w, "noticing an error") 40 41 if txn := newrelic.FromContext(r.Context()); txn != nil { 42 txn.NoticeError(newrelic.Error{ 43 Message: "uh oh. something went very wrong", 44 Class: "errors are aggregated by class", 45 Attributes: map[string]interface{}{ 46 "important_number": 97232, 47 "relevant_string": "zap", 48 }, 49 }) 50 } 51 } 52 53 func customEvent(w http.ResponseWriter, r *http.Request) { 54 txn := newrelic.FromContext(r.Context()) 55 56 io.WriteString(w, "recording a custom event") 57 58 if nil != txn { 59 txn.Application().RecordCustomEvent("my_event_type", map[string]interface{}{ 60 "myString": "hello", 61 "myFloat": 0.603, 62 "myInt": 123, 63 "myBool": true, 64 }) 65 } 66 } 67 68 func setName(w http.ResponseWriter, r *http.Request) { 69 io.WriteString(w, "changing the transaction's name") 70 71 if txn := newrelic.FromContext(r.Context()); txn != nil { 72 txn.SetName("other-name") 73 } 74 } 75 76 func addAttribute(w http.ResponseWriter, r *http.Request) { 77 io.WriteString(w, "adding attributes") 78 79 if txn := newrelic.FromContext(r.Context()); txn != nil { 80 txn.AddAttribute("myString", "hello") 81 txn.AddAttribute("myInt", 123) 82 } 83 } 84 85 func ignore(w http.ResponseWriter, r *http.Request) { 86 if coinFlip := (0 == rand.Intn(2)); coinFlip { 87 if txn := newrelic.FromContext(r.Context()); txn != nil { 88 txn.Ignore() 89 } 90 io.WriteString(w, "ignoring the transaction") 91 } else { 92 io.WriteString(w, "not ignoring the transaction") 93 } 94 } 95 96 func segments(w http.ResponseWriter, r *http.Request) { 97 txn := newrelic.FromContext(r.Context()) 98 99 func() { 100 defer newrelic.StartSegment(txn, "f1").End() 101 102 func() { 103 defer newrelic.StartSegment(txn, "f2").End() 104 105 io.WriteString(w, "segments!") 106 time.Sleep(10 * time.Millisecond) 107 }() 108 time.Sleep(15 * time.Millisecond) 109 }() 110 time.Sleep(20 * time.Millisecond) 111 } 112 113 func mysql(w http.ResponseWriter, r *http.Request) { 114 txn := newrelic.FromContext(r.Context()) 115 s := newrelic.DatastoreSegment{ 116 StartTime: newrelic.StartSegmentNow(txn), 117 // Product, Collection, and Operation are the most important 118 // fields to populate because they are used in the breakdown 119 // metrics. 120 Product: newrelic.DatastoreMySQL, 121 Collection: "users", 122 Operation: "INSERT", 123 124 ParameterizedQuery: "INSERT INTO users (name, age) VALUES ($1, $2)", 125 QueryParameters: map[string]interface{}{ 126 "name": "Dracula", 127 "age": 439, 128 }, 129 Host: "mysql-server-1", 130 PortPathOrID: "3306", 131 DatabaseName: "my_database", 132 } 133 defer s.End() 134 135 time.Sleep(20 * time.Millisecond) 136 io.WriteString(w, `performing fake query "INSERT * from users"`) 137 } 138 139 func message(w http.ResponseWriter, r *http.Request) { 140 txn := newrelic.FromContext(r.Context()) 141 s := newrelic.MessageProducerSegment{ 142 StartTime: newrelic.StartSegmentNow(txn), 143 Library: "RabbitMQ", 144 DestinationType: newrelic.MessageQueue, 145 DestinationName: "myQueue", 146 } 147 defer s.End() 148 149 time.Sleep(20 * time.Millisecond) 150 io.WriteString(w, `producing a message queue message`) 151 } 152 153 func external(w http.ResponseWriter, r *http.Request) { 154 txn := newrelic.FromContext(r.Context()) 155 req, _ := http.NewRequest("GET", "http://example.com", nil) 156 157 // Using StartExternalSegment is recommended because it does distributed 158 // tracing header setup, but if you don't have an *http.Request and 159 // instead only have a url string then you can start the external 160 // segment like this: 161 // 162 // es := newrelic.ExternalSegment{ 163 // StartTime: newrelic.StartSegmentNow(txn), 164 // URL: urlString, 165 // } 166 // 167 es := newrelic.StartExternalSegment(txn, req) 168 resp, err := http.DefaultClient.Do(req) 169 es.End() 170 171 if nil != err { 172 io.WriteString(w, err.Error()) 173 return 174 } 175 defer resp.Body.Close() 176 io.Copy(w, resp.Body) 177 } 178 179 func roundtripper(w http.ResponseWriter, r *http.Request) { 180 // NewRoundTripper allows you to instrument external calls without 181 // calling StartExternalSegment by modifying the http.Client's Transport 182 // field. If the Transaction parameter is nil, the RoundTripper 183 // returned will look for a Transaction in the request's context (using 184 // FromContext). This is recommended because it allows you to reuse the 185 // same client for multiple transactions. 186 client := &http.Client{} 187 client.Transport = newrelic.NewRoundTripper(nil, client.Transport) 188 189 request, _ := http.NewRequest("GET", "http://example.com", nil) 190 // Since the transaction is already added to the inbound request's 191 // context by WrapHandleFunc, we just need to copy the context from the 192 // inbound request to the external request. 193 request = request.WithContext(r.Context()) 194 // Alternatively, if you don't want to copy entire context, and instead 195 // wanted just to add the transaction to the external request's context, 196 // you could do that like this: 197 // 198 // txn := newrelic.FromContext(r.Context()) 199 // request = newrelic.RequestWithTransactionContext(request, txn) 200 201 resp, err := client.Do(request) 202 if nil != err { 203 io.WriteString(w, err.Error()) 204 return 205 } 206 defer resp.Body.Close() 207 io.Copy(w, resp.Body) 208 } 209 210 func async(w http.ResponseWriter, r *http.Request) { 211 txn := newrelic.FromContext(r.Context()) 212 wg := &sync.WaitGroup{} 213 wg.Add(1) 214 go func(txn newrelic.Transaction) { 215 defer wg.Done() 216 defer newrelic.StartSegment(txn, "async").End() 217 time.Sleep(100 * time.Millisecond) 218 }(txn.NewGoroutine()) 219 220 segment := newrelic.StartSegment(txn, "wg.Wait") 221 wg.Wait() 222 segment.End() 223 w.Write([]byte("done!")) 224 } 225 226 func customMetric(w http.ResponseWriter, r *http.Request) { 227 txn := newrelic.FromContext(r.Context()) 228 for _, vals := range r.Header { 229 for _, v := range vals { 230 // This custom metric will have the name 231 // "Custom/HeaderLength" in the New Relic UI. 232 if nil != txn { 233 txn.Application().RecordCustomMetric("HeaderLength", float64(len(v))) 234 } 235 } 236 } 237 io.WriteString(w, "custom metric recorded") 238 } 239 240 func browser(w http.ResponseWriter, r *http.Request) { 241 txn := newrelic.FromContext(r.Context()) 242 hdr, err := txn.BrowserTimingHeader() 243 if nil != err { 244 log.Printf("unable to create browser timing header: %v", err) 245 } 246 // BrowserTimingHeader() will always return a header whose methods can 247 // be safely called. 248 if js := hdr.WithTags(); js != nil { 249 w.Write(js) 250 } 251 io.WriteString(w, "browser header page") 252 } 253 254 func mustGetEnv(key string) string { 255 if val := os.Getenv(key); "" != val { 256 return val 257 } 258 panic(fmt.Sprintf("environment variable %s unset", key)) 259 } 260 261 func main() { 262 cfg := newrelic.NewConfig("Example App", mustGetEnv("NEW_RELIC_LICENSE_KEY")) 263 cfg.Logger = newrelic.NewDebugLogger(os.Stdout) 264 265 app, err := newrelic.NewApplication(cfg) 266 if nil != err { 267 fmt.Println(err) 268 os.Exit(1) 269 } 270 271 http.HandleFunc(newrelic.WrapHandleFunc(app, "/", index)) 272 http.HandleFunc(newrelic.WrapHandleFunc(app, "/version", versionHandler)) 273 http.HandleFunc(newrelic.WrapHandleFunc(app, "/notice_error", noticeError)) 274 http.HandleFunc(newrelic.WrapHandleFunc(app, "/notice_error_with_attributes", noticeErrorWithAttributes)) 275 http.HandleFunc(newrelic.WrapHandleFunc(app, "/custom_event", customEvent)) 276 http.HandleFunc(newrelic.WrapHandleFunc(app, "/set_name", setName)) 277 http.HandleFunc(newrelic.WrapHandleFunc(app, "/add_attribute", addAttribute)) 278 http.HandleFunc(newrelic.WrapHandleFunc(app, "/ignore", ignore)) 279 http.HandleFunc(newrelic.WrapHandleFunc(app, "/segments", segments)) 280 http.HandleFunc(newrelic.WrapHandleFunc(app, "/mysql", mysql)) 281 http.HandleFunc(newrelic.WrapHandleFunc(app, "/external", external)) 282 http.HandleFunc(newrelic.WrapHandleFunc(app, "/roundtripper", roundtripper)) 283 http.HandleFunc(newrelic.WrapHandleFunc(app, "/custommetric", customMetric)) 284 http.HandleFunc(newrelic.WrapHandleFunc(app, "/browser", browser)) 285 http.HandleFunc(newrelic.WrapHandleFunc(app, "/async", async)) 286 http.HandleFunc(newrelic.WrapHandleFunc(app, "/message", message)) 287 288 http.HandleFunc("/background", func(w http.ResponseWriter, req *http.Request) { 289 // Transactions started without an http.Request are classified as 290 // background transactions. 291 txn := app.StartTransaction("background", nil, nil) 292 defer txn.End() 293 294 io.WriteString(w, "background transaction") 295 time.Sleep(150 * time.Millisecond) 296 }) 297 298 http.ListenAndServe(":8000", nil) 299 }