This repository has been archived by the owner on Oct 3, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 79
/
stackdriver.go
532 lines (480 loc) · 20.6 KB
/
stackdriver.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
// Copyright 2018, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package stackdriver contains the OpenCensus exporters for
// Stackdriver Monitoring and Stackdriver Tracing.
//
// This exporter can be used to send metrics to Stackdriver Monitoring and traces
// to Stackdriver trace.
//
// The package uses Application Default Credentials to authenticate by default.
// See: https://developers.google.com/identity/protocols/application-default-credentials
//
// Alternatively, pass the authentication options in both the MonitoringClientOptions
// and the TraceClientOptions fields of Options.
//
// # Stackdriver Monitoring
//
// This exporter support exporting OpenCensus views to Stackdriver Monitoring.
// Each registered view becomes a metric in Stackdriver Monitoring, with the
// tags becoming labels.
//
// The aggregation function determines the metric kind: LastValue aggregations
// generate Gauge metrics and all other aggregations generate Cumulative metrics.
//
// In order to be able to push your stats to Stackdriver Monitoring, you must:
//
// 1. Create a Cloud project: https://support.google.com/cloud/answer/6251787?hl=en
// 2. Enable billing: https://support.google.com/cloud/answer/6288653#new-billing
// 3. Enable the Stackdriver Monitoring API: https://console.cloud.google.com/apis/dashboard
//
// These steps enable the API but don't require that your app is hosted on Google Cloud Platform.
//
// # Stackdriver Trace
//
// This exporter supports exporting Trace Spans to Stackdriver Trace. It also
// supports the Google "Cloud Trace" propagation format header.
package stackdriver // import "contrib.go.opencensus.io/exporter/stackdriver"
import (
"context"
"errors"
"fmt"
"log"
"os"
"path"
"strings"
"time"
metadataapi "cloud.google.com/go/compute/metadata"
traceapi "cloud.google.com/go/trace/apiv2"
"contrib.go.opencensus.io/exporter/stackdriver/monitoredresource"
opencensus "go.opencensus.io"
"go.opencensus.io/resource"
"go.opencensus.io/resource/resourcekeys"
"go.opencensus.io/stats/view"
"go.opencensus.io/trace"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
metricpb "google.golang.org/genproto/googleapis/api/metric"
monitoredrespb "google.golang.org/genproto/googleapis/api/monitoredres"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
commonpb "github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1"
metricspb "github.com/census-instrumentation/opencensus-proto/gen-go/metrics/v1"
resourcepb "github.com/census-instrumentation/opencensus-proto/gen-go/resource/v1"
"go.opencensus.io/metric/metricdata"
)
// Options contains options for configuring the exporter.
type Options struct {
// ProjectID is the identifier of the Stackdriver
// project the user is uploading the stats data to.
// If not set, this will default to your "Application Default Credentials".
// For details see: https://developers.google.com/accounts/docs/application-default-credentials.
//
// It will be used in the project_id label of a Stackdriver monitored
// resource if the resource does not inherently belong to a specific
// project, e.g. on-premise resource like k8s_container or generic_task.
ProjectID string
// Location is the identifier of the GCP or AWS cloud region/zone in which
// the data for a resource is stored.
// If not set, it will default to the location provided by the metadata server.
//
// It will be used in the location label of a Stackdriver monitored resource
// if the resource does not inherently belong to a specific project, e.g.
// on-premise resource like k8s_container or generic_task.
Location string
// OnError is the hook to be called when there is
// an error uploading the stats or tracing data.
// If no custom hook is set, errors are logged.
// Optional.
OnError func(err error)
// MonitoringClientOptions are additional options to be passed
// to the underlying Stackdriver Monitoring API client.
// Optional.
MonitoringClientOptions []option.ClientOption
// TraceClientOptions are additional options to be passed
// to the underlying Stackdriver Trace API client.
// Optional.
TraceClientOptions []option.ClientOption
// BundleDelayThreshold determines the max amount of time
// the exporter can wait before uploading view data or trace spans to
// the backend.
// Optional.
BundleDelayThreshold time.Duration
// BundleCountThreshold determines how many view data events or trace spans
// can be buffered before batch uploading them to the backend.
// Optional.
BundleCountThreshold int
// TraceSpansBufferMaxBytes is the maximum size (in bytes) of spans that
// will be buffered in memory before being dropped.
//
// If unset, a default of 8MB will be used.
TraceSpansBufferMaxBytes int
// Resource sets the MonitoredResource against which all views will be
// recorded by this exporter.
//
// All Stackdriver metrics created by this exporter are custom metrics,
// so only a limited number of MonitoredResource types are supported, see:
// https://cloud.google.com/monitoring/custom-metrics/creating-metrics#which-resource
//
// An important consideration when setting the Resource here is that
// Stackdriver Monitoring only allows a single writer per
// TimeSeries, see: https://cloud.google.com/monitoring/api/v3/metrics-details#intro-time-series
// A TimeSeries is uniquely defined by the metric type name
// (constructed from the view name and the MetricPrefix), the Resource field,
// and the set of label key/value pairs (in OpenCensus terminology: tag).
//
// If no custom Resource is set, a default MonitoredResource
// with type global and no resource labels will be used. If you explicitly
// set this field, you may also want to set custom DefaultMonitoringLabels.
//
// Deprecated: Use MonitoredResource instead.
Resource *monitoredrespb.MonitoredResource
// MonitoredResource sets the MonitoredResource against which all views will be
// recorded by this exporter.
//
// All Stackdriver metrics created by this exporter are custom metrics,
// so only a limited number of MonitoredResource types are supported, see:
// https://cloud.google.com/monitoring/custom-metrics/creating-metrics#which-resource
//
// An important consideration when setting the MonitoredResource here is that
// Stackdriver Monitoring only allows a single writer per
// TimeSeries, see: https://cloud.google.com/monitoring/api/v3/metrics-details#intro-time-series
// A TimeSeries is uniquely defined by the metric type name
// (constructed from the view name and the MetricPrefix), the MonitoredResource field,
// and the set of label key/value pairs (in OpenCensus terminology: tag).
//
// If no custom MonitoredResource is set AND if Resource is also not set then
// a default MonitoredResource with type global and no resource labels will be used.
// If you explicitly set this field, you may also want to set custom DefaultMonitoringLabels.
//
// This field replaces Resource field. If this is set then it will override the
// Resource field.
// Optional, but encouraged.
MonitoredResource monitoredresource.Interface
// ResourceDetector provides a hook to discover arbitrary resource information.
//
// The translation function provided in MapResource must be able to conver the
// the resource information to a Stackdriver monitored resource.
//
// If this field is unset, resource type and tags will automatically be discovered through
// the OC_RESOURCE_TYPE and OC_RESOURCE_LABELS environment variables.
ResourceDetector resource.Detector
// MapResource converts a OpenCensus resource to a Stackdriver monitored resource.
//
// If this field is unset, DefaultMapResource will be used which encodes a set of default
// conversions from auto-detected resources to well-known Stackdriver monitored resources.
MapResource func(*resource.Resource) *monitoredrespb.MonitoredResource
// MetricPrefix overrides the prefix of a Stackdriver metric names.
// Optional. If unset defaults to "custom.googleapis.com/opencensus/".
// If GetMetricPrefix is non-nil, this option is ignored.
MetricPrefix string
// GetMetricDisplayName allows customizing the display name for the metric
// associated with the given view. By default it will be:
// MetricPrefix + view.Name
GetMetricDisplayName func(view *view.View) string
// GetMetricType allows customizing the metric type for the given view.
// By default, it will be:
// "custom.googleapis.com/opencensus/" + view.Name
//
// See: https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.metricDescriptors#MetricDescriptor
// Depreacted. Use GetMetricPrefix instead.
GetMetricType func(view *view.View) string
// GetMetricPrefix allows customizing the metric prefix for the given metric name.
// If it is not set, MetricPrefix is used. If MetricPrefix is not set, it defaults to:
// "custom.googleapis.com/opencensus/"
//
// See: https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.metricDescriptors#MetricDescriptor
GetMetricPrefix func(name string) string
// DefaultTraceAttributes will be appended to every span that is exported to
// Stackdriver Trace.
DefaultTraceAttributes map[string]interface{}
// DefaultMonitoringLabels are labels added to every metric created by this
// exporter in Stackdriver Monitoring.
//
// If unset, this defaults to a single label with key "opencensus_task" and
// value "go-<pid>@<hostname>". This default ensures that the set of labels
// together with the default Resource (global) are unique to this
// process, as required by Stackdriver Monitoring.
//
// If you set DefaultMonitoringLabels, make sure that the Resource field
// together with these labels is unique to the
// current process. This is to ensure that there is only a single writer to
// each TimeSeries in Stackdriver.
//
// Set this to &Labels{} (a pointer to an empty Labels) to avoid getting the
// default "opencensus_task" label. You should only do this if you know that
// the Resource you set uniquely identifies this Go process.
DefaultMonitoringLabels *Labels
// Context allows you to provide a custom context for API calls.
//
// This context will be used several times: first, to create Stackdriver
// trace and metric clients, and then every time a new batch of traces or
// stats needs to be uploaded.
//
// Do not set a timeout on this context. Instead, set the Timeout option.
//
// If unset, context.Background() will be used.
Context context.Context
// SkipCMD enforces to skip all the CreateMetricDescriptor calls.
// These calls are important in order to configure the unit of the metrics,
// but in some cases all the exported metrics are builtin (unit is configured)
// or the unit is not important.
SkipCMD bool
// Timeout for all API calls. If not set, defaults to 12 seconds.
Timeout time.Duration
// ReportingInterval sets the interval between reporting metrics.
// If it is set to zero then default value is used.
ReportingInterval time.Duration
// NumberOfWorkers sets the number of go rountines that send requests
// to Stackdriver Monitoring and Trace. The minimum number of workers is 1.
NumberOfWorkers int
// ResourceByDescriptor may be provided to supply monitored resource dynamically
// based on the metric Descriptor. Most users will not need to set this,
// but should instead set ResourceDetector.
//
// The MonitoredResource and ResourceDetector fields are ignored if this
// field is set to a non-nil value.
//
// The ResourceByDescriptor is called to derive monitored resources from
// metric.Descriptor and the label map associated with the time-series.
// If any label is used for the derived resource then it will be removed
// from the label map. The remaining labels in the map are returned to
// be used with the time-series.
//
// If the func set to this field does not return valid resource even for one
// time-series then it will result into an error for the entire CreateTimeSeries request
// which may contain more than one time-series.
ResourceByDescriptor func(*metricdata.Descriptor, map[string]string) (map[string]string, monitoredresource.Interface)
// Override the user agent value supplied to Monitoring APIs and included as an
// attribute in trace data.
UserAgent string
}
const defaultTimeout = 12 * time.Second
var defaultDomain = path.Join("custom.googleapis.com", "opencensus")
var defaultUserAgent = fmt.Sprintf("opencensus-go %s; stackdriver-exporter %s", opencensus.Version(), version)
// Exporter is a stats and trace exporter that uploads data to Stackdriver.
//
// You can create a single Exporter and register it as both a trace exporter
// (to export to Stackdriver Trace) and a stats exporter (to integrate with
// Stackdriver Monitoring).
type Exporter struct {
traceExporter *traceExporter
statsExporter *statsExporter
}
// NewExporter creates a new Exporter that implements both stats.Exporter and
// trace.Exporter.
func NewExporter(o Options) (*Exporter, error) {
if o.ProjectID == "" {
ctx := o.Context
if ctx == nil {
ctx = context.Background()
}
creds, err := google.FindDefaultCredentials(ctx, traceapi.DefaultAuthScopes()...)
if err != nil {
return nil, fmt.Errorf("stackdriver: %v", err)
}
if creds.ProjectID == "" {
return nil, errors.New("stackdriver: no project found with application default credentials")
}
o.ProjectID = creds.ProjectID
}
if o.Location == "" {
if metadataapi.OnGCE() {
zone, err := metadataapi.Zone()
if err != nil {
// This error should be logged with a warning level.
err = fmt.Errorf("setting Stackdriver default location failed: %s", err)
if o.OnError != nil {
o.OnError(err)
} else {
log.Print(err)
}
} else {
o.Location = zone
}
}
}
if o.MonitoredResource != nil {
o.Resource = convertMonitoredResourceToPB(o.MonitoredResource)
}
if o.MapResource == nil {
o.MapResource = DefaultMapResource
}
if o.ResourceDetector != nil {
// For backwards-compatibility we still respect the deprecated resource field.
if o.Resource != nil {
return nil, errors.New("stackdriver: ResourceDetector must not be used in combination with deprecated resource fields")
}
res, err := o.ResourceDetector(o.Context)
if err != nil {
return nil, fmt.Errorf("stackdriver: detect resource: %s", err)
}
// Populate internal resource labels for defaulting project_id, location, and
// generic resource labels of applicable monitored resources.
if res.Labels == nil {
res.Labels = make(map[string]string)
}
res.Labels[stackdriverProjectID] = o.ProjectID
res.Labels[resourcekeys.CloudKeyZone] = o.Location
res.Labels[stackdriverGenericTaskNamespace] = "default"
res.Labels[stackdriverGenericTaskJob] = path.Base(os.Args[0])
res.Labels[stackdriverGenericTaskID] = getTaskValue()
log.Printf("OpenCensus detected resource: %v", res)
o.Resource = o.MapResource(res)
log.Printf("OpenCensus using monitored resource: %v", o.Resource)
}
if o.MetricPrefix != "" && !strings.HasSuffix(o.MetricPrefix, "/") {
o.MetricPrefix = o.MetricPrefix + "/"
}
if o.UserAgent == "" {
o.UserAgent = defaultUserAgent
}
se, err := newStatsExporter(o)
if err != nil {
return nil, err
}
te, err := newTraceExporter(o)
if err != nil {
return nil, err
}
return &Exporter{
statsExporter: se,
traceExporter: te,
}, nil
}
// ExportView exports to the Stackdriver Monitoring if view data
// has one or more rows.
// Deprecated: use ExportMetrics and StartMetricsExporter instead.
func (e *Exporter) ExportView(vd *view.Data) {
e.statsExporter.ExportView(vd)
}
// ExportMetricsProto exports OpenCensus Metrics Proto to Stackdriver Monitoring synchronously,
// without de-duping or adding proto metrics to the bundler.
func (e *Exporter) ExportMetricsProto(ctx context.Context, node *commonpb.Node, rsc *resourcepb.Resource, metrics []*metricspb.Metric) error {
_, err := e.statsExporter.PushMetricsProto(ctx, node, rsc, metrics)
return err
}
// PushMetricsProto similar with ExportMetricsProto but returns the number of dropped timeseries.
func (e *Exporter) PushMetricsProto(ctx context.Context, node *commonpb.Node, rsc *resourcepb.Resource, metrics []*metricspb.Metric) (int, error) {
return e.statsExporter.PushMetricsProto(ctx, node, rsc, metrics)
}
// ExportMetrics exports OpenCensus Metrics to Stackdriver Monitoring
func (e *Exporter) ExportMetrics(ctx context.Context, metrics []*metricdata.Metric) error {
return e.statsExporter.ExportMetrics(ctx, metrics)
}
// StartMetricsExporter starts exporter by creating an interval reader that reads metrics
// from all registered producers at set interval and exports them.
// Use StopMetricsExporter to stop exporting metrics.
// Previously, it required registering exporter to export stats collected by opencensus.
//
// exporter := stackdriver.NewExporter(stackdriver.Option{})
// view.RegisterExporter(exporter)
//
// Now, it requires to call StartMetricsExporter() to export stats and metrics collected by opencensus.
//
// exporter := stackdriver.NewExporter(stackdriver.Option{})
// exporter.StartMetricsExporter()
// defer exporter.StopMetricsExporter()
//
// Both approach should not be used simultaneously. Otherwise it may result into unknown behavior.
// Previous approach continues to work as before but will not report newly define metrics such
// as gauges.
func (e *Exporter) StartMetricsExporter() error {
return e.statsExporter.startMetricsReader()
}
// StopMetricsExporter stops exporter from exporting metrics.
func (e *Exporter) StopMetricsExporter() {
e.statsExporter.stopMetricsReader()
}
// Close closes client connections.
func (e *Exporter) Close() error {
tErr := e.traceExporter.close()
mErr := e.statsExporter.close()
// If the trace and stats exporter share client connections,
// closing the stats exporter will return an error indicating
// it is already closed. Ignore this error.
if status.Code(mErr) == codes.Canceled {
mErr = nil
}
if mErr != nil || tErr != nil {
return fmt.Errorf("error(s) closing trace client (%v), or metrics client (%v)", tErr, mErr)
}
return nil
}
// ExportSpan exports a SpanData to Stackdriver Trace.
func (e *Exporter) ExportSpan(sd *trace.SpanData) {
if len(e.traceExporter.o.DefaultTraceAttributes) > 0 {
sd = e.sdWithDefaultTraceAttributes(sd)
}
e.traceExporter.ExportSpan(sd)
}
// PushTraceSpans exports a bundle of OpenCensus Spans.
// Returns number of dropped spans.
func (e *Exporter) PushTraceSpans(ctx context.Context, node *commonpb.Node, rsc *resourcepb.Resource, spans []*trace.SpanData) (int, error) {
return e.traceExporter.pushTraceSpans(ctx, node, rsc, spans)
}
func (e *Exporter) sdWithDefaultTraceAttributes(sd *trace.SpanData) *trace.SpanData {
newSD := *sd
newSD.Attributes = make(map[string]interface{})
for k, v := range e.traceExporter.o.DefaultTraceAttributes {
newSD.Attributes[k] = v
}
for k, v := range sd.Attributes {
newSD.Attributes[k] = v
}
return &newSD
}
// Flush waits for exported data to be uploaded.
//
// This is useful if your program is ending and you do not
// want to lose recent stats or spans.
func (e *Exporter) Flush() {
e.statsExporter.Flush()
e.traceExporter.Flush()
}
// ViewToMetricDescriptor converts an OpenCensus view to a MetricDescriptor.
//
// This is useful for cases when you want to use your Go code as source of
// truth of metric descriptors. You can extract or define views in a central
// place, then call this method to generate MetricDescriptors.
func (e *Exporter) ViewToMetricDescriptor(ctx context.Context, v *view.View) (*metricpb.MetricDescriptor, error) {
return e.statsExporter.viewToMetricDescriptor(ctx, v)
}
func (o Options) handleError(err error) {
if o.OnError != nil {
o.OnError(err)
return
}
log.Printf("Failed to export to Stackdriver: %v", err)
}
func newContextWithTimeout(ctx context.Context, timeout time.Duration) (context.Context, func()) {
if ctx == nil {
ctx = context.Background()
}
if timeout <= 0 {
timeout = defaultTimeout
}
return context.WithTimeout(ctx, timeout)
}
// convertMonitoredResourceToPB converts MonitoredResource data in to
// protocol buffer.
func convertMonitoredResourceToPB(mr monitoredresource.Interface) *monitoredrespb.MonitoredResource {
mrpb := new(monitoredrespb.MonitoredResource)
var labels map[string]string
mrpb.Type, labels = mr.MonitoredResource()
mrpb.Labels = make(map[string]string)
for k, v := range labels {
mrpb.Labels[k] = v
}
return mrpb
}