Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add host.id to resource auto-detection #3812

Merged
merged 12 commits into from
Mar 21, 2023
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

- The `WithHostID` option to `go.opentelemetry.io/otel/sdk/resource`. (#3812)
- The `WithoutTimestamps` option to `go.opentelemetry.io/otel/exporters/stdout/stdoutmetric` to sets all timestamps to zero. (#3828)
- The new `Exemplar` type is added to `go.opentelemetry.io/otel/sdk/metric/metricdata`.
Both the `DataPoint` and `HistogramDataPoint` types from that package have a new field of `Exemplars` containing the sampled exemplars for their timeseries. (#3849)
Expand Down
5 changes: 5 additions & 0 deletions sdk/resource/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ func WithHost() Option {
return WithDetectors(host{})
}

// WithHostID adds host ID information to the configured resource.
func WithHostID() Option {
return WithDetectors(hostIDDetector{})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we add hostIDDetector to WithHost()? We might consider adding WithHostName to offer a resource detector that only sets host.name resource attribute.

Copy link
Member Author

@mwear mwear Mar 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the rules for detecting HostID are different for containerized vs non-containerized hosts I didn't want to couple the hostIDDetector to WithHost, although I had considered it. My thoughts were that someone who wanted to use WithHost on a cloud provider wouldn't have to worry about detector order when detecting HostID. However, I could see having WithHost that detects HostID and HostName, and separate options WithHostID and WithHostName to collect the attributes individually. Let me know what your preference is.

Copy link
Member

@pellared pellared Mar 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT: After second thought, I have no strong preference. Maybe we should improve the docs and list all the attributes that given detector (option) should set? This could be addressed in a separate issue as I feel this is a problem not only for this detector/option.

}

// WithTelemetrySDK adds TelemetrySDK version info to the configured resource.
func WithTelemetrySDK() Option {
return WithDetectors(telemetrySDK{})
Expand Down
142 changes: 142 additions & 0 deletions sdk/resource/host_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// 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 "go.opentelemetry.io/otel/sdk/resource"

import (
"context"
"errors"
"os"
"os/exec"
"strings"

semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)

type hostIDProvider func() (string, error)

var defaultHostIDProvider hostIDProvider = platformHostIDReader.read

var hostID = defaultHostIDProvider

type hostIDReader interface {
read() (string, error)
}

type fileReader func(string) (string, error)

type commandExecutor func(string, ...string) (string, error)

func readFile(filename string) (string, error) {
b, err := os.ReadFile(filename)
if err != nil {
return "", nil
}

return string(b), nil
}

// nolint: unused // This is used by the hostReaderBSD, gated by build tags.
func execCommand(name string, arg ...string) (string, error) {
cmd := exec.Command(name, arg...)
b, err := cmd.Output()
if err != nil {
return "", err
}

return string(b), nil
}

// hostIDReaderBSD implements hostIDReader.
type hostIDReaderBSD struct {
execCommand commandExecutor
readFile fileReader
}

// read attempts to read the machine-id from /etc/hostid. If not found it will
// execute `kenv -q smbios.system.uuid`. If neither location yields an id an
// error will be returned.
func (r *hostIDReaderBSD) read() (string, error) {
if result, err := r.readFile("/etc/hostid"); err == nil {
return strings.TrimSpace(result), nil
}

if result, err := r.execCommand("kenv", "-q", "smbios.system.uuid"); err == nil {
return strings.TrimSpace(result), nil
}

return "", errors.New("host id not found in: /etc/hostid or kenv")
}

// hostIDReaderDarwin implements hostIDReader.
type hostIDReaderDarwin struct {
execCommand commandExecutor
}

// read executes `ioreg -rd1 -c "IOPlatformExpertDevice"` and parses host id
// from the IOPlatformUUID line. If the command fails or the uuid cannot be
// parsed an error will be returned.
func (r *hostIDReaderDarwin) read() (string, error) {
result, err := r.execCommand("ioreg", "-rd1", "-c", "IOPlatformExpertDevice")
if err != nil {
return "", err
}

lines := strings.Split(result, "\n")
for _, line := range lines {
if strings.Contains(line, "IOPlatformUUID") {
parts := strings.Split(line, " = ")
if len(parts) == 2 {
return strings.Trim(parts[1], "\""), nil
}
break
}
}

return "", errors.New("could not parse IOPlatformUUID")
}

type hostIDReaderLinux struct {
readFile fileReader
}

// read attempts to read the machine-id from /etc/machine-id followed by
// /var/lib/dbus/machine-id. If neither location yields an ID an error will
// be returned.
func (r *hostIDReaderLinux) read() (string, error) {
if result, err := r.readFile("/etc/machine-id"); err == nil {
return strings.TrimSpace(result), nil
}

if result, err := r.readFile("/var/lib/dbus/machine-id"); err == nil {
return strings.TrimSpace(result), nil
}

return "", errors.New("host id not found in: /etc/machine-id or /var/lib/dbus/machine-id")
}

type hostIDDetector struct{}

// Detect returns a *Resource containing the platform specific host id.
func (hostIDDetector) Detect(ctx context.Context) (*Resource, error) {
hostID, err := hostID()
if err != nil {
return nil, err
}

return NewWithAttributes(
semconv.SchemaURL,
semconv.HostID(hostID),
), nil
}
28 changes: 28 additions & 0 deletions sdk/resource/host_id_bsd.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.

//go:build dragonfly || freebsd || netbsd || openbsd || solaris
// +build dragonfly freebsd netbsd openbsd solaris

package resource // import "go.opentelemetry.io/otel/sdk/resource"

import (
"errors"
"strings"
)

var platformHostIDReader hostIDReader = &hostIDReaderBSD{
execCommand: execCommand,
readFile: readFile,
}
19 changes: 19 additions & 0 deletions sdk/resource/host_id_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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 "go.opentelemetry.io/otel/sdk/resource"

var platformHostIDReader hostIDReader = &hostIDReaderDarwin{
execCommand: execCommand,
}
37 changes: 37 additions & 0 deletions sdk/resource/host_id_export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// 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_test

import (
"github.com/stretchr/testify/assert"

"go.opentelemetry.io/otel/sdk/resource"
)

func mockHostIDProvider() {
resource.SetHostIDProvider(
func() (string, error) { return "f2c668b579780554f70f72a063dc0864", nil },
)
}

func mockHostIDProviderWithError() {
resource.SetHostIDProvider(
func() (string, error) { return "", assert.AnError },
)
}

func restoreHostIDProvider() {
resource.SetDefaultHostIDProvider()
}
22 changes: 22 additions & 0 deletions sdk/resource/host_id_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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.

//go:build linux
// +build linux

package resource // import "go.opentelemetry.io/otel/sdk/resource"

var platformHostIDReader hostIDReader = &hostIDReaderLinux{
readFile: readFile,
}
Loading