Skip to content

Commit

Permalink
OT resource detector (#939)
Browse files Browse the repository at this point in the history
* first

* all included

* constant

* res keys

* env detector

* Update sdk/detect/env.go

Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>

* new test cases and strings operation to handle labels

* solved conflict

* typo

* corrected comments

* deleted env var handling

* push again

* rerun

* restructuring

* new way to aggregate errors

* all in resources package and verbose comments

* solved merge conflicts

* included new error types

* improved error handling

* updated changelog.md

* updated changelog

* updated changelog.md

* updated changelog

* Update CHANGELOG.md

Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>
Co-authored-by: Liz Fong-Jones <lizf@honeycomb.io>
Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
  • Loading branch information
4 people authored Jul 20, 2020
1 parent b2b23e1 commit 99c2998
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Added

- Define a general Resources Detector Interface and allow resources to be detected from environment variables. (#939)
- Github action to generate protobuf Go bindings locally in `internal/opentelemetry-proto-gen`. (#938)
- OTLP .proto files from `open-telemetry/opentelemetry-proto` imported as a git submodule under `internal/opentelemetry-proto`.
References to `github.com/open-telemetry/opentelemetry-proto` changed to `go.opentelemetry.io/otel/internal/opentelemetry-proto-gen`. (#942)
Expand Down
67 changes: 67 additions & 0 deletions sdk/resource/auto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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 resource

import (
"context"
"errors"
"fmt"
)

var (
// ErrMissingResource is returned by a detector when source information
// is unavailable for a Resource.
ErrMissingResource = errors.New("missing resource")
// ErrPartialResource is returned by a detector when complete source
// information for a Resource is unavailable or the source information
// contains invalid values that are omitted from the returned Resource.
ErrPartialResource = errors.New("partial resource")
)

// Detector detects OpenTelemetry resource information
type Detector interface {
// Detect returns an initialized Resource based on gathered information.
// If source information to construct a Resource is inaccessible, an
// uninitialized Resource is returned with an appropriately wrapped
// ErrMissingResource error is returned. If the source information to
// construct a Resource contains invalid values, a Resource is returned
// with the valid parts of the source information used for
// initialization along with an appropriately wrapped ErrPartialResource
// error.
Detect(ctx context.Context) (*Resource, error)
}

// Detect calls all input detectors sequentially and merges each result with the previous one.
// It returns the merged error too.
func Detect(ctx context.Context, detectors ...Detector) (*Resource, error) {
var autoDetectedRes *Resource
var errInfo []string
for _, detector := range detectors {
res, err := detector.Detect(ctx)
if err != nil {
errInfo = append(errInfo, err.Error())
if !errors.Is(err, ErrPartialResource) {
continue
}
}
autoDetectedRes = Merge(autoDetectedRes, res)
}

var aggregatedError error
if len(errInfo) > 0 {
aggregatedError = fmt.Errorf("detecting resources: %s", errInfo)
}
return autoDetectedRes, aggregatedError
}
28 changes: 28 additions & 0 deletions sdk/resource/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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 resource provides detecting and representing resources.
//
// The fundamental struct is a Resource which holds identifying information
// about the entities for which telemetry is exported.
//
// To automatically construct Resources from an environment a Detector
// interface is defined. Implementations of this interface can be passed to
// the Detect function to generate a Resource from the merged information.
//
// To load a user defined Resource from the environment variable
// OTEL_RESOURCE_LABELS the FromEnv Detector can be used. It will interpret
// the value as a list of comma delimited key/value pairs
// (e.g. `<key1>=<value1>,<key2>=<value2>,...`).
package resource
69 changes: 69 additions & 0 deletions sdk/resource/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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 resource

import (
"context"
"fmt"
"os"
"strings"

"go.opentelemetry.io/otel/api/kv"
)

// envVar is the environment variable name OpenTelemetry Resource information can be assigned to.
const envVar = "OTEL_RESOURCE_LABELS"

var (
//errMissingValue is returned when a resource value is missing.
errMissingValue = fmt.Errorf("%w: missing value", ErrPartialResource)
)

// FromEnv is a detector that implements the Detector and collects resources
// from environment
type FromEnv struct{}

// compile time assertion that FromEnv implements Detector interface
var _ Detector = (*FromEnv)(nil)

// Detect collects resources from environment
func (d *FromEnv) Detect(context.Context) (*Resource, error) {
labels := strings.TrimSpace(os.Getenv(envVar))

if labels == "" {
return Empty(), ErrMissingResource
}
return constructOTResources(labels)
}

func constructOTResources(s string) (*Resource, error) {
pairs := strings.Split(s, ",")
labels := []kv.KeyValue{}
var invalid []string
for _, p := range pairs {
field := strings.SplitN(p, "=", 2)
if len(field) != 2 {
invalid = append(invalid, p)
continue
}
k, v := strings.TrimSpace(field[0]), strings.TrimSpace(field[1])
labels = append(labels, kv.String(k, v))
}
var err error
if len(invalid) > 0 {
err = fmt.Errorf("%w: %v", errMissingValue, invalid)
}
return New(labels...), err
}
73 changes: 73 additions & 0 deletions sdk/resource/env_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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 resource

import (
"context"
"fmt"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.opentelemetry.io/otel/api/kv"
)

func TestDetectOnePair(t *testing.T) {
os.Setenv(envVar, "key=value")

detector := &FromEnv{}
res, err := detector.Detect(context.Background())
require.NoError(t, err)
assert.Equal(t, New(kv.String("key", "value")), res)
}

func TestDetectMultiPairs(t *testing.T) {
os.Setenv("x", "1")
os.Setenv(envVar, "key=value, k = v , a= x, a=z")

detector := &FromEnv{}
res, err := detector.Detect(context.Background())
require.NoError(t, err)
assert.Equal(t, res, New(
kv.String("key", "value"),
kv.String("k", "v"),
kv.String("a", "x"),
kv.String("a", "z"),
))
}

func TestEmpty(t *testing.T) {
os.Setenv(envVar, "")

detector := &FromEnv{}
res, err := detector.Detect(context.Background())
require.Error(t, err)
assert.Equal(t, err, ErrMissingResource)
assert.Equal(t, Empty(), res)
}

func TestMissingKeyError(t *testing.T) {
os.Setenv(envVar, "key=value,key")

detector := &FromEnv{}
res, err := detector.Detect(context.Background())
assert.Error(t, err)
assert.Equal(t, err, fmt.Errorf("%w: %v", errMissingValue, "[key]"))
assert.Equal(t, res, New(
kv.String("key", "value"),
))
}
4 changes: 1 addition & 3 deletions sdk/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package resource provides functionality for resource, which capture
// identifying information about the entities for which signals are exported.
package resource

import (
Expand All @@ -34,7 +32,7 @@ type Resource struct {

var emptyResource Resource

// Key creates a resource from a set of attributes. If there are
// New creates a resource from a set of attributes. If there are
// duplicate keys present in the list of attributes, then the last
// value found for the key is preserved.
func New(kvs ...kv.KeyValue) *Resource {
Expand Down

0 comments on commit 99c2998

Please sign in to comment.