Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package appinsightsexporter

import (
"errors"
"fmt"
"strings"
)

const (
defaultIngestionPrefix = "dc"
Comment thread
weikanglim marked this conversation as resolved.
defaultBaseEndpoint = "https://dc.services.visualstudio.com"
defaultEndpointPath = "v2/track"
)

const (
instrumentationKey_Setting = "InstrumentationKey"
endpointSuffix_Setting = "EndpointSuffix"
ingestionEndpoint_Setting = "IngestionEndpoint"
)

type EndpointConfig struct {
EndpointUrl string
InstrumentationKey string
}

// NewEndpointConfig parses a connection string, returning the endpoint configuration from the connection string.
Comment thread
weikanglim marked this conversation as resolved.
//
// The connection string schema for AppInsights can be found at https://learn.microsoft.com/en-us/azure/azure-monitor/app/sdk-connection-string?tabs=net#schema
func NewEndpointConfig(connectionString string) (EndpointConfig, error) {
tc := EndpointConfig{}
settings, err := parseSettings(connectionString)
if err != nil {
return tc, err
}

var iKey string
if key, has := settings[instrumentationKey_Setting]; has {
iKey = key
} else {
return tc, fmt.Errorf("%s is missing", instrumentationKey_Setting)
}

var baseEndpoint string
if ingestion, has := settings[ingestionEndpoint_Setting]; has {
baseEndpoint = ingestion
} else {
if endpointSuffix, has := settings[endpointSuffix_Setting]; has {
endpointSuffix := strings.TrimLeft(endpointSuffix, ".")
endpointSuffix = strings.TrimRight(endpointSuffix, "/")

baseEndpoint = fmt.Sprintf("https://%s.%s", defaultIngestionPrefix, endpointSuffix)
} else {
baseEndpoint = defaultBaseEndpoint
}
}

tc.InstrumentationKey = iKey
baseEndpoint = strings.TrimRight(baseEndpoint, "/")
tc.EndpointUrl = fmt.Sprintf("%s/%s", baseEndpoint, defaultEndpointPath)
return tc, nil
}

func parseSettings(connectionString string) (map[string]string, error) {
results := map[string]string{}
// Split "foo=bar;bar=baz"
settings := strings.Split(connectionString, ";")

for _, setting := range settings {
// Split "foo=bar"
kvp := strings.Split(setting, "=")
if len(kvp) != 2 {
return nil, errors.New("malformed setting. setting should be in the form of 'key=value'")
}

if kvp[0] == "" {
return nil, errors.New("malformed setting. setting key cannot be empty")
}

results[kvp[0]] = kvp[1]
}

return results, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package appinsightsexporter

import (
"testing"

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

func TestNewEndpointConfig(t *testing.T) {
type args struct {
connectionString string
}
tests := []struct {
name string
args args
want EndpointConfig
wantErr bool
}{
// Valid cases
{"IKeyOnly", args{"InstrumentationKey=foobar"}, EndpointConfig{InstrumentationKey: "foobar", EndpointUrl: "https://dc.services.visualstudio.com/v2/track"}, false},
{"EndpointSuffix", args{"InstrumentationKey=foobar;EndpointSuffix=localhost:1010"}, EndpointConfig{InstrumentationKey: "foobar", EndpointUrl: "https://dc.localhost:1010/v2/track"}, false},
{"ExplicitEndpoint", args{"InstrumentationKey=foobar;IngestionEndpoint=https://localhost:1030"}, EndpointConfig{InstrumentationKey: "foobar", EndpointUrl: "https://localhost:1030/v2/track"}, false},
{"ExplicitEndpointOverride", args{"InstrumentationKey=foobar;IngestionEndpoint=https://localhost:1030;EndpointSuffix=localhost:1010"}, EndpointConfig{InstrumentationKey: "foobar", EndpointUrl: "https://localhost:1030/v2/track"}, false},

// Cases where we reformat input for user-friendly handling
{"ExplicitEndpointTrailingSlash", args{"InstrumentationKey=foobar;IngestionEndpoint=https://localhost:1030//"}, EndpointConfig{InstrumentationKey: "foobar", EndpointUrl: "https://localhost:1030/v2/track"}, false},
{"EndpointSuffixLeadingDot", args{"InstrumentationKey=foobar;EndpointSuffix=..localhost:1010"}, EndpointConfig{InstrumentationKey: "foobar", EndpointUrl: "https://dc.localhost:1010/v2/track"}, false},
{"EndpointSuffixTrailingSlash", args{"InstrumentationKey=foobar;EndpointSuffix=localhost:1010//"}, EndpointConfig{InstrumentationKey: "foobar", EndpointUrl: "https://dc.localhost:1010/v2/track"}, false},

// Invalid cases
{"Empty", args{""}, EndpointConfig{}, true},
{"InvalidMissingIKey", args{"IngestionEndpoint=https://localhost:1030"}, EndpointConfig{}, true},
{"InvalidSettingKey", args{"InstrumentationKey=foobar;=Invalid"}, EndpointConfig{}, true},
{"InvalidSettingValue", args{"InstrumentationKey=foobar;Invalid"}, EndpointConfig{}, true},
{"InvalidSettingSeparator", args{"InstrumentationKey=foobar;;"}, EndpointConfig{}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config, err := NewEndpointConfig(tt.args.connectionString)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want, config)
}
})
}
}
32 changes: 14 additions & 18 deletions cli/azd/internal/telemetry/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/config"
"github.com/benbjohnson/clock"
"github.com/gofrs/flock"
"github.com/microsoft/ApplicationInsights-Go/appinsights"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"
)
Expand All @@ -29,8 +28,8 @@ const collectTelemetryEnvVar = "AZURE_DEV_COLLECT_TELEMETRY"

