You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

197 lines
5.0 KiB

  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. // Package zipkin contains an trace exporter for Zipkin.
  15. package zipkin // import "go.opencensus.io/exporter/zipkin"
  16. import (
  17. "encoding/binary"
  18. "strconv"
  19. "github.com/openzipkin/zipkin-go/model"
  20. "github.com/openzipkin/zipkin-go/reporter"
  21. "go.opencensus.io/trace"
  22. )
  23. // Exporter is an implementation of trace.Exporter that uploads spans to a
  24. // Zipkin server.
  25. type Exporter struct {
  26. reporter reporter.Reporter
  27. localEndpoint *model.Endpoint
  28. }
  29. // NewExporter returns an implementation of trace.Exporter that uploads spans
  30. // to a Zipkin server.
  31. //
  32. // reporter is a Zipkin Reporter which will be used to send the spans. These
  33. // can be created with the openzipkin library, using one of the packages under
  34. // github.com/openzipkin/zipkin-go/reporter.
  35. //
  36. // localEndpoint sets the local endpoint of exported spans. It can be
  37. // constructed with github.com/openzipkin/zipkin-go.NewEndpoint, e.g.:
  38. // localEndpoint, err := NewEndpoint("my server", listener.Addr().String())
  39. // localEndpoint can be nil.
  40. func NewExporter(reporter reporter.Reporter, localEndpoint *model.Endpoint) *Exporter {
  41. return &Exporter{
  42. reporter: reporter,
  43. localEndpoint: localEndpoint,
  44. }
  45. }
  46. // ExportSpan exports a span to a Zipkin server.
  47. func (e *Exporter) ExportSpan(s *trace.SpanData) {
  48. e.reporter.Send(zipkinSpan(s, e.localEndpoint))
  49. }
  50. const (
  51. statusCodeTagKey = "error"
  52. statusDescriptionTagKey = "opencensus.status_description"
  53. )
  54. var (
  55. sampledTrue = true
  56. canonicalCodes = [...]string{
  57. "OK",
  58. "CANCELLED",
  59. "UNKNOWN",
  60. "INVALID_ARGUMENT",
  61. "DEADLINE_EXCEEDED",
  62. "NOT_FOUND",
  63. "ALREADY_EXISTS",
  64. "PERMISSION_DENIED",
  65. "RESOURCE_EXHAUSTED",
  66. "FAILED_PRECONDITION",
  67. "ABORTED",
  68. "OUT_OF_RANGE",
  69. "UNIMPLEMENTED",
  70. "INTERNAL",
  71. "UNAVAILABLE",
  72. "DATA_LOSS",
  73. "UNAUTHENTICATED",
  74. }
  75. )
  76. func canonicalCodeString(code int32) string {
  77. if code < 0 || int(code) >= len(canonicalCodes) {
  78. return "error code " + strconv.FormatInt(int64(code), 10)
  79. }
  80. return canonicalCodes[code]
  81. }
  82. func convertTraceID(t trace.TraceID) model.TraceID {
  83. return model.TraceID{
  84. High: binary.BigEndian.Uint64(t[:8]),
  85. Low: binary.BigEndian.Uint64(t[8:]),
  86. }
  87. }
  88. func convertSpanID(s trace.SpanID) model.ID {
  89. return model.ID(binary.BigEndian.Uint64(s[:]))
  90. }
  91. func spanKind(s *trace.SpanData) model.Kind {
  92. switch s.SpanKind {
  93. case trace.SpanKindClient:
  94. return model.Client
  95. case trace.SpanKindServer:
  96. return model.Server
  97. }
  98. return model.Undetermined
  99. }
  100. func zipkinSpan(s *trace.SpanData, localEndpoint *model.Endpoint) model.SpanModel {
  101. sc := s.SpanContext
  102. z := model.SpanModel{
  103. SpanContext: model.SpanContext{
  104. TraceID: convertTraceID(sc.TraceID),
  105. ID: convertSpanID(sc.SpanID),
  106. Sampled: &sampledTrue,
  107. },
  108. Kind: spanKind(s),
  109. Name: s.Name,
  110. Timestamp: s.StartTime,
  111. Shared: false,
  112. LocalEndpoint: localEndpoint,
  113. }
  114. if s.ParentSpanID != (trace.SpanID{}) {
  115. id := convertSpanID(s.ParentSpanID)
  116. z.ParentID = &id
  117. }
  118. if s, e := s.StartTime, s.EndTime; !s.IsZero() && !e.IsZero() {
  119. z.Duration = e.Sub(s)
  120. }
  121. // construct Tags from s.Attributes and s.Status.
  122. if len(s.Attributes) != 0 {
  123. m := make(map[string]string, len(s.Attributes)+2)
  124. for key, value := range s.Attributes {
  125. switch v := value.(type) {
  126. case string:
  127. m[key] = v
  128. case bool:
  129. if v {
  130. m[key] = "true"
  131. } else {
  132. m[key] = "false"
  133. }
  134. case int64:
  135. m[key] = strconv.FormatInt(v, 10)
  136. case float64:
  137. m[key] = strconv.FormatFloat(v, 'f', -1, 64)
  138. }
  139. }
  140. z.Tags = m
  141. }
  142. if s.Status.Code != 0 || s.Status.Message != "" {
  143. if z.Tags == nil {
  144. z.Tags = make(map[string]string, 2)
  145. }
  146. if s.Status.Code != 0 {
  147. z.Tags[statusCodeTagKey] = canonicalCodeString(s.Status.Code)
  148. }
  149. if s.Status.Message != "" {
  150. z.Tags[statusDescriptionTagKey] = s.Status.Message
  151. }
  152. }
  153. // construct Annotations from s.Annotations and s.MessageEvents.
  154. if len(s.Annotations) != 0 || len(s.MessageEvents) != 0 {
  155. z.Annotations = make([]model.Annotation, 0, len(s.Annotations)+len(s.MessageEvents))
  156. for _, a := range s.Annotations {
  157. z.Annotations = append(z.Annotations, model.Annotation{
  158. Timestamp: a.Time,
  159. Value: a.Message,
  160. })
  161. }
  162. for _, m := range s.MessageEvents {
  163. a := model.Annotation{
  164. Timestamp: m.Time,
  165. }
  166. switch m.EventType {
  167. case trace.MessageEventTypeSent:
  168. a.Value = "SENT"
  169. case trace.MessageEventTypeRecv:
  170. a.Value = "RECV"
  171. default:
  172. a.Value = "<?>"
  173. }
  174. z.Annotations = append(z.Annotations, a)
  175. }
  176. }
  177. return z
  178. }