Skip to content

Commit

Permalink
feat: add honeycomb marker exporter structure (#27001)
Browse files Browse the repository at this point in the history
**Description:** This component will export markers to be consumed by
the Honeycomb Markers API to highlight user events based initially on
preset configurations.

**Link to tracking Issue:** #26653 

**Testing:** Unit testing for factory and config

**Documentation:** README describing component usage

---------

Co-authored-by: Tyler Helmuth <[email protected]>
  • Loading branch information
fchikwekwe and TylerHelmuth authored Oct 12, 2023
1 parent dc2658b commit 628a6cb
Show file tree
Hide file tree
Showing 20 changed files with 797 additions and 10 deletions.
27 changes: 27 additions & 0 deletions .chloggen/feat_marker-exporter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: 'new_component'

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: 'honeycombmarkerexporter'

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: "This component will export markers to be consumed by the Honeycomb Markers API to highlight user events"

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [26653]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ exporter/fileexporter/ @open-te
exporter/googlecloudexporter/ @open-telemetry/collector-contrib-approvers @aabmass @dashpole @jsuereth @punya @damemi @psx95
exporter/googlecloudpubsubexporter/ @open-telemetry/collector-contrib-approvers @alexvanboxel
exporter/googlemanagedprometheusexporter/ @open-telemetry/collector-contrib-approvers @aabmass @dashpole @jsuereth @punya @damemi @psx95
exporter/honeycombmarkerexporter/ @open-telemetry/collector-contrib-approvers @fchikwekwe @TylerHelmuth
exporter/influxdbexporter/ @open-telemetry/collector-contrib-approvers @jacobmarble
exporter/instanaexporter/ @open-telemetry/collector-contrib-approvers @jpkrohling @hickeyma
exporter/kafkaexporter/ @open-telemetry/collector-contrib-approvers @pavolloffay @MovieStoreGuy
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ body:
- exporter/googlecloud
- exporter/googlecloudpubsub
- exporter/googlemanagedprometheus
- exporter/honeycombmarker
- exporter/influxdb
- exporter/instana
- exporter/kafka
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ body:
- exporter/googlecloud
- exporter/googlecloudpubsub
- exporter/googlemanagedprometheus
- exporter/honeycombmarker
- exporter/influxdb
- exporter/instana
- exporter/kafka
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/other.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ body:
- exporter/googlecloud
- exporter/googlecloudpubsub
- exporter/googlemanagedprometheus
- exporter/honeycombmarker
- exporter/influxdb
- exporter/instana
- exporter/kafka
Expand Down
20 changes: 10 additions & 10 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,6 @@ updates:
schedule:
interval: "weekly"
day: "wednesday"
- package-ecosystem: "gomod"
directory: "/cmd/mdatagen"
schedule:
interval: "weekly"
day: "wednesday"
- package-ecosystem: "gomod"
directory: "/cmd/opampsupervisor"
schedule:
interval: "weekly"
day: "wednesday"
- package-ecosystem: "gomod"
directory: "/cmd/otelcontribcol"
schedule:
Expand Down Expand Up @@ -172,6 +162,11 @@ updates:
schedule:
interval: "weekly"
day: "wednesday"
- package-ecosystem: "gomod"
directory: "/exporter/honeycombmarkerexporter"
schedule:
interval: "weekly"
day: "wednesday"
- package-ecosystem: "gomod"
directory: "/exporter/influxdbexporter"
schedule:
Expand Down Expand Up @@ -272,6 +267,11 @@ updates:
schedule:
interval: "weekly"
day: "wednesday"
- package-ecosystem: "gomod"
directory: "/exporter/syslogexporter"
schedule:
interval: "weekly"
day: "wednesday"
- package-ecosystem: "gomod"
directory: "/exporter/tanzuobservabilityexporter"
schedule:
Expand Down
1 change: 1 addition & 0 deletions exporter/honeycombmarkerexporter/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
42 changes: 42 additions & 0 deletions exporter/honeycombmarkerexporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@@ -0,0 +1,18 @@
# Honeycomb Marker Exporter

This exporter allows creating markers, via the Honeycomb Markers API, based on the look of incoming telemetry.

The following configuration options are supported:

* `api_key` (Required): This is the API key (also called Write Key) for your Honeycomb account.
* `api_url` (Required): You can set the hostname to send marker data to.
* `markers` (Required): This specifies the exact configuration to create an event marker.
* `type`: Specifies the marker type. Markers with the same type will appear in the same color in Honeycomb. MarkerType or MarkerColor should be set.
* `color`: Specifies the marker color. Will only be used if MarkerType is not set.
* `messagefield`: This is the attribute that will be used as the message. If necessary the value will be converted to a string.
* `urlfield`: This is the attribute that will be used as the url. If necessary the value will be converted to a string.
* `rules`: This is a list of OTTL rules that determine when to create an event marker.
* `resourceconditions`: A list of ottlresource conditions that determine a match
* `logconditions`: A list of ottllog conditions that determine a match
Example:

```yaml
exporters:
honeycomb:
api_key: "my-api-key"
api_url: "https://api.testhost.io"
markers:
- type: "fooType",
messagefield: "test message",
urlfield: "https://api.testhost.io",
rules:
- resourceconditions:
- IsMatch(attributes["test"], ".*")
- logconditions:
- body == "test"
- color: "green",
messagefield: "another test message",
urlfield: "https://api.testhost.io",
rules:
- resourceconditions:
- IsMatch(attributes["test"], ".*")
- logconditions:
- body == "test"
```
90 changes: 90 additions & 0 deletions exporter/honeycombmarkerexporter/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package honeycombmarkerexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/honeycombmarkerexporter"

import (
"fmt"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configopaque"
"go.uber.org/zap"

"github.com/open-telemetry/opentelemetry-collector-contrib/internal/filter/filterottl"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

// Config defines configuration for the Honeycomb Marker exporter.
type Config struct {
// APIKey is the authentication token associated with the Honeycomb account.
APIKey configopaque.String `mapstructure:"api_key"`

// API URL to use (defaults to https://api.honeycomb.io)
APIURL string `mapstructure:"api_url"`

// Markers is the list of markers to create
Markers []Marker `mapstructure:"markers"`
}

type Marker struct {
// Type defines the type of Marker. Markers with the same type appear in Honeycomb with the same color
Type string `mapstructure:"type"`

// Color is the color of the Marker. Will only be used if the Type does not already exist.
Color string `mapstructure:"color"`

// MessageField is the attribute that will be used as the message.
// If necessary the value will be converted to a string.
MessageField string `mapstructure:"message_field"`

// URLField is the attribute that will be used as the url.
// If necessary the value will be converted to a string.
URLField string `mapstructure:"url_field"`

// Rules are the OTTL rules that determine when a piece of telemetry should be turned into a Marker
Rules Rules `mapstructure:"rules"`
}

type Rules struct {
// ResourceConditions is the list of ottlresource conditions that determine a match
ResourceConditions []string `mapstructure:"resource_conditions"`

// LogConditions is the list of ottllog conditions that determine a match
LogConditions []string `mapstructure:"log_conditions"`
}

var defaultCfg = createDefaultConfig().(*Config)

func (cfg *Config) Validate() error {
if cfg == nil {
cfg = defaultCfg
}

if cfg.APIKey == "" {
return fmt.Errorf("invalid API Key")
}

if len(cfg.Markers) != 0 {
for _, m := range cfg.Markers {
if len(m.Rules.ResourceConditions) == 0 && len(m.Rules.LogConditions) == 0 {
return fmt.Errorf("no rules supplied for Marker %v", m)
}

_, err := filterottl.NewBoolExprForResource(m.Rules.ResourceConditions, filterottl.StandardResourceFuncs(), ottl.PropagateError, component.TelemetrySettings{Logger: zap.NewNop()})
if err != nil {
return err
}

_, err = filterottl.NewBoolExprForLog(m.Rules.LogConditions, filterottl.StandardLogFuncs(), ottl.PropagateError, component.TelemetrySettings{Logger: zap.NewNop()})
if err != nil {
return err
}
}
} else {
return fmt.Errorf("no markers supplied")
}

return nil
}

var _ component.Config = (*Config)(nil)
113 changes: 113 additions & 0 deletions exporter/honeycombmarkerexporter/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package honeycombmarkerexporter

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/confmap/confmaptest"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/honeycombmarkerexporter/internal/metadata"
)

func TestLoadConfig(t *testing.T) {
t.Parallel()

cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
require.NoError(t, err)

tests := []struct {
id component.ID
expected component.Config
}{
{
id: component.NewIDWithName("honeycomb", ""),
expected: &Config{
APIKey: "test-apikey",
APIURL: "https://api.testhost.io",
Markers: []Marker{
{
Type: "fooType",
MessageField: "test message",
URLField: "https://api.testhost.io",
Rules: Rules{
ResourceConditions: []string{
`IsMatch(attributes["test"], ".*")`,
},
LogConditions: []string{
`body == "test"`,
},
},
},
},
},
},
{
id: component.NewIDWithName("honeycomb", "color_no_type"),
expected: &Config{
APIKey: "test-apikey",
APIURL: "https://api.testhost.io",
Markers: []Marker{
{
Color: "green",
MessageField: "test message",
URLField: "https://api.testhost.io",
Rules: Rules{
ResourceConditions: []string{
`IsMatch(attributes["test"], ".*")`,
},
LogConditions: []string{
`body == "test"`,
},
},
},
},
},
},
{
id: component.NewIDWithName(metadata.Type, "bad_syntax_log"),
},
{
id: component.NewIDWithName(metadata.Type, "no_conditions"),
},
{
id: component.NewIDWithName(metadata.Type, "no_api_key"),
},
{
id: component.NewIDWithName(metadata.Type, "no_markers_supplied"),
},
}

for _, tt := range tests {
t.Run(tt.id.String(), func(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()

sub, err := cm.Sub(tt.id.String())
require.NoError(t, err)
require.NoError(t, component.UnmarshalConfig(sub, cfg))

if tt.expected == nil {
err = component.ValidateConfig(cfg)
assert.Error(t, err)
return
}

assert.NoError(t, component.ValidateConfig(cfg))
assert.Equal(t, tt.expected, cfg)
})
}
}

func withDefaultConfig(fns ...func(*Config)) *Config {
cfg := createDefaultConfig().(*Config)
for _, fn := range fns {
fn(cfg)
}
return cfg
}
7 changes: 7 additions & 0 deletions exporter/honeycombmarkerexporter/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

//go:generate mdatagen metadata.yaml

// Package honeycombmarkerexporter exports Marker data to Honeycomb.
package honeycombmarkerexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/honeycombmarkerexporter"
Loading

0 comments on commit 628a6cb

Please sign in to comment.