Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
52 changes: 52 additions & 0 deletions aitelemetry/connection_string_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package aitelemetry

import (
"strings"

"github.com/pkg/errors"
)

type connectionVars struct {
instrumentationKey string
ingestionUrl string

Check failure on line 11 in aitelemetry/connection_string_parser.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

var-naming: struct field ingestionUrl should be ingestionURL (revive)

Check failure on line 11 in aitelemetry/connection_string_parser.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

var-naming: struct field ingestionUrl should be ingestionURL (revive)

Check failure on line 11 in aitelemetry/connection_string_parser.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

var-naming: struct field ingestionUrl should be ingestionURL (revive)

Check failure on line 11 in aitelemetry/connection_string_parser.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

var-naming: struct field ingestionUrl should be ingestionURL (revive)
}

func (c *connectionVars) String() string {
return "InstrumentationKey=" + c.instrumentationKey + ";IngestionEndpoint=" + c.ingestionUrl
}

func parseConnectionString(connectionString string) (*connectionVars, error) {
connectionVars := &connectionVars{}

if connectionString == "" {
return nil, errors.New("connection string cannot be empty")
}

pairs := strings.Split(connectionString, ";")
for _, pair := range pairs {
kv := strings.SplitN(pair, "=", 2)
if len(kv) != 2 {
return nil, errors.Errorf("invalid connection string format: %s", pair)
}
key, value := strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])

if key == "" {
return nil, errors.Errorf("key in connection string cannot be empty")
}

switch strings.ToLower(key) {
case "instrumentationkey":
connectionVars.instrumentationKey = value
case "ingestionendpoint":
if value != "" {
connectionVars.ingestionUrl = value + "v2.1/track"
}
}
}

if connectionVars.instrumentationKey == "" || connectionVars.ingestionUrl == "" {
return nil, errors.Errorf("missing required fields in connection string: %s", connectionVars)
}

return connectionVars, nil
}
66 changes: 66 additions & 0 deletions aitelemetry/connection_string_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package aitelemetry

import (
"testing"

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

const connectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://ingestion.endpoint.com/;LiveEndpoint=https://live.endpoint.com/;ApplicationId=11111111-1111-1111-1111-111111111111"

func TestParseConnectionString(t *testing.T) {
tests := []struct {
name string
connectionString string
want *connectionVars
wantErr bool
}{
{
name: "Valid connection string and instrumentation key",
connectionString: connectionString,
want: &connectionVars{
instrumentationKey: "00000000-0000-0000-0000-000000000000",
ingestionUrl: "https://ingestion.endpoint.com/v2.1/track",
},
wantErr: false,
},
{
name: "Invalid connection string format",
connectionString: "InvalidConnectionString",
want: nil,
wantErr: true,
},
{
name: "Valid instrumentation key with missing ingestion endpoint",
connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=",
want: nil,
wantErr: true,
},
{
name: "Missing instrumentation key with valid ingestion endpoint",
connectionString: "InstrumentationKey=;IngestionEndpoint=https://ingestion.endpoint.com/",
want: nil,
wantErr: true,
},
{
name: "Empty connection string",
connectionString: "",
want: nil,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseConnectionString(tt.connectionString)
if tt.wantErr {
require.Error(t, err, "Expected error but got none")
} else {
require.NoError(t, err, "Expected no error but got one")
require.NotNil(t, got, "Expected a non-nil result")
require.Equal(t, tt.want.instrumentationKey, got.instrumentationKey, "Instrumentation Key does not match")
require.Equal(t, tt.want.ingestionUrl, got.ingestionUrl, "Ingestion URL does not match")
}
})
}
}
43 changes: 42 additions & 1 deletion aitelemetry/telemetrywrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"github.com/Azure/azure-container-networking/store"
"github.com/microsoft/ApplicationInsights-Go/appinsights"
"github.com/microsoft/ApplicationInsights-Go/appinsights/contracts"
"github.com/pkg/errors"
)

