github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/middleware/xray/segment.go (about) 1 package xray 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net" 7 "os" 8 "strconv" 9 "sync" 10 11 "github.com/pkg/errors" 12 ) 13 14 type ( 15 // Segment represents a AWS X-Ray segment document. 16 Segment struct { 17 // Mutex used to synchronize access to segment. 18 *sync.Mutex 19 // Name is the name of the service reported to X-Ray. 20 Name string `json:"name"` 21 // Namespace identifies the source that created the segment. 22 Namespace string `json:"namespace"` 23 // Type is either the empty string or "subsegment". 24 Type string `json:"type,omitempty"` 25 // ID is a unique ID for the segment. 26 ID string `json:"id"` 27 // TraceID is the ID of the root trace. 28 TraceID string `json:"trace_id,omitempty"` 29 // ParentID is the ID of the parent segment when it is from a 30 // remote service. It is only initialized for the root segment. 31 ParentID string `json:"parent_id,omitempty"` 32 // StartTime is the segment start time. 33 StartTime float64 `json:"start_time,omitempty"` 34 // EndTime is the segment end time. 35 EndTime float64 `json:"end_time,omitempty"` 36 // InProgress is true if the segment hasn't completed yet. 37 InProgress bool `json:"in_progress"` 38 // HTTP contains the HTTP request and response information and is 39 // only initialized for the root segment. 40 HTTP *HTTP `json:"http,omitempty"` 41 // Cause contains information about an error that occurred while 42 // processing the request. 43 Cause *Cause `json:"cause,omitempty"` 44 // Error is true when a request causes an internal error. It is 45 // automatically set by Close when the response status code is 46 // 500 or more. 47 Error bool `json:"error"` 48 // Fault is true when a request results in an error that is due 49 // to the user. Typically it should be set when the response 50 // status code is between 400 and 500 (but not 429). 51 Fault bool `json:"fault"` 52 // Throttle is true when a request is throttled. It is set to 53 // true when the segment closes and the response status code is 54 // 429. Client code may set it to true manually as well. 55 Throttle bool `json:"throttle"` 56 // Annotations contains the segment annotations. 57 Annotations map[string]interface{} `json:"annotations,omitempty"` 58 // Metadata contains the segment metadata. 59 Metadata map[string]map[string]interface{} `json:"metadata,omitempty"` 60 // Subsegments contains all the subsegments. 61 Subsegments []*Segment `json:"subsegments,omitempty"` 62 // Parent is the subsegment parent, it's nil for the root 63 // segment. 64 Parent *Segment `json:"-"` 65 // conn is the UDP client to the X-Ray daemon. 66 conn net.Conn 67 // counter keeps track of the number of subsegments that have not 68 // completed yet. 69 counter int 70 } 71 72 // HTTP describes a HTTP request. 73 HTTP struct { 74 // Request contains the data reported about the incoming request. 75 Request *Request `json:"request,omitempty"` 76 // Response contains the data reported about the HTTP response. 77 Response *Response `json:"response,omitempty"` 78 } 79 80 // Request describes a HTTP request. 81 Request struct { 82 Method string `json:"method,omitempty"` 83 URL string `json:"url,omitempty"` 84 UserAgent string `json:"user_agent,omitempty"` 85 ClientIP string `json:"client_ip,omitempty"` 86 } 87 88 // Response describes a HTTP response. 89 Response struct { 90 Status int `json:"status"` 91 ContentLength int `json:"content_length"` 92 } 93 94 // Cause list errors that happens during the request. 95 Cause struct { 96 // ID to segment where error originated, exclusive with other 97 // fields. 98 ID string `json:"id,omitempty"` 99 // WorkingDirectory when error occurred. Exclusive with ID. 100 WorkingDirectory string `json:"working_directory,omitempty"` 101 // Exceptions contains the details on the error(s) that occurred 102 // when the request as processing. 103 Exceptions []*Exception `json:"exceptions,omitempty"` 104 } 105 106 // Exception describes an error. 107 Exception struct { 108 // Message contains the error message. 109 Message string `json:"message"` 110 // Stack is the error stack trace as initialized via the 111 // github.com/pkg/errors package. 112 Stack []*StackEntry `json:"stack"` 113 } 114 115 // StackEntry represents an entry in a error stacktrace. 116 StackEntry struct { 117 // Path to code file 118 Path string `json:"path"` 119 // Line number 120 Line int `json:"line"` 121 // Label is the line label if any 122 Label string `json:"label,omitempty"` 123 } 124 125 // key is the type used for context keys. 126 key int 127 ) 128 129 const ( 130 // udpHeader is the header of each segment sent to the daemon. 131 udpHeader = "{\"format\": \"json\", \"version\": 1}\n" 132 133 // maxStackDepth is the maximum number of stack frames reported. 134 maxStackDepth = 100 135 ) 136 137 type ( 138 causer interface { 139 Cause() error 140 } 141 stackTracer interface { 142 StackTrace() errors.StackTrace 143 } 144 ) 145 146 // NewSegment creates a new segment that gets written to the given connection 147 // on close. 148 func NewSegment(name, traceID, spanID string, conn net.Conn) *Segment { 149 return &Segment{ 150 Mutex: &sync.Mutex{}, 151 Name: name, 152 TraceID: traceID, 153 ID: spanID, 154 StartTime: now(), 155 InProgress: true, 156 conn: conn, 157 } 158 } 159 160 // RecordError traces an error. The client may also want to initialize the 161 // fault field of s. 162 // 163 // The trace contains a stack trace and a cause for the error if the argument 164 // was created using one of the New, Errorf, Wrap or Wrapf functions of the 165 // github.com/pkg/errors package. Otherwise the Stack and Cause fields are empty. 166 func (s *Segment) RecordError(e error) { 167 var xerr *Exception 168 if c, ok := e.(causer); ok { 169 xerr = &Exception{Message: c.Cause().Error()} 170 } else { 171 xerr = &Exception{Message: e.Error()} 172 } 173 if s, ok := e.(stackTracer); ok { 174 st := s.StackTrace() 175 ln := len(st) 176 if ln > maxStackDepth { 177 ln = maxStackDepth 178 } 179 frames := make([]*StackEntry, ln) 180 for i := 0; i < ln; i++ { 181 f := st[i] 182 line, _ := strconv.Atoi(fmt.Sprintf("%d", f)) 183 frames[i] = &StackEntry{ 184 Path: fmt.Sprintf("%s", f), 185 Line: line, 186 Label: fmt.Sprintf("%n", f), 187 } 188 } 189 xerr.Stack = frames 190 } 191 192 s.Lock() 193 defer s.Unlock() 194 if s.Cause == nil { 195 wd, _ := os.Getwd() 196 s.Cause = &Cause{WorkingDirectory: wd} 197 } 198 s.Cause.Exceptions = append(s.Cause.Exceptions, xerr) 199 p := s.Parent 200 for p != nil { 201 if p.Cause == nil { 202 p.Cause = &Cause{ID: s.ID} 203 } 204 p = p.Parent 205 } 206 } 207 208 // NewSubsegment creates a subsegment of s. 209 func (s *Segment) NewSubsegment(name string) *Segment { 210 s.Lock() 211 defer s.Unlock() 212 sub := &Segment{ 213 Mutex: &sync.Mutex{}, 214 ID: NewID(), 215 TraceID: s.TraceID, 216 ParentID: s.ID, 217 Type: "subsegment", 218 Name: name, 219 StartTime: now(), 220 InProgress: true, 221 Parent: s, 222 conn: s.conn, 223 } 224 s.Subsegments = append(s.Subsegments, sub) 225 s.counter++ 226 return sub 227 } 228 229 // Capture creates a subsegment to record the execution of the given function. 230 // Usage: 231 // 232 // s := xray.ContextSegment(ctx) 233 // s.Capture("slow-func", func() { 234 // // ... some long executing code 235 // }) 236 // 237 func (s *Segment) Capture(name string, fn func()) { 238 sub := s.NewSubsegment(name) 239 defer sub.Close() 240 fn() 241 } 242 243 // AddAnnotation adds a key-value pair that can be queried by AWS X-Ray. 244 func (s *Segment) AddAnnotation(key string, value string) { 245 s.addAnnotation(key, value) 246 } 247 248 // AddInt64Annotation adds a key-value pair that can be queried by AWS X-Ray. 249 func (s *Segment) AddInt64Annotation(key string, value int64) { 250 s.addAnnotation(key, value) 251 } 252 253 // AddBoolAnnotation adds a key-value pair that can be queried by AWS X-Ray. 254 func (s *Segment) AddBoolAnnotation(key string, value bool) { 255 s.addAnnotation(key, value) 256 } 257 258 // addAnnotation adds a key-value pair that can be queried by AWS X-Ray. 259 // AWS X-Ray only supports annotations of type string, integer or boolean. 260 func (s *Segment) addAnnotation(key string, value interface{}) { 261 s.Lock() 262 defer s.Unlock() 263 if s.Annotations == nil { 264 s.Annotations = make(map[string]interface{}) 265 } 266 s.Annotations[key] = value 267 } 268 269 // AddMetadata adds a key-value pair to the metadata.default attribute. 270 // Metadata is not queryable, but is recorded. 271 func (s *Segment) AddMetadata(key string, value string) { 272 s.addMetadata(key, value) 273 } 274 275 // AddInt64Metadata adds a key-value pair that can be queried by AWS X-Ray. 276 func (s *Segment) AddInt64Metadata(key string, value int64) { 277 s.addMetadata(key, value) 278 } 279 280 // AddBoolMetadata adds a key-value pair that can be queried by AWS X-Ray. 281 func (s *Segment) AddBoolMetadata(key string, value bool) { 282 s.addMetadata(key, value) 283 } 284 285 // addMetadata adds a key-value pair that can be queried by AWS X-Ray. 286 // AWS X-Ray only supports annotations of type string, integer or boolean. 287 func (s *Segment) addMetadata(key string, value interface{}) { 288 s.Lock() 289 defer s.Unlock() 290 if s.Metadata == nil { 291 s.Metadata = make(map[string]map[string]interface{}) 292 s.Metadata["default"] = make(map[string]interface{}) 293 } 294 s.Metadata["default"][key] = value 295 } 296 297 // Close closes the segment by setting its EndTime. 298 func (s *Segment) Close() { 299 s.Lock() 300 defer s.Unlock() 301 s.EndTime = now() 302 s.InProgress = false 303 if s.Parent != nil { 304 s.Parent.decrementCounter() 305 } 306 if s.counter <= 0 { 307 s.flush() 308 } 309 } 310 311 // flush sends the segment to the AWS X-Ray daemon. 312 func (s *Segment) flush() { 313 b, _ := json.Marshal(s) 314 // append so we make only one call to Write to be goroutine-safe 315 s.conn.Write(append([]byte(udpHeader), b...)) 316 } 317 318 // decrementCounter decrements the segment counter and flushes it if it's 0. 319 func (s *Segment) decrementCounter() { 320 s.Lock() 321 defer s.Unlock() 322 s.counter-- 323 if s.counter <= 0 && s.EndTime != 0 { 324 // Segment is closed and last subsegment closed, flush it 325 s.flush() 326 } 327 }