Skip to content

Commit

Permalink
Add telemetryClient interface and update trace exporter unit tests
Browse files Browse the repository at this point in the history
Signed-off-by: whitneygriffith <[email protected]>
  • Loading branch information
whitneygriffith committed Oct 14, 2024
1 parent 1df90fd commit 8abcfc1
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 22 deletions.
48 changes: 48 additions & 0 deletions exporter/azuremonitorexporter/clients.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package azuremonitorexporter

import "github.com/microsoft/ApplicationInsights-Go/appinsights"

// telemetryClient is an interface for telemetry clients.
type telemetryClient interface {
Track(telemetry appinsights.Telemetry)
Channel() telemetryChannel
}

// telemetryChannel is an interface for telemetry channels.
type telemetryChannel interface {
Flush()
Close()
}

// appInsightsTelemetryClient is a wrapper around appinsights.TelemetryClient.
type appInsightsTelemetryClient struct {
client appinsights.TelemetryClient
}

// Track sends telemetry data using the underlying client.
func (t *appInsightsTelemetryClient) Track(telemetry appinsights.Telemetry) {
t.client.Track(telemetry)
}

// Channel returns the telemetry channel of the underlying client.
func (t *appInsightsTelemetryClient) Channel() telemetryChannel {
return &appInsightsTelemetryChannel{channel: t.client.Channel()}
}

// appInsightsTelemetryChannel is a wrapper around appinsights.TelemetryChannel.
type appInsightsTelemetryChannel struct {
channel appinsights.TelemetryChannel
}

// Flush flushes the telemetry data.
func (t *appInsightsTelemetryChannel) Flush() {
t.channel.Flush()
}

