Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,21 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http
### Added

- Cache offsets for `google.golang.org/grpc` `1.72.0-dev`. ([#1849](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1849))
- The auto binary (built from `auto/cli`) can now be passed the target process PID directly using the `-target-pid` CLI option. ([#1890](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1890))
- The auto binary (built from `auto/cli`) can now be passed the path of the target process executable directly using the `-target-exe` CLI option. ([#1890](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1890))
- The auto binary (built from `auto/cli`) now resolves the target PID from the environment variable `"OTEL_GO_AUTO_TARGET_PID"` if no target options are passed. ([#1890](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1890))
- The auto binary (built from `auto/cli`) will now only resolve the target process executable from the environment variable `"OTEL_GO_AUTO_TARGET_EXE"` if no target options are passed and `"OTEL_GO_AUTO_TARGET_PID"` is not set. ([#1890](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1890))

### Removed

- Build support for Go 1.22 has been removed.
Use Go >= 1.23 to develop and build the project. ([#1841](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1841))
- Resolution of the environment variable `"OTEL_GO_AUTO_TARGET_EXE"` has been removed from `WithEnv`.
Note, the built binary (`auto/cli`) still supports resolution and use of this value.
If using the `auto` package directly, you will need to resolve this value yourself and pass the discovered process PID using `WithPID`. ([#1890](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1890))
- The `WithTarget` function is removed.
The `auto` package no longer supports process discovery (note: the built binary (`auto/cli`) still supports process discovery).
Once a target process has been identified, use `WithPID` to configure `Instrumentation` instead. ([#1890](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/1890))

## [v0.21.0] - 2025-02-18

Expand Down
119 changes: 101 additions & 18 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,62 @@ package main

import (
"context"
"errors"
"flag"
"fmt"
"log/slog"
"os"
"os/signal"
"path/filepath"
"strconv"
"syscall"

"go.opentelemetry.io/auto"
"go.opentelemetry.io/auto/internal/pkg/process"
)

const help = `Usage of %s:
-global-impl
Record telemetry from the OpenTelemetry default global implementation
-target-pid int
PID of target process
-target-exe string
Executable path run by the target process
-log-level string
logging level ("debug", "info", "warn", "error")
Logging level ("debug", "info", "warn", "error")

Runs the OpenTelemetry auto-instrumentation for Go applications using eBPF.

If both -target-pid and -target-exe are provided -target-exe will be ignored
and -target-pid used.

Environment variable configuration:

- OTEL_GO_AUTO_TARGET_EXE: sets the target binary
- OTEL_LOG_LEVEL: sets the log level (flag takes precedence)
- OTEL_SERVICE_NAME (or OTEL_RESOURCE_ATTRIBUTES): sets the service name
- OTEL_TRACES_EXPORTER: sets the trace exporter
- OTEL_GO_AUTO_TARGET_PID: PID of the target process
- OTEL_GO_AUTO_TARGET_EXE: executable path run by the target process
- OTEL_LOG_LEVEL: log level (flag takes precedence)
- OTEL_SERVICE_NAME (or OTEL_RESOURCE_ATTRIBUTES): service name
- OTEL_TRACES_EXPORTER: trace exporter identifier

If the OTEL_GO_AUTO_TARGET_PID is only resolved if -target-exe or -target-pid
is not provided. If none of these are set, OTEL_GO_AUTO_TARGET_EXE will be
resolved.

The OTEL_TRACES_EXPORTER environment variable value is resolved using the
autoexport (go.opentelemetry.io/contrib/exporters/autoexport) package. See that
package's documentation for information on supported values and registration of
custom exporters.
`

// envLogLevelKey is the key for the environment variable value containing the
// log level.
const envLogLevelKey = "OTEL_LOG_LEVEL"
const (
// envLogLevelKey is the key for the environment variable value containing the
// log level.
envLogLevelKey = "OTEL_LOG_LEVEL"
// envTargetPIDKey is the environment variable key containing the target
// process ID to instrument.
envTargetPIDKey = "OTEL_GO_AUTO_TARGET_PID"
// envTargetExeKey is the environment variable key containing the path to
// target binary to instrument.
envTargetExeKey = "OTEL_GO_AUTO_TARGET_EXE"
)

func usage() {
program := filepath.Base(os.Args[0])
Expand Down Expand Up @@ -75,9 +94,13 @@ func newLogger(lvlStr string) *slog.Logger {
func main() {
var globalImpl bool
var logLevel string
var targetPID int
var targetExe string

flag.BoolVar(&globalImpl, "global-impl", false, "Record telemetry from the OpenTelemetry default global implementation")
flag.StringVar(&logLevel, "log-level", "", `logging level ("debug", "info", "warn", "error")`)
flag.StringVar(&logLevel, "log-level", "", `Logging level ("debug", "info", "warn", "error")`)
flag.IntVar(&targetPID, "target-pid", -1, `PID of target process`)
flag.StringVar(&targetExe, "target-exe", "", `Executable path run by the target process`)

flag.Usage = usage
flag.Parse()
Expand All @@ -100,19 +123,26 @@ func main() {
}
}()

logger.Info(
"building OpenTelemetry Go instrumentation ...",
"globalImpl", globalImpl,
"version", newVersion(),
)

instOptions := []auto.InstrumentationOption{
auto.WithEnv(),
auto.WithLogger(logger),
}
if globalImpl {
instOptions = append(instOptions, auto.WithGlobal())
}
pid, err := findPID(ctx, logger, targetPID, targetExe)
if err != nil {
logger.Error("failed to find target", "error", err)
return
}
instOptions = append(instOptions, auto.WithPID(pid))

logger.Info(
"building OpenTelemetry Go instrumentation ...",
"globalImpl", globalImpl,
"PID", pid,
"version", newVersion(),
)

inst, err := auto.NewInstrumentation(ctx, instOptions...)
if err != nil {
Expand All @@ -128,7 +158,60 @@ func main() {

logger.Info("instrumentation loaded successfully, starting...")

if err = inst.Run(ctx); err != nil && !errors.Is(err, process.ErrInterrupted) {
if err = inst.Run(ctx); err != nil {
logger.Error("instrumentation crashed", "error", err)
}
}

var errNoPID = fmt.Errorf(
"no target: -target-pid or -target-exe not provided and the env vars %s and %s are unset",
envTargetPIDKey, envTargetExeKey,
)

func findPID(ctx context.Context, l *slog.Logger, pid int, binPath string) (int, error) {
// Priority:
// 1. pid
// 2. binPath
// 3. OTEL_GO_AUTO_TARGET_PID
// 4. OTEL_GO_AUTO_TARGET_EXE

l.Debug(
"finding target PID",
"PID", pid,
"executable", binPath,
envTargetPIDKey, os.Getenv(envTargetPIDKey),
envTargetExeKey, os.Getenv(envTargetExeKey),
)

if pid >= 0 {
return pid, nil
}

if binPath != "" {
return findExeFn(ctx, l, binPath)
}

pidStr := os.Getenv(envTargetPIDKey)
if pidStr != "" {
pid, err := strconv.Atoi(pidStr)
if err != nil {
return 0, fmt.Errorf("invalid OTEL_GO_AUTO_TARGET_PID value: %s: %w", pidStr, err)
}
return pid, nil
}

binPath = os.Getenv(envTargetExeKey)
if binPath != "" {
return findExeFn(ctx, l, binPath)
}

return -1, errNoPID
}

// Used for testing.
var findExeFn = findExe

func findExe(ctx context.Context, l *slog.Logger, exe string) (int, error) {
pp := ProcessPoller{Logger: l, BinPath: exe}
return pp.Poll(ctx)
}
109 changes: 109 additions & 0 deletions cli/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"context"
"errors"
"log/slog"
"os"
"testing"

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

const (
appPath = "/home/fake/bin/app"
appPathPID int = 1000

altPath = "/home/fake/bin/alt"
altPathPID int = 1001

missingPath = "/home/fake/bin/missing"
)

var pids = map[string]int{appPath: appPathPID, altPath: altPathPID}

var errMissing = errors.New("missing")

func fakeFindExe(_ context.Context, _ *slog.Logger, exe string) (int, error) {
pid, ok := pids[exe]
if !ok {
return -1, errMissing
}
return pid, nil
}

func TestFindPID(t *testing.T) {
orig := findExeFn
findExeFn = fakeFindExe
t.Cleanup(func() { findExeFn = orig })

ctx := context.Background()

t.Run("Empty", func(t *testing.T) {
got, err := findPID(ctx, discardLogger, -1, "")
assert.Equal(t, -1, got)
assert.ErrorIs(t, err, errNoPID)
})

t.Run("PID", func(t *testing.T) {
const pid = 4
got, err := findPID(ctx, discardLogger, pid, appPath)
assert.NoError(t, err)
assert.Equal(t, int(pid), got)
})

t.Run("BinPath", func(t *testing.T) {
got, err := findPID(ctx, discardLogger, -1, appPath)
assert.NoError(t, err)
assert.Equal(t, appPathPID, got)

got, err = findPID(ctx, discardLogger, -1, missingPath)
assert.Equal(t, -1, got)
assert.ErrorIs(t, err, errMissing)
})

t.Run("OTEL_GO_AUTO_TARGET_PID", func(t *testing.T) {
t.Setenv(envTargetPIDKey, "2000")
got, err := findPID(ctx, discardLogger, -1, "")
assert.NoError(t, err)
assert.Equal(t, int(2000), got)

t.Setenv(envTargetPIDKey, "invalid")
_, err = findPID(ctx, discardLogger, -1, "")
assert.Error(t, err)
})

t.Run("OTEL_GO_AUTO_TARGET_EXE", func(t *testing.T) {
t.Setenv(envTargetExeKey, appPath)
got, err := findPID(ctx, discardLogger, -1, "")
assert.NoError(t, err)
assert.Equal(t, appPathPID, got)
})

t.Run("Precedence", func(t *testing.T) {
t.Setenv(envTargetPIDKey, "2000")
t.Setenv(envTargetExeKey, altPath)

const pid = 4
got, err := findPID(ctx, discardLogger, pid, appPath)
assert.NoError(t, err)
assert.Equal(t, int(pid), got)

got, err = findPID(ctx, discardLogger, -1, appPath)
assert.NoError(t, err)
assert.Equal(t, appPathPID, got)

got, err = findPID(ctx, discardLogger, -1, "")
assert.NoError(t, err)
assert.Equal(t, 2000, got)

os.Unsetenv(envTargetPIDKey)

got, err = findPID(ctx, discardLogger, -1, "")
assert.NoError(t, err)
assert.Equal(t, altPathPID, got)
})
}
Loading
Loading