trace.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. // Copyright 2018 Google LLC
  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 trace
  15. import (
  16. "context"
  17. "errors"
  18. "fmt"
  19. "os"
  20. "strings"
  21. "sync"
  22. "go.opencensus.io/trace"
  23. "go.opentelemetry.io/otel"
  24. "go.opentelemetry.io/otel/attribute"
  25. "go.opentelemetry.io/otel/codes"
  26. ottrace "go.opentelemetry.io/otel/trace"
  27. "google.golang.org/api/googleapi"
  28. "google.golang.org/genproto/googleapis/rpc/code"
  29. "google.golang.org/grpc/status"
  30. )
  31. const (
  32. // TelemetryPlatformTracingOpenCensus is the value to which the environment
  33. // variable GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING should be
  34. // set to enable OpenCensus tracing.
  35. TelemetryPlatformTracingOpenCensus = "opencensus"
  36. // TelemetryPlatformTracingOpenCensus is the value to which the environment
  37. // variable GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING should be
  38. // set to enable OpenTelemetry tracing.
  39. TelemetryPlatformTracingOpenTelemetry = "opentelemetry"
  40. // TelemetryPlatformTracingOpenCensus is the name of the environment
  41. // variable that can be set to change the default tracing from OpenCensus
  42. // to OpenTelemetry.
  43. TelemetryPlatformTracingVar = "GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING"
  44. // OpenTelemetryTracerName is the name given to the OpenTelemetry Tracer
  45. // when it is obtained from the OpenTelemetry TracerProvider.
  46. OpenTelemetryTracerName = "cloud.google.com/go"
  47. )
  48. var (
  49. // openTelemetryTracingEnabledMu guards access to openTelemetryTracingEnabled field
  50. openTelemetryTracingEnabledMu = sync.RWMutex{}
  51. // openTelemetryTracingEnabled is true if the environment variable
  52. // GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING is set to the
  53. // case-insensitive value "opentelemetry".
  54. openTelemetryTracingEnabled bool = strings.EqualFold(strings.TrimSpace(
  55. os.Getenv(TelemetryPlatformTracingVar)), TelemetryPlatformTracingOpenTelemetry)
  56. )
  57. // SetOpenTelemetryTracingEnabledField programmatically sets the value provided by GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING for the purpose of unit testing.
  58. // Do not invoke it directly. Intended for use only in unit tests. Restore original value after each test.
  59. func SetOpenTelemetryTracingEnabledField(enabled bool) {
  60. openTelemetryTracingEnabledMu.Lock()
  61. defer openTelemetryTracingEnabledMu.Unlock()
  62. openTelemetryTracingEnabled = enabled
  63. }
  64. // IsOpenCensusTracingEnabled returns true if the environment variable
  65. // GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING is NOT set to the
  66. // case-insensitive value "opentelemetry".
  67. func IsOpenCensusTracingEnabled() bool {
  68. return !IsOpenTelemetryTracingEnabled()
  69. }
  70. // IsOpenTelemetryTracingEnabled returns true if the environment variable
  71. // GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING is set to the
  72. // case-insensitive value "opentelemetry".
  73. func IsOpenTelemetryTracingEnabled() bool {
  74. openTelemetryTracingEnabledMu.RLock()
  75. defer openTelemetryTracingEnabledMu.RUnlock()
  76. return openTelemetryTracingEnabled
  77. }
  78. // StartSpan adds a span to the trace with the given name. If IsOpenCensusTracingEnabled
  79. // returns true, the span will be an OpenCensus span. If IsOpenTelemetryTracingEnabled
  80. // returns true, the span will be an OpenTelemetry span. Set the environment variable
  81. // GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING to the case-insensitive
  82. // value "opentelemetry" before loading the package to use OpenTelemetry tracing.
  83. // The default will remain OpenCensus until May 29, 2024, at which time the default will
  84. // switch to "opentelemetry" and explicitly setting the environment variable to
  85. // "opencensus" will be required to continue using OpenCensus tracing.
  86. func StartSpan(ctx context.Context, name string) context.Context {
  87. if IsOpenTelemetryTracingEnabled() {
  88. ctx, _ = otel.GetTracerProvider().Tracer(OpenTelemetryTracerName).Start(ctx, name)
  89. } else {
  90. ctx, _ = trace.StartSpan(ctx, name)
  91. }
  92. return ctx
  93. }
  94. // EndSpan ends a span with the given error. If IsOpenCensusTracingEnabled
  95. // returns true, the span will be an OpenCensus span. If IsOpenTelemetryTracingEnabled
  96. // returns true, the span will be an OpenTelemetry span. Set the environment variable
  97. // GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING to the case-insensitive
  98. // value "opentelemetry" before loading the package to use OpenTelemetry tracing.
  99. // The default will remain OpenCensus until May 29, 2024, at which time the default will
  100. // switch to "opentelemetry" and explicitly setting the environment variable to
  101. // "opencensus" will be required to continue using OpenCensus tracing.
  102. func EndSpan(ctx context.Context, err error) {
  103. if IsOpenTelemetryTracingEnabled() {
  104. span := ottrace.SpanFromContext(ctx)
  105. if err != nil {
  106. span.SetStatus(codes.Error, toOpenTelemetryStatusDescription(err))
  107. span.RecordError(err)
  108. }
  109. span.End()
  110. } else {
  111. span := trace.FromContext(ctx)
  112. if err != nil {
  113. span.SetStatus(toStatus(err))
  114. }
  115. span.End()
  116. }
  117. }
  118. // toStatus converts an error to an equivalent OpenCensus status.
  119. func toStatus(err error) trace.Status {
  120. var err2 *googleapi.Error
  121. if ok := errors.As(err, &err2); ok {
  122. return trace.Status{Code: httpStatusCodeToOCCode(err2.Code), Message: err2.Message}
  123. } else if s, ok := status.FromError(err); ok {
  124. return trace.Status{Code: int32(s.Code()), Message: s.Message()}
  125. } else {
  126. return trace.Status{Code: int32(code.Code_UNKNOWN), Message: err.Error()}
  127. }
  128. }
  129. // toOpenTelemetryStatus converts an error to an equivalent OpenTelemetry status description.
  130. func toOpenTelemetryStatusDescription(err error) string {
  131. var err2 *googleapi.Error
  132. if ok := errors.As(err, &err2); ok {
  133. return err2.Message
  134. } else if s, ok := status.FromError(err); ok {
  135. return s.Message()
  136. } else {
  137. return err.Error()
  138. }
  139. }
  140. // TODO(deklerk): switch to using OpenCensus function when it becomes available.
  141. // Reference: https://github.com/googleapis/googleapis/blob/26b634d2724ac5dd30ae0b0cbfb01f07f2e4050e/google/rpc/code.proto
  142. func httpStatusCodeToOCCode(httpStatusCode int) int32 {
  143. switch httpStatusCode {
  144. case 200:
  145. return int32(code.Code_OK)
  146. case 499:
  147. return int32(code.Code_CANCELLED)
  148. case 500:
  149. return int32(code.Code_UNKNOWN) // Could also be Code_INTERNAL, Code_DATA_LOSS
  150. case 400:
  151. return int32(code.Code_INVALID_ARGUMENT) // Could also be Code_OUT_OF_RANGE
  152. case 504:
  153. return int32(code.Code_DEADLINE_EXCEEDED)
  154. case 404:
  155. return int32(code.Code_NOT_FOUND)
  156. case 409:
  157. return int32(code.Code_ALREADY_EXISTS) // Could also be Code_ABORTED
  158. case 403:
  159. return int32(code.Code_PERMISSION_DENIED)
  160. case 401:
  161. return int32(code.Code_UNAUTHENTICATED)
  162. case 429:
  163. return int32(code.Code_RESOURCE_EXHAUSTED)
  164. case 501:
  165. return int32(code.Code_UNIMPLEMENTED)
  166. case 503:
  167. return int32(code.Code_UNAVAILABLE)
  168. default:
  169. return int32(code.Code_UNKNOWN)
  170. }
  171. }
  172. // TracePrintf retrieves the current OpenCensus or OpenTelemetry span from context, then:
  173. // * calls Span.Annotatef if OpenCensus is enabled; or
  174. // * calls Span.AddEvent if OpenTelemetry is enabled.
  175. //
  176. // If IsOpenCensusTracingEnabled returns true, the expected span must be an
  177. // OpenCensus span. If IsOpenTelemetryTracingEnabled returns true, the expected
  178. // span must be an OpenTelemetry span. Set the environment variable
  179. // GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING to the case-insensitive
  180. // value "opentelemetry" before loading the package to use OpenTelemetry tracing.
  181. // The default will remain OpenCensus until May 29, 2024, at which time the default will
  182. // switch to "opentelemetry" and explicitly setting the environment variable to
  183. // "opencensus" will be required to continue using OpenCensus tracing.
  184. func TracePrintf(ctx context.Context, attrMap map[string]interface{}, format string, args ...interface{}) {
  185. if IsOpenTelemetryTracingEnabled() {
  186. attrs := otAttrs(attrMap)
  187. ottrace.SpanFromContext(ctx).AddEvent(fmt.Sprintf(format, args...), ottrace.WithAttributes(attrs...))
  188. } else {
  189. attrs := ocAttrs(attrMap)
  190. // TODO: (odeke-em): perhaps just pass around spans due to the cost
  191. // incurred from using trace.FromContext(ctx) yet we could avoid
  192. // throwing away the work done by ctx, span := trace.StartSpan.
  193. trace.FromContext(ctx).Annotatef(attrs, format, args...)
  194. }
  195. }
  196. // ocAttrs converts a generic map to OpenCensus attributes.
  197. func ocAttrs(attrMap map[string]interface{}) []trace.Attribute {
  198. var attrs []trace.Attribute
  199. for k, v := range attrMap {
  200. var a trace.Attribute
  201. switch v := v.(type) {
  202. case string:
  203. a = trace.StringAttribute(k, v)
  204. case bool:
  205. a = trace.BoolAttribute(k, v)
  206. case int:
  207. a = trace.Int64Attribute(k, int64(v))
  208. case int64:
  209. a = trace.Int64Attribute(k, v)
  210. default:
  211. a = trace.StringAttribute(k, fmt.Sprintf("%#v", v))
  212. }
  213. attrs = append(attrs, a)
  214. }
  215. return attrs
  216. }
  217. // otAttrs converts a generic map to OpenTelemetry attributes.
  218. func otAttrs(attrMap map[string]interface{}) []attribute.KeyValue {
  219. var attrs []attribute.KeyValue
  220. for k, v := range attrMap {
  221. var a attribute.KeyValue
  222. switch v := v.(type) {
  223. case string:
  224. a = attribute.Key(k).String(v)
  225. case bool:
  226. a = attribute.Key(k).Bool(v)
  227. case int:
  228. a = attribute.Key(k).Int(v)
  229. case int64:
  230. a = attribute.Key(k).Int64(v)
  231. default:
  232. a = attribute.Key(k).String(fmt.Sprintf("%#v", v))
  233. }
  234. attrs = append(attrs, a)
  235. }
  236. return attrs
  237. }