github.com/aldelo/common@v1.5.1/wrapper/gin/ginxray.go (about)

     1  package gin
     2  
     3  /*
     4   * Copyright 2020-2023 Aldelo, LP
     5   *
     6   * Licensed under the Apache License, Version 2.0 (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   *     http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  import (
    20  	"bytes"
    21  	util "github.com/aldelo/common"
    22  	"github.com/aldelo/common/wrapper/xray"
    23  	awsxray "github.com/aws/aws-xray-sdk-go/xray"
    24  	"github.com/gin-gonic/gin"
    25  	"log"
    26  	"net/http"
    27  	"strings"
    28  )
    29  
    30  const X_AMZN_TRACE_ID string = "X-Amzn-Trace-Id"
    31  const X_AMZN_SEG_ID string = "X-Amzn-Seg-Id"
    32  const X_AMZN_TR_ID string = "X-Amzn-Tr-Id"
    33  
    34  // XRayMiddleware to trace gin actions with aws xray
    35  //
    36  // if the method call is related to a prior xray segment,
    37  // use Headers "X-Amzn-Seg-Id" and "X-Amzn-Tr-Id" to deliver the parent SegmentID and TraceID to this call stack
    38  func XRayMiddleware() gin.HandlerFunc {
    39  	return func(c *gin.Context) {
    40  		if c == nil {
    41  			log.Println("!!! XRay Middleware Failed: Gin Context Nil !!!")
    42  			return
    43  		}
    44  
    45  		if strings.ToLower(util.Right(c.Request.URL.Path, 7)) != "/health" {
    46  			if seg := xray.NewSegmentFromHeader(c.Request); seg != nil && seg.Ready() {
    47  				// if there were parent segment ID and trace ID, relate it to newly created segment here
    48  				parentSegID := c.GetHeader(X_AMZN_SEG_ID)
    49  				traceID := c.GetHeader(X_AMZN_TR_ID)
    50  
    51  				if util.LenTrim(parentSegID) > 0 && util.LenTrim(traceID) > 0 {
    52  					seg.SetParentSegment(parentSegID, traceID)
    53  				}
    54  
    55  				// close segment
    56  				defer seg.Close()
    57  
    58  				c.Request = c.Request.WithContext(seg.Ctx)
    59  
    60  				w := &ResponseBodyWriterInterceptor{
    61  					ResponseWriter: c.Writer,
    62  					RespBody:       &bytes.Buffer{},
    63  				}
    64  				c.Writer = w
    65  
    66  				traceRequestData(c, seg.Seg)
    67  				c.Next()
    68  				traceResponseData(c, seg.Seg, w.RespBody)
    69  			} else {
    70  				c.Next()
    71  			}
    72  		} else {
    73  			c.Next()
    74  		}
    75  	}
    76  }
    77  
    78  func traceRequestData(c *gin.Context, seg *awsxray.Segment) {
    79  	if c == nil {
    80  		log.Println("!!! XRay Middleware Request Trace Failed: Gin Context Nil !!!")
    81  		return
    82  	}
    83  
    84  	if c.Request == nil {
    85  		log.Println("!!! XRay Middleware Request Trace Failed: Gin Context Http Request Nil !!!")
    86  		return
    87  	}
    88  
    89  	if c.Request.Header == nil {
    90  		log.Println("!!! XRay Middleware Request Trace Failed: Gin Context Http Request Header Nil !!!")
    91  		return
    92  	}
    93  
    94  	if c.Writer == nil {
    95  		log.Println("!!! XRay Middleware Request Trace Failed: Gin Context Http Response Writer Nil !!!")
    96  		return
    97  	}
    98  
    99  	if seg == nil {
   100  		log.Println("!!! XRay Middleware Request Trace Failed: XRay Segment Nil !!!")
   101  		return
   102  	}
   103  
   104  	req := c.Request
   105  
   106  	seg.Lock()
   107  
   108  	if segReq := getSegmentRequest(seg); segReq != nil {
   109  		segReq.Method = req.Method
   110  
   111  		if req.URL != nil {
   112  			segReq.URL = req.URL.String()
   113  		}
   114  
   115  		if xForwardedFor := req.Header.Get("X-Forwarded-For"); util.LenTrim(xForwardedFor) > 0 {
   116  			segReq.XForwardedFor = true
   117  			segReq.ClientIP = util.Trim(strings.Split(xForwardedFor, ",")[0])
   118  		} else {
   119  			segReq.XForwardedFor = false
   120  			segReq.ClientIP = req.RemoteAddr
   121  		}
   122  
   123  		segReq.UserAgent = req.UserAgent()
   124  
   125  		c.Writer.Header().Set(X_AMZN_TRACE_ID, getAmznTraceHeader(req, seg))
   126  
   127  		seg.Unlock()
   128  
   129  		reqHdr, _ := util.ReadHttpRequestHeaders(req)
   130  		if reqHdr != nil {
   131  			_ = seg.AddMetadata("Request_Headers", reqHdr)
   132  		} else {
   133  			_ = seg.AddMetadata("Request_Headers", "")
   134  		}
   135  
   136  		reqBdy, _ := util.ReadHttpRequestBody(req)
   137  		_ = seg.AddMetadata("Request_Body", string(reqBdy))
   138  	} else {
   139  		seg.Unlock()
   140  	}
   141  }
   142  
   143  func getSegmentRequest(seg *awsxray.Segment) *awsxray.RequestData {
   144  	if seg != nil {
   145  		if h := seg.GetHTTP(); h != nil {
   146  			if r := h.GetRequest(); r != nil {
   147  				return r
   148  			}
   149  		}
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  func getSegmentResponse(seg *awsxray.Segment) *awsxray.ResponseData {
   156  	if seg != nil {
   157  		if h := seg.GetHTTP(); h != nil {
   158  			if r := h.GetResponse(); r != nil {
   159  				return r
   160  			}
   161  		}
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  func getAmznTraceHeader(req *http.Request, seg *awsxray.Segment) string {
   168  	if req == nil {
   169  		log.Println("!!! XRay Middleware Request Trace Failed: (getAmznTraceHeader) Gin Context Http Request Nil !!!")
   170  		return ""
   171  	}
   172  
   173  	if req.Header == nil {
   174  		log.Println("!!! XRay Middleware Request Trace Failed: (getAmznTraceHeader) Gin Context Http Request Header Nil !!!")
   175  		return ""
   176  	}
   177  
   178  	if seg == nil {
   179  		log.Println("!!! XRay Middleware Request Trace Failed: (getAmznTraceHeader) XRay Segment Nil !!!")
   180  		return ""
   181  	}
   182  
   183  	// parse x-amzn-trace-id header parts to trace map
   184  	trace := make(map[string]string)
   185  	hdr := req.Header.Get(X_AMZN_TRACE_ID)
   186  
   187  	for _, p := range strings.Split(hdr, ";") {
   188  		kv := strings.SplitN(p, "=", 2)
   189  		k := util.Trim(kv[0])
   190  		v := ""
   191  		if len(kv) > 1 {
   192  			v = util.Trim(kv[1])
   193  		}
   194  		trace[k] = v
   195  	}
   196  
   197  	if util.LenTrim(trace["Root"]) > 0 {
   198  		seg.TraceID = trace["Root"]
   199  		seg.RequestWasTraced = true
   200  	}
   201  
   202  	if util.LenTrim(trace["Parent"]) > 0 {
   203  		seg.ParentID = trace["Parent"]
   204  	}
   205  
   206  	buf := bytes.Buffer{}
   207  	buf.WriteString("Root=")
   208  	buf.WriteString(seg.TraceID)
   209  
   210  	return buf.String()
   211  }
   212  
   213  func traceResponseData(c *gin.Context, seg *awsxray.Segment, respBody *bytes.Buffer) {
   214  	if c == nil {
   215  		log.Println("!!! XRay Middleware Response Trace Failed: Gin Context Nil !!!")
   216  		return
   217  	}
   218  
   219  	if c.Writer == nil {
   220  		log.Println("!!! XRay Middleware Response Trace Failed: Gin Context Http Response Writer Nil !!!")
   221  		return
   222  	}
   223  
   224  	if seg == nil {
   225  		log.Println("!!! XRay Middleware Request Trace Failed: XRay Segment Nil !!!")
   226  		return
   227  	}
   228  
   229  	status := c.Writer.Status()
   230  
   231  	seg.Lock()
   232  
   233  	if segResp := getSegmentResponse(seg); segResp != nil {
   234  		segResp.Status = status
   235  		segResp.ContentLength = c.Writer.Size()
   236  
   237  		if status >= 400 && status < 500 {
   238  			seg.Error = true
   239  		}
   240  
   241  		if status == 429 {
   242  			seg.Throttle = true
   243  		}
   244  
   245  		if status >= 500 && status < 600 {
   246  			seg.Fault = true
   247  		}
   248  
   249  		seg.Unlock()
   250  
   251  		respHdr, _ := util.ParseHttpHeader(c.Writer.Header())
   252  		if respHdr != nil {
   253  			_ = seg.AddMetadata("Response_Headers", respHdr)
   254  		} else {
   255  			_ = seg.AddMetadata("Response_Headers", "")
   256  		}
   257  
   258  		if respBody != nil {
   259  			_ = seg.AddMetadata("Response_Body", string(respBody.Bytes()))
   260  		} else {
   261  			_ = seg.AddMetadata("Response_Body", "")
   262  		}
   263  	} else {
   264  		seg.Unlock()
   265  	}
   266  }