Skip to content

Commit

Permalink
Merge pull request #7 from songy23/stackdriver
Browse files Browse the repository at this point in the history
 Add Stackdriver Exporter and configs.
  • Loading branch information
Paulo Janotti authored Jul 17, 2019
2 parents 1aa826a + 6f456c6 commit 68b55bb
Show file tree
Hide file tree
Showing 8 changed files with 707 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"`
}
53 changes: 53 additions & 0 deletions exporter/stackdriverexporter/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// 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"
)

func TestLoadConfig(t *testing.T) {
receivers, processors, exporters, err := config.ExampleComponents()
assert.Nil(t, err)

factory := &Factory{}
exporters[typeStr] = factory
cfg, err := config.LoadConfigFile(
t, path.Join(".", "testdata", "config.yaml"), receivers, processors, exporters,
)

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,
})
}
65 changes: 65 additions & 0 deletions exporter/stackdriverexporter/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 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 (
"go.uber.org/zap"

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

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

// Factory is the factory for Stackdriver exporter.
type Factory struct {
}

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

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

// CreateTraceExporter creates a trace exporter based on this config.
func (f *Factory) 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 *Factory) 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)
}
39 changes: 39 additions & 0 deletions exporter/stackdriverexporter/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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"

"github.com/stretchr/testify/assert"
"go.uber.org/zap"
)

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

func TestCreateExporter(t *testing.T) {
factory := Factory{}
cfg := factory.CreateDefaultConfig()

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

_, _, err = factory.CreateMetricsExporter(zap.NewNop(), cfg)
assert.Nil(t, err)
}
121 changes: 121 additions & 0 deletions exporter/stackdriverexporter/stackdriver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 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"
"time"

"contrib.go.opencensus.io/exporter/stackdriver"
"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)

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,
"opentelemetry.service.exporter.stackdriver.ExportMetricsData",
trace.WithSampler(trace.NeverSample()))
defer span.End()

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

return err
}
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]

11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/open-telemetry/opentelemetry-service-contrib

go 1.12

require (
contrib.go.opencensus.io/exporter/stackdriver v0.12.2
github.com/open-telemetry/opentelemetry-service v0.0.0-20190717170248-83e99c292bb7
github.com/stretchr/testify v1.3.0
go.opencensus.io v0.22.0
go.uber.org/zap v1.10.0
)
Loading

0 comments on commit 68b55bb

Please sign in to comment.