2019-10-09 08:33:19 +02:00
|
|
|
// Copyright 2019, OpenTelemetry 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
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
traceapi "cloud.google.com/go/trace/apiv2"
|
|
|
|
"golang.org/x/oauth2/google"
|
|
|
|
"google.golang.org/api/option"
|
|
|
|
|
2019-11-01 20:40:29 +02:00
|
|
|
"go.opentelemetry.io/otel/api/key"
|
2019-11-05 23:08:55 +02:00
|
|
|
export "go.opentelemetry.io/otel/sdk/export/trace"
|
2019-10-09 08:33:19 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Option is function type that is passed to the exporter initialization function.
|
|
|
|
type Option func(*options)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
// DefaultTraceAttributes will be appended to every span that is exported to
|
|
|
|
// Stackdriver Trace.
|
|
|
|
DefaultTraceAttributes map[string]interface{}
|
|
|
|
|
|
|
|
// 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 5 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. This is only used for Proto metrics export
|
|
|
|
// for now. The minimum number of workers is 1.
|
|
|
|
NumberOfWorkers int
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithProjectID sets Google Cloud Platform project as projectID.
|
|
|
|
// Without using this option, it automatically detects the project ID
|
|
|
|
// from the default credential detection process.
|
|
|
|
// Please find the detailed order of the default credentail detection proecess on the doc:
|
|
|
|
// https://godoc.org/golang.org/x/oauth2/google#FindDefaultCredentials
|
|
|
|
func WithProjectID(projectID string) func(o *options) {
|
|
|
|
return func(o *options) {
|
|
|
|
o.ProjectID = projectID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithOnError sets the hook to be called when there is an error
|
|
|
|
// occurred on uploading the span data to Stackdriver.
|
|
|
|
// If no custom hook is set, errors are logged.
|
|
|
|
func WithOnError(onError func(err error)) func(o *options) {
|
|
|
|
return func(o *options) {
|
|
|
|
o.OnError = onError
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithTraceClientOptions sets additionial client options for tracing.
|
|
|
|
func WithTraceClientOptions(opts []option.ClientOption) func(o *options) {
|
|
|
|
return func(o *options) {
|
|
|
|
o.TraceClientOptions = opts
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithContext sets the context that trace exporter and metric exporter
|
|
|
|
// relies on.
|
|
|
|
func WithContext(ctx context.Context) func(o *options) {
|
|
|
|
return func(o *options) {
|
|
|
|
o.Context = ctx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-14 19:45:17 +02:00
|
|
|
// WithTimeout sets the timeout for trace exporter and metric exporter
|
|
|
|
func WithTimeout(t time.Duration) func(o *options) {
|
|
|
|
return func(o *options) {
|
|
|
|
o.Timeout = t
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-09 08:33:19 +02:00
|
|
|
func (o *options) handleError(err error) {
|
|
|
|
if o.OnError != nil {
|
|
|
|
o.OnError(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Printf("Failed to export to Stackdriver: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaultTimeout is used as default when timeout is not set in newContextWithTimout.
|
|
|
|
const defaultTimeout = 5 * time.Second
|
|
|
|
|
|
|
|
// Exporter is a trace exporter that uploads data to Stackdriver.
|
|
|
|
//
|
|
|
|
// TODO(yoshifumi): add a metrics exporter once the spec definition
|
|
|
|
// process and the sampler implementation are done.
|
|
|
|
type Exporter struct {
|
|
|
|
traceExporter *traceExporter
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewExporter creates a new Exporter thats implements trace.Exporter.
|
|
|
|
//
|
|
|
|
// TODO(yoshifumi): add a metrics exporter one the spec definition
|
|
|
|
// process and the sampler implementation are done.
|
|
|
|
func NewExporter(opts ...Option) (*Exporter, error) {
|
2019-11-14 19:45:17 +02:00
|
|
|
o := options{Context: context.Background()}
|
2019-10-09 08:33:19 +02:00
|
|
|
for _, opt := range opts {
|
|
|
|
opt(&o)
|
|
|
|
}
|
|
|
|
if o.ProjectID == "" {
|
2019-11-14 19:45:17 +02:00
|
|
|
creds, err := google.FindDefaultCredentials(o.Context, traceapi.DefaultAuthScopes()...)
|
2019-10-09 08:33:19 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
te, err := newTraceExporter(&o)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Exporter{
|
|
|
|
traceExporter: te,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newContextWithTimeout(ctx context.Context, timeout time.Duration) (context.Context, func()) {
|
|
|
|
if timeout <= 0 {
|
|
|
|
timeout = defaultTimeout
|
|
|
|
}
|
|
|
|
return context.WithTimeout(ctx, timeout)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExportSpan exports a SpanData to Stackdriver Trace.
|
|
|
|
func (e *Exporter) ExportSpan(ctx context.Context, sd *export.SpanData) {
|
|
|
|
if len(e.traceExporter.o.DefaultTraceAttributes) > 0 {
|
|
|
|
sd = e.sdWithDefaultTraceAttributes(sd)
|
|
|
|
}
|
|
|
|
e.traceExporter.ExportSpan(ctx, sd)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExportSpans exports a slice of SpanData to Stackdriver Trace in batch
|
|
|
|
func (e *Exporter) ExportSpans(ctx context.Context, sds []*export.SpanData) {
|
|
|
|
e.traceExporter.ExportSpans(ctx, sds)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Exporter) sdWithDefaultTraceAttributes(sd *export.SpanData) *export.SpanData {
|
|
|
|
newSD := *sd
|
|
|
|
for k, v := range e.traceExporter.o.DefaultTraceAttributes {
|
|
|
|
switch val := v.(type) {
|
|
|
|
case bool:
|
|
|
|
newSD.Attributes = append(newSD.Attributes, key.New(k).Bool(val))
|
|
|
|
case int64:
|
|
|
|
newSD.Attributes = append(newSD.Attributes, key.New(k).Int64(val))
|
|
|
|
case float64:
|
|
|
|
newSD.Attributes = append(newSD.Attributes, key.New(k).Float64(val))
|
|
|
|
case string:
|
|
|
|
newSD.Attributes = append(newSD.Attributes, key.New(k).String(val))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
newSD.Attributes = append(newSD.Attributes, sd.Attributes...)
|
|
|
|
return &newSD
|
|
|
|
}
|