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  }