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 a host resource detector #5399

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Expand Up @@ -172,6 +172,15 @@ updates:
schedule:
interval: weekly
day: sunday
- package-ecosystem: gomod
directory: /detectors/host
labels:
- dependencies
- go
- Skip Changelog
schedule:
interval: weekly
day: sunday
- package-ecosystem: gomod
directory: /exporters/autoexport
labels:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- Add the new `go.opentelemetry.io/contrib/instrgen` package to provide auto-generated source code instrumentation. (#3068, #3108)
- `NewSDK` in `go.opentelemetry.io/contrib/config` now returns a configured SDK with a valid `MeterProvider`. (#4804)
- Add the new `go.opentelemetry.io/contrib/detectors/host` package to provide a resource detector for physical hosts. (#5399)

### Changed

Expand Down
35 changes: 35 additions & 0 deletions detectors/host/README.md
@@ -0,0 +1,35 @@
# Host Resource detector

The host resource detector supports detecting host-specific attributes on physical hosts.

## Usage

```golang
// Instantiate a new host resource detector
hostResourceDetector := host.New()
resource, err := hostResourceDetector.Detect(context.Background())
```

To populate optional attributes, the resource detector constructor accepts functional options `WithIPAddresses` to enable `host.ip`, and `WithMACAddresses` to enable `host.mac`.

```golang
// Instantiate a new host resource detector with all opt-in attributes
hostResourceDetector := host.New(
WithIPAddresses(),
WithMACAddresses(),
)
resource, err := hostResourceDetector.Detect(context.Background())
```

## Supported attributes

According to [semantic conventions for host resources](https://opentelemetry.io/docs/specs/semconv/resource/host/), each of the following attributes is added if it is available:

* `host.arch`
* `host.id`
* `host.name`

The following attributes require an explicit opt-in during the initialization of the host resource detector:

* `host.ip`
* `host.mac`
20 changes: 20 additions & 0 deletions detectors/host/go.mod
@@ -0,0 +1,20 @@
module go.opentelemetry.io/contrib/detectors/host

go 1.21

require (
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/otel v1.25.0
go.opentelemetry.io/otel/sdk v1.25.0
golang.org/x/sys v0.18.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/otel/metric v1.25.0 // indirect
go.opentelemetry.io/otel/trace v1.25.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
27 changes: 27 additions & 0 deletions detectors/host/go.sum
@@ -0,0 +1,27 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k=
go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg=
go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA=
go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s=
go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo=
go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw=
go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM=
go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
141 changes: 141 additions & 0 deletions detectors/host/host.go
@@ -0,0 +1,141 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package host // import "go.opentelemetry.io/contrib/detectors/host"

import (
"context"
"net"
"os"
"runtime"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)

type config struct {
optInIPAddresses bool
optInMACAddresses bool
}

func newConfig(options ...Option) config {
c := config{}
for _, option := range options {
c = option.apply(c)
}

return c
}

// Option applies a host detector configuration option.
type Option interface {
apply(config) config
}

type optionFunc func(config) config

func (fn optionFunc) apply(c config) config {
return fn(c)
}

// WithIPAddresses adds the optional attribute "host.ip".
func WithIPAddresses() Option {
return optionFunc(func(c config) config {
c.optInIPAddresses = true

return c
})
}

// WithMACAddresses adds the optional attribute "host.mac".
func WithMACAddresses() Option {
return optionFunc(func(c config) config {
c.optInMACAddresses = true

return c
})
}

type resourceDetector struct {
config config
}

// NewResourceDetector returns a [resource.Detector] that will detect host resources.
func New(opts ...Option) resource.Detector {
c := newConfig(opts...)
return &resourceDetector{config: c}
}

// Detect detects associated resources when running on a physical host.
func (detector *resourceDetector) Detect(ctx context.Context) (*resource.Resource, error) {
attributes := []attribute.KeyValue{
semconv.HostArchKey.String(runtime.GOARCH),
}

hostName, err := os.Hostname()
if err == nil {
attributes = append(attributes, semconv.HostName(hostName))
}

machineId, err := getHostId()
if err == nil {
attributes = append(attributes, semconv.HostID(machineId))
}

if detector.config.optInIPAddresses {
ipAddresses := getIPAddresses()
if len(ipAddresses) > 0 {
attributes = append(attributes, semconv.HostIP(ipAddresses...))
}
}

if detector.config.optInMACAddresses {
macAddresses := getMACAddresses()
if len(macAddresses) > 0 {
attributes = append(attributes, semconv.HostMac(macAddresses...))
}
}

return resource.NewWithAttributes(semconv.SchemaURL, attributes...), nil
}

func getIPAddresses() []string {
var ipAddresses []string

ifaces, err := net.Interfaces()
if err == nil {
for _, iface := range ifaces {
if iface.Flags&net.FlagLoopback != 0 {
continue
}

addrs, err := iface.Addrs()
if err != nil {
continue

Check warning on line 115 in detectors/host/host.go

View check run for this annotation

Codecov / codecov/patch

detectors/host/host.go#L115

Added line #L115 was not covered by tests
}
for _, addr := range addrs {
ipAddresses = append(ipAddresses, addr.String())
}
}
}

return ipAddresses
}

func getMACAddresses() []string {
var macAddresses []string

ifaces, err := net.Interfaces()
if err == nil {
for _, iface := range ifaces {
if iface.Flags&net.FlagLoopback != 0 {
continue
}

macAddresses = append(macAddresses, iface.HardwareAddr.String())
}
}

return macAddresses
}
28 changes: 28 additions & 0 deletions detectors/host/host_id_bsd.go
@@ -0,0 +1,28 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

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

package host // import "go.opentelemetry.io/contrib/detectors/host"

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

func getHostId() (string, error) {
machineId, err := os.ReadFile("/etc/machine-id")
if err == nil {
return strings.Trim(string(machineId), "\n"), nil
}

machineId, err = exec.Command("kenv", "-q", "smbios.system.uuid").Output()
if err == nil {
return strings.Trim(string(machineId), "\n"), nil
}

return "", errors.New("host id not found in: /etc/hostid or kenv")
}
21 changes: 21 additions & 0 deletions detectors/host/host_id_darwin.go
@@ -0,0 +1,21 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

//go:build darwin
// +build darwin

package host // import "go.opentelemetry.io/contrib/detectors/host"

import (
"os/exec"
"strings"
)

func getHostId() (string, error) {
machineId, err := exec.Command("ioreg", "-rd1", "-c", "IOPlatformExpertDevice").Output()
if err != nil {
return "", err
}

return strings.Trim(string(machineId), "\n"), nil
}
21 changes: 21 additions & 0 deletions detectors/host/host_id_linux.go
@@ -0,0 +1,21 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

//go:build linux
// +build linux

package host // import "go.opentelemetry.io/contrib/detectors/host"

import (
"os"
"strings"
)

func getHostId() (string, error) {
machineId, err := os.ReadFile("/etc/machine-id")
if err != nil {
return "", err

Check warning on line 17 in detectors/host/host_id_linux.go

View check run for this annotation

Codecov / codecov/patch

detectors/host/host_id_linux.go#L17

Added line #L17 was not covered by tests
}

return strings.Trim(string(machineId), "\n"), nil
}
21 changes: 21 additions & 0 deletions detectors/host/host_id_unsupported.go
@@ -0,0 +1,21 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// +build !darwin
// +build !dragonfly
// +build !freebsd
// +build !linux
// +build !netbsd
// +build !openbsd
// +build !solaris
// +build !windows

package host // import "go.opentelemetry.io/contrib/detectors/host"

// hostIDReaderUnsupported is a placeholder implementation for operating systems
// for which this project currently doesn't support host.id
// attribute detection. See build tags declaration early on this file
// for a list of unsupported OSes.
func getHostId() (string, error) {
return "<unknown>", nil
}
24 changes: 24 additions & 0 deletions detectors/host/host_id_windows.go
@@ -0,0 +1,24 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

//go:build windows
// +build windows

package host // import "go.opentelemetry.io/contrib/detectors/host"

import (
"golang.org/x/sys/windows/registry"
)

func getHostId() (string, error) {
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Cryptography`, registry.QUERY_VALUE|registry.WOW64_64KEY)
if err != nil {
return "", err
}

defer key.Close()

machineId, _, err := key.GetStringValue("MachineGuid")

return machineId, err
}