1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,69 @@
# Azure Application Insights Output Plugin
This plugin writes metrics to the [Azure Application Insights][insights]
service.
⭐ Telegraf v1.7.0
🏷️ applications, cloud
💻 all
[insights]: https://azure.microsoft.com/en-us/services/application-insights/
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Send metrics to Azure Application Insights
[[outputs.application_insights]]
## Instrumentation key of the Application Insights resource.
instrumentation_key = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
## Regions that require endpoint modification https://docs.microsoft.com/en-us/azure/azure-monitor/app/custom-endpoints
# endpoint_url = "https://dc.services.visualstudio.com/v2/track"
## Timeout for closing (default: 5s).
# timeout = "5s"
## Enable additional diagnostic logging.
# enable_diagnostic_logging = false
## NOTE: Due to the way TOML is parsed, tables must be at the END of the
## plugin definition, otherwise additional config options are read as part of
## the table
## Context Tag Sources add Application Insights context tags to a tag value.
##
## For list of allowed context tag keys see:
## https://github.com/microsoft/ApplicationInsights-Go/blob/master/appinsights/contracts/contexttagkeys.go
# [outputs.application_insights.context_tag_sources]
# "ai.cloud.role" = "kubernetes_container_name"
# "ai.cloud.roleInstance" = "kubernetes_pod_name"
```
## Metric Encoding
For each field an Application Insights Telemetry record is created named based
on the measurement name and field.
**Example:** Create the telemetry records `foo_first` and `foo_second`:
```text
foo,host=a first=42,second=43 1525293034000000000
```
In the special case of a single field named `value`, a single telemetry record
is created named using only the measurement name
**Example:** Create a telemetry record `bar`:
```text
bar,host=a value=42 1525293034000000000
```

View file

@ -0,0 +1,321 @@
//go:generate ../../../tools/readme_config_includer/generator
package application_insights
import (
_ "embed"
"errors"
"fmt"
"math"
"strconv"
"time"
"github.com/microsoft/ApplicationInsights-Go/appinsights"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/outputs"
)
//go:embed sample.conf
var sampleConfig string
type TelemetryTransmitter interface {
Track(appinsights.Telemetry)
Close() <-chan struct{}
}
type DiagnosticsMessageSubscriber interface {
Subscribe(appinsights.DiagnosticsMessageHandler) appinsights.DiagnosticsMessageListener
}
type ApplicationInsights struct {
InstrumentationKey string `toml:"instrumentation_key"`
EndpointURL string `toml:"endpoint_url"`
Timeout config.Duration `toml:"timeout"`
EnableDiagnosticLogging bool `toml:"enable_diagnostic_logging"`
ContextTagSources map[string]string `toml:"context_tag_sources"`
Log telegraf.Logger `toml:"-"`
diagMsgSubscriber DiagnosticsMessageSubscriber
transmitter TelemetryTransmitter
diagMsgListener appinsights.DiagnosticsMessageListener
}
func (*ApplicationInsights) SampleConfig() string {
return sampleConfig
}
func (a *ApplicationInsights) Connect() error {
if a.InstrumentationKey == "" {
return errors.New("instrumentation key is required")
}
if a.transmitter == nil {
a.transmitter = NewTransmitter(a.InstrumentationKey, a.EndpointURL)
}
if a.EnableDiagnosticLogging && a.diagMsgSubscriber != nil {
a.diagMsgListener = a.diagMsgSubscriber.Subscribe(func(msg string) error {
a.Log.Info(msg)
return nil
})
}
return nil
}
func (a *ApplicationInsights) Write(metrics []telegraf.Metric) error {
for _, metric := range metrics {
allMetricTelemetry := a.createTelemetry(metric)
for _, telemetry := range allMetricTelemetry {
a.transmitter.Track(telemetry)
}
}
return nil
}
func (a *ApplicationInsights) Close() error {
if a.diagMsgListener != nil {
// We want to listen to diagnostic messages during closing
// That is why we stop listening only after Close() ends (or a timeout occurs)
defer a.diagMsgListener.Remove()
}
if a.transmitter == nil {
return nil
}
select {
case <-a.transmitter.Close():
a.Log.Info("Closed")
case <-time.After(time.Duration(a.Timeout)):
a.Log.Warnf("Close operation timed out after %v", time.Duration(a.Timeout))
}
return nil
}
func (a *ApplicationInsights) createTelemetry(metric telegraf.Metric) []appinsights.Telemetry {
aggregateTelemetry, usedFields := a.createAggregateMetricTelemetry(metric)
if aggregateTelemetry != nil {
telemetry := a.createTelemetryForUnusedFields(metric, usedFields)
telemetry = append(telemetry, aggregateTelemetry)
return telemetry
}
fields := metric.Fields()
if len(fields) == 1 && metric.FieldList()[0].Key == "value" {
// Just use metric name as the telemetry name
telemetry := a.createSimpleMetricTelemetry(metric, "value", false)
if telemetry != nil {
return []appinsights.Telemetry{telemetry}
}
return nil
}
// AppInsights does not support multi-dimensional metrics at the moment, so we need to disambiguate resulting telemetry
// by adding field name as the telemetry name suffix
return a.createTelemetryForUnusedFields(metric, nil)
}
func (a *ApplicationInsights) createSimpleMetricTelemetry(
metric telegraf.Metric,
fieldName string,
useFieldNameInTelemetryName bool,
) *appinsights.MetricTelemetry {
telemetryValue, err := getFloat64TelemetryPropertyValue([]string{fieldName}, metric, nil)
if err != nil {
return nil
}
var telemetryName string
if useFieldNameInTelemetryName {
telemetryName = metric.Name() + "_" + fieldName
} else {
telemetryName = metric.Name()
}
telemetry := appinsights.NewMetricTelemetry(telemetryName, telemetryValue)
telemetry.Properties = metric.Tags()
a.addContextTags(metric, telemetry)
telemetry.Timestamp = metric.Time()
return telemetry
}
func (a *ApplicationInsights) createAggregateMetricTelemetry(metric telegraf.Metric) (*appinsights.AggregateMetricTelemetry, []string) {
usedFields := make([]string, 0, 6) // We will use up to 6 fields
// Get the sum of all individual measurements(mandatory property)
telemetryValue, err := getFloat64TelemetryPropertyValue([]string{"sum", "value"}, metric, &usedFields)
if err != nil {
return nil, nil
}
// Get the count of measurements (mandatory property)
telemetryCount, err := getIntTelemetryPropertyValue([]string{"count", "samples"}, metric, &usedFields)
if err != nil {
return nil, nil
}
telemetry := appinsights.NewAggregateMetricTelemetry(metric.Name())
telemetry.Value = telemetryValue
telemetry.Count = telemetryCount
telemetry.Properties = metric.Tags()
a.addContextTags(metric, telemetry)
telemetry.Timestamp = metric.Time()
// We attempt to set min, max, variance and stddev fields but do not really care if they are not present--
// they are not essential for aggregate metric.
// By convention AppInsights prefers stddev over variance, so to be consistent, we test for stddev after testing for variance.
//nolint:errcheck // see above
telemetry.Min, _ = getFloat64TelemetryPropertyValue([]string{"min"}, metric, &usedFields)
//nolint:errcheck // see above
telemetry.Max, _ = getFloat64TelemetryPropertyValue([]string{"max"}, metric, &usedFields)
//nolint:errcheck // see above
telemetry.Variance, _ = getFloat64TelemetryPropertyValue([]string{"variance"}, metric, &usedFields)
//nolint:errcheck // see above
telemetry.StdDev, _ = getFloat64TelemetryPropertyValue([]string{"stddev"}, metric, &usedFields)
return telemetry, usedFields
}
func (a *ApplicationInsights) createTelemetryForUnusedFields(metric telegraf.Metric, usedFields []string) []appinsights.Telemetry {
fields := metric.Fields()
retval := make([]appinsights.Telemetry, 0, len(fields))
for fieldName := range fields {
if contains(usedFields, fieldName) {
continue
}
telemetry := a.createSimpleMetricTelemetry(metric, fieldName, true)
if telemetry != nil {
retval = append(retval, telemetry)
}
}
return retval
}
func (a *ApplicationInsights) addContextTags(metric telegraf.Metric, telemetry appinsights.Telemetry) {
for contextTagName, tagSourceName := range a.ContextTagSources {
if contextTagValue, found := metric.GetTag(tagSourceName); found {
telemetry.ContextTags()[contextTagName] = contextTagValue
}
}
}
func getFloat64TelemetryPropertyValue(
candidateFields []string,
metric telegraf.Metric,
usedFields *[]string,
) (float64, error) {
for _, fieldName := range candidateFields {
fieldValue, found := metric.GetField(fieldName)
if !found {
continue
}
metricValue, err := toFloat64(fieldValue)
if err != nil {
continue
}
if usedFields != nil {
*usedFields = append(*usedFields, fieldName)
}
return metricValue, nil
}
return 0.0, errors.New("no field from the candidate list was found in the metric")
}
func getIntTelemetryPropertyValue(
candidateFields []string,
metric telegraf.Metric,
usedFields *[]string,
) (int, error) {
for _, fieldName := range candidateFields {
fieldValue, found := metric.GetField(fieldName)
if !found {
continue
}
metricValue, err := toInt(fieldValue)
if err != nil {
continue
}
if usedFields != nil {
*usedFields = append(*usedFields, fieldName)
}
return metricValue, nil
}
return 0, errors.New("no field from the candidate list was found in the metric")
}
func contains(set []string, val string) bool {
for _, elem := range set {
if elem == val {
return true
}
}
return false
}
func toFloat64(value interface{}) (float64, error) {
// Out of all Golang numerical types Telegraf only uses int64, unit64 and float64 for fields
switch v := value.(type) {
case int64:
return float64(v), nil
case uint64:
return float64(v), nil
case float64:
return v, nil
}
return 0.0, fmt.Errorf("[%s] cannot be converted to a float64 value", value)
}
func toInt(value interface{}) (int, error) {
// Out of all Golang numerical types Telegraf only uses int64, unit64 and float64 for fields
switch v := value.(type) {
case uint64:
if strconv.IntSize == 32 {
if v > math.MaxInt32 {
return 0, fmt.Errorf("value [%d] out of range of 32-bit integers", v)
}
} else {
if v > math.MaxInt64 {
return 0, fmt.Errorf("value [%d] out of range of 64-bit integers", v)
}
}
return int(v), nil
case int64:
if strconv.IntSize == 32 {
if v > math.MaxInt32 || v < math.MinInt32 {
return 0, fmt.Errorf("value [%d] out of range of 32-bit integers", v)
}
}
return int(v), nil
}
return 0.0, fmt.Errorf("[%s] cannot be converted to an int value", value)
}
func init() {
outputs.Add("application_insights", func() telegraf.Output {
return &ApplicationInsights{
Timeout: config.Duration(time.Second * 5),
diagMsgSubscriber: diagnosticsMessageSubscriber{},
// It is very common to set Cloud.RoleName and Cloud.RoleInstance context properties, hence initial capacity of two
ContextTagSources: make(map[string]string, 2),
}
})
}

View file

@ -0,0 +1,462 @@
package application_insights
import (
"math"
"testing"
"time"
"github.com/microsoft/ApplicationInsights-Go/appinsights"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/metric"
"github.com/influxdata/telegraf/plugins/outputs/application_insights/mocks"
"github.com/influxdata/telegraf/testutil"
)
func TestConnectFailsIfNoIkey(t *testing.T) {
transmitter := new(mocks.Transmitter)
transmitter.On("Close").Return(closed)
ai := ApplicationInsights{
transmitter: transmitter,
// Very long timeout to ensure we do not rely on timeouts for closing the transmitter
Timeout: config.Duration(time.Hour),
Log: testutil.Logger{},
}
err := ai.Connect()
require.Error(t, err)
}
func TestOutputCloseTimesOut(t *testing.T) {
transmitter := new(mocks.Transmitter)
transmitter.On("Close").Return(unfinished)
ai := ApplicationInsights{
transmitter: transmitter,
Timeout: config.Duration(time.Millisecond * 50),
Log: testutil.Logger{},
}
err := ai.Close()
require.NoError(t, err)
transmitter.AssertCalled(t, "Close")
}
func TestCloseRemovesDiagMsgListener(t *testing.T) {
transmitter := new(mocks.Transmitter)
transmitter.On("Close").Return(closed)
diagMsgListener := new(mocks.DiagnosticsMessageListener)
diagMsgListener.On("Remove")
diagMsgSubscriber := new(mocks.DiagnosticsMessageSubscriber)
diagMsgSubscriber.
On("Subscribe", mock.AnythingOfType("appinsights.DiagnosticsMessageHandler")).
Return(diagMsgListener)
ai := ApplicationInsights{
transmitter: transmitter,
Timeout: config.Duration(time.Hour),
EnableDiagnosticLogging: true,
diagMsgSubscriber: diagMsgSubscriber,
InstrumentationKey: "1234", // Fake, but necessary to enable tracking
Log: testutil.Logger{},
}
err := ai.Connect()
require.NoError(t, err)
diagMsgSubscriber.AssertCalled(t, "Subscribe", mock.AnythingOfType("appinsights.DiagnosticsMessageHandler"))
err = ai.Close()
require.NoError(t, err)
transmitter.AssertCalled(t, "Close")
diagMsgListener.AssertCalled(t, "Remove")
}
func TestAggregateMetricCreated(t *testing.T) {
tests := []struct {
name string
fields map[string]interface{}
valueField string
countField string
additionalMetricValueFields []string
}{
{"value and count", map[string]interface{}{"value": 16.5, "count": 23}, "value", "count", nil},
{"value and samples", map[string]interface{}{"value": 16.5, "samples": 23}, "value", "samples", nil},
{"sum and count", map[string]interface{}{"sum": 16.5, "count": 23}, "sum", "count", nil},
{"sum and samples", map[string]interface{}{"samples": 23, "sum": 16.5}, "sum", "samples", nil},
{"value and count, sum is wrong type", map[string]interface{}{"sum": "J23", "value": 16.5, "count": 23}, "value", "count", nil},
{
"with aggregates",
map[string]interface{}{
"value": 16.5,
"count": 23,
"min": -2.1,
"max": 34,
"stddev": 3.4,
},
"value",
"count",
nil,
},
{
"some aggregates with invalid values",
map[string]interface{}{
"value": 16.5,
"count": 23,
"min": "min",
"max": []float64{3.4, 5.6},
"stddev": struct {
name string
value float64
}{"delta", 7.0},
},
"value",
"count",
nil,
},
{
"aggregate with additional fields",
map[string]interface{}{"value": 16.5, "samples": 23, "alpha": -34e12, "bravo": -3, "charlie": "charlie"},
"value",
"samples",
[]string{"alpha", "bravo"},
},
}
for _, tt := range tests {
tf := func(t *testing.T) {
now := time.Now().UTC()
transmitter := new(mocks.Transmitter)
transmitter.On("Track", mock.Anything)
metricName := "ShouldBeAggregateMetric"
m := metric.New(
metricName,
nil, // tags
tt.fields,
now,
)
ai := ApplicationInsights{
transmitter: transmitter,
InstrumentationKey: "1234", // Fake, but necessary to enable tracking
Log: testutil.Logger{},
}
err := ai.Connect()
require.NoError(t, err)
mSet := []telegraf.Metric{m}
err = ai.Write(mSet)
require.NoError(t, err)
transmitter.AssertNumberOfCalls(t, "Track", 1+len(tt.additionalMetricValueFields))
var pAggregateTelemetry *appinsights.AggregateMetricTelemetry
require.IsType(
t,
pAggregateTelemetry,
transmitter.Calls[len(transmitter.Calls)-1].Arguments.Get(0),
"Expected last telemetry to be AggregateMetricTelemetry",
)
aggregateTelemetry := transmitter.Calls[len(transmitter.Calls)-1].Arguments.Get(0).(*appinsights.AggregateMetricTelemetry)
verifyAggregateTelemetry(t, m, tt.valueField, tt.countField, aggregateTelemetry)
verifyAdditionalTelemetry(t, m, transmitter, tt.additionalMetricValueFields, metricName)
}
t.Run(tt.name, tf)
}
}
func TestSimpleMetricCreated(t *testing.T) {
tests := []struct {
name string
fields map[string]interface{}
primaryMetricValueField string
additionalMetricValueFields []string
}{
{"just a single value field", map[string]interface{}{"value": 16.5}, "value", nil},
{"single field not named value", map[string]interface{}{"first": 32.9}, "first", nil},
{"value but no count", map[string]interface{}{"value": 16.5, "other": "bulba"}, "", []string{"value"}},
{"count but no value", map[string]interface{}{"v1": "v1Val", "count": 23}, "", []string{"count"}},
{"neither value nor count", map[string]interface{}{"v1": "alpha", "v2": 45.8}, "", []string{"v2"}},
{"value is of wrong type", map[string]interface{}{"value": "alpha", "count": 15}, "", []string{"count"}},
{"count is of wrong type", map[string]interface{}{"value": 23.77, "count": 7.5}, "", []string{"count", "value"}},
{"count is out of range", map[string]interface{}{"value": -98.45e4, "count": float64(math.MaxUint64 - uint64(20))}, "", []string{"value", "count"}},
{
"several additional fields",
map[string]interface{}{"alpha": 10, "bravo": "bravo", "charlie": 30, "delta": 40.7},
"",
[]string{"alpha", "charlie", "delta"},
},
}
for _, tt := range tests {
tf := func(t *testing.T) {
now := time.Now().UTC()
transmitter := new(mocks.Transmitter)
transmitter.On("Track", mock.Anything)
metricName := "ShouldBeSimpleMetric"
m := metric.New(
metricName,
nil, // tags
tt.fields,
now,
)
ai := ApplicationInsights{
transmitter: transmitter,
InstrumentationKey: "1234", // Fake, but necessary to enable tracking
Log: testutil.Logger{},
}
err := ai.Connect()
require.NoError(t, err)
mSet := []telegraf.Metric{m}
err = ai.Write(mSet)
require.NoError(t, err)
expectedNumberOfCalls := len(tt.additionalMetricValueFields)
if tt.primaryMetricValueField != "" {
expectedNumberOfCalls++
}
transmitter.AssertNumberOfCalls(t, "Track", expectedNumberOfCalls)
if tt.primaryMetricValueField != "" {
var pMetricTelemetry *appinsights.MetricTelemetry
require.IsType(t, pMetricTelemetry, transmitter.Calls[0].Arguments.Get(0), "First created telemetry should be simple MetricTelemetry")
metricTelemetry := transmitter.Calls[0].Arguments.Get(0).(*appinsights.MetricTelemetry)
var expectedTelemetryName string
if tt.primaryMetricValueField == "value" {
expectedTelemetryName = m.Name()
} else {
expectedTelemetryName = m.Name() + "_" + tt.primaryMetricValueField
}
verifySimpleTelemetry(t, m, tt.primaryMetricValueField, expectedTelemetryName, metricTelemetry)
}
verifyAdditionalTelemetry(t, m, transmitter, tt.additionalMetricValueFields, metricName)
}
t.Run(tt.name, tf)
}
}
func TestTagsAppliedToTelemetry(t *testing.T) {
tests := []struct {
name string
fields map[string]interface{}
tags map[string]string
metricValueFields []string
}{
{
"value but no count",
map[string]interface{}{"value": 16.5, "alpha": 3.5, "bravo": 17},
map[string]string{"alpha": "a tag is not a field", "charlie": "charlie"},
[]string{"value", "alpha", "bravo"},
},
}
for _, tt := range tests {
tf := func(t *testing.T) {
now := time.Now().UTC()
transmitter := new(mocks.Transmitter)
transmitter.On("Track", mock.Anything)
metricName := "ShouldBeSimpleMetric"
m := metric.New(
metricName,
tt.tags,
tt.fields,
now,
)
ai := ApplicationInsights{
transmitter: transmitter,
InstrumentationKey: "1234", // Fake, but necessary to enable tracking
Log: testutil.Logger{},
}
err := ai.Connect()
require.NoError(t, err)
mSet := []telegraf.Metric{m}
err = ai.Write(mSet)
require.NoError(t, err)
transmitter.AssertNumberOfCalls(t, "Track", len(tt.metricValueFields))
transmitter.AssertCalled(t, "Track", mock.AnythingOfType("*appinsights.MetricTelemetry"))
// Will verify that all original tags are present in telemetry.Properties map
verifyAdditionalTelemetry(t, m, transmitter, tt.metricValueFields, metricName)
}
t.Run(tt.name, tf)
}
}
func TestContextTagsSetOnSimpleTelemetry(t *testing.T) {
now := time.Now().UTC()
transmitter := new(mocks.Transmitter)
transmitter.On("Track", mock.Anything)
m := metric.New(
"SimpleMetric",
map[string]string{"kubernetes_container_name": "atcsvc", "kubernetes_pod_name": "bunkie17554"},
map[string]interface{}{"value": 23.0},
now,
)
ai := ApplicationInsights{
transmitter: transmitter,
InstrumentationKey: "1234", // Fake, but necessary to enable tracking
ContextTagSources: map[string]string{
"ai.cloud.role": "kubernetes_container_name",
"ai.cloud.roleInstance": "kubernetes_pod_name",
"ai.user.id": "nonexistent",
},
Log: testutil.Logger{},
}
err := ai.Connect()
require.NoError(t, err)
mSet := []telegraf.Metric{m}
err = ai.Write(mSet)
require.NoError(t, err)
transmitter.AssertNumberOfCalls(t, "Track", 1)
metricTelemetry := transmitter.Calls[0].Arguments.Get(0).(*appinsights.MetricTelemetry)
cloudTags := metricTelemetry.Tags.Cloud()
require.Equal(t, "atcsvc", cloudTags.GetRole())
require.Equal(t, "bunkie17554", cloudTags.GetRoleInstance())
}
func TestContextTagsSetOnAggregateTelemetry(t *testing.T) {
now := time.Now().UTC()
transmitter := new(mocks.Transmitter)
transmitter.On("Track", mock.Anything)
m := metric.New(
"AggregateMetric",
map[string]string{"kubernetes_container_name": "atcsvc", "kubernetes_pod_name": "bunkie17554"},
map[string]interface{}{"value": 23.0, "count": 5},
now,
)
ai := ApplicationInsights{
transmitter: transmitter,
InstrumentationKey: "1234", // Fake, but necessary to enable tracking
ContextTagSources: map[string]string{
"ai.cloud.role": "kubernetes_container_name",
"ai.cloud.roleInstance": "kubernetes_pod_name",
"ai.user.id": "nonexistent",
},
Log: testutil.Logger{},
}
err := ai.Connect()
require.NoError(t, err)
mSet := []telegraf.Metric{m}
err = ai.Write(mSet)
require.NoError(t, err)
transmitter.AssertNumberOfCalls(t, "Track", 1)
metricTelemetry := transmitter.Calls[0].Arguments.Get(0).(*appinsights.AggregateMetricTelemetry)
cloudTags := metricTelemetry.Tags.Cloud()
require.Equal(t, "atcsvc", cloudTags.GetRole())
require.Equal(t, "bunkie17554", cloudTags.GetRoleInstance())
}
func closed() <-chan struct{} {
closed := make(chan struct{})
close(closed)
return closed
}
func unfinished() <-chan struct{} {
unfinished := make(chan struct{})
return unfinished
}
func verifyAggregateTelemetry(t *testing.T, m telegraf.Metric, valueField, countField string, telemetry *appinsights.AggregateMetricTelemetry) {
verifyAggregateField := func(fieldName string, telemetryValue float64) {
metricRawFieldValue, found := m.Fields()[fieldName]
if !found {
return
}
if _, err := toFloat64(metricRawFieldValue); err == nil {
require.InDelta(t, metricRawFieldValue, telemetryValue, testutil.DefaultDelta, "Telemetry property %s does not match the metric field", fieldName)
}
}
require.Equal(t, m.Name(), telemetry.Name, "Telemetry name should be the same as metric name")
require.InDelta(t, m.Fields()[valueField], telemetry.Value, testutil.DefaultDelta, "Telemetry value does not match metric value field")
require.EqualValues(t, m.Fields()[countField], telemetry.Count, "Telemetry sample count does not mach metric sample count field")
verifyAggregateField("min", telemetry.Min)
verifyAggregateField("max", telemetry.Max)
verifyAggregateField("stdev", telemetry.StdDev)
verifyAggregateField("variance", telemetry.Variance)
require.Equal(t, m.Time(), telemetry.Timestamp, "Telemetry and metric timestamps do not match")
assertMapContains(t, m.Tags(), telemetry.Properties)
}
func verifySimpleTelemetry(t *testing.T, m telegraf.Metric, valueField, expectedTelemetryName string, telemetry *appinsights.MetricTelemetry) {
require.Equal(t, expectedTelemetryName, telemetry.Name, "Telemetry name is not what was expected")
require.InDelta(t, m.Fields()[valueField], telemetry.Value, testutil.DefaultDelta, "Telemetry value does not match metric value field")
require.Equal(t, m.Time(), telemetry.Timestamp, "Telemetry and metric timestamps do not match")
assertMapContains(t, m.Tags(), telemetry.Properties)
}
func verifyAdditionalTelemetry(
t *testing.T,
m telegraf.Metric,
transmitter *mocks.Transmitter,
additionalMetricValueFields []string,
telemetryNamePrefix string,
) {
for _, fieldName := range additionalMetricValueFields {
expectedTelemetryName := telemetryNamePrefix + "_" + fieldName
telemetry := findTransmittedTelemetry(transmitter, expectedTelemetryName)
require.NotNil(t, telemetry, "Expected telemetry named %s to be created, but could not find it", expectedTelemetryName)
if telemetry != nil {
verifySimpleTelemetry(t, m, fieldName, expectedTelemetryName, telemetry)
}
}
}
func findTransmittedTelemetry(transmitter *mocks.Transmitter, telemetryName string) *appinsights.MetricTelemetry {
for _, call := range transmitter.Calls {
telemetry, isMetricTelemetry := call.Arguments.Get(0).(*appinsights.MetricTelemetry)
if isMetricTelemetry && telemetry.Name == telemetryName {
return telemetry
}
}
return nil
}
func assertMapContains(t *testing.T, expected, actual map[string]string) {
if expected == nil && actual == nil {
return
}
require.NotNil(t, expected, "Maps not equal: expected is nil but actual is not")
require.NotNil(t, actual, "Maps not equal: actual is nil but expected is not")
for k, v := range expected {
av, ok := actual[k]
require.True(t, ok, "Actual map does not contain a value for key %q", k)
require.Equal(t, v, av, "The expected value for key %q is %q but the actual value is %q", k, v, av)
}
}

View file

@ -0,0 +1,12 @@
package application_insights
import (
"github.com/microsoft/ApplicationInsights-Go/appinsights"
)
type diagnosticsMessageSubscriber struct {
}
func (diagnosticsMessageSubscriber) Subscribe(handler appinsights.DiagnosticsMessageHandler) appinsights.DiagnosticsMessageListener {
return appinsights.NewDiagnosticsMessageListener(handler)
}

View file

@ -0,0 +1,12 @@
package mocks
import "github.com/stretchr/testify/mock"
// DiagnosticsMessageSubscriber is an autogenerated mock type for the DiagnosticsMessageSubscriber type
type DiagnosticsMessageListener struct {
mock.Mock
}
func (_m *DiagnosticsMessageListener) Remove() {
_m.Called()
}

View file

@ -0,0 +1,28 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
package mocks
import (
"github.com/microsoft/ApplicationInsights-Go/appinsights"
"github.com/stretchr/testify/mock"
)
// DiagnosticsMessageSubscriber is an autogenerated mock type for the DiagnosticsMessageSubscriber type
type DiagnosticsMessageSubscriber struct {
mock.Mock
}
// Subscribe provides a mock function with given fields: _a0
func (_m *DiagnosticsMessageSubscriber) Subscribe(_a0 appinsights.DiagnosticsMessageHandler) appinsights.DiagnosticsMessageListener {
ret := _m.Called(_a0)
var r0 appinsights.DiagnosticsMessageListener
if rf, ok := ret.Get(0).(func(appinsights.DiagnosticsMessageHandler) appinsights.DiagnosticsMessageListener); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(appinsights.DiagnosticsMessageListener)
}
}
return r0
}

View file

@ -0,0 +1,33 @@
// Code generated by mockery v1.0.0. DO NOT EDIT.
package mocks
import (
"github.com/microsoft/ApplicationInsights-Go/appinsights"
"github.com/stretchr/testify/mock"
)
// Transmitter is an autogenerated mock type for the Transmitter type
type Transmitter struct {
mock.Mock
}
// Close provides a mock function with given fields:
func (_m *Transmitter) Close() <-chan struct{} {
ret := _m.Called()
var r0 <-chan struct{}
if rf, ok := ret.Get(0).(func() <-chan struct{}); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(<-chan struct{})
}
}
return r0
}
// Track provides a mock function with given fields: _a0
func (_m *Transmitter) Track(_a0 appinsights.Telemetry) {
_m.Called(_a0)
}

View file

@ -0,0 +1,25 @@
# Send metrics to Azure Application Insights
[[outputs.application_insights]]
## Instrumentation key of the Application Insights resource.
instrumentation_key = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
## Regions that require endpoint modification https://docs.microsoft.com/en-us/azure/azure-monitor/app/custom-endpoints
# endpoint_url = "https://dc.services.visualstudio.com/v2/track"
## Timeout for closing (default: 5s).
# timeout = "5s"
## Enable additional diagnostic logging.
# enable_diagnostic_logging = false
## NOTE: Due to the way TOML is parsed, tables must be at the END of the
## plugin definition, otherwise additional config options are read as part of
## the table
## Context Tag Sources add Application Insights context tags to a tag value.
##
## For list of allowed context tag keys see:
## https://github.com/microsoft/ApplicationInsights-Go/blob/master/appinsights/contracts/contexttagkeys.go
# [outputs.application_insights.context_tag_sources]
# "ai.cloud.role" = "kubernetes_container_name"
# "ai.cloud.roleInstance" = "kubernetes_pod_name"

View file

@ -0,0 +1,27 @@
package application_insights
import (
"github.com/microsoft/ApplicationInsights-Go/appinsights"
)
type Transmitter struct {
client appinsights.TelemetryClient
}
func NewTransmitter(ikey, endpointURL string) *Transmitter {
if len(endpointURL) == 0 {
return &Transmitter{client: appinsights.NewTelemetryClient(ikey)}
}
telemetryConfig := appinsights.NewTelemetryConfiguration(ikey)
telemetryConfig.EndpointUrl = endpointURL
return &Transmitter{client: appinsights.NewTelemetryClientFromConfig(telemetryConfig)}
}
func (t *Transmitter) Track(telemetry appinsights.Telemetry) {
t.client.Track(telemetry)
}
func (t *Transmitter) Close() <-chan struct{} {
return t.client.Channel().Close(0)
}