github.com/klaytn/klaytn@v1.10.2/networks/rpc/http_datadog.go (about) 1 package rpc 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "os" 9 "strconv" 10 "strings" 11 12 httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http" 13 "gopkg.in/DataDog/dd-trace-go.v1/ddtrace" 14 "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" 15 ) 16 17 type tagInfo struct { 18 header string 19 key string 20 } 21 22 type DatadogTracer struct { 23 Tags []tagInfo 24 Service string 25 KlaytnResponse bool 26 } 27 28 func newDatadogTracer() *DatadogTracer { 29 v := os.Getenv("DD_TRACE_ENABLED") 30 if v == "" { 31 return nil 32 } 33 34 if ddTraceEnabled, err := strconv.ParseBool(v); ddTraceEnabled == false || err != nil { 35 return nil 36 } 37 38 headers := strings.Split(os.Getenv("DD_TRACE_HEADER_TAGS"), ",") 39 tags := make([]tagInfo, len(headers)) 40 for i, header := range headers { 41 header = strings.TrimSpace(header) 42 header = strings.ToLower(header) 43 key := fmt.Sprintf("http.%s", header) 44 45 tags[i].header = header 46 tags[i].key = key 47 } 48 service := os.Getenv("DD_SERVICE") 49 50 klaytnResponse := false 51 if v := os.Getenv("DD_KLAYTN_RPC_RESPONSE"); v != "" { 52 var err error 53 klaytnResponse, err = strconv.ParseBool(v) 54 if err != nil { 55 return nil 56 } 57 } 58 59 tracer.Start() 60 61 return &DatadogTracer{tags, service, klaytnResponse} 62 } 63 64 func newDatadogHTTPHandler(ddTracer *DatadogTracer, handler http.Handler) http.Handler { 65 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 66 defer func() { 67 if err := recover(); err != nil { 68 logger.ErrorWithStack("Datadog http handler panic", "err", err) 69 } 70 }() 71 72 reqMethod := "" 73 reqParam := "" 74 75 // parse RPC requests 76 reqs, isBatch, err := getRPCRequests(r) 77 if err != nil || len(reqs) < 1 { 78 // The error will be handled in `handler.ServeHTTP()` and printed with `printRPCErrorLog()` 79 logger.Debug("failed to parse RPC request", "err", err, "len(reqs)", len(reqs)) 80 } else { 81 reqMethod = reqs[0].Method 82 if isBatch { 83 reqMethod += "_batch" 84 } 85 encoded, _ := json.Marshal(reqs[0].Params) 86 reqParam = string(encoded) 87 } 88 89 // datadog transaction name contains the first API method of the request 90 resource := fmt.Sprintf("%s %s %s", r.Method, r.URL.String(), reqMethod) 91 92 // duplicate writer 93 dupW := &dupWriter{ 94 ResponseWriter: w, 95 body: bytes.NewBufferString(""), 96 } 97 98 spanOpts := []ddtrace.StartSpanOption{ 99 tracer.Tag("request.method", reqMethod), 100 tracer.Tag("request.params", reqParam), 101 } 102 103 for _, ti := range ddTracer.Tags { 104 var tag tracer.StartSpanOption 105 if ti.header == "remote_addr" { 106 tag = tracer.Tag(ti.key, r.RemoteAddr) 107 } else { 108 tag = tracer.Tag(ti.key, r.Header.Get(ti.header)) 109 } 110 spanOpts = append(spanOpts, tag) 111 } 112 113 responseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 114 handler.ServeHTTP(w, r) 115 if isBatch { 116 var rpcReturns []interface{} 117 if err := json.Unmarshal(dupW.body.Bytes(), &rpcReturns); err == nil { 118 for i, rpcReturn := range rpcReturns { 119 ddTracer.traceBatchRpcResponse(r, rpcReturn, reqs[i], i) 120 } 121 } 122 } else { 123 span, _ := tracer.SpanFromContext(r.Context()) 124 ddTracer.traceRpcResponse(dupW.body.Bytes(), reqMethod, span) 125 } 126 }) 127 128 httptrace.TraceAndServe(responseHandler, dupW, r, &httptrace.ServeConfig{ 129 Service: ddTracer.Service, 130 Resource: resource, 131 QueryParams: true, 132 SpanOpts: spanOpts, 133 }) 134 }) 135 } 136 137 func (dt *DatadogTracer) traceBatchRpcResponse(r *http.Request, rpcReturn interface{}, req *jsonrpcMessage, offset int) { 138 span, _ := tracer.StartSpanFromContext(r.Context(), "response.batch") 139 defer span.Finish() 140 span.SetTag("offset", offset) 141 if data, err := json.Marshal(rpcReturn); err == nil { 142 dt.traceRpcResponse(data, req.Method, span) 143 } 144 } 145 146 func (dt *DatadogTracer) traceRpcResponse(response []byte, method string, span tracer.Span) { 147 var rpcError jsonErrResponse 148 if err := json.Unmarshal(response, &rpcError); err == nil && rpcError.Error.Code != 0 { 149 span.SetTag("response.code", rpcError.Error.Code) 150 151 errJson, _ := json.Marshal(rpcError.Error) 152 span.SetTag("response.error", string(errJson)) 153 span.SetTag("error", string(errJson)) 154 155 message := fmt.Sprintf("RPC error response %v", span) 156 logger.Error(message, "rpcErr", rpcError.Error.Message, "method", method) 157 } else { 158 span.SetTag("response.code", 0) 159 } 160 161 if dt.KlaytnResponse { 162 var rpcSuccess jsonSuccessResponse 163 if err := json.Unmarshal(response, &rpcSuccess); err == nil && rpcSuccess.Result != nil { 164 successJson, _ := json.Marshal(rpcSuccess.Result) 165 span.SetTag("response.success", string(successJson)) 166 } 167 } 168 }