1// Copyright 2017, OpenCensus Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package stackdriver 16 17import ( 18 "fmt" 19 "math" 20 "strconv" 21 "time" 22 "unicode/utf8" 23 24 timestamppb "github.com/golang/protobuf/ptypes/timestamp" 25 wrapperspb "github.com/golang/protobuf/ptypes/wrappers" 26 "go.opencensus.io/plugin/ochttp" 27 "go.opencensus.io/trace" 28 monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres" 29 tracepb "google.golang.org/genproto/googleapis/devtools/cloudtrace/v2" 30 statuspb "google.golang.org/genproto/googleapis/rpc/status" 31) 32 33const ( 34 maxAnnotationEventsPerSpan = 32 35 maxMessageEventsPerSpan = 128 36 maxAttributeStringValue = 256 37 agentLabel = "g.co/agent" 38 39 labelHTTPHost = `/http/host` 40 labelHTTPMethod = `/http/method` 41 labelHTTPStatusCode = `/http/status_code` 42 labelHTTPPath = `/http/path` 43 labelHTTPUserAgent = `/http/user_agent` 44) 45 46// proto returns a protocol buffer representation of a SpanData. 47func protoFromSpanData(s *trace.SpanData, projectID string, mr *monitoredrespb.MonitoredResource) *tracepb.Span { 48 if s == nil { 49 return nil 50 } 51 52 traceIDString := s.SpanContext.TraceID.String() 53 spanIDString := s.SpanContext.SpanID.String() 54 55 name := s.Name 56 switch s.SpanKind { 57 case trace.SpanKindClient: 58 name = "Sent." + name 59 case trace.SpanKindServer: 60 name = "Recv." + name 61 } 62 63 sp := &tracepb.Span{ 64 Name: "projects/" + projectID + "/traces/" + traceIDString + "/spans/" + spanIDString, 65 SpanId: spanIDString, 66 DisplayName: trunc(name, 128), 67 StartTime: timestampProto(s.StartTime), 68 EndTime: timestampProto(s.EndTime), 69 SameProcessAsParentSpan: &wrapperspb.BoolValue{Value: !s.HasRemoteParent}, 70 } 71 if p := s.ParentSpanID; p != (trace.SpanID{}) { 72 sp.ParentSpanId = p.String() 73 } 74 if s.Status.Code != 0 || s.Status.Message != "" { 75 sp.Status = &statuspb.Status{Code: s.Status.Code, Message: s.Status.Message} 76 } 77 78 var annotations, droppedAnnotationsCount, messageEvents, droppedMessageEventsCount int 79 copyAttributes(&sp.Attributes, s.Attributes) 80 81 // Copy MonitoredResources as span Attributes 82 sp.Attributes = copyMonitoredResourceAttributes(sp.Attributes, mr) 83 84 as := s.Annotations 85 for i, a := range as { 86 if annotations >= maxAnnotationEventsPerSpan { 87 droppedAnnotationsCount = len(as) - i 88 break 89 } 90 annotation := &tracepb.Span_TimeEvent_Annotation{Description: trunc(a.Message, maxAttributeStringValue)} 91 copyAttributes(&annotation.Attributes, a.Attributes) 92 event := &tracepb.Span_TimeEvent{ 93 Time: timestampProto(a.Time), 94 Value: &tracepb.Span_TimeEvent_Annotation_{Annotation: annotation}, 95 } 96 annotations++ 97 if sp.TimeEvents == nil { 98 sp.TimeEvents = &tracepb.Span_TimeEvents{} 99 } 100 sp.TimeEvents.TimeEvent = append(sp.TimeEvents.TimeEvent, event) 101 } 102 103 if sp.Attributes == nil { 104 sp.Attributes = &tracepb.Span_Attributes{ 105 AttributeMap: make(map[string]*tracepb.AttributeValue), 106 } 107 } 108 109 // Only set the agent label if it is not already set. That enables the 110 // OpenCensus agent/collector to set the agent label based on the library that 111 // sent the span to the agent. 112 if _, hasAgent := sp.Attributes.AttributeMap[agentLabel]; !hasAgent { 113 sp.Attributes.AttributeMap[agentLabel] = &tracepb.AttributeValue{ 114 Value: &tracepb.AttributeValue_StringValue{ 115 StringValue: trunc(userAgent, maxAttributeStringValue), 116 }, 117 } 118 } 119 120 es := s.MessageEvents 121 for i, e := range es { 122 if messageEvents >= maxMessageEventsPerSpan { 123 droppedMessageEventsCount = len(es) - i 124 break 125 } 126 messageEvents++ 127 if sp.TimeEvents == nil { 128 sp.TimeEvents = &tracepb.Span_TimeEvents{} 129 } 130 sp.TimeEvents.TimeEvent = append(sp.TimeEvents.TimeEvent, &tracepb.Span_TimeEvent{ 131 Time: timestampProto(e.Time), 132 Value: &tracepb.Span_TimeEvent_MessageEvent_{ 133 MessageEvent: &tracepb.Span_TimeEvent_MessageEvent{ 134 Type: tracepb.Span_TimeEvent_MessageEvent_Type(e.EventType), 135 Id: e.MessageID, 136 UncompressedSizeBytes: e.UncompressedByteSize, 137 CompressedSizeBytes: e.CompressedByteSize, 138 }, 139 }, 140 }) 141 } 142 143 if droppedAnnotationsCount != 0 || droppedMessageEventsCount != 0 { 144 if sp.TimeEvents == nil { 145 sp.TimeEvents = &tracepb.Span_TimeEvents{} 146 } 147 sp.TimeEvents.DroppedAnnotationsCount = clip32(droppedAnnotationsCount) 148 sp.TimeEvents.DroppedMessageEventsCount = clip32(droppedMessageEventsCount) 149 } 150 151 if len(s.Links) > 0 { 152 sp.Links = &tracepb.Span_Links{} 153 sp.Links.Link = make([]*tracepb.Span_Link, 0, len(s.Links)) 154 for _, l := range s.Links { 155 link := &tracepb.Span_Link{ 156 TraceId: l.TraceID.String(), 157 SpanId: l.SpanID.String(), 158 Type: tracepb.Span_Link_Type(l.Type), 159 } 160 copyAttributes(&link.Attributes, l.Attributes) 161 sp.Links.Link = append(sp.Links.Link, link) 162 } 163 } 164 return sp 165} 166 167// timestampProto creates a timestamp proto for a time.Time. 168func timestampProto(t time.Time) *timestamppb.Timestamp { 169 return ×tamppb.Timestamp{ 170 Seconds: t.Unix(), 171 Nanos: int32(t.Nanosecond()), 172 } 173} 174 175// copyMonitoredResourceAttributes copies proto monitoredResource to proto map field (Span_Attributes) 176// it creates the map if it is nil. 177func copyMonitoredResourceAttributes(out *tracepb.Span_Attributes, mr *monitoredrespb.MonitoredResource) *tracepb.Span_Attributes { 178 if mr == nil { 179 return out 180 } 181 if out == nil { 182 out = &tracepb.Span_Attributes{} 183 } 184 if out.AttributeMap == nil { 185 out.AttributeMap = make(map[string]*tracepb.AttributeValue) 186 } 187 for k, v := range mr.Labels { 188 av := attributeValue(v) 189 out.AttributeMap[fmt.Sprintf("g.co/r/%s/%s", mr.Type, k)] = av 190 } 191 return out 192} 193 194// copyAttributes copies a map of attributes to a proto map field. 195// It creates the map if it is nil. 196func copyAttributes(out **tracepb.Span_Attributes, in map[string]interface{}) { 197 if len(in) == 0 { 198 return 199 } 200 if *out == nil { 201 *out = &tracepb.Span_Attributes{} 202 } 203 if (*out).AttributeMap == nil { 204 (*out).AttributeMap = make(map[string]*tracepb.AttributeValue) 205 } 206 var dropped int32 207 for key, value := range in { 208 av := attributeValue(value) 209 if av == nil { 210 continue 211 } 212 switch key { 213 case ochttp.PathAttribute: 214 (*out).AttributeMap[labelHTTPPath] = av 215 case ochttp.HostAttribute: 216 (*out).AttributeMap[labelHTTPHost] = av 217 case ochttp.MethodAttribute: 218 (*out).AttributeMap[labelHTTPMethod] = av 219 case ochttp.UserAgentAttribute: 220 (*out).AttributeMap[labelHTTPUserAgent] = av 221 case ochttp.StatusCodeAttribute: 222 (*out).AttributeMap[labelHTTPStatusCode] = av 223 default: 224 if len(key) > 128 { 225 dropped++ 226 continue 227 } 228 (*out).AttributeMap[key] = av 229 } 230 } 231 (*out).DroppedAttributesCount = dropped 232} 233 234func attributeValue(v interface{}) *tracepb.AttributeValue { 235 switch value := v.(type) { 236 case bool: 237 return &tracepb.AttributeValue{ 238 Value: &tracepb.AttributeValue_BoolValue{BoolValue: value}, 239 } 240 case int64: 241 return &tracepb.AttributeValue{ 242 Value: &tracepb.AttributeValue_IntValue{IntValue: value}, 243 } 244 case float64: 245 // TODO: set double value if Stackdriver Trace support it in the future. 246 return &tracepb.AttributeValue{ 247 Value: &tracepb.AttributeValue_StringValue{ 248 StringValue: trunc(strconv.FormatFloat(value, 'f', -1, 64), 249 maxAttributeStringValue)}, 250 } 251 case string: 252 return &tracepb.AttributeValue{ 253 Value: &tracepb.AttributeValue_StringValue{StringValue: trunc(value, maxAttributeStringValue)}, 254 } 255 } 256 return nil 257} 258 259// trunc returns a TruncatableString truncated to the given limit. 260func trunc(s string, limit int) *tracepb.TruncatableString { 261 if len(s) > limit { 262 b := []byte(s[:limit]) 263 for { 264 r, size := utf8.DecodeLastRune(b) 265 if r == utf8.RuneError && size == 1 { 266 b = b[:len(b)-1] 267 } else { 268 break 269 } 270 } 271 return &tracepb.TruncatableString{ 272 Value: string(b), 273 TruncatedByteCount: clip32(len(s) - len(b)), 274 } 275 } 276 return &tracepb.TruncatableString{ 277 Value: s, 278 TruncatedByteCount: 0, 279 } 280} 281 282// clip32 clips an int to the range of an int32. 283func clip32(x int) int32 { 284 if x < math.MinInt32 { 285 return math.MinInt32 286 } 287 if x > math.MaxInt32 { 288 return math.MaxInt32 289 } 290 return int32(x) 291} 292