Skip to content

Commit

Permalink
Add DataDog exporter back from old fork
Browse files Browse the repository at this point in the history
commit 99129fb96e29e9c1a92da00b7e3f8efcae8a31e8
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Thu Sep 3 18:10:28 2020 +0200

    Handle namespace at initialization time

commit babca25927926a60c0c416294af3aadf784d41b9
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Thu Sep 3 17:23:53 2020 +0200

    Initialize on a separate function
    This way the variables can be checked without worrying about the env

commit 24d0cb4cc566fa5313a8650c904a27bea68bf555
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Thu Sep 3 14:30:35 2020 +0200

    Check environment variables for unified service tagging

commit 6695f8297ab8b1fcae71b05acb027c4a0992e3a0
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Wed Sep 2 14:57:37 2020 +0200

    Add support for sending metrics through the API
    - Use datadog.Metric type for simplicity
    - Get host if unset

commit c366603
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Wed Sep 2 09:56:24 2020 +0200

    Disable Queue and Retry settings (#72)

    These are handled by the statsd package.
    OpenTelemetry docs are confusing and the default configuration (disabled)
    is different from the one returned by "GetDefault..." functions

commit a660b56
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Tue Sep 1 15:26:14 2020 +0200

    Add support for summary and distribution metric types (#65)

    * Add support for summary metric type

    * Add support for distribution metrics

    * Refactor metrics construction
    - Drop name in Metrics (now they act as Metric values)
    - Refactor constructor so that errors happen at compile-time

    * Report Summary total sum and count values
    Snapshot values are not filled in by OpenTelemetry

    * Report p00 and p100 as `.min` and `.max`
    This is more similar to what we do for our own non-additive type

    * Keep hostname if it has not been overridden

commit c95adc4
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Thu Aug 27 13:00:02 2020 +0200

    Update dependencies and `make gofmt`
    The collector was updated to 0.9.0 upstream

commit 20afb0e
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Wed Aug 26 18:25:49 2020 +0200

    Refactor configuration (#45)

    * Refactor configuration
    * Implement telemetry and tags configuration handling
    * Update example configuration and README file
    Co-authored-by: Kylian Serrania <kylian.serrania@datadoghq.com>

commit fdc98b5
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Fri Aug 21 11:09:08 2020 +0200

    Initial DogStatsD implementation (#15)

    Initial metrics exporter through DogStatsD with support for all metric types but summary and distribution

commit e848a60
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Fri Aug 21 10:42:45 2020 +0200

    Bump collector version

commit 58be9a4
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Thu Aug 6 10:04:32 2020 +0200

    Address linter

commit 695430c
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Tue Aug 4 13:28:01 2020 +0200

    Fix field name error

    MetricsEndpoint was renamed to MetricsURL

commit 168b319
Author: Pablo Baeyens <pablo.baeyens@datadoghq.com>
Date:   Mon Aug 3 11:05:01 2020 +0200

    Create initial outline for Datadog exporter (#1)

    * Add support for basic configuration options
    * Documents configuration options
  • Loading branch information
mx-psi committed Sep 21, 2020
1 parent bff19a2 commit 0345c29
Show file tree
Hide file tree
Showing 18 changed files with 3,108 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cmd/otelcontribcol/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/awsxrayexporter"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/azuremonitorexporter"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/carbonexporter"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/elasticexporter"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/honeycombexporter"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/jaegerthrifthttpexporter"
Expand Down Expand Up @@ -115,6 +116,7 @@ func components() (component.Factories, error) {
elasticexporter.NewFactory(),
alibabacloudlogserviceexporter.NewFactory(),
sentryexporter.NewFactory(),
datadogexporter.NewFactory(),
}
for _, exp := range factories.Exporters {
exporters = append(exporters, exp)
Expand Down
1 change: 1 addition & 0 deletions exporter/datadogexporter/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
22 changes: 22 additions & 0 deletions exporter/datadogexporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Datadog Exporter

This exporter sends metric data to [Datadog](https://datadoghq.com).

## Configuration

The metrics exporter has two modes:
- If sending metrics through DogStatsD (the default mode), there are no required settings.
- If sending metrics without an Agent the mode must be set explicitly and
you must provide a [Datadog API key](https://app.datadoghq.com/account/settings#api).
```yaml
datadog:
api:
key: "<API key>"
# site: datadoghq.eu for sending data to the Datadog EU site
metrics:
mode: agentless
```
The hostname, environment, service and version can be set in the configuration for unified service tagging.
See the sample configuration file under the `example` folder for other available options.
211 changes: 211 additions & 0 deletions exporter/datadogexporter/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package datadogexporter

import (
"errors"
"fmt"
"os"
"strings"

"go.opentelemetry.io/collector/config/configmodels"
)

var (
errUnsetAPIKey = errors.New("the Datadog API key is unset")
)

const (
NoneMode = "none"
AgentlessMode = "agentless"
DogStatsDMode = "dogstatsd"
)

// APIConfig defines the API configuration options
type APIConfig struct {
// Key is the Datadog API key to associate your Agent's data with your organization.
// Create a new API key here: https://app.datadoghq.com/account/settings
Key string `mapstructure:"key"`

// Site is the site of the Datadog intake to send data to.
// The default value is "datadoghq.com".
Site string `mapstructure:"site"`
}

// GetCensoredKey returns the API key censored for logging purposes
func (api *APIConfig) GetCensoredKey() string {
if len(api.Key) <= 5 {
return api.Key
}
return strings.Repeat("*", len(api.Key)-5) + api.Key[:len(api.Key)-5]
}

// DogStatsDConfig defines the DogStatsd related configuration
type DogStatsDConfig struct {
// Endpoint is the DogStatsD address.
// The default value is 127.0.0.1:8125
// A Unix address is supported
Endpoint string `mapstructure:"endpoint"`

// Telemetry states whether to send metrics
Telemetry bool `mapstructure:"telemetry"`
}

// AgentlessConfig defines the Agentless related configuration
type AgentlessConfig struct {
// Endpoint is the host of the Datadog intake server to send metrics to.
// If unset, the value is obtained from the Site.
Endpoint string `mapstructure:"endpoint"`
}

// MetricsConfig defines the metrics exporter specific configuration options
type MetricsConfig struct {
// Namespace is the namespace under which the metrics are sent
// By default metrics are not namespaced
Namespace string `mapstructure:"namespace"`

// Mode is the metrics sending mode: either 'dogstatsd' or 'agentless'
Mode string `mapstructure:"mode"`

// Percentiles states whether to report percentiles for summary metrics,
// including the minimum and maximum
Percentiles bool `mapstructure:"report_percentiles"`

// Buckets states whether to report buckets from distribution metrics
Buckets bool `mapstructure:"report_buckets"`

// DogStatsD defines the DogStatsD configuration options.
DogStatsD DogStatsDConfig `mapstructure:"dogstatsd"`

// Agentless defines the Agentless configuration options.
Agentless AgentlessConfig `mapstructure:"agentless"`
}

// TagsConfig defines the tag-related configuration
// It is embedded in the configuration
type TagsConfig struct {
// Hostname is the host name for unified service tagging.
// If unset, it is determined automatically.
// See https://docs.datadoghq.com/agent/faq/how-datadog-agent-determines-the-hostname
// for more details.
Hostname string `mapstructure:"hostname"`

// Env is the environment for unified service tagging.
// It can also be set through the `DD_ENV` environment variable.
Env string `mapstructure:"env"`

// Service is the service for unified service tagging.
// It can also be set through the `DD_SERVICE` environment variable.
Service string `mapstructure:"service"`

// Version is the version for unified service tagging.
// It can also be set through the `DD_VERSION` version variable.
Version string `mapstructure:"version"`

// Tags is the list of default tags to add to every metric or trace.
Tags []string `mapstructure:"tags"`
}

// UpdateWithEnv gets the unified service tagging information
// from the environment variables.
func (t *TagsConfig) UpdateWithEnv() {
if t.Hostname == "" {
t.Hostname = os.Getenv("DD_HOST")
}

if t.Env == "" {
t.Env = os.Getenv("DD_ENV")
}

if t.Service == "" {
t.Service = os.Getenv("DD_SERVICE")
}

if t.Version == "" {
t.Version = os.Getenv("DD_VERSION")
}
}

// GetTags gets the default tags extracted from the configuration
func (t *TagsConfig) GetTags(addHost bool) []string {
tags := make([]string, 0, 4)

vars := map[string]string{
"env": t.Env,
"service": t.Service,
"version": t.Version,
}

if addHost {
vars["host"] = t.Hostname
}

for name, val := range vars {
if val != "" {
tags = append(tags, fmt.Sprintf("%s:%s", name, val))
}
}

tags = append(tags, t.Tags...)

return tags
}

// Config defines configuration for the Datadog exporter.
type Config struct {
configmodels.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct.

TagsConfig `mapstructure:",squash"`

// API defines the Datadog API configuration.
API APIConfig `mapstructure:"api"`

// Metrics defines the Metrics exporter specific configuration
Metrics MetricsConfig `mapstructure:"metrics"`
}

// Sanitize tries to sanitize a given configuration
func (c *Config) Sanitize() error {

if c.Metrics.Mode != AgentlessMode && c.Metrics.Mode != DogStatsDMode {
return fmt.Errorf("Metrics mode '%s' is not recognized", c.Metrics.Mode)
}

// Get info from environment variables
// if unset
c.TagsConfig.UpdateWithEnv()

// Add '.' at the end of namespace
// to have the same behavior on DogStatsD and the API
if c.Metrics.Namespace != "" && !strings.HasSuffix(c.Metrics.Namespace, ".") {
c.Metrics.Namespace = c.Metrics.Namespace + "."
}

// Exactly one configuration for metrics must be set
if c.Metrics.Mode == AgentlessMode {
if c.API.Key == "" {
return errUnsetAPIKey
}

c.API.Key = strings.TrimSpace(c.API.Key)

// Set the endpoint based on the Site
if c.Metrics.Agentless.Endpoint == "" {
c.Metrics.Agentless.Endpoint = fmt.Sprintf("https://api.%s", c.API.Site)
}
}

return nil
}
131 changes: 131 additions & 0 deletions exporter/datadogexporter/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package datadogexporter

import (
"path"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configmodels"
"go.opentelemetry.io/collector/config/configtest"
)

// TestLoadConfig tests that the configuration is loaded correctly
func TestLoadConfig(t *testing.T) {
factories, err := componenttest.ExampleComponents()
assert.NoError(t, err)

factory := NewFactory()
factories.Exporters[typeStr] = factory
cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories)

require.NoError(t, err)
require.NotNil(t, cfg)

apiConfig := cfg.Exporters["datadog/api"].(*Config)
err = apiConfig.Sanitize()

require.NoError(t, err)
assert.Equal(t, apiConfig, &Config{
ExporterSettings: configmodels.ExporterSettings{
NameVal: "datadog/api",
TypeVal: "datadog",
},

TagsConfig: TagsConfig{
Hostname: "customhostname",
Env: "prod",
Service: "myservice",
Version: "myversion",
Tags: []string{"example:tag"},
},

API: APIConfig{
Key: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
Site: "datadoghq.eu",
},

Metrics: MetricsConfig{
Mode: AgentlessMode,
Namespace: "opentelemetry.",
Percentiles: false,

DogStatsD: DogStatsDConfig{
Endpoint: "127.0.0.1:8125",
Telemetry: true,
},

Agentless: AgentlessConfig{
Endpoint: "https://api.datadoghq.eu",
},
},
})

dogstatsdConfig := cfg.Exporters["datadog/dogstatsd"].(*Config)
err = dogstatsdConfig.Sanitize()

require.NoError(t, err)
assert.Equal(t, dogstatsdConfig, &Config{
ExporterSettings: configmodels.ExporterSettings{
NameVal: "datadog/dogstatsd",
TypeVal: "datadog",
},

TagsConfig: TagsConfig{},
API: APIConfig{Site: "datadoghq.com"},

Metrics: MetricsConfig{
Mode: DogStatsDMode,
Percentiles: true,
DogStatsD: DogStatsDConfig{
Endpoint: "127.0.0.1:8125",
Telemetry: true,
},

Agentless: AgentlessConfig{},
},
})

invalidConfig := cfg.Exporters["datadog/invalid"].(*Config)
err = invalidConfig.Sanitize()
require.Error(t, err)

invalidConfig2 := cfg.Exporters["datadog/agentless/invalid"].(*Config)
err = invalidConfig2.Sanitize()
require.Error(t, err)

}

// TestOverrideMetricsURL tests that the metrics URL is overridden
// correctly when set manually.
func TestOverrideMetricsURL(t *testing.T) {

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

cfg := Config{
API: APIConfig{Key: "notnull", Site: DefaultSite},
Metrics: MetricsConfig{
Mode: AgentlessMode,
Agentless: AgentlessConfig{Endpoint: DebugEndpoint},
},
}

err := cfg.Sanitize()
require.NoError(t, err)
assert.Equal(t, cfg.Metrics.Agentless.Endpoint, DebugEndpoint)
}
Loading

0 comments on commit 0345c29

Please sign in to comment.