const telemetryItemExtension = ".trn"
const (
devInstrumentationKey = "cf5f7d89-5383-47a8-8d27-ad237c3613d9"
prodInstrumentationKey = ""
devConnectionString = "InstrumentationKey=cf5f7d89-5383-47a8-8d27-ad237c3613d9;IngestionEndpoint=https://westus-0.in.applicationinsights.azure.com/;LiveEndpoint=https://westus.livediagnostics.monitor.azure.com/"
prodConnectionString = "InstrumentationKey=a9e6fa10-a9ac-4525-8388-22d39336ecc2;IngestionEndpoint=https://centralus-2.in.applicationinsights.azure.com/;LiveEndpoint=https://centralus.livediagnostics.monitor.azure.com/"
)

const appInsightsMaxIngestionDelay = time.Duration(48) * time.Hour
Expand All @@ -40,7 +39,7 @@ type TelemetrySystem struct {
tracerProvider *trace.TracerProvider
exporter *Exporter

instrumentationKey string
config appinsightsexporter.EndpointConfig
telemetryDirectory string
}

Expand Down Expand Up @@ -77,12 +76,6 @@ func GetTelemetrySystem() *TelemetrySystem {
}

func initialize() (*TelemetrySystem, error) {
// Feature guard: Disable for production until dependencies are met in production
isDev := internal.IsNonProdVersion()
if !isDev {
return nil, nil
}

if !IsTelemetryEnabled() {
log.Println("telemetry is disabled by user and will not be initialized.")
return nil, nil
Expand All @@ -102,14 +95,18 @@ func initialize() (*TelemetrySystem, error) {
return nil, fmt.Errorf("failed to initialize storage queue: %w", err)
}

var instrumentationKey string
if isDev {
instrumentationKey = devInstrumentationKey
var connectionString string
if internal.IsNonProdVersion() {
connectionString = devConnectionString
} else {
instrumentationKey = prodInstrumentationKey
connectionString = prodConnectionString
}
config, err := appinsightsexporter.NewEndpointConfig(connectionString)
if err != nil {
return nil, fmt.Errorf("failed to parse appInsights connection string: %w", err)
}

exporter := NewExporter(storageQueue, instrumentationKey)
exporter := NewExporter(storageQueue, config.InstrumentationKey)

tp := trace.NewTracerProvider(
trace.WithBatcher(exporter),
Expand All @@ -121,7 +118,7 @@ func initialize() (*TelemetrySystem, error) {
storageQueue: storageQueue,
tracerProvider: tp,
exporter: exporter,
instrumentationKey: instrumentationKey,
config: config,
telemetryDirectory: telemetryDir,
}, nil
}
Expand All @@ -142,8 +139,7 @@ func (ts *TelemetrySystem) EmittedAnyTelemetry() bool {
}

func (ts *TelemetrySystem) NewUploader(enableDebugLogging bool) Uploader {
config := appinsights.NewTelemetryConfiguration(ts.instrumentationKey)
transmitter := appinsightsexporter.NewTransmitter(config.EndpointUrl, nil)
transmitter := appinsightsexporter.NewTransmitter(ts.config.EndpointUrl, nil)

uploader := NewUploader(ts.GetTelemetryQueue(), transmitter, clock.New(), enableDebugLogging)
return uploader
Expand Down
1 change: 0 additions & 1 deletion cli/azd/internal/telemetry/telemetry_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (

func getEnvironmentAttributes(env *environment.Environment) []attribute.KeyValue {
return []attribute.KeyValue{
fields.ObjectIdKey.String(env.GetPrincipalId()),
fields.SubscriptionIdKey.String(env.GetSubscriptionId()),
}
}
Expand Down
29 changes: 17 additions & 12 deletions cli/azd/internal/telemetry/telemetry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,34 @@ import (
"testing"

"github.com/azure/azure-dev/cli/azd/internal"
appinsightsexporter "github.com/azure/azure-dev/cli/azd/internal/telemetry/appinsights-exporter"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGetTelemetrySystem(t *testing.T) {
devEndpointConfig, err := appinsightsexporter.NewEndpointConfig(devConnectionString)
require.NoError(t, err)
prodEndpointConfig, err := appinsightsexporter.NewEndpointConfig(prodConnectionString)
require.NoError(t, err)

type args struct {
version string
disableTelemetryEnvVarValue string
}
tests := []struct {
name string
args args
expectNil bool
expectIKey string
name string
args args
expectNil bool
expectConfig appinsightsexporter.EndpointConfig
}{
{"DevVersion", args{"0.0.0-dev.0 (commit 0000000000000000000000000000000000000000)", "unset"}, false, devInstrumentationKey},
{"DevVersionTelemetryEnabled", args{"0.0.0-dev.0 (commit 0000000000000000000000000000000000000000)", "yes"}, false, devInstrumentationKey},
{"DevVersionTelemetryDisabled", args{"0.0.0-dev.0 (commit 0000000000000000000000000000000000000000)", "no"}, true, devInstrumentationKey},
{"DevVersion", args{"0.0.0-dev.0 (commit 0000000000000000000000000000000000000000)", "unset"}, false, devEndpointConfig},
{"DevVersionTelemetryEnabled", args{"0.0.0-dev.0 (commit 0000000000000000000000000000000000000000)", "yes"}, false, devEndpointConfig},
{"DevVersionTelemetryDisabled", args{"0.0.0-dev.0 (commit 0000000000000000000000000000000000000000)", "no"}, true, devEndpointConfig},

// Currently, prod version should always be disabled.
{"ProdVersion", args{"1.0.0 (commit 13ec2b11aa755b11640fa16b8664cb8741d5d300)", "no"}, true, prodInstrumentationKey},
{"ProdVersion", args{"1.0.0 (commit 13ec2b11aa755b11640fa16b8664cb8741d5d300)", "unset"}, true, prodInstrumentationKey},
{"ProdVersion", args{"1.0.0 (commit 13ec2b11aa755b11640fa16b8664cb8741d5d300)", "yes"}, true, prodInstrumentationKey},
{"ProdVersion", args{"1.0.0 (commit 13ec2b11aa755b11640fa16b8664cb8741d5d300)", "no"}, true, prodEndpointConfig},
Comment thread
weikanglim marked this conversation as resolved.
{"ProdVersion", args{"1.0.0 (commit 13ec2b11aa755b11640fa16b8664cb8741d5d300)", "unset"}, false, prodEndpointConfig},
{"ProdVersion", args{"1.0.0 (commit 13ec2b11aa755b11640fa16b8664cb8741d5d300)", "yes"}, false, prodEndpointConfig},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -49,7 +54,7 @@ func TestGetTelemetrySystem(t *testing.T) {
assert.Nil(t, ts)
} else {
require.NotNil(t, ts)
assert.Equal(t, tt.expectIKey, ts.instrumentationKey)
assert.Equal(t, tt.expectConfig, ts.config)
assert.NotNil(t, ts.GetTelemetryQueue())
assert.NotNil(t, ts.NewUploader(true))

Expand Down
2 changes: 1 addition & 1 deletion cli/azd/internal/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func IsNonProdVersion() bool {
// This can be improved to instead check for any presence of prerelease versioning
// once the product is GA.
ver := GetVersionNumber()
return strings.Contains(ver, "pr") || strings.Contains(ver, "daily")
return strings.Contains(ver, "pr")
}

// GetVersionNumber splits the cmd.Version string to get the
Expand Down