Skip to content

Commit

Permalink
Add Stackdriver Exporter and configs.
Browse files Browse the repository at this point in the history
  • Loading branch information
songy23 committed Jul 17, 2019
1 parent 1aa826a commit be95842
Show file tree
Hide file tree
Showing 6 changed files with 388 additions and 0 deletions.
26 changes: 26 additions & 0 deletions exporter/stackdriverexporter/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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 stackdriverexporter

import "github.com/open-telemetry/opentelemetry-service/config/configmodels"

// Config defines configuration for Stackdriver exporter.
type Config struct {
configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct.
ProjectID string `mapstructure:"project"`
EnableTracing bool `mapstructure:"enable_tracing"`
EnableMetrics bool `mapstructure:"enable_metrics"`
Prefix string `mapstructure:"metric_prefix"`
}
52 changes: 52 additions & 0 deletions exporter/stackdriverexporter/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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 stackdriverexporter

import (
"path"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/open-telemetry/opentelemetry-service/config"
"github.com/open-telemetry/opentelemetry-service/config/configmodels"
"github.com/open-telemetry/opentelemetry-service/exporter"
)

var _ = config.RegisterTestFactories()

func TestLoadConfig(t *testing.T) {

factory := exporter.GetFactory(typeStr)

cfg, err := config.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"))

require.NoError(t, err)
require.NotNil(t, cfg)

assert.Equal(t, len(cfg.Exporters), 2)

r0 := cfg.Exporters["stackdriver"]
assert.Equal(t, r0, factory.CreateDefaultConfig())

r1 := cfg.Exporters["stackdriver/customname"].(*Config)
assert.Equal(t, r1.ExporterSettings,
configmodels.ExporterSettings{
TypeVal: typeStr,
NameVal: "stackdriver/customname",
Enabled: true,
})
}
66 changes: 66 additions & 0 deletions exporter/stackdriverexporter/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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 stackdriverexporter

import (
"github.com/open-telemetry/opentelemetry-service/config/configmodels"
"github.com/open-telemetry/opentelemetry-service/consumer"
"github.com/open-telemetry/opentelemetry-service/exporter"
"go.uber.org/zap"
)

var _ = exporter.RegisterFactory(&exporterFactory{})

const (
// The value of "type" key in configuration.
typeStr = "stackdriver"
)

// exporterFactory is the factory for OpenCensus exporter.
type exporterFactory struct {
}

// Type gets the type of the Exporter config created by this factory.
func (f *exporterFactory) Type() string {
return typeStr
}

// CreateDefaultConfig creates the default configuration for exporter.
func (f *exporterFactory) CreateDefaultConfig() configmodels.Exporter {
return &Config{
ExporterSettings: configmodels.ExporterSettings{
TypeVal: typeStr,
NameVal: typeStr,
},
}
}

// CreateTraceExporter creates a trace exporter based on this config.
func (f *exporterFactory) CreateTraceExporter(logger *zap.Logger, cfg configmodels.Exporter) (consumer.TraceConsumer, exporter.StopFunc, error) {
eCfg := cfg.(*Config)
if !eCfg.EnableTracing {
return nil, nil, nil
}
return newStackdriverTraceExporter(eCfg.ProjectID, eCfg.Prefix)
}

// CreateMetricsExporter creates a metrics exporter based on this config.
func (f *exporterFactory) CreateMetricsExporter(logger *zap.Logger, cfg configmodels.Exporter) (consumer.MetricsConsumer, exporter.StopFunc, error) {
eCfg := cfg.(*Config)
if !eCfg.EnableMetrics {
return nil, nil, nil
}
return newStackdriverMetricsExporter(eCfg.ProjectID, eCfg.Prefix)
}
42 changes: 42 additions & 0 deletions exporter/stackdriverexporter/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// 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 stackdriverexporter

import (
"testing"

"go.uber.org/zap"

"github.com/stretchr/testify/assert"

"github.com/open-telemetry/opentelemetry-service/exporter"
)

func TestCreateDefaultConfig(t *testing.T) {
factory := exporter.GetFactory(typeStr)
cfg := factory.CreateDefaultConfig()
assert.NotNil(t, cfg, "failed to create default config")
}

func TestCreateExporter(t *testing.T) {
factory := exporter.GetFactory(typeStr)
cfg := factory.CreateDefaultConfig()

_, _, err := factory.CreateTraceExporter(zap.NewNop(), cfg)
assert.Nil(t, err)

_, _, err = factory.CreateMetricsExporter(zap.NewNop(), cfg)
assert.Nil(t, err)
}
181 changes: 181 additions & 0 deletions exporter/stackdriverexporter/stackdriver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// 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 stackdriverexporter contains the wrapper for OpenTelemetry-Stackdriver
// exporter to be used in OpenTelemetry-Service.
package stackdriverexporter

import (
"context"
"fmt"
"sync"
"time"

"contrib.go.opencensus.io/exporter/stackdriver"
"github.com/spf13/viper"
"go.opencensus.io/trace"

"github.com/open-telemetry/opentelemetry-service/consumer"
"github.com/open-telemetry/opentelemetry-service/consumer/consumerdata"
"github.com/open-telemetry/opentelemetry-service/exporter/exporterwrapper"
)

type stackdriverConfig struct {
ProjectID string `mapstructure:"project,omitempty"`
EnableTracing bool `mapstructure:"enable_tracing,omitempty"`
EnableMetrics bool `mapstructure:"enable_metrics,omitempty"`
MetricPrefix string `mapstructure:"metric_prefix,omitempty"`
}