// Close closes the telemetry channel.
func (t *appInsightsTelemetryChannel) Close() {
t.channel.Close()
}
41 changes: 39 additions & 2 deletions exporter/azuremonitorexporter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ func NewFactory() exporter.Factory {

// Implements the interface from go.opentelemetry.io/collector/exporter/factory.go
type factory struct {
tChannel transportChannel
tChannel transportChannel
telemetryClient telemetryClient
}

func createDefaultConfig() component.Config {
Expand All @@ -65,12 +66,18 @@ func (f *factory) createTracesExporter(
return nil, errUnexpectedConfigurationType
}

// TODO: deprecate in favor of telemetryClient
tc, errInstrumentationKeyOrConnectionString := f.getTransportChannel(exporterConfig, set.Logger)
if errInstrumentationKeyOrConnectionString != nil {
return nil, errInstrumentationKeyOrConnectionString
}

return newTracesExporter(exporterConfig, tc, set)
telemetryClient, errInstrumentationKeyOrConnectionString := f.getTelemetryClient(exporterConfig, set.Logger)
if errInstrumentationKeyOrConnectionString != nil {
return nil, errInstrumentationKeyOrConnectionString
}

return newTracesExporter(exporterConfig, tc, telemetryClient, set)
}

func (f *factory) createLogsExporter(
Expand Down Expand Up @@ -144,3 +151,33 @@ func (f *factory) getTransportChannel(exporterConfig *Config, logger *zap.Logger

return f.tChannel, nil
}

// Configures the telemetry client.
func (f *factory) getTelemetryClient(exporterConfig *Config, logger *zap.Logger) (telemetryClient, error) {

if f.telemetryClient == nil {
connectionVars, err := parseConnectionString(exporterConfig)
if err != nil {
return nil, err
}

exporterConfig.InstrumentationKey = configopaque.String(connectionVars.InstrumentationKey)
exporterConfig.Endpoint = connectionVars.IngestionURL
telemetryConfiguration := appinsights.NewTelemetryConfiguration(string(exporterConfig.InstrumentationKey))
telemetryConfiguration.EndpointUrl = exporterConfig.Endpoint
telemetryConfiguration.MaxBatchSize = exporterConfig.MaxBatchSize
telemetryConfiguration.MaxBatchInterval = exporterConfig.MaxBatchInterval

f.telemetryClient = &appInsightsTelemetryClient{
client: appinsights.NewTelemetryClientFromConfig(telemetryConfiguration),
}

if checkedEntry := logger.Check(zap.DebugLevel, ""); checkedEntry != nil {
appinsights.NewDiagnosticsMessageListener(func(msg string) error {
logger.Debug(msg)
return nil
})
}
}
return f.telemetryClient, nil
}
53 changes: 53 additions & 0 deletions exporter/azuremonitorexporter/mock_telemetryClient.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 7 additions & 8 deletions exporter/azuremonitorexporter/traceexporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package azuremonitorexporter // import "github.com/open-telemetry/opentelemetry-
import (
"context"

"github.com/microsoft/ApplicationInsights-Go/appinsights"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter"
"go.opentelemetry.io/collector/exporter/exporterhelper"
Expand All @@ -16,8 +15,10 @@ import (
)

type traceExporter struct {
config *Config
config *Config
// TODO: deprecate in favor of telemetryClient
transportChannel transportChannel
telemetryClient telemetryClient
logger *zap.Logger
}

Expand All @@ -33,9 +34,6 @@ func (v *traceVisitor) visit(
scope pcommon.InstrumentationScope,
span ptrace.Span) (ok bool) {

instrumentationKey := string(v.exporter.config.InstrumentationKey)
client := appinsights.NewTelemetryClient(instrumentationKey)

telemetryTraces, err := spanToTelemetryTraces(resource, scope, span, v.exporter.config.SpanEventsEnabled, v.exporter.logger)
if err != nil {
// record the error and short-circuit
Expand All @@ -45,11 +43,11 @@ func (v *traceVisitor) visit(

for _, trace := range telemetryTraces {
// This is a fire and forget operation
client.Track(trace)
v.exporter.telemetryClient.Track(trace)

}
// Flush the telemetry client to ensure all data is sent and take advantage of batching
client.Channel().Flush()
v.exporter.telemetryClient.Channel().Flush()
v.processed++

return true
Expand All @@ -67,10 +65,11 @@ func (exporter *traceExporter) onTraceData(_ context.Context, traceData ptrace.T
}

// Returns a new instance of the trace exporter
func newTracesExporter(config *Config, transportChannel transportChannel, set exporter.Settings) (exporter.Traces, error) {
func newTracesExporter(config *Config, transportChannel transportChannel, telemetryClient telemetryClient, set exporter.Settings) (exporter.Traces, error) {
exporter := &traceExporter{
config: config,
transportChannel: transportChannel,
telemetryClient: telemetryClient,
logger: set.Logger,
}

Expand Down
41 changes: 29 additions & 12 deletions exporter/azuremonitorexporter/traceexporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,22 @@ var (
// Tests the export onTraceData callback with no Spans
func TestExporterTraceDataCallbackNoSpans(t *testing.T) {
mockTransportChannel := getMockTransportChannel()
exporter := getExporter(defaultConfig, mockTransportChannel)
mockTelemetryClient := getMockTelemetryClient()
exporter := getExporter(defaultConfig, mockTransportChannel, mockTelemetryClient)

traces := ptrace.NewTraces()

assert.NoError(t, exporter.onTraceData(context.Background(), traces))

mockTransportChannel.AssertNumberOfCalls(t, "Send", 0)
mockTelemetryClient.AssertNumberOfCalls(t, "Track", 0)
}

// Tests the export onTraceData callback with a single Span
func TestExporterTraceDataCallbackSingleSpan(t *testing.T) {
mockTransportChannel := getMockTransportChannel()
exporter := getExporter(defaultConfig, mockTransportChannel)
mockTelemetryClient := getMockTelemetryClient()
exporter := getExporter(defaultConfig, mockTransportChannel, mockTelemetryClient)

// re-use some test generation method(s) from trace_to_envelope_test
resource := getResource()
Expand All @@ -50,16 +53,16 @@ func TestExporterTraceDataCallbackSingleSpan(t *testing.T) {
span.CopyTo(ilss.Spans().AppendEmpty())

assert.NoError(t, exporter.onTraceData(context.Background(), traces))

mockTransportChannel.AssertNumberOfCalls(t, "Send", 1)
mockTelemetryClient.Channel().(*mockTelemetryChannel).AssertNumberOfCalls(t, "Flush", 1)
}

// Tests the export onTraceData callback with a single Span with SpanEvents
func TestExporterTraceDataCallbackSingleSpanWithSpanEvents(t *testing.T) {
mockTransportChannel := getMockTransportChannel()
mockTelemetryClient := getMockTelemetryClient()
config := createDefaultConfig().(*Config)
config.SpanEventsEnabled = true
exporter := getExporter(config, mockTransportChannel)
exporter := getExporter(config, mockTransportChannel, mockTelemetryClient)

// re-use some test generation method(s) from trace_to_envelope_test
resource := getResource()
Expand All @@ -83,13 +86,16 @@ func TestExporterTraceDataCallbackSingleSpanWithSpanEvents(t *testing.T) {

assert.NoError(t, exporter.onTraceData(context.Background(), traces))

mockTransportChannel.AssertNumberOfCalls(t, "Send", 3)
mockTransportChannel.AssertNumberOfCalls(t, "Send", 0)
mockTelemetryClient.AssertNumberOfCalls(t, "Track", 3)
}

// Tests the export onTraceData callback with a single Span that fails to produce an envelope
// TODO: Depercate this test when transport channel is removed as we will not be using envelopes anymore
func TestExporterTraceDataCallbackSingleSpanNoEnvelope(t *testing.T) {
mockTransportChannel := getMockTransportChannel()
exporter := getExporter(defaultConfig, mockTransportChannel)
mockTelemetryClient := getMockTelemetryClient()
exporter := getExporter(defaultConfig, mockTransportChannel, mockTelemetryClient)

// re-use some test generation method(s) from trace_to_envelope_test
resource := getResource()
Expand All @@ -113,6 +119,7 @@ func TestExporterTraceDataCallbackSingleSpanNoEnvelope(t *testing.T) {
assert.True(t, consumererror.IsPermanent(err), "error should be permanent")

mockTransportChannel.AssertNumberOfCalls(t, "Send", 0)
mockTelemetryClient.AssertNumberOfCalls(t, "Track", 0)
}

func getMockTransportChannel() *mockTransportChannel {
Expand All @@ -121,10 +128,20 @@ func getMockTransportChannel() *mockTransportChannel {
return &transportChannelMock
}

func getExporter(config *Config, transportChannel transportChannel) *traceExporter {
func getMockTelemetryClient() *mockTelemetryClient {
mockClient := mockTelemetryClient{}
mockChannel := &mockTelemetryChannel{}
mockClient.On("Track", mock.Anything)
mockClient.On("Channel").Return(mockChannel)
mockChannel.On("Flush")
return &mockClient
}

func getExporter(config *Config, transportChannel transportChannel, telemetryClient telemetryClient) *traceExporter {
return &traceExporter{
config,
transportChannel,
zap.NewNop(),
config: config,
transportChannel: transportChannel,
telemetryClient: telemetryClient,
logger: zap.NewNop(),
}
}
}

0 comments on commit 8abcfc1

Please sign in to comment.