Skip to content

Commit 4abb732

Browse files
[exporter/azuremonitor] Add Connection String Support to Azure Monitor Exporter (#28854)
**Description:** <Describe what has changed.> <!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> This pull request introduces the ability to configure the Azure Monitor Exporter using a connection string, aligning the exporter configuration with Azure Monitor's recommended practices. The current implementation requires users to set the instrumentation key directly, which will soon be deprecated in favor of using the connection string. **Changes Made:** 1. Configuration Update: Modified the `Config` struct and related configuration parsing logic to support a `ConnectionString` field. 2. Parsing Logic: Implemented functionality to parse the connection string and extract necessary details, such as `InstrumentationKey` and `IngestionEndpoint`. 3. Updated Tests: Revised existing tests and added new ones to ensure coverage of the new configuration option. **Benefits:** * Streamlines the configuration process for end-users. * Aligns with Azure Monitor's best practices and recommended configuration approach. * Paves the way for the upcoming deprecation of direct instrumentation key configuration. **Backwards Compatibility:** This update maintains full backwards compatibility. Users currently utilizing the instrumentation key for configuration can continue to do so but are advised to transition to using the connection string. **To-Do** * Documentation Update in a follow up PR * Deprecation Notice: A future update will introduce a deprecation warning for users still configuring the exporter with the instrumentation key, encouraging them to switch to using a connection string. * Add support for `EndpointSuffix` in connection string - https://learn.microsoft.com/en-us/azure/azure-monitor/app/sdk-connection-string?tabs=dotnet5#connection-string-with-an-endpoint-suffix **Link to tracking Issue:** <Issue number if applicable> #28853 **Testing:** <Describe what testing was performed and which tests were added.> Conducted comprehensive testing, including unit tests, to validate that the new configuration option works as expected and does not introduce regressions. All tests are currently passing. ``` [Wed Nov 1 12:53:42 PDT 2023] --------- Transmitting 27 items --------- [Wed Nov 1 12:53:43 PDT 2023] Telemetry transmitted in 331.926261ms [Wed Nov 1 12:53:43 PDT 2023] Response: 200 [Wed Nov 1 12:53:43 PDT 2023] Items accepted/received: 27/27 [Wed Nov 1 12:53:53 PDT 2023] --------- Transmitting 30 items --------- [Wed Nov 1 12:53:53 PDT 2023] Telemetry transmitted in 73.171392ms [Wed Nov 1 12:53:53 PDT 2023] Response: 200 [Wed Nov 1 12:53:53 PDT 2023] Items accepted/received: 30/30 [Wed Nov 1 12:54:04 PDT 2023] --------- Transmitting 27 items --------- [Wed Nov 1 12:54:04 PDT 2023] Telemetry transmitted in 68.037724ms [Wed Nov 1 12:54:04 PDT 2023] Response: 200 [Wed Nov 1 12:54:04 PDT 2023] Items accepted/received: 27/27 ``` **Documentation:** <Describe the documentation added.> TODO, in a follow up PR.
1 parent 040426f commit 4abb732

File tree

11 files changed

+307
-13
lines changed

11 files changed

+307
-13
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: azuremonitorexporter
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Added connection string support to the Azure Monitor Exporter
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [28853]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
This enhancement simplifies the configuration process and aligns the exporter with Azure Monitor's recommended practices.
20+
The Connection String method allows the inclusion of various fields such as the InstrumentationKey and IngestionEndpoint
21+
within a single string, facilitating an easier and more integrated setup.
22+
While the traditional InstrumentationKey method remains supported for backward compatibility, it will be phased out.
23+
Users are encouraged to adopt the Connection String approach to ensure future compatibility and to leverage the broader
24+
configuration options it enables.
25+
26+
# If your change doesn't affect end users or the exported elements of any package,
27+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
28+
# Optional: The change log or logs in which this entry should be included.
29+
# e.g. '[user]' or '[user, api]'
30+
# Include 'user' if the change is relevant to end users.
31+
# Include 'api' if there is a change to a library API.
32+
# Default: '[user]'
33+
change_logs: [user]

cmd/otelcontribcol/exporters_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/stretchr/testify/require"
1818
"go.opentelemetry.io/collector/component"
1919
"go.opentelemetry.io/collector/config/configgrpc"
20+
"go.opentelemetry.io/collector/config/configopaque"
2021
"go.opentelemetry.io/collector/exporter"
2122
"go.opentelemetry.io/collector/exporter/exportertest"
2223
"go.opentelemetry.io/collector/exporter/otlpexporter"
@@ -298,6 +299,7 @@ func TestDefaultExporters(t *testing.T) {
298299
getConfigFn: func() component.Config {
299300
cfg := expFactories["azuremonitor"].CreateDefaultConfig().(*azuremonitorexporter.Config)
300301
cfg.Endpoint = "http://" + endpoint
302+
cfg.ConnectionString = configopaque.String("InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=" + cfg.Endpoint)
301303

302304
return cfg
303305
},

cmd/otelcontribcol/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ require (
186186
github.com/stretchr/testify v1.8.4
187187
go.opentelemetry.io/collector/component v0.88.1-0.20231026220224-6405e152a2d9
188188
go.opentelemetry.io/collector/config/configgrpc v0.88.1-0.20231026220224-6405e152a2d9
189+
go.opentelemetry.io/collector/config/configopaque v0.88.1-0.20231026220224-6405e152a2d9
189190
go.opentelemetry.io/collector/confmap v0.88.1-0.20231026220224-6405e152a2d9
190191
go.opentelemetry.io/collector/connector v0.88.1-0.20231026220224-6405e152a2d9
191192
go.opentelemetry.io/collector/connector/forwardconnector v0.88.1-0.20231026220224-6405e152a2d9
@@ -637,7 +638,6 @@ require (
637638
go.opentelemetry.io/collector/config/configcompression v0.88.1-0.20231026220224-6405e152a2d9 // indirect
638639
go.opentelemetry.io/collector/config/confighttp v0.88.1-0.20231026220224-6405e152a2d9 // indirect
639640
go.opentelemetry.io/collector/config/confignet v0.88.1-0.20231026220224-6405e152a2d9 // indirect
640-
go.opentelemetry.io/collector/config/configopaque v0.88.1-0.20231026220224-6405e152a2d9 // indirect
641641
go.opentelemetry.io/collector/config/configtelemetry v0.88.1-0.20231026220224-6405e152a2d9 // indirect
642642
go.opentelemetry.io/collector/config/configtls v0.88.1-0.20231026220224-6405e152a2d9 // indirect
643643
go.opentelemetry.io/collector/config/internal v0.88.1-0.20231026220224-6405e152a2d9 // indirect

exporter/azuremonitorexporter/README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,37 @@ This exporter sends logs, traces and metrics to [Azure Monitor](https://docs.mic
1717

1818
## Configuration
1919

20-
The following settings are required:
20+
To configure the Azure Monitor Exporter, you must specify one of the following settings:
2121

22-
- `instrumentation_key` (no default): Application Insights instrumentation key, which can be found in the Application Insights resource in the Azure Portal.
22+
- `connection_string` (recommended): The Azure Application Insights Connection String is required to send telemetry data to the monitoring service. It is the recommended method for configuring the exporter, aligning with Azure Monitor's best practices. If you need guidance on creating Azure resources, please refer to the step-by-step guides to [Create an Application Insights resource](https://docs.microsoft.com/azure/azure-monitor/app/create-new-resource) and [find your connection string](https://docs.microsoft.com/azure/azure-monitor/app/sdk-connection-string?tabs=net#find-your-connection-string).
23+
- `instrumentation_key`: Application Insights instrumentation key, which can be found in the Application Insights resource in the Azure Portal. While it is currently supported, its use is discouraged and it is slated for deprecation. It is highly encouraged to use the `connection_string` setting for new configurations and migrate existing configurations to use the `connection_string` as soon as possible.
24+
25+
**Important**: Only one of `connection_string` or `instrumentation_key` should be specified in your configuration. If both are provided, `connection_string` will be used as the priority setting.
2326

2427
The following settings can be optionally configured:
2528

26-
- `endpoint` (default = `https://dc.services.visualstudio.com/v2/track`): The endpoint URL where data will be submitted.
29+
- `endpoint` (default = `https://dc.services.visualstudio.com/v2/track`): The endpoint URL where data will be submitted. While this option remains available, it is important to note that the use of the `connection_string` is recommended, as it encompasses the endpoint information. The direct configuration of the `endpoint` is considered to be on a deprecation path.
2730
- `maxbatchsize` (default = 1024): The maximum number of telemetry items that can be submitted in each request. If this many items are buffered, the buffer will be flushed before `maxbatchinterval` expires.
2831
- `maxbatchinterval` (default = 10s): The maximum time to wait before sending a batch of telemetry.
2932
- `spaneventsenabled` (default = false): Enables export of span events.
3033

3134
Example:
3235

3336
```yaml
37+
# Recommended Configuration:
38+
# It is highly recommended to use the connection string which includes the InstrumentationKey and IngestionEndpoint
39+
# This is the preferred method over using 'instrumentation_key' alone.
40+
exporters:
41+
azuremonitor:
42+
connection_string: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://ingestion.azuremonitor.com/"
43+
```
44+
45+
(or)
46+
47+
```yaml
48+
# Legacy Configuration:
49+
# The use of 'instrumentation_key' alone is not recommended and will be deprecated in the future. It is advised to use the connection_string instead.
50+
# This example is provided primarily for existing configurations that have not yet transitioned to the connection string.
3451
exporters:
3552
azuremonitor:
3653
instrumentation_key: b1cd0778-85fc-4677-a3fa-79d3c23e0efd

exporter/azuremonitorexporter/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
// Config defines configuration for Azure Monitor
1313
type Config struct {
1414
Endpoint string `mapstructure:"endpoint"`
15+
ConnectionString configopaque.String `mapstructure:"connection_string"`
1516
InstrumentationKey configopaque.String `mapstructure:"instrumentation_key"`
1617
MaxBatchSize int `mapstructure:"maxbatchsize"`
1718
MaxBatchInterval time.Duration `mapstructure:"maxbatchinterval"`

exporter/azuremonitorexporter/config_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ func TestLoadConfig(t *testing.T) {
3535
id: component.NewIDWithName(metadata.Type, "2"),
3636
expected: &Config{
3737
Endpoint: defaultEndpoint,
38-
InstrumentationKey: "abcdefg",
38+
ConnectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://ingestion.azuremonitor.com/",
39+
InstrumentationKey: "00000000-0000-0000-0000-000000000000",
3940
MaxBatchSize: 100,
4041
MaxBatchInterval: 10 * time.Second,
4142
SpanEventsEnabled: false,
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package azuremonitorexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/azuremonitorexporter"
5+
6+
import (
7+
"fmt"
8+
"net/url"
9+
"path"
10+
"strings"
11+
)
12+
13+
type ConnectionVars struct {
14+
InstrumentationKey string
15+
IngestionURL string
16+
}
17+
18+
const (
19+
DefaultIngestionEndpoint = "https://dc.services.visualstudio.com/"
20+
IngestionEndpointKey = "IngestionEndpoint"
21+
InstrumentationKey = "InstrumentationKey"
22+
ConnectionStringMaxLength = 4096
23+
)
24+
25+
func parseConnectionString(exporterConfig *Config) (*ConnectionVars, error) {
26+
connectionString := string(exporterConfig.ConnectionString)
27+
instrumentationKey := string(exporterConfig.InstrumentationKey)
28+
connectionVars := &ConnectionVars{}
29+
30+
if connectionString == "" && instrumentationKey == "" {
31+
return nil, fmt.Errorf("ConnectionString and InstrumentationKey cannot be empty")
32+
}
33+
if len(connectionString) > ConnectionStringMaxLength {
34+
return nil, fmt.Errorf("ConnectionString exceeds maximum length of %d characters", ConnectionStringMaxLength)
35+
}
36+
if connectionString == "" {
37+
connectionVars.InstrumentationKey = instrumentationKey
38+
connectionVars.IngestionURL = getIngestionURL(DefaultIngestionEndpoint)
39+
return connectionVars, nil
40+
}
41+
42+
pairs := strings.Split(connectionString, ";")
43+
values := make(map[string]string)
44+
for _, pair := range pairs {
45+
kv := strings.SplitN(strings.TrimSpace(pair), "=", 2)
46+
if len(kv) != 2 {
47+
return nil, fmt.Errorf("invalid format for connection string: %s", pair)
48+
}
49+
50+
key, value := strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])
51+
if key == "" {
52+
return nil, fmt.Errorf("key cannot be empty")
53+
}
54+
values[key] = value
55+
}
56+
57+
var ok bool
58+
if connectionVars.InstrumentationKey, ok = values[InstrumentationKey]; !ok || connectionVars.InstrumentationKey == "" {
59+
return nil, fmt.Errorf("%s is required", InstrumentationKey)
60+
}
61+
62+
var ingestionEndpoint string
63+
if ingestionEndpoint, ok = values[IngestionEndpointKey]; !ok || ingestionEndpoint == "" {
64+
ingestionEndpoint = DefaultIngestionEndpoint
65+
}
66+
67+
connectionVars.IngestionURL = getIngestionURL(ingestionEndpoint)
68+
69+
return connectionVars, nil
70+
}
71+
72+
func getIngestionURL(ingestionEndpoint string) string {
73+
ingestionURL, err := url.Parse(ingestionEndpoint)
74+
if err != nil {
75+
ingestionURL, _ = url.Parse(DefaultIngestionEndpoint)
76+
}
77+
78+
ingestionURL.Path = path.Join(ingestionURL.Path, "/v2/track")
79+
return ingestionURL.String()
80+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package azuremonitorexporter
5+
6+
import (
7+
"strings"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
"go.opentelemetry.io/collector/config/configopaque"
13+
)
14+
15+
func TestParseConnectionString(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
config *Config
19+
want *ConnectionVars
20+
wantError bool
21+
}{
22+
{
23+
name: "Valid connection string and instrumentation key",
24+
config: &Config{
25+
ConnectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://ingestion.azuremonitor.com/",
26+
InstrumentationKey: "00000000-0000-0000-0000-00000000IKEY",
27+
},
28+
want: &ConnectionVars{
29+
InstrumentationKey: "00000000-0000-0000-0000-000000000000",
30+
IngestionURL: "https://ingestion.azuremonitor.com/v2/track",
31+
},
32+
wantError: false,
33+
},
34+
{
35+
name: "Empty connection string with valid instrumentation key",
36+
config: &Config{
37+
InstrumentationKey: "00000000-0000-0000-0000-000000000000",
38+
},
39+
want: &ConnectionVars{
40+
InstrumentationKey: "00000000-0000-0000-0000-000000000000",
41+
IngestionURL: DefaultIngestionEndpoint + "v2/track",
42+
},
43+
wantError: false,
44+
},
45+
{
46+
name: "Valid connection string with empty instrumentation key",
47+
config: &Config{
48+
ConnectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://ingestion.azuremonitor.com/",
49+
},
50+
want: &ConnectionVars{
51+
InstrumentationKey: "00000000-0000-0000-0000-000000000000",
52+
IngestionURL: "https://ingestion.azuremonitor.com/v2/track",
53+
},
54+
wantError: false,
55+
},
56+
{
57+
name: "Empty connection string and instrumentation key",
58+
config: &Config{
59+
ConnectionString: "",
60+
InstrumentationKey: "",
61+
},
62+
want: nil,
63+
wantError: true,
64+
},
65+
{
66+
name: "Invalid connection string format",
67+
config: &Config{
68+
ConnectionString: "InvalidConnectionString",
69+
},
70+
want: nil,
71+
wantError: true,
72+
},
73+
{
74+
name: "Missing InstrumentationKey in connection string",
75+
config: &Config{
76+
ConnectionString: "IngestionEndpoint=https://ingestion.azuremonitor.com/",
77+
},
78+
want: nil,
79+
wantError: true,
80+
},
81+
{
82+
name: "Empty InstrumentationKey in connection string",
83+
config: &Config{
84+
ConnectionString: "InstrumentationKey=;IngestionEndpoint=https://ingestion.azuremonitor.com/",
85+
},
86+
want: nil,
87+
wantError: true,
88+
},
89+
{
90+
name: "Extra parameters in connection string",
91+
config: &Config{
92+
ConnectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://ingestion.azuremonitor.com/;ExtraParam=extra",
93+
},
94+
want: &ConnectionVars{
95+
InstrumentationKey: "00000000-0000-0000-0000-000000000000",
96+
IngestionURL: "https://ingestion.azuremonitor.com/v2/track",
97+
},
98+
wantError: false,
99+
},
100+
{
101+
name: "Spaces around equals in connection string",
102+
config: &Config{
103+
ConnectionString: "InstrumentationKey = 00000000-0000-0000-0000-000000000000 ; IngestionEndpoint = https://ingestion.azuremonitor.com/",
104+
},
105+
want: &ConnectionVars{
106+
InstrumentationKey: "00000000-0000-0000-0000-000000000000",
107+
IngestionURL: "https://ingestion.azuremonitor.com/v2/track",
108+
},
109+
wantError: false,
110+
},
111+
{
112+
name: "Connection string too long",
113+
config: &Config{
114+
ConnectionString: configopaque.String(strings.Repeat("a", ConnectionStringMaxLength+1)),
115+
},
116+
want: nil,
117+
wantError: true,
118+
},
119+
}
120+
121+
for _, tt := range tests {
122+
t.Run(tt.name, func(t *testing.T) {
123+
got, err := parseConnectionString(tt.config)
124+
if tt.wantError {
125+
require.Error(t, err, "Expected an error but got none")
126+
} else {
127+
require.NoError(t, err, "Unexpected error: %v", err)
128+
require.NotNil(t, got, "Expected a non-nil result")
129+
assert.Equal(t, tt.want.InstrumentationKey, got.InstrumentationKey, "InstrumentationKey does not match")
130+
assert.Equal(t, tt.want.IngestionURL, got.IngestionURL, "IngestionEndpoint does not match")
131+
}
132+
})
133+
}
134+
}

0 commit comments

Comments
 (0)