distributed_tracing.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. package internal
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "fmt"
  6. "time"
  7. )
  8. type distTraceVersion [2]int
  9. func (v distTraceVersion) major() int { return v[0] }
  10. func (v distTraceVersion) minor() int { return v[1] }
  11. const (
  12. // CallerType is the Type field's value for outbound payloads.
  13. CallerType = "App"
  14. )
  15. var (
  16. currentDistTraceVersion = distTraceVersion([2]int{0 /* Major */, 1 /* Minor */})
  17. callerUnknown = payloadCaller{Type: "Unknown", App: "Unknown", Account: "Unknown", TransportType: "Unknown"}
  18. )
  19. // timestampMillis allows raw payloads to use exact times, and marshalled
  20. // payloads to use times in millis.
  21. type timestampMillis time.Time
  22. func (tm *timestampMillis) UnmarshalJSON(data []byte) error {
  23. var millis uint64
  24. if err := json.Unmarshal(data, &millis); nil != err {
  25. return err
  26. }
  27. *tm = timestampMillis(timeFromUnixMilliseconds(millis))
  28. return nil
  29. }
  30. func (tm timestampMillis) MarshalJSON() ([]byte, error) {
  31. return json.Marshal(TimeToUnixMilliseconds(tm.Time()))
  32. }
  33. func (tm timestampMillis) Time() time.Time { return time.Time(tm) }
  34. func (tm *timestampMillis) Set(t time.Time) { *tm = timestampMillis(t) }
  35. // Payload is the distributed tracing payload.
  36. type Payload struct {
  37. payloadCaller
  38. TransactionID string `json:"tx,omitempty"`
  39. ID string `json:"id,omitempty"`
  40. TracedID string `json:"tr"`
  41. Priority Priority `json:"pr"`
  42. Sampled *bool `json:"sa"`
  43. Timestamp timestampMillis `json:"ti"`
  44. TransportDuration time.Duration `json:"-"`
  45. }
  46. type payloadCaller struct {
  47. TransportType string `json:"-"`
  48. Type string `json:"ty"`
  49. App string `json:"ap"`
  50. Account string `json:"ac"`
  51. TrustedAccountKey string `json:"tk,omitempty"`
  52. }
  53. // IsValid validates the payload data by looking for missing fields.
  54. // Returns an error if there's a problem, nil if everything's fine
  55. func (p Payload) IsValid() error {
  56. // If a payload is missing both `guid` and `transactionId` is received,
  57. // a ParseException supportability metric should be generated.
  58. if "" == p.TransactionID && "" == p.ID {
  59. return ErrPayloadMissingField{message: "missing both guid/id and TransactionId/tx"}
  60. }
  61. if "" == p.Type {
  62. return ErrPayloadMissingField{message: "missing Type/ty"}
  63. }
  64. if "" == p.Account {
  65. return ErrPayloadMissingField{message: "missing Account/ac"}
  66. }
  67. if "" == p.App {
  68. return ErrPayloadMissingField{message: "missing App/ap"}
  69. }
  70. if "" == p.TracedID {
  71. return ErrPayloadMissingField{message: "missing TracedID/tr"}
  72. }
  73. if p.Timestamp.Time().IsZero() || 0 == p.Timestamp.Time().Unix() {
  74. return ErrPayloadMissingField{message: "missing Timestamp/ti"}
  75. }
  76. return nil
  77. }
  78. func (p Payload) text(v distTraceVersion) []byte {
  79. js, _ := json.Marshal(struct {
  80. Version distTraceVersion `json:"v"`
  81. Data Payload `json:"d"`
  82. }{
  83. Version: v,
  84. Data: p,
  85. })
  86. return js
  87. }
  88. // Text implements newrelic.DistributedTracePayload.
  89. func (p Payload) Text() string {
  90. t := p.text(currentDistTraceVersion)
  91. return string(t)
  92. }
  93. // HTTPSafe implements newrelic.DistributedTracePayload.
  94. func (p Payload) HTTPSafe() string {
  95. t := p.text(currentDistTraceVersion)
  96. return base64.StdEncoding.EncodeToString(t)
  97. }
  98. // SetSampled lets us set a value for our *bool,
  99. // which we can't do directly since a pointer
  100. // needs something to point at.
  101. func (p *Payload) SetSampled(sampled bool) {
  102. p.Sampled = &sampled
  103. }
  104. // ErrPayloadParse indicates that the payload was malformed.
  105. type ErrPayloadParse struct{ err error }
  106. func (e ErrPayloadParse) Error() string {
  107. return fmt.Sprintf("unable to parse inbound payload: %s", e.err.Error())
  108. }
  109. // ErrPayloadMissingField indicates there's a required field that's missing
  110. type ErrPayloadMissingField struct{ message string }
  111. func (e ErrPayloadMissingField) Error() string {
  112. return fmt.Sprintf("payload is missing required fields: %s", e.message)
  113. }
  114. // ErrTrustedAccountKey indicates we don't trust the account, per the
  115. // new trusted_account_key routine.
  116. type ErrTrustedAccountKey struct{ Message string }
  117. func (e ErrTrustedAccountKey) Error() string {
  118. return fmt.Sprintf("trusted account key error: %s", e.Message)
  119. }
  120. // ErrUnsupportedPayloadVersion indicates that the major version number is
  121. // unknown.
  122. type ErrUnsupportedPayloadVersion struct{ version int }
  123. func (e ErrUnsupportedPayloadVersion) Error() string {
  124. return fmt.Sprintf("unsupported major version number %d", e.version)
  125. }
  126. // AcceptPayload parses the inbound distributed tracing payload.
  127. func AcceptPayload(p interface{}) (*Payload, error) {
  128. var payload Payload
  129. if byteSlice, ok := p.([]byte); ok {
  130. p = string(byteSlice)
  131. }
  132. switch v := p.(type) {
  133. case string:
  134. if "" == v {
  135. return nil, nil
  136. }
  137. var decoded []byte
  138. if '{' == v[0] {
  139. decoded = []byte(v)
  140. } else {
  141. var err error
  142. decoded, err = base64.StdEncoding.DecodeString(v)
  143. if nil != err {
  144. return nil, ErrPayloadParse{err: err}
  145. }
  146. }
  147. envelope := struct {
  148. Version distTraceVersion `json:"v"`
  149. Data json.RawMessage `json:"d"`
  150. }{}
  151. if err := json.Unmarshal(decoded, &envelope); nil != err {
  152. return nil, ErrPayloadParse{err: err}
  153. }
  154. if 0 == envelope.Version.major() && 0 == envelope.Version.minor() {
  155. return nil, ErrPayloadMissingField{message: "missing v"}
  156. }
  157. if envelope.Version.major() > currentDistTraceVersion.major() {
  158. return nil, ErrUnsupportedPayloadVersion{
  159. version: envelope.Version.major(),
  160. }
  161. }
  162. if err := json.Unmarshal(envelope.Data, &payload); nil != err {
  163. return nil, ErrPayloadParse{err: err}
  164. }
  165. case Payload:
  166. payload = v
  167. default:
  168. // Could be a shim payload (if the app is not yet connected).
  169. return nil, nil
  170. }
  171. // Ensure that we don't have a reference to the input payload: we don't
  172. // want to change it, it could be used multiple times.
  173. alloc := new(Payload)
  174. *alloc = payload
  175. return alloc, nil
  176. }