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 WithServiceName config option for instrumentation #353

Merged
merged 10 commits into from
Sep 29, 2023
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http

## [Unreleased]

### Added

- Add `WithServiceName` config option for instrumentation. ([#353](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/353))

### Changed

- Fix runtime panic if OTEL_GO_AUTO_TARGET_EXE is not set. ([#339](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/339))
Expand Down
55 changes: 55 additions & 0 deletions instConfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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 auto

import (
"fmt"
"os"
"testing"

"github.com/stretchr/testify/assert"

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

func TestWithServiceName(t *testing.T) {
testServiceName := "test_serviceName"

// Use WithServiceName to config the service name
c := newInstConfig([]InstrumentationOption{WithServiceName((testServiceName))})
assert.Equal(t, testServiceName, c.serviceName)

// No service name provided - check for default value
c = newInstConfig([]InstrumentationOption{})
assert.Equal(t, serviceNameDefault, c.serviceName)

// OTEL_RESOURCE_ATTRIBUTES
resServiceName := "resValue"
err := os.Setenv(envResourceAttrKey, fmt.Sprintf("key1=val1,%s=%s", string(semconv.ServiceNameKey), resServiceName))
if err != nil {
t.Error(err)
}
c = newInstConfig([]InstrumentationOption{WithServiceName((testServiceName))})
assert.Equal(t, resServiceName, c.serviceName)

// Add env var to take precedence
envServiceName := "env_serviceName"
err = os.Setenv(envServiceNameKey, envServiceName)
if err != nil {
t.Error(err)
}
c = newInstConfig([]InstrumentationOption{WithServiceName((testServiceName))})
assert.Equal(t, envServiceName, c.serviceName)
}
77 changes: 72 additions & 5 deletions instrumentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,29 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"strings"

semconv "go.opentelemetry.io/otel/semconv/v1.21.0"

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

// envTargetExeKey is the key for the environment variable value pointing to the
// target binary to instrument.
const envTargetExeKey = "OTEL_GO_AUTO_TARGET_EXE"
const (
// envTargetExeKey is the key for the environment variable value pointing to the
// target binary to instrument.
envTargetExeKey = "OTEL_GO_AUTO_TARGET_EXE"
// envServiceName is the key for the envoriment variable value containing the service name.
envServiceNameKey = "OTEL_SERVICE_NAME"
// envResourceAttrKey is the key for the environment variable value containing
// OpenTelemetry Resource attributes.
envResourceAttrKey = "OTEL_RESOURCE_ATTRIBUTES"
// serviceNameDefault is the default service name prefix used if a user does not provide one.
serviceNameDefault = "unknown_service"
)

// Instrumentation manages and controls all OpenTelemetry Go
// auto-instrumentation.
Expand Down Expand Up @@ -55,7 +68,7 @@ func NewInstrumentation(opts ...InstrumentationOption) (*Instrumentation, error)
return nil, err
}

ctrl, err := opentelemetry.NewController(Version())
ctrl, err := opentelemetry.NewController(Version(), c.serviceName)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -104,7 +117,8 @@ type InstrumentationOption interface {
}

type instConfig struct {
target *process.TargetArgs
target *process.TargetArgs
serviceName string
}

func newInstConfig(opts []InstrumentationOption) instConfig {
Expand All @@ -120,6 +134,45 @@ func (c instConfig) applyEnv() instConfig {
if v, ok := os.LookupEnv(envTargetExeKey); ok {
c.target = &process.TargetArgs{ExePath: v}
}
if v, ok := os.LookupEnv(envServiceNameKey); ok {
RonFed marked this conversation as resolved.
Show resolved Hide resolved
c.serviceName = v
} else {
c = c.applyResourceAtrrEnv()
if c.serviceName == "" {
c = c.setDefualtServiceName()
}
}
return c
}

func (c instConfig) setDefualtServiceName() instConfig {
if c.target != nil {
c.serviceName = fmt.Sprintf("%s:%s", serviceNameDefault, filepath.Base(c.target.ExePath))
} else {
c.serviceName = serviceNameDefault
RonFed marked this conversation as resolved.
Show resolved Hide resolved
}
return c
}

func (c instConfig) applyResourceAtrrEnv() instConfig {
attrs := strings.TrimSpace(os.Getenv(envResourceAttrKey))

if attrs == "" {
return c
}

pairs := strings.Split(attrs, ",")
for _, p := range pairs {
k, v, found := strings.Cut(p, "=")
if !found {
continue
}
key := strings.TrimSpace(k)
if key == string(semconv.ServiceNameKey) {
c.serviceName = strings.TrimSpace(v)
}
}

return c
}

Expand Down Expand Up @@ -148,3 +201,17 @@ func WithTarget(path string) InstrumentationOption {
return c
})
}

// WithServiceName returns an [InstrumentationOption] defining the name of the service running.
//
// If multiple of these options are provided to an [Instrumentation], the last
// one will be used.
//
// If OTEL_SERVICE_NAME is defined it will take precedence over any value
// passed here.
func WithServiceName(serviceName string) InstrumentationOption {
return fnOpt(func(c instConfig) instConfig {
c.serviceName = serviceName
return c
})
}
12 changes: 1 addition & 11 deletions internal/pkg/opentelemetry/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package opentelemetry
import (
"context"
"fmt"
"os"
"runtime"
"strings"
"time"
Expand All @@ -36,10 +35,6 @@ import (
"go.opentelemetry.io/auto/internal/pkg/log"
)

const (
otelServiceNameEnvVar = "OTEL_SERVICE_NAME"
)

// Information about the runtime environment for inclusion in User-Agent, e.g. "go/1.18.2 (linux/amd64)".
var runtimeInfo = fmt.Sprintf("%s (%s/%s)", strings.Replace(runtime.Version(), "go", "go/", 1), runtime.GOOS, runtime.GOARCH)

Expand Down Expand Up @@ -90,12 +85,7 @@ func (c *Controller) convertTime(t int64) time.Time {
}

// NewController returns a new initialized [Controller].
func NewController(version string) (*Controller, error) {
serviceName, exists := os.LookupEnv(otelServiceNameEnvVar)
if !exists {
return nil, fmt.Errorf("%s env var must be set", otelServiceNameEnvVar)
}

func NewController(version string, serviceName string) (*Controller, error) {
ctx := context.Background()
res, err := resource.New(ctx,
resource.WithAttributes(
Expand Down
Loading