Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a trace exporter to Azure Monitor #39

Merged
7 changes: 6 additions & 1 deletion cmd/otelcontribcol/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/open-telemetry/opentelemetry-collector/oterr"
"github.com/open-telemetry/opentelemetry-collector/receiver"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/azuremonitorexporter"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/stackdriverexporter"
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinscribereceiver"
)
Expand All @@ -41,7 +42,11 @@ func components() (config.Factories, error) {
errs = append(errs, err)
}

exporters := []exporter.Factory{&stackdriverexporter.Factory{}}
exporters := []exporter.Factory{
&stackdriverexporter.Factory{},
&azuremonitorexporter.Factory{},
}

for _, exp := range factories.Exporters {
exporters = append(exporters, exp)
}
Expand Down
1 change: 1 addition & 0 deletions exporter/azuremonitorexporter/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
21 changes: 21 additions & 0 deletions exporter/azuremonitorexporter/channels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// 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 azuremonitorexporter

import "github.com/Microsoft/ApplicationInsights-Go/appinsights/contracts"

type transportChannel interface {
Send(*contracts.Envelope)
}
29 changes: 29 additions & 0 deletions exporter/azuremonitorexporter/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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 azuremonitorexporter

import (
"github.com/open-telemetry/opentelemetry-collector/config/configmodels"
)

// Config defines configuration for Azure Monitor
type Config struct {
// squash ensures fields are correctly decoded in embedded struct.
configmodels.ExporterSettings `mapstructure:",squash"`
Endpoint string `mapstructure:"endpoint"`
InstrumentationKey string `mapstructure:"instrumentation_key"`
MaxBatchSize int `mapstructure:"maxbatchsize"`
MaxBatchIntervalInSeconds int `mapstructure:"maxbatchinterval_sec"`
pcwiese marked this conversation as resolved.
Show resolved Hide resolved
}
60 changes: 60 additions & 0 deletions exporter/azuremonitorexporter/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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 azuremonitorexporter

import (
"path"
"testing"

pcwiese marked this conversation as resolved.
Show resolved Hide resolved
"github.com/open-telemetry/opentelemetry-collector/config"
"github.com/open-telemetry/opentelemetry-collector/config/configcheck"
"github.com/open-telemetry/opentelemetry-collector/config/configmodels"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

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

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

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

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

exporterType := typeStr
exporter := cfg.Exporters[exporterType]
assert.Equal(t, factory.CreateDefaultConfig(), exporter)

exporterType = typeStr + "/2"
exporter = cfg.Exporters[exporterType].(*Config)
assert.NoError(t, configcheck.ValidateConfig(exporter))
assert.Equal(
t,
&Config{
ExporterSettings: configmodels.ExporterSettings{TypeVal: typeStr, NameVal: exporterType},
Endpoint: "https://foo.bar",
InstrumentationKey: "abcdefg",
MaxBatchSize: 100,
MaxBatchIntervalInSeconds: 10,
},
exporter)
}
107 changes: 107 additions & 0 deletions exporter/azuremonitorexporter/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// 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 azuremonitorexporter

import (
"errors"
"time"

"github.com/Microsoft/ApplicationInsights-Go/appinsights"
"github.com/open-telemetry/opentelemetry-collector/config/configerror"
"github.com/open-telemetry/opentelemetry-collector/config/configmodels"
"github.com/open-telemetry/opentelemetry-collector/exporter"
"go.uber.org/zap"
)

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

var (
errUnexpectedConfigurationType = errors.New("failed to cast configuration to Azure Monitor Config")
)

// Factory for Azure Monitor exporter.
// Implements the interface from github.com/open-telemetry/opentelemetry-collector/exporter/factory.go
type Factory struct {
TransportChannel transportChannel
}

// 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,
},
Endpoint: "https://dc.services.visualstudio.com/v2/track",
MaxBatchSize: 1024,
MaxBatchIntervalInSeconds: int(time.Duration(10) * time.Second),
SergeyKanzhelev marked this conversation as resolved.
Show resolved Hide resolved
}
}

// CreateTraceExporter creates a trace exporter based on this config.
func (f *Factory) CreateTraceExporter(logger *zap.Logger, config configmodels.Exporter) (exporter.TraceExporter, error) {
exporterConfig, ok := config.(*Config)

if !ok {
return nil, errUnexpectedConfigurationType
}

transportChannel := f.getTransportChannel(exporterConfig, logger)
return newTraceExporter(exporterConfig, transportChannel, logger)
}