// TODO: Add metrics support to the exporterwrapper.
type stackdriverExporter struct {
exporter *stackdriver.Exporter
}

var _ consumer.MetricsConsumer = (*stackdriverExporter)(nil)

// StackdriverTraceExportersFromViper unmarshals the viper and returns an consumer.TraceConsumer targeting
// Stackdriver according to the configuration settings.
func StackdriverTraceExportersFromViper(v *viper.Viper) (tps []consumer.TraceConsumer, mps []consumer.MetricsConsumer, doneFns []func() error, err error) {
var cfg struct {
Stackdriver *stackdriverConfig `mapstructure:"stackdriver"`
}
if err := v.Unmarshal(&cfg); err != nil {
return nil, nil, nil, err
}
sc := cfg.Stackdriver
if sc == nil {
return nil, nil, nil, nil
}
if !sc.EnableTracing && !sc.EnableMetrics {
return nil, nil, nil, nil
}

sde, serr := newStackdriverExporter(sc.ProjectID, sc.MetricPrefix)

if serr != nil {
return nil, nil, nil, fmt.Errorf("Cannot configure Stackdriver exporter: %v", serr)
}

exp := &stackdriverExporter{
exporter: sde,
}

sdte, err := exporterwrapper.NewExporterWrapper("stackdriver_trace", "ocservice.exporter.Stackdriver.ConsumeTraceData", sde)
if err != nil {
return nil, nil, nil, err
}

// TODO: Examine "contrib.go.opencensus.io/exporter/stackdriver" to see
// if trace.ExportSpan was constraining and if perhaps the Stackdriver
// upload can use the context and information from the Node.
if sc.EnableTracing {
tps = append(tps, sdte)
}

if sc.EnableMetrics {
mps = append(mps, exp)
}

doneFns = append(doneFns, func() error {
sde.Flush()
return nil
})
return
}

func newStackdriverTraceExporter(ProjectID, MetricPrefix string) (consumer.TraceConsumer, func() error, error) {
sde, serr := newStackdriverExporter(ProjectID, MetricPrefix)
if serr != nil {
return nil, nil, fmt.Errorf("Cannot configure Stackdriver Trace exporter: %v", serr)
}

tExp, err := exporterwrapper.NewExporterWrapper("stackdriver_trace", "ocservice.exporter.Stackdriver.ConsumeTraceData", sde)
if err != nil {
return nil, nil, err
}
// TODO: Examine "contrib.go.opencensus.io/exporter/stackdriver" to see
// if trace.ExportSpan was constraining and if perhaps the Stackdriver
// upload can use the context and information from the Node.

doneFn := func() error {
sde.Flush()
return nil
}

return tExp, doneFn, nil
}

func newStackdriverMetricsExporter(ProjectID, MetricPrefix string) (consumer.MetricsConsumer, func() error, error) {
sde, serr := newStackdriverExporter(ProjectID, MetricPrefix)
if serr != nil {
return nil, nil, fmt.Errorf("Cannot configure Stackdriver metric exporter: %v", serr)
}

mExp := &stackdriverExporter{
exporter: sde,
}

doneFn := func() error {
sde.Flush()
return nil
}

return mExp, doneFn, nil
}

func newStackdriverExporter(ProjectID, MetricPrefix string) (*stackdriver.Exporter, error) {
// TODO: For each ProjectID, create a different exporter
// or at least a unique Stackdriver client per ProjectID.

return stackdriver.NewExporter(stackdriver.Options{
// If the project ID is an empty string, it will be set by default based on
// the project this is running on in GCP.
ProjectID: ProjectID,

MetricPrefix: MetricPrefix,

// Stackdriver Metrics mandates a minimum of 60 seconds for
// reporting metrics. We have to enforce this as per the advisory
// at https://cloud.google.com/monitoring/custom-metrics/creating-metrics#writing-ts
// which says:
//
// "If you want to write more than one point to the same time series, then use a separate call
// to the timeSeries.create method for each point. Don't make the calls faster than one time per
// minute. If you are adding data points to different time series, then there is no rate limitation."
BundleDelayThreshold: 61 * time.Second,
})
}

func (sde *stackdriverExporter) ConsumeMetricsData(ctx context.Context, md consumerdata.MetricsData) error {
ctx, span := trace.StartSpan(ctx,
"opencensus.service.exporter.stackdriver.ExportMetricsData",
trace.WithSampler(trace.NeverSample()))
defer span.End()

var setErrorOnce sync.Once

err := sde.exporter.ExportMetricsProto(ctx, md.Node, md.Resource, md.Metrics)
if err != nil {
setErrorOnce.Do(func() {
span.SetStatus(trace.Status{Code: trace.StatusCodeInternal, Message: err.Error()})
})

span.Annotate([]trace.Attribute{
trace.StringAttribute("error", err.Error()),
}, "Error encountered")
}

return nil
}
21 changes: 21 additions & 0 deletions exporter/stackdriverexporter/testdata/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
receivers:
examplereceiver:

processors:
exampleprocessor:

exporters:
stackdriver:
stackdriver/customname:
enabled: true
project: my-project
enable_tracing: true
enable_metrics: true
metric_prefix: prefix

pipelines:
traces:
receivers: [examplereceiver]
processors: [exampleprocessor]
exporters: [stackdriver]

0 comments on commit be95842

Please sign in to comment.