@@ -16,35 +16,74 @@ package trace
16
16
17
17
import (
18
18
"context"
19
+ "errors"
19
20
"fmt"
21
+ "os"
22
+ "sort"
23
+ "strings"
20
24
21
25
"go.opencensus.io/trace"
22
- "golang.org/x/xerrors"
26
+ "go.opentelemetry.io/otel"
27
+ "go.opentelemetry.io/otel/attribute"
28
+ "go.opentelemetry.io/otel/codes"
29
+ ottrace "go.opentelemetry.io/otel/trace"
23
30
"google.golang.org/api/googleapi"
24
31
"google.golang.org/genproto/googleapis/rpc/code"
25
32
"google.golang.org/grpc/status"
26
33
)
27
34
35
+ const (
36
+ telemetryPlatformTracing = "GOOGLE_API_GO_EXPERIMENTAL_TELEMETRY_PLATFORM_TRACING"
37
+ telemetryPlatformTracingOpenCensus = "opencensus"
38
+ telemetryPlatformTracingOpenTelemetry = "opentelemetry"
39
+ )
40
+
41
+ var (
42
+ // TODO(chrisdsmith): What is the correct name for the OpenTelemetry tracer?
43
+ OpenTelemetryTracerName string = "cloud.google.com/go/internal/trace"
44
+ )
45
+
46
+ func IsOpenCensusTracingEnabled () bool {
47
+ return ! IsOpenTelemetryTracingEnabled ()
48
+ }
49
+
50
+ func IsOpenTelemetryTracingEnabled () bool {
51
+ env := strings .TrimSpace (os .Getenv (telemetryPlatformTracing ))
52
+ return strings .EqualFold (env , telemetryPlatformTracingOpenTelemetry )
53
+ }
54
+
28
55
// StartSpan adds a span to the trace with the given name.
29
56
func StartSpan (ctx context.Context , name string ) context.Context {
30
- ctx , _ = trace .StartSpan (ctx , name )
57
+ if IsOpenTelemetryTracingEnabled () {
58
+ ctx , _ = otel .GetTracerProvider ().Tracer (OpenTelemetryTracerName ).Start (ctx , name )
59
+ } else {
60
+ ctx , _ = trace .StartSpan (ctx , name )
61
+ }
31
62
return ctx
32
63
}
33
64
34
65
// EndSpan ends a span with the given error.
35
66
func EndSpan (ctx context.Context , err error ) {
36
- span := trace .FromContext (ctx )
37
- if err != nil {
38
- span .SetStatus (toStatus (err ))
67
+ if IsOpenTelemetryTracingEnabled () {
68
+ span := ottrace .SpanFromContext (ctx )
69
+ if err != nil {
70
+ span .SetStatus (codes .Error , toOpenTelemetryStatusDescription (err ))
71
+ span .RecordError (err )
72
+ }
73
+ span .End ()
74
+ } else {
75
+ span := trace .FromContext (ctx )
76
+ if err != nil {
77
+ span .SetStatus (toStatus (err ))
78
+ }
79
+ span .End ()
39
80
}
40
- span .End ()
41
81
}
42
82
43
- // toStatus interrogates an error and converts it to an appropriate
44
- // OpenCensus status.
83
+ // toStatus converts an error to an equivalent OpenCensus status.
45
84
func toStatus (err error ) trace.Status {
46
85
var err2 * googleapi.Error
47
- if ok := xerrors .As (err , & err2 ); ok {
86
+ if ok := errors .As (err , & err2 ); ok {
48
87
return trace.Status {Code : httpStatusCodeToOCCode (err2 .Code ), Message : err2 .Message }
49
88
} else if s , ok := status .FromError (err ); ok {
50
89
return trace.Status {Code : int32 (s .Code ()), Message : s .Message ()}
@@ -53,6 +92,18 @@ func toStatus(err error) trace.Status {
53
92
}
54
93
}
55
94
95
+ // toOpenTelemetryStatus converts an error to an equivalent OpenTelemetry status description.
96
+ func toOpenTelemetryStatusDescription (err error ) string {
97
+ var err2 * googleapi.Error
98
+ if ok := errors .As (err , & err2 ); ok {
99
+ return err2 .Message
100
+ } else if s , ok := status .FromError (err ); ok {
101
+ return s .Message ()
102
+ } else {
103
+ return err .Error ()
104
+ }
105
+ }
106
+
56
107
// TODO(deklerk): switch to using OpenCensus function when it becomes available.
57
108
// Reference: https://github.com/googleapis/googleapis/blob/26b634d2724ac5dd30ae0b0cbfb01f07f2e4050e/google/rpc/code.proto
58
109
func httpStatusCodeToOCCode (httpStatusCode int ) int32 {
@@ -86,10 +137,25 @@ func httpStatusCodeToOCCode(httpStatusCode int) int32 {
86
137
}
87
138
}
88
139
140
+ // TracePrintf retrieves the current OpenCensus or OpenTelemetry span from context, then:
141
+ // * calls Span.Annotatef if OpenCensus is enabled; or
142
+ // * calls Span.AddEvent if OpenTelemetry is enabled.
143
+ //
89
144
// TODO: (odeke-em): perhaps just pass around spans due to the cost
90
145
// incurred from using trace.FromContext(ctx) yet we could avoid
91
146
// throwing away the work done by ctx, span := trace.StartSpan.
92
147
func TracePrintf (ctx context.Context , attrMap map [string ]interface {}, format string , args ... interface {}) {
148
+ if IsOpenTelemetryTracingEnabled () {
149
+ attrs := otAttrs (attrMap )
150
+ ottrace .SpanFromContext (ctx ).AddEvent (fmt .Sprintf (format , args ... ), ottrace .WithAttributes (attrs ... ))
151
+ } else {
152
+ attrs := ocAttrs (attrMap )
153
+ trace .FromContext (ctx ).Annotatef (attrs , format , args ... )
154
+ }
155
+ }
156
+
157
+ // ocAttrs converts a generic map to OpenCensus attributes.
158
+ func ocAttrs (attrMap map [string ]interface {}) []trace.Attribute {
93
159
var attrs []trace.Attribute
94
160
for k , v := range attrMap {
95
161
var a trace.Attribute
@@ -107,5 +173,34 @@ func TracePrintf(ctx context.Context, attrMap map[string]interface{}, format str
107
173
}
108
174
attrs = append (attrs , a )
109
175
}
110
- trace .FromContext (ctx ).Annotatef (attrs , format , args ... )
176
+ return attrs
177
+ }
178
+
179
+ // otAttrs converts a generic map to OpenTelemetry attributes.
180
+ func otAttrs (attrMap map [string ]interface {}) []attribute.KeyValue {
181
+ var attrs []attribute.KeyValue
182
+ // Sort the input map by its keys for predictable order in tests.
183
+ keys := make ([]string , 0 , len (attrMap ))
184
+ for k := range attrMap {
185
+ keys = append (keys , k )
186
+ }
187
+ sort .Strings (keys )
188
+ for _ , k := range keys {
189
+ v := attrMap [k ]
190
+ var a attribute.KeyValue
191
+ switch v := v .(type ) {
192
+ case string :
193
+ a = attribute .Key (k ).String (v )
194
+ case bool :
195
+ a = attribute .Key (k ).Bool (v )
196
+ case int :
197
+ a = attribute .Key (k ).Int (v )
198
+ case int64 :
199
+ a = attribute .Key (k ).Int64 (v )
200
+ default :
201
+ a = attribute .Key (k ).String (fmt .Sprintf ("%#v" , v ))
202
+ }
203
+ attrs = append (attrs , a )
204
+ }
205
+ return attrs
111
206
}
0 commit comments