const (
Expand Down Expand Up @@ -161,7 +162,7 @@
return true, nil
} else if err == nil {
debugLog("[AppInsights] This is not azure public cloud:%s", cloudName)
return false, fmt.Errorf("Not an azure public cloud: %s", cloudName)
return false, fmt.Errorf("not an azure public cloud: %s", cloudName)

Check failure on line 165 in aitelemetry/telemetrywrapper.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"not an azure public cloud: %s\", cloudName)" (err113)

Check failure on line 165 in aitelemetry/telemetrywrapper.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"not an azure public cloud: %s\", cloudName)" (err113)

Check failure on line 165 in aitelemetry/telemetrywrapper.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"not an azure public cloud: %s\", cloudName)" (err113)

Check failure on line 165 in aitelemetry/telemetrywrapper.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"not an azure public cloud: %s\", cloudName)" (err113)
}

debugLog("GetAzureCloud returned err :%v", err)
Expand Down Expand Up @@ -214,6 +215,46 @@
return th, nil
}

// NewWithConnectionString creates telemetry handle with user specified appinsights connection string.
func NewWithConnectionString(connectionString string, aiConfig AIConfig) (TelemetryHandle, error) {
debugMode = aiConfig.DebugMode

if connectionString == "" {
debugLog("Empty connection string")
return nil, errors.New("AI connection string is empty")
}

setAIConfigDefaults(&aiConfig)

connectionVars, err := parseConnectionString(connectionString)
if err != nil {
debugLog("Error parsing connection string: %v", err)
return nil, err
}

telemetryConfig := appinsights.NewTelemetryConfiguration(connectionVars.instrumentationKey)
telemetryConfig.EndpointUrl = connectionVars.ingestionUrl
telemetryConfig.MaxBatchSize = aiConfig.BatchSize
telemetryConfig.MaxBatchInterval = time.Duration(aiConfig.BatchInterval) * time.Second

th := &telemetryHandle{
client: appinsights.NewTelemetryClientFromConfig(telemetryConfig),
appName: aiConfig.AppName,
appVersion: aiConfig.AppVersion,
diagListener: messageListener(),
disableMetadataRefreshThread: aiConfig.DisableMetadataRefreshThread,
refreshTimeout: aiConfig.RefreshTimeout,
}

if th.disableMetadataRefreshThread {
getMetadata(th)
} else {
go getMetadata(th)
}

return th, nil
}

// TrackLog function sends report (trace) to appinsights resource. It overrides few of the existing columns with app information
// and for rest it uses custom dimesion
func (th *telemetryHandle) TrackLog(report Report) {
Expand Down
108 changes: 58 additions & 50 deletions aitelemetry/telemetrywrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
)

var (
th TelemetryHandle
aiConfig AIConfig
hostAgentUrl = "localhost:3501"
getCloudResponse = "AzurePublicCloud"
httpURL = "http://" + hostAgentUrl
Expand Down Expand Up @@ -54,6 +54,18 @@
return
}

aiConfig = AIConfig{
AppName: "testapp",
AppVersion: "v1.0.26",
BatchSize: 4096,
BatchInterval: 2,
RefreshTimeout: 10,
GetEnvRetryCount: 1,
GetEnvRetryWaitTimeInSecs: 2,
DebugMode: true,
DisableMetadataRefreshThread: true,
}

exitCode := m.Run()

if runtime.GOOS == "linux" {
Expand All @@ -75,67 +87,78 @@
w.Write([]byte(getCloudResponse))
}

func initTelemetry(t *testing.T) (TelemetryHandle, TelemetryHandle) {

Check failure on line 90 in aitelemetry/telemetrywrapper_test.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, ubuntu-latest)

unnamedResult: consider giving a name to these results (gocritic)

Check failure on line 90 in aitelemetry/telemetrywrapper_test.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, ubuntu-latest)

unnamedResult: consider giving a name to these results (gocritic)

Check failure on line 90 in aitelemetry/telemetrywrapper_test.go

View workflow job for this annotation

GitHub Actions / Lint (1.22.x, windows-latest)

unnamedResult: consider giving a name to these results (gocritic)

Check failure on line 90 in aitelemetry/telemetrywrapper_test.go

View workflow job for this annotation

GitHub Actions / Lint (1.23.x, windows-latest)

