sqlcomment.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. // Unless explicitly stated otherwise all files in this repository are licensed
  2. // under the Apache License Version 2.0.
  3. // This product includes software developed at Datadog (https://www.datadoghq.com/).
  4. // Copyright 2016 Datadog, Inc.
  5. package tracer
  6. import (
  7. "strconv"
  8. "strings"
  9. "gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
  10. "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
  11. "gopkg.in/DataDog/dd-trace-go.v1/internal/globalconfig"
  12. "gopkg.in/DataDog/dd-trace-go.v1/internal/log"
  13. )
  14. // SQLCommentInjectionMode represents the mode of SQL comment injection.
  15. //
  16. // Deprecated: Use DBMPropagationMode instead.
  17. type SQLCommentInjectionMode DBMPropagationMode
  18. const (
  19. // SQLInjectionUndefined represents the comment injection mode is not set. This is the same as SQLInjectionDisabled.
  20. SQLInjectionUndefined SQLCommentInjectionMode = SQLCommentInjectionMode(DBMPropagationModeUndefined)
  21. // SQLInjectionDisabled represents the comment injection mode where all injection is disabled.
  22. SQLInjectionDisabled SQLCommentInjectionMode = SQLCommentInjectionMode(DBMPropagationModeDisabled)
  23. // SQLInjectionModeService represents the comment injection mode where only service tags (name, env, version) are injected.
  24. SQLInjectionModeService SQLCommentInjectionMode = SQLCommentInjectionMode(DBMPropagationModeService)
  25. // SQLInjectionModeFull represents the comment injection mode where both service tags and tracing tags. Tracing tags include span id, trace id and sampling priority.
  26. SQLInjectionModeFull SQLCommentInjectionMode = SQLCommentInjectionMode(DBMPropagationModeFull)
  27. )
  28. // DBMPropagationMode represents the mode of dbm propagation.
  29. //
  30. // Note that enabling sql comment propagation results in potentially confidential data (service names)
  31. // being stored in the databases which can then be accessed by other 3rd parties that have been granted
  32. // access to the database.
  33. type DBMPropagationMode string
  34. const (
  35. // DBMPropagationModeUndefined represents the dbm propagation mode not being set. This is the same as DBMPropagationModeDisabled.
  36. DBMPropagationModeUndefined DBMPropagationMode = ""
  37. // DBMPropagationModeDisabled represents the dbm propagation mode where all propagation is disabled.
  38. DBMPropagationModeDisabled DBMPropagationMode = "disabled"
  39. // DBMPropagationModeService represents the dbm propagation mode where only service tags (name, env, version) are propagated to dbm.
  40. DBMPropagationModeService DBMPropagationMode = "service"
  41. // DBMPropagationModeFull represents the dbm propagation mode where both service tags and tracing tags are propagated. Tracing tags include span id, trace id and the sampled flag.
  42. DBMPropagationModeFull DBMPropagationMode = "full"
  43. )
  44. // Key names for SQL comment tags.
  45. const (
  46. sqlCommentTraceParent = "traceparent"
  47. sqlCommentParentService = "ddps"
  48. sqlCommentDBService = "dddbs"
  49. sqlCommentParentVersion = "ddpv"
  50. sqlCommentEnv = "dde"
  51. )
  52. // Current trace context version (see https://www.w3.org/TR/trace-context/#version)
  53. const w3cContextVersion = "00"
  54. // SQLCommentCarrier is a carrier implementation that injects a span context in a SQL query in the form
  55. // of a sqlcommenter formatted comment prepended to the original query text.
  56. // See https://google.github.io/sqlcommenter/spec/ for more details.
  57. type SQLCommentCarrier struct {
  58. Query string
  59. Mode DBMPropagationMode
  60. DBServiceName string
  61. SpanID uint64
  62. }
  63. // Inject injects a span context in the carrier's Query field as a comment.
  64. func (c *SQLCommentCarrier) Inject(spanCtx ddtrace.SpanContext) error {
  65. c.SpanID = generateSpanID(now())
  66. tags := make(map[string]string)
  67. switch c.Mode {
  68. case DBMPropagationModeUndefined:
  69. fallthrough
  70. case DBMPropagationModeDisabled:
  71. return nil
  72. case DBMPropagationModeFull:
  73. var (
  74. samplingPriority int
  75. traceID uint64
  76. )
  77. if ctx, ok := spanCtx.(*spanContext); ok {
  78. if sp, ok := ctx.samplingPriority(); ok {
  79. samplingPriority = sp
  80. }
  81. traceID = ctx.TraceID()
  82. }
  83. if traceID == 0 {
  84. traceID = c.SpanID
  85. }
  86. sampled := int64(0)
  87. if samplingPriority > 0 {
  88. sampled = 1
  89. }
  90. tags[sqlCommentTraceParent] = encodeTraceParent(traceID, c.SpanID, sampled)
  91. fallthrough
  92. case DBMPropagationModeService:
  93. var env, version string
  94. if ctx, ok := spanCtx.(*spanContext); ok {
  95. if e, ok := ctx.meta(ext.Environment); ok {
  96. env = e
  97. }
  98. if v, ok := ctx.meta(ext.Version); ok {
  99. version = v
  100. }
  101. }
  102. if globalconfig.ServiceName() != "" {
  103. tags[sqlCommentParentService] = globalconfig.ServiceName()
  104. }
  105. if env != "" {
  106. tags[sqlCommentEnv] = env
  107. }
  108. if version != "" {
  109. tags[sqlCommentParentVersion] = version
  110. }
  111. tags[sqlCommentDBService] = c.DBServiceName
  112. }
  113. c.Query = commentQuery(c.Query, tags)
  114. return nil
  115. }
  116. // encodeTraceParent encodes trace parent as per the w3c trace context spec (https://www.w3.org/TR/trace-context/#version).
  117. func encodeTraceParent(traceID uint64, spanID uint64, sampled int64) string {
  118. var b strings.Builder
  119. // traceparent has a fixed length of 55:
  120. // 2 bytes for the version, 32 for the trace id, 16 for the span id, 2 for the sampled flag and 3 for separators
  121. b.Grow(55)
  122. b.WriteString(w3cContextVersion)
  123. b.WriteRune('-')
  124. tid := strconv.FormatUint(traceID, 16)
  125. for i := 0; i < 32-len(tid); i++ {
  126. b.WriteRune('0')
  127. }
  128. b.WriteString(tid)
  129. b.WriteRune('-')
  130. sid := strconv.FormatUint(spanID, 16)
  131. for i := 0; i < 16-len(sid); i++ {
  132. b.WriteRune('0')
  133. }
  134. b.WriteString(sid)
  135. b.WriteRune('-')
  136. b.WriteRune('0')
  137. b.WriteString(strconv.FormatInt(sampled, 16))
  138. return b.String()
  139. }
  140. var (
  141. keyReplacer = strings.NewReplacer(" ", "%20", "!", "%21", "#", "%23", "$", "%24", "%", "%25", "&", "%26", "'", "%27", "(", "%28", ")", "%29", "*", "%2A", "+", "%2B", ",", "%2C", "/", "%2F", ":", "%3A", ";", "%3B", "=", "%3D", "?", "%3F", "@", "%40", "[", "%5B", "]", "%5D")
  142. valueReplacer = strings.NewReplacer(" ", "%20", "!", "%21", "#", "%23", "$", "%24", "%", "%25", "&", "%26", "'", "%27", "(", "%28", ")", "%29", "*", "%2A", "+", "%2B", ",", "%2C", "/", "%2F", ":", "%3A", ";", "%3B", "=", "%3D", "?", "%3F", "@", "%40", "[", "%5B", "]", "%5D", "'", "\\'")
  143. )
  144. // commentQuery returns the given query with the tags from the SQLCommentCarrier applied to it as a
  145. // prepended SQL comment. The format of the comment follows the sqlcommenter spec.
  146. // See https://google.github.io/sqlcommenter/spec/ for more details.
  147. func commentQuery(query string, tags map[string]string) string {
  148. if len(tags) == 0 {
  149. return ""
  150. }
  151. var b strings.Builder
  152. // the sqlcommenter specification dictates that tags should be sorted. Since we know all injected keys,
  153. // we skip a sorting operation by specifying the order of keys statically
  154. orderedKeys := []string{sqlCommentDBService, sqlCommentEnv, sqlCommentParentService, sqlCommentParentVersion, sqlCommentTraceParent}
  155. first := true
  156. for _, k := range orderedKeys {
  157. if v, ok := tags[k]; ok {
  158. // we need to URL-encode both keys and values and escape single quotes in values
  159. // https://google.github.io/sqlcommenter/spec/
  160. key := keyReplacer.Replace(k)
  161. val := valueReplacer.Replace(v)
  162. if first {
  163. b.WriteString("/*")
  164. } else {
  165. b.WriteRune(',')
  166. }
  167. b.WriteString(key)
  168. b.WriteRune('=')
  169. b.WriteRune('\'')
  170. b.WriteString(val)
  171. b.WriteRune('\'')
  172. first = false
  173. }
  174. }
  175. if b.Len() == 0 {
  176. return query
  177. }
  178. b.WriteString("*/")
  179. if query == "" {
  180. return b.String()
  181. }
  182. log.Debug("Injected sql comment: %s", b.String())
  183. b.WriteRune(' ')
  184. b.WriteString(query)
  185. return b.String()
  186. }
  187. // Extract is not implemented on SQLCommentCarrier
  188. func (c *SQLCommentCarrier) Extract() (ddtrace.SpanContext, error) {
  189. return nil, nil
  190. }