| // 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 |
| } |