Skip to content

Commit

Permalink
[processor/resourcedetectionprocessor] add heroku detector (open-tele…
Browse files Browse the repository at this point in the history
…metry#16840)

* [processor/resourcedetectionprocessor] add heroku detector
  • Loading branch information
atoulme committed Jan 6, 2023
1 parent a5e43c7 commit 37542d8
Show file tree
Hide file tree
Showing 11 changed files with 445 additions and 200 deletions.
16 changes: 16 additions & 0 deletions .chloggen/heroku-resourcedetection.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

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

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add support to detect Heroku resources

# One or more tracking issues related to the change
issues: [16833]

# (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:
399 changes: 200 additions & 199 deletions .github/CODEOWNERS

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ body:
- processor/resource
- processor/resourcedetection
- processor/resourcedetection/internal/azure
- processor/resourcedetection/internal/heroku
- processor/routing
- processor/schema
- processor/servicegraph
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 @@ -123,6 +123,7 @@ body:
- processor/resource
- processor/resourcedetection
- processor/resourcedetection/internal/azure
- processor/resourcedetection/internal/heroku
- processor/routing
- processor/schema
- processor/servicegraph
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 @@ -123,6 +123,7 @@ body:
- processor/resource
- processor/resourcedetection
- processor/resourcedetection/internal/azure
- processor/resourcedetection/internal/heroku
- processor/routing
- processor/schema
- processor/servicegraph
Expand Down
28 changes: 27 additions & 1 deletion processor/resourcedetectionprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ Queries the Docker daemon to retrieve the following resource attributes from the
You need to mount the Docker socket (`/var/run/docker.sock` on Linux) to contact the Docker daemon.
Docker detection does not work on macOS.

### Heroku dyno id

In a Heroku application, the [dyno id](https://devcenter.heroku.com/articles/dyno-metadata) is the identifier of the virtualized environment ("dyno") where the application runs.


Example:

```yaml
Expand Down Expand Up @@ -290,10 +295,31 @@ processors:
override: false
```

### Heroku

** You must first enable the [Heroku metadata feature](https://devcenter.heroku.com/articles/dyno-metadata) on the application **

Queries [Heroku metadata](https://devcenter.heroku.com/articles/dyno-metadata) to retrieve the following resource attributes:

* heroku.release.version (identifier for the current release)
* heroku.release.creation_timestamp (time and date the release was created)
* heroku.release.commit (commit hash for the current release)
* heroku.app.name (application name)
* heroku.app.id (unique identifier for the application)
* heroku.dyno.id (dyno identifier. Used as host name)

```yaml
processors:
resourcedetection/heroku:
detectors: [env, heroku]
timeout: 2s
override: false
```

## Configuration

```yaml
# a list of resource detectors to run, valid options are: "env", "system", "gce", "gke", "ec2", "ecs", "elastic_beanstalk", "eks", "azure"
# a list of resource detectors to run, valid options are: "env", "system", "gce", "gke", "ec2", "ecs", "elastic_beanstalk", "eks", "azure", "heroku"
detectors: [ <string> ]
# determines if existing resource attributes should be overridden or preserved, defaults to true
override: <bool>
Expand Down
15 changes: 15 additions & 0 deletions processor/resourcedetectionprocessor/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/aws/ec2"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/heroku"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/system"
)

Expand Down Expand Up @@ -76,6 +77,14 @@ func TestLoadConfig(t *testing.T) {
Attributes: []string{"a", "b"},
},
},
{
id: component.NewIDWithName(typeStr, "heroku"),
expected: &Config{
Detectors: []string{"env", "heroku"},
HTTPClientSettings: cfg,
Override: false,
},
},
{
id: component.NewIDWithName(typeStr, "invalid"),
errorMessage: "hostname_sources contains invalid value: \"invalid_source\"",
Expand Down Expand Up @@ -144,6 +153,12 @@ func TestGetConfigFromType(t *testing.T) {
HostnameSources: []string{"os"},
},
},
{
name: "Get Heroku Config",
detectorType: heroku.TypeStr,
inputDetectorConfig: DetectorConfig{},
expectedConfig: nil,
},
}

for _, tt := range tests {
Expand Down
2 changes: 2 additions & 0 deletions processor/resourcedetectionprocessor/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/docker"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/env"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/gcp"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/heroku"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/system"
)

Expand Down Expand Up @@ -71,6 +72,7 @@ func NewFactory() processor.Factory {
elasticbeanstalk.TypeStr: elasticbeanstalk.NewDetector,
env.TypeStr: env.NewDetector,
gcp.TypeStr: gcp.NewDetector,
heroku.TypeStr: heroku.NewDetector,
system.TypeStr: system.NewDetector,
})

Expand Down
88 changes: 88 additions & 0 deletions processor/resourcedetectionprocessor/internal/heroku/heroku.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// 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:https://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 heroku // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/heroku"

import (
"context"
"os"

"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/processor"
conventions "go.opentelemetry.io/collector/semconv/v1.6.1"
"go.uber.org/zap"

"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal"
)

const (
// TypeStr is type of detector.
TypeStr = "heroku"

// The identifier for the current release
herokuReleaseVersion = "heroku.release.version"
// The time and date the release was created.
herokuReleaseCreationTimestamp = "heroku.release.creation_timestamp"
// The commit hash for the current release
herokuReleaseCommit = "heroku.release.commit"
// The application name
herokuAppName = "heroku.app.name"
// The unique identifier for the application
herokuAppID = "heroku.app.id"
// The dyno identifier. Used as host name
herokuDynoID = "heroku.dyno.id"
)

// NewDetector returns a detector which can detect resource attributes on Heroku
func NewDetector(set processor.CreateSettings, _ internal.DetectorConfig) (internal.Detector, error) {
return &detector{
logger: set.Logger,
}, nil
}

type detector struct {
logger *zap.Logger
}

// Detect detects heroku metadata and returns a resource with the available ones
func (d *detector) Detect(ctx context.Context) (resource pcommon.Resource, schemaURL string, err error) {
res := pcommon.NewResource()
dynoID, ok := os.LookupEnv("HEROKU_DYNO_ID")
if !ok {
d.logger.Debug("heroku metadata unavailable", zap.Error(err))
return res, "", nil
}

attrs := res.Attributes()

attrs.PutStr(herokuDynoID, dynoID)
attrs.PutStr(conventions.AttributeHostName, dynoID)
if v, ok := os.LookupEnv("HEROKU_APP_ID"); ok {
attrs.PutStr(herokuAppID, v)
}
if v, ok := os.LookupEnv("HEROKU_APP_NAME"); ok {
attrs.PutStr(herokuAppName, v)
}
if v, ok := os.LookupEnv("HEROKU_RELEASE_CREATED_AT"); ok {
attrs.PutStr(herokuReleaseCreationTimestamp, v)
}
if v, ok := os.LookupEnv("HEROKU_RELEASE_VERSION"); ok {
attrs.PutStr(herokuReleaseVersion, v)
}
if v, ok := os.LookupEnv("HEROKU_SLUG_COMMIT"); ok {
attrs.PutStr(herokuReleaseCommit, v)
}

return res, conventions.SchemaURL, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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:https://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 heroku

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/processor/processortest"
conventions "go.opentelemetry.io/collector/semconv/v1.6.1"
"go.uber.org/zap"

"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal"
)

func TestNewDetector(t *testing.T) {
d, err := NewDetector(processortest.NewNopCreateSettings(), nil)
assert.NotNil(t, d)
assert.NoError(t, err)
}

func TestDetectTrue(t *testing.T) {
t.Setenv("HEROKU_DYNO_ID", "foo")
t.Setenv("HEROKU_APP_ID", "appid")
t.Setenv("HEROKU_APP_NAME", "appname")
t.Setenv("HEROKU_RELEASE_CREATED_AT", "createdat")
t.Setenv("HEROKU_RELEASE_VERSION", "v1")
t.Setenv("HEROKU_SLUG_COMMIT", "23456")

detector := &detector{}
res, schemaURL, err := detector.Detect(context.Background())
assert.Equal(t, conventions.SchemaURL, schemaURL)
require.NoError(t, err)
assert.Equal(t, map[string]any{
"heroku.app.id": "appid",
"heroku.app.name": "appname",
"heroku.dyno.id": "foo",
"heroku.release.commit": "23456",
"heroku.release.creation_timestamp": "createdat",
"heroku.release.version": "v1",
"host.name": "foo",
},
res.Attributes().AsRaw())
}

func TestDetectTruePartial(t *testing.T) {
t.Setenv("HEROKU_DYNO_ID", "foo")
t.Setenv("HEROKU_APP_ID", "appid")
t.Setenv("HEROKU_APP_NAME", "appname")
t.Setenv("HEROKU_RELEASE_VERSION", "v1")

detector := &detector{}
res, schemaURL, err := detector.Detect(context.Background())
assert.Equal(t, conventions.SchemaURL, schemaURL)
require.NoError(t, err)
assert.Equal(t, map[string]any{
"heroku.app.id": "appid",
"heroku.app.name": "appname",
"heroku.dyno.id": "foo",
"heroku.release.version": "v1",
"host.name": "foo",
},
res.Attributes().AsRaw())
}

func TestDetectFalse(t *testing.T) {

detector := &detector{
logger: zap.NewNop(),
}
res, schemaURL, err := detector.Detect(context.Background())
require.NoError(t, err)
assert.Equal(t, "", schemaURL)
assert.True(t, internal.IsEmptyResource(res))
}
5 changes: 5 additions & 0 deletions processor/resourcedetectionprocessor/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ resourcedetection/azure:
timeout: 2s
override: false

resourcedetection/heroku:
detectors: [env, heroku]
timeout: 2s
override: false

resourcedetection/invalid:
detectors: [env, system]
timeout: 2s
Expand Down

0 comments on commit 37542d8

Please sign in to comment.