unnamedResult: consider giving a name to these results (gocritic)
th1, err1 := NewAITelemetry(httpURL, "00ca2a73-c8d6-4929-a0c2-cf84545ec225", aiConfig)
if err1 != nil {
fmt.Printf("Error initializing AI telemetry: %v", err1)
}

th2, err2 := NewWithConnectionString(connectionString, aiConfig)
if err2 != nil {
fmt.Printf("Error initializing AI telemetry with connection string: %v", err2)
}

return th1, th2
}

func TestEmptyAIKey(t *testing.T) {
var err error

aiConfig := AIConfig{
AppName: "testapp",
AppVersion: "v1.0.26",
BatchSize: 4096,
BatchInterval: 2,
RefreshTimeout: 10,
DebugMode: true,
DisableMetadataRefreshThread: true,
}
_, err = NewAITelemetry(httpURL, "", aiConfig)
if err == nil {
t.Errorf("Error intializing AI telemetry:%v", err)
t.Errorf("Error initializing AI telemetry:%v", err)
}

_, err = NewWithConnectionString("", aiConfig)
if err == nil {
t.Errorf("Error initializing AI telemetry with connection string:%v", err)
}
}

func TestNewAITelemetry(t *testing.T) {
var err error

aiConfig := AIConfig{
AppName: "testapp",
AppVersion: "v1.0.26",
BatchSize: 4096,
BatchInterval: 2,
RefreshTimeout: 10,
GetEnvRetryCount: 1,
GetEnvRetryWaitTimeInSecs: 2,
DebugMode: true,
DisableMetadataRefreshThread: true,
th1, th2 := initTelemetry(t)
if th1 == nil {
t.Errorf("Error initializing AI telemetry: %v", err)
}
th, err = NewAITelemetry(httpURL, "00ca2a73-c8d6-4929-a0c2-cf84545ec225", aiConfig)
if th == nil {
t.Errorf("Error intializing AI telemetry: %v", err)

if th2 == nil {
t.Errorf("Error initializing AI telemetry with connection string: %v", err)
}
}

func TestTrackMetric(t *testing.T) {
th1, th2 := initTelemetry(t)

metric := Metric{
Name: "test",
Value: 1.0,
CustomDimensions: make(map[string]string),
}

metric.CustomDimensions["dim1"] = "col1"
th.TrackMetric(metric)
th1.TrackMetric(metric)
th2.TrackMetric(metric)
}

func TestTrackLog(t *testing.T) {
th1, th2 := initTelemetry(t)

report := Report{
Message: "test",
Context: "10a",
CustomDimensions: make(map[string]string),
}

report.CustomDimensions["dim1"] = "col1"
th.TrackLog(report)
th1.TrackLog(report)
th2.TrackLog(report)
}

func TestTrackEvent(t *testing.T) {
th1, th2 := initTelemetry(t)

event := Event{
EventName: "testEvent",
ResourceID: "SomeResourceId",
Expand All @@ -144,35 +167,20 @@

event.Properties["P1"] = "V1"
event.Properties["P2"] = "V2"
th.TrackEvent(event)
th1.TrackEvent(event)
th2.TrackEvent(event)
}

func TestFlush(t *testing.T) {
th.Flush()
}
th1, th2 := initTelemetry(t)

func TestClose(t *testing.T) {
th.Close(10)
th1.Flush()
th2.Flush()
}

func TestClosewithoutSend(t *testing.T) {
var err error

aiConfig := AIConfig{
AppName: "testapp",
AppVersion: "v1.0.26",
BatchSize: 4096,
BatchInterval: 2,
DisableMetadataRefreshThread: true,
RefreshTimeout: 10,
GetEnvRetryCount: 1,
GetEnvRetryWaitTimeInSecs: 2,
}

thtest, err := NewAITelemetry(httpURL, "00ca2a73-c8d6-4929-a0c2-cf84545ec225", aiConfig)
if thtest == nil {
t.Errorf("Error intializing AI telemetry:%v", err)
}
func TestClose(t *testing.T) {
th1, th2 := initTelemetry(t)

thtest.Close(10)
th1.Close(10)
th2.Close(10)
}
Loading
Loading