// CreateMetricsExporter creates a metrics exporter based on this config.
func (f *Factory) CreateMetricsExporter(
logger *zap.Logger,
cfg configmodels.Exporter,
) (exporter.MetricsExporter, error) {
return nil, configerror.ErrDataTypeIsNotSupported
}

// Configures the transport channel.
// This method is not thread-safe
func (f *Factory) getTransportChannel(exporterConfig *Config, logger *zap.Logger) transportChannel {

// The default transport channel uses the default send mechanism from the AppInsights telemetry client.
// This default channel handles batching, appropriate retries, and is backed by memory.
if f.TransportChannel == nil {
telemetryConfiguration := appinsights.NewTelemetryConfiguration(exporterConfig.InstrumentationKey)
telemetryConfiguration.EndpointUrl = exporterConfig.Endpoint
telemetryConfiguration.MaxBatchSize = exporterConfig.MaxBatchSize
telemetryConfiguration.MaxBatchInterval = time.Duration(exporterConfig.MaxBatchIntervalInSeconds) * time.Second
pcwiese marked this conversation as resolved.
Show resolved Hide resolved
telemetryClient := appinsights.NewTelemetryClientFromConfig(telemetryConfiguration)

f.TransportChannel = telemetryClient.Channel()

// Don't even bother enabling the AppInsights diagnostics listener unless debug logging is enabled
if checkedEntry := logger.Check(zap.DebugLevel, ""); checkedEntry != nil {
appinsights.NewDiagnosticsMessageListener(func(msg string) error {
logger.Debug(msg)
pcwiese marked this conversation as resolved.
Show resolved Hide resolved
return nil
})
}
}

return f.TransportChannel
}
57 changes: 57 additions & 0 deletions exporter/azuremonitorexporter/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 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 azuremonitorexporter

import (
"testing"

"github.com/open-telemetry/opentelemetry-collector/config/configerror"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
)

func TestExporterTypeKey(t *testing.T) {
factory := Factory{}

assert.Equal(t, typeStr, factory.Type())
}

func TestCreateMetricsExporter(t *testing.T) {
factory := Factory{}

exporter, err := factory.CreateMetricsExporter(zap.NewNop(), &Config{})

// unsupported
assert.Nil(t, exporter)
assert.Equal(t, configerror.ErrDataTypeIsNotSupported, err)
}

func TestCreateTraceExporterUsingSpecificTransportChannel(t *testing.T) {
// mock transport channel creation
factory := Factory{TransportChannel: &mockTransportChannel{}}
exporter, err := factory.CreateTraceExporter(zap.NewNop(), factory.CreateDefaultConfig())
assert.NotNil(t, exporter)
assert.Nil(t, err)
}

func TestCreateTraceExporterUsingDefaultTransportChannel(t *testing.T) {
// We get the default transport channel creation, if we dont't specify one during factory creation
factory := Factory{}
assert.Nil(t, factory.TransportChannel)
exporter, err := factory.CreateTraceExporter(zap.NewNop(), factory.CreateDefaultConfig())
assert.NotNil(t, exporter)
assert.Nil(t, err)
assert.NotNil(t, factory.TransportChannel)
}
27 changes: 27 additions & 0 deletions exporter/azuremonitorexporter/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module azuremonitorexporter

go 1.12

require (
cloud.google.com/go v0.45.1 // indirect
code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect
github.com/Microsoft/ApplicationInsights-Go v0.4.2
github.com/aws/aws-sdk-go v1.23.20 // indirect
github.com/census-instrumentation/opencensus-proto v0.2.1
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect
github.com/golang/protobuf v1.3.2
github.com/open-telemetry/opentelemetry-collector v0.2.1-0.20191016224815-dfabfb0c1d1e
github.com/satori/go.uuid v1.2.0 // indirect
github.com/stretchr/testify v1.4.0
github.com/tedsuo/ifrit v0.0.0-20191009134036-9a97d0632f00 // indirect
go.opencensus.io v0.22.2 // indirect
go.uber.org/multierr v1.4.0 // indirect
go.uber.org/zap v1.13.0
golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 // indirect
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
golang.org/x/sys v0.0.0-20191119195528-f068ffe820e4 // indirect
golang.org/x/tools v0.0.0-20191119175705-11e13f1c3fd7 // indirect
google.golang.org/appengine v1.6.2 // indirect
google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 // indirect
google.golang.org/grpc v1.25.1
)
Loading