github.com/newrelic/go-agent@v3.26.0+incompatible/_integrations/nrmicro/nrmicro.go (about) 1 // Copyright 2020 New Relic Corporation. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package nrmicro 5 6 import ( 7 "context" 8 "net/http" 9 "net/url" 10 "strings" 11 12 "github.com/micro/go-micro/client" 13 "github.com/micro/go-micro/errors" 14 "github.com/micro/go-micro/metadata" 15 "github.com/micro/go-micro/registry" 16 "github.com/micro/go-micro/server" 17 newrelic "github.com/newrelic/go-agent" 18 "github.com/newrelic/go-agent/internal" 19 "github.com/newrelic/go-agent/internal/integrationsupport" 20 ) 21 22 type nrWrapper struct { 23 client.Client 24 } 25 26 var addrMap = make(map[string]string) 27 28 func startExternal(ctx context.Context, procedure, host string) (context.Context, newrelic.ExternalSegment) { 29 var seg newrelic.ExternalSegment 30 if txn := newrelic.FromContext(ctx); nil != txn { 31 seg = newrelic.ExternalSegment{ 32 StartTime: newrelic.StartSegmentNow(txn), 33 Procedure: procedure, 34 Library: "Micro", 35 Host: host, 36 } 37 ctx = addDTPayloadToContext(ctx, txn) 38 } 39 return ctx, seg 40 } 41 42 func startMessage(ctx context.Context, topic string) (context.Context, *newrelic.MessageProducerSegment) { 43 var seg *newrelic.MessageProducerSegment 44 if txn := newrelic.FromContext(ctx); nil != txn { 45 seg = &newrelic.MessageProducerSegment{ 46 StartTime: newrelic.StartSegmentNow(txn), 47 Library: "Micro", 48 DestinationType: newrelic.MessageTopic, 49 DestinationName: topic, 50 } 51 ctx = addDTPayloadToContext(ctx, txn) 52 } 53 return ctx, seg 54 } 55 56 func addDTPayloadToContext(ctx context.Context, txn newrelic.Transaction) context.Context { 57 payload := txn.CreateDistributedTracePayload() 58 if txt := payload.Text(); "" != txt { 59 md, _ := metadata.FromContext(ctx) 60 md = metadata.Copy(md) 61 md[newrelic.DistributedTracePayloadHeader] = txt 62 ctx = metadata.NewContext(ctx, md) 63 } 64 return ctx 65 } 66 67 func extractHost(addr string) string { 68 if host, ok := addrMap[addr]; ok { 69 return host 70 } 71 72 host := addr 73 if strings.HasPrefix(host, "unix://") { 74 host = "localhost" 75 } else if u, err := url.Parse(host); nil == err { 76 if "" != u.Host { 77 host = u.Host 78 } else { 79 host = u.Path 80 } 81 } 82 83 addrMap[addr] = host 84 return host 85 } 86 87 func (n *nrWrapper) Publish(ctx context.Context, msg client.Message, opts ...client.PublishOption) error { 88 ctx, seg := startMessage(ctx, msg.Topic()) 89 defer seg.End() 90 return n.Client.Publish(ctx, msg, opts...) 91 } 92 93 func (n *nrWrapper) Stream(ctx context.Context, req client.Request, opts ...client.CallOption) (client.Stream, error) { 94 ctx, seg := startExternal(ctx, req.Endpoint(), req.Service()) 95 defer seg.End() 96 return n.Client.Stream(ctx, req, opts...) 97 } 98 99 func (n *nrWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error { 100 ctx, seg := startExternal(ctx, req.Endpoint(), req.Service()) 101 defer seg.End() 102 return n.Client.Call(ctx, req, rsp, opts...) 103 } 104 105 // ClientWrapper wraps a Micro `client.Client` 106 // (https://godoc.org/github.com/micro/go-micro/client#Client) instance. External 107 // segments will be created for each call to the client's `Call`, `Publish`, or 108 // `Stream` methods. The `newrelic.Transaction` must be put into the context 109 // using `newrelic.NewContext` 110 // (https://godoc.org/github.com/newrelic/go-agent#NewContext) when calling one 111 // of those methods. 112 func ClientWrapper() client.Wrapper { 113 return func(c client.Client) client.Client { 114 return &nrWrapper{c} 115 } 116 } 117 118 // CallWrapper wraps the `Call` method of a Micro `client.Client` 119 // (https://godoc.org/github.com/micro/go-micro/client#Client) instance. 120 // External segments will be created for each call to the client's `Call` 121 // method. The `newrelic.Transaction` must be put into the context using 122 // `newrelic.NewContext` 123 // (https://godoc.org/github.com/newrelic/go-agent#NewContext) when calling 124 // `Call`. 125 func CallWrapper() client.CallWrapper { 126 return func(cf client.CallFunc) client.CallFunc { 127 return func(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error { 128 ctx, seg := startExternal(ctx, req.Endpoint(), req.Service()) 129 defer seg.End() 130 return cf(ctx, node, req, rsp, opts) 131 } 132 } 133 } 134 135 // HandlerWrapper wraps a Micro `server.Server` 136 // (https://godoc.org/github.com/micro/go-micro/server#Server) handler. 137 // 138 // This wrapper creates transactions for inbound calls. The transaction is 139 // added to the call context and can be accessed in your method handlers using 140 // `newrelic.FromContext` 141 // (https://godoc.org/github.com/newrelic/go-agent#FromContext). 142 // 143 // When an error is returned and it is of type Micro `errors.Error` 144 // (https://godoc.org/github.com/micro/go-micro/errors#Error), the error that 145 // is recorded is based on the HTTP response code (found in the Code field). 146 // Values above 400 or below 100 that are not in the IgnoreStatusCodes 147 // (https://godoc.org/github.com/newrelic/go-agent#Config) configuration list 148 // are recorded as errors. A 500 response code and corresponding error is 149 // recorded when the error is of any other type. A 200 response code is 150 // recorded if no error is returned. 151 func HandlerWrapper(app newrelic.Application) server.HandlerWrapper { 152 return func(fn server.HandlerFunc) server.HandlerFunc { 153 if app == nil { 154 return fn 155 } 156 return func(ctx context.Context, req server.Request, rsp interface{}) error { 157 txn := startWebTransaction(ctx, app, req) 158 defer txn.End() 159 err := fn(newrelic.NewContext(ctx, txn), req, rsp) 160 var code int 161 if err != nil { 162 if t, ok := err.(*errors.Error); ok { 163 code = int(t.Code) 164 } else { 165 code = 500 166 } 167 } else { 168 code = 200 169 } 170 txn.WriteHeader(code) 171 return err 172 } 173 } 174 } 175 176 // SubscriberWrapper wraps a Micro `server.Subscriber` 177 // (https://godoc.org/github.com/micro/go-micro/server#Subscriber) instance. 178 // 179 // This wrapper creates background transactions for inbound calls. The 180 // transaction is added to the subscriber context and can be accessed in your 181 // subscriber handlers using `newrelic.FromContext` 182 // (https://godoc.org/github.com/newrelic/go-agent#FromContext). 183 // 184 // The attribute `"message.routingKey"` is added to the transaction and will 185 // appear on transaction events, transaction traces, error events, and error 186 // traces. It corresponds to the `server.Message`'s Topic 187 // (https://godoc.org/github.com/micro/go-micro/server#Message). 188 // 189 // If a Subscriber returns an error, it will be recorded and reported. 190 func SubscriberWrapper(app newrelic.Application) server.SubscriberWrapper { 191 return func(fn server.SubscriberFunc) server.SubscriberFunc { 192 if app == nil { 193 return fn 194 } 195 return func(ctx context.Context, m server.Message) (err error) { 196 namer := internal.MessageMetricKey{ 197 Library: "Micro", 198 DestinationType: string(newrelic.MessageTopic), 199 DestinationName: m.Topic(), 200 Consumer: true, 201 } 202 txn := app.StartTransaction(namer.Name(), nil, nil) 203 defer txn.End() 204 integrationsupport.AddAgentAttribute(txn, internal.AttributeMessageRoutingKey, m.Topic(), nil) 205 md, ok := metadata.FromContext(ctx) 206 if ok { 207 txn.AcceptDistributedTracePayload(newrelic.TransportHTTP, md[newrelic.DistributedTracePayloadHeader]) 208 } 209 ctx = newrelic.NewContext(ctx, txn) 210 err = fn(ctx, m) 211 if err != nil { 212 txn.NoticeError(err) 213 } 214 return err 215 } 216 } 217 } 218 219 func startWebTransaction(ctx context.Context, app newrelic.Application, req server.Request) newrelic.Transaction { 220 var hdrs http.Header 221 if md, ok := metadata.FromContext(ctx); ok { 222 hdrs = make(http.Header, len(md)) 223 for k, v := range md { 224 hdrs.Add(k, v) 225 } 226 } 227 txn := app.StartTransaction(req.Endpoint(), nil, nil) 228 u := &url.URL{ 229 Scheme: "micro", 230 Host: req.Service(), 231 Path: req.Endpoint(), 232 } 233 234 webReq := newrelic.NewStaticWebRequest(hdrs, u, req.Method(), newrelic.TransportHTTP) 235 txn.SetWebRequest(webReq) 236 237 return txn 238 }