Skip to content

Commit 20afb0e

Browse files
mx-psiKSerrania
andcommitted
Refactor configuration (#45)
* Refactor configuration * Implement telemetry and tags configuration handling * Update example configuration and README file Co-authored-by: Kylian Serrania <[email protected]>
1 parent fdc98b5 commit 20afb0e

File tree

9 files changed

+355
-118
lines changed

9 files changed

+355
-118
lines changed

exporter/datadogexporter/README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,19 @@ This exporter sends metric data to [Datadog](https://datadoghq.com).
44

55
## Configuration
66

7-
The only required setting is a [Datadog API key](https://app.datadoghq.com/account/settings#api).
8-
To send your Agent data to the Datadog EU site, set the site parameter to:
9-
``` yaml
10-
site: datadoghq.eu
11-
```
7+
The metrics exporter has two modes:
8+
- If sending metrics through DogStatsD (the default mode), there are no required settings.
9+
- If sending metrics without an Agent the mode must be set explicitly and
10+
you must provide a [Datadog API key](https://app.datadoghq.com/account/settings#api).
11+
```yaml
12+
datadog:
13+
api:
14+
key: "<API key>"
15+
# site: datadoghq.eu for sending data to the Datadog EU site
16+
metrics:
17+
mode: agentless
18+
```
19+
20+
The hostname, environment, service and version can be set in the configuration for unified service tagging.
1221
1322
See the sample configuration file under the `example` folder for other available options.

exporter/datadogexporter/config.go

Lines changed: 114 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,61 +24,143 @@ import (
2424

2525
var (
2626
errUnsetAPIKey = errors.New("the Datadog API key is unset")
27-
errConflict = errors.New("'site' and 'api_key' must not be set with agent mode")
2827
)
2928

30-
// Config defines configuration for the Datadog exporter.
31-
type Config struct {
32-
configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct.
29+
const (
30+
NoneMode = "none"
31+
AgentlessMode = "agentless"
32+
DogStatsDMode = "dogstatsd"
33+
)
3334

34-
// ApiKey is the Datadog API key to associate your Agent's data with your organization.
35+
// APIConfig defines the API configuration options
36+
type APIConfig struct {
37+
// Key is the Datadog API key to associate your Agent's data with your organization.
3538
// Create a new API key here: https://app.datadoghq.com/account/settings
36-
APIKey string `mapstructure:"api_key"`
39+
Key string `mapstructure:"key"`
3740

3841
// Site is the site of the Datadog intake to send data to.
3942
// The default value is "datadoghq.com".
4043
Site string `mapstructure:"site"`
44+
}
45+
46+
// DogStatsDConfig defines the DogStatsd related configuration
47+
type DogStatsDConfig struct {
48+
// Endpoint is the DogStatsD address.
49+
// The default value is 127.0.0.1:8125
50+
// A Unix address is supported
51+
Endpoint string `mapstructure:"endpoint"`
52+
53+
// Telemetry states whether to send metrics
54+
Telemetry bool `mapstructure:"telemetry"`
55+
}
56+
57+
// AgentlessConfig defines the Agentless related configuration
58+
type AgentlessConfig struct {
59+
// Endpoint is the host of the Datadog intake server to send metrics to.
60+
// If unset, the value is obtained from the Site.
61+
Endpoint string `mapstructure:"endpoint"`
62+
}
63+
64+
// MetricsConfig defines the metrics exporter specific configuration options
65+
type MetricsConfig struct {
66+
// Namespace is the namespace under which the metrics are sent
67+
// By default metrics are not namespaced
68+
Namespace string `mapstructure:"namespace"`
4169

42-
// MetricsURL is the host of the Datadog intake server or Dogstatsd server to send metrics to.
43-
// If not set, the value is obtained from the Site and the sending method.
44-
MetricsURL string `mapstructure:"metrics_url"`
70+
// Mode is the metrics sending mode: either 'dogstatsd' or 'agentless'
71+
Mode string `mapstructure:"mode"`
4572

46-
// Tags is the list of default tags to add to every metric or trace
73+
// DogStatsD defines the DogStatsD configuration options.
74+
DogStatsD DogStatsDConfig `mapstructure:"dogstatsd"`
75+
76+
// Agentless defines the Agentless configuration options.
77+
Agentless AgentlessConfig `mapstructure:"agentless"`
78+
}
79+
80+
// TagsConfig defines the tag-related configuration
81+
// It is embedded in the configuration
82+
type TagsConfig struct {
83+
// Hostname is the host name for unified service tagging.
84+
// If unset, it is determined automatically.
85+
// See https://docs.datadoghq.com/agent/faq/how-datadog-agent-determines-the-hostname
86+
// for more details.
87+
Hostname string `mapstructure:"hostname"`
88+
89+
// Env is the environment for unified service tagging.
90+
// It can also be set through the `DD_ENV` environment variable.
91+
Env string `mapstructure:"env"`
92+
93+
// Service is the service for unified service tagging.
94+
// It can also be set through the `DD_SERVICE` environment variable.
95+
Service string `mapstructure:"service"`
96+
97+
// Version is the version for unified service tagging.
98+
// It can also be set through the `DD_VERSION` version variable.
99+
Version string `mapstructure:"version"`
100+
101+
// Tags is the list of default tags to add to every metric or trace.
47102
Tags []string `mapstructure:"tags"`
103+
}
104+
105+
// GetTags gets the default tags extracted from the configuration
106+
func (t *TagsConfig) GetTags() []string {
107+
tags := make([]string, 0, 4)
108+
109+
if t.Hostname != "" {
110+
tags = append(tags, fmt.Sprintf("host:%s", t.Hostname))
111+
}
48112

49-
// Mode states the mode for sending metrics and traces.
50-
// The possible values are "api" and "agent".
51-
// The default value is "agent".
52-
Mode string `mapstructure:"sending_method"`
113+
if t.Env != "" {
114+
tags = append(tags, fmt.Sprintf("env:%s", t.Env))
115+
}
116+
117+
if t.Service != "" {
118+
tags = append(tags, fmt.Sprintf("service:%s", t.Service))
119+
}
120+
121+
if t.Version != "" {
122+
tags = append(tags, fmt.Sprintf("version:%s", t.Version))
123+
}
124+
125+
if len(t.Tags) > 0 {
126+
tags = append(tags, t.Tags...)
127+
}
128+
129+
return tags
53130
}
54131

55-
// Sanitize tries to sanitize a given configuration
56-
func (c *Config) Sanitize() error {
57-
if c.Mode == AgentMode {
132+
// Config defines configuration for the Datadog exporter.
133+
type Config struct {
134+
configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct.
58135

59-
if c.APIKey != "" || c.Site != DefaultSite {
60-
return errConflict
61-
}
136+
TagsConfig `mapstructure:",squash"`
62137

63-
if c.MetricsURL == "" {
64-
c.MetricsURL = "127.0.0.1:8125"
65-
}
138+
// API defines the Datadog API configuration.
139+
API APIConfig `mapstructure:"api"`
66140

67-
} else if c.Mode == APIMode {
141+
// Metrics defines the Metrics exporter specific configuration
142+
Metrics MetricsConfig `mapstructure:"metrics"`
143+
}
68144

69-
if c.APIKey == "" {
145+
// Sanitize tries to sanitize a given configuration
146+
func (c *Config) Sanitize() error {
147+
148+
if c.Metrics.Mode != AgentlessMode && c.Metrics.Mode != DogStatsDMode {
149+
return fmt.Errorf("Metrics mode '%s' is not recognized", c.Metrics.Mode)
150+
}
151+
152+
// Exactly one configuration for metrics must be set
153+
if c.Metrics.Mode == AgentlessMode {
154+
if c.API.Key == "" {
70155
return errUnsetAPIKey
71156
}
72157

73-
// Sanitize API key
74-
c.APIKey = strings.TrimSpace(c.APIKey)
158+
c.API.Key = strings.TrimSpace(c.API.Key)
75159

76-
if c.MetricsURL == "" {
77-
c.MetricsURL = fmt.Sprintf("https://api.%s", c.Site)
160+
// Set the endpoint based on the Site
161+
if c.Metrics.Agentless.Endpoint == "" {
162+
c.Metrics.Agentless.Endpoint = fmt.Sprintf("https://api.%s", c.API.Site)
78163
}
79-
80-
} else {
81-
return fmt.Errorf("Selected mode '%s' is invalid", c.Mode)
82164
}
83165

84166
return nil

exporter/datadogexporter/config_test.go

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -37,40 +37,75 @@ func TestLoadConfig(t *testing.T) {
3737
require.NoError(t, err)
3838
require.NotNil(t, cfg)
3939

40-
e0 := cfg.Exporters["datadog"].(*Config)
41-
err = e0.Sanitize()
40+
apiConfig := cfg.Exporters["datadog/api"].(*Config)
41+
err = apiConfig.Sanitize()
4242

4343
require.NoError(t, err)
44-
assert.Equal(t, e0, &Config{
44+
assert.Equal(t, apiConfig, &Config{
4545
ExporterSettings: configmodels.ExporterSettings{
46-
NameVal: "datadog",
46+
NameVal: "datadog/api",
4747
TypeVal: "datadog",
4848
},
49-
Site: DefaultSite,
50-
MetricsURL: "127.0.0.1:8125",
51-
Tags: []string{"tool:opentelemetry", "version:0.1.0"},
52-
Mode: AgentMode,
49+
50+
TagsConfig: TagsConfig{
51+
Hostname: "customhostname",
52+
Env: "prod",
53+
Service: "myservice",
54+
Version: "myversion",
55+
Tags: []string{"example:tag"},
56+
},
57+
58+
API: APIConfig{
59+
Key: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
60+
Site: "datadoghq.eu",
61+
},
62+
63+
Metrics: MetricsConfig{
64+
Mode: AgentlessMode,
65+
Namespace: "opentelemetry",
66+
67+
DogStatsD: DogStatsDConfig{
68+
Endpoint: "127.0.0.1:8125",
69+
Telemetry: true,
70+
},
71+
72+
Agentless: AgentlessConfig{
73+
Endpoint: "https://api.datadoghq.eu",
74+
},
75+
},
5376
})
5477

55-
e1 := cfg.Exporters["datadog/2"].(*Config)
56-
err = e1.Sanitize()
78+
dogstatsdConfig := cfg.Exporters["datadog/dogstatsd"].(*Config)
79+
err = dogstatsdConfig.Sanitize()
5780

5881
require.NoError(t, err)
59-
assert.Equal(t, e1,
60-
&Config{
61-
ExporterSettings: configmodels.ExporterSettings{
62-
NameVal: "datadog/2",
63-
TypeVal: "datadog",
82+
assert.Equal(t, dogstatsdConfig, &Config{
83+
ExporterSettings: configmodels.ExporterSettings{
84+
NameVal: "datadog/dogstatsd",
85+
TypeVal: "datadog",
86+
},
87+
88+
TagsConfig: TagsConfig{},
89+
API: APIConfig{Site: "datadoghq.com"},
90+
91+
Metrics: MetricsConfig{
92+
Mode: DogStatsDMode,
93+
94+
DogStatsD: DogStatsDConfig{
95+
Endpoint: "127.0.0.1:8125",
96+
Telemetry: true,
6497
},
65-
APIKey: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
66-
Site: "datadoghq.eu",
67-
MetricsURL: "https://api.datadoghq.eu",
68-
Tags: DefaultTags,
69-
Mode: APIMode,
70-
})
71-
72-
e3 := cfg.Exporters["datadog/invalid"].(*Config)
73-
err = e3.Sanitize()
98+
99+
Agentless: AgentlessConfig{},
100+
},
101+
})
102+
103+
invalidConfig := cfg.Exporters["datadog/invalid"].(*Config)
104+
err = invalidConfig.Sanitize()
105+
require.Error(t, err)
106+
107+
invalidConfig2 := cfg.Exporters["datadog/agentless/invalid"].(*Config)
108+
err = invalidConfig2.Sanitize()
74109
require.Error(t, err)
75110

76111
}
@@ -81,24 +116,15 @@ func TestOverrideMetricsURL(t *testing.T) {
81116

82117
const DebugEndpoint string = "http://localhost:8080"
83118

84-
cfg := &Config{
85-
APIKey: "notnull",
86-
Site: DefaultSite,
87-
MetricsURL: DebugEndpoint,
88-
Mode: APIMode,
119+
cfg := Config{
120+
API: APIConfig{Key: "notnull", Site: DefaultSite},
121+
Metrics: MetricsConfig{
122+
Mode: AgentlessMode,
123+
Agentless: AgentlessConfig{Endpoint: DebugEndpoint},
124+
},
89125
}
90126

91127
err := cfg.Sanitize()
92128
require.NoError(t, err)
93-
assert.Equal(t, cfg.MetricsURL, DebugEndpoint)
94-
}
95-
96-
// TestUnsetAPIKey tests that the config sanitizing throws an error
97-
// when the API key has not been set
98-
func TestUnsetAPIKey(t *testing.T) {
99-
100-
cfg := &Config{}
101-
err := cfg.Sanitize()
102-
103-
require.Error(t, err)
104-
}
129+
assert.Equal(t, cfg.Metrics.Agentless.Endpoint, DebugEndpoint)
130+
}

exporter/datadogexporter/dogstatsd.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,18 @@ type dogStatsDExporter struct {
3232

3333
func newDogStatsDExporter(logger *zap.Logger, cfg *Config) (*dogStatsDExporter, error) {
3434

35+
options := []statsd.Option{
36+
statsd.WithNamespace(cfg.Metrics.Namespace),
37+
statsd.WithTags(cfg.TagsConfig.GetTags()),
38+
}
39+
40+
if !cfg.Metrics.DogStatsD.Telemetry {
41+
options = append(options, statsd.WithoutTelemetry())
42+
}
43+
3544
client, err := statsd.New(
36-
cfg.MetricsURL,
37-
statsd.WithNamespace("opentelemetry."),
38-
statsd.WithTags(cfg.Tags),
45+
cfg.Metrics.DogStatsD.Endpoint,
46+
options...,
3947
)
4048

4149
if err != nil {

0 commit comments

Comments
 (0)