diff --git a/.github/workflows/e2e/k8s/collector-helm-values.yml b/.github/workflows/e2e/k8s/collector-helm-values.yml index 6d4ca3cf2..3085e1ed1 100644 --- a/.github/workflows/e2e/k8s/collector-helm-values.yml +++ b/.github/workflows/e2e/k8s/collector-helm-values.yml @@ -1,6 +1,12 @@ mode: "statefulset" config: + receivers: + otlp: + protocols: + http: + endpoint: ${env:MY_POD_IP}:4318 + exporters: logging: {} file/trace: diff --git a/.github/workflows/e2e/k8s/sample-job.yml b/.github/workflows/e2e/k8s/sample-job.yml index 28c2cb57a..62f24e63c 100644 --- a/.github/workflows/e2e/k8s/sample-job.yml +++ b/.github/workflows/e2e/k8s/sample-job.yml @@ -30,7 +30,7 @@ spec: - name: OTEL_GO_AUTO_TARGET_EXE value: /sample-app/main - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: "http://test-opentelemetry-collector:4317" + value: "http://test-opentelemetry-collector:4318" - name: OTEL_SERVICE_NAME value: "sample-app" - name: OTEL_PROPAGATORS diff --git a/CHANGELOG.md b/CHANGELOG.md index 91b432018..97543729c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http The `Instrumentation` will no longer by default parse the environment. This option needs to be used to enable environment parsing, and the order it is passed influences the environment precedence. All options passed before this one will be overridden if there are conflicts, and those passed after will override the environment. ([#417](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/417)) +- Add the `WithTraceExporter` `InstrumentationOption` to configure the trace `SpanExporter` used by an `Instrumentation`. ([#426](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/426)) - Add HTTP status code attribute to `net/http` server instrumentation. ([#428](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/428)) ### Changed @@ -21,10 +22,14 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http - Documentation no longer says that `SYS_PTRACE` capabilty is needed. ([#388](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/388)) - The `NewInstrumentation` no longer parses environment variables by default. Use the new `WithEnv` option to enable environment parsing. ([#417](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/417)) +- `NewInstrumentation` now requires a `context.Context` as its first argument. + This context is used in the instantiation of exporters. ([#426](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/426)) +- `Instrumentation` now uses an OTLP over HTTP/protobuf exporter (changed from gRPC/protobuf). ([#426](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/426)) ### Fixed - Parse Go versions that contain `GOEXPERIMENT` suffixes. ([#389](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/389)) +- Include the schema URL for the semantic convention used in the exported resource. ([#426](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/426)) ### Removed diff --git a/README.md b/README.md index f720cfe19..c74c6f72b 100644 --- a/README.md +++ b/README.md @@ -65,12 +65,12 @@ To instrument an application on the same host, follow these steps: instrument. For example, `/home/bin/service_executable` - `OTEL_SERVICE_NAME`: Name of your service or application - `OTEL_EXPORTER_OTLP_ENDPOINT`: Your observability backend. For example, - `http://localhost:4317`. + `http://localhost:4318`. For example: ```sh - sudo OTEL_GO_AUTO_TARGET_EXE=/home/bin/service_executable OTEL_SERVICE_NAME=my_service OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317./otel-go-instrumentation` + sudo OTEL_GO_AUTO_TARGET_EXE=/home/bin/service_executable OTEL_SERVICE_NAME=my_service OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318./otel-go-instrumentation` ``` 3. Run the OpenTelemetry Go Automatic Instrumentation with root privileges. @@ -94,7 +94,7 @@ network, a shared volume, and a service for the application. privileged: true pid: "host" environment: - - OTEL_EXPORTER_OTLP_ENDPOINT=http://:4317 + - OTEL_EXPORTER_OTLP_ENDPOINT=http://:4318 - OTEL_GO_AUTO_TARGET_EXE= - OTEL_SERVICE_NAME= - OTEL_PROPAGATORS=tracecontext,baggage @@ -119,7 +119,7 @@ To instrument an application running in Kubernetes, follow these steps: - name: OTEL_GO_AUTO_TARGET_EXE value: - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: "http://:4317" + value: "http://:4318" - name: OTEL_SERVICE_NAME value: "" securityContext: diff --git a/cli/main.go b/cli/main.go index 78adf7396..f31cb665a 100644 --- a/cli/main.go +++ b/cli/main.go @@ -48,13 +48,6 @@ func newLogger() logr.Logger { func main() { logger := newLogger().WithName("go.opentelemetry.io/auto") - logger.Info("building OpenTelemetry Go instrumentation ...") - inst, err := auto.NewInstrumentation(auto.WithEnv()) - if err != nil { - logger.Error(err, "failed to create instrumentation") - return - } - // Trap Ctrl+C and SIGTERM and call cancel on the context. ctx, cancel := context.WithCancel(context.Background()) ch := make(chan os.Signal, 1) @@ -71,6 +64,13 @@ func main() { } }() + logger.Info("building OpenTelemetry Go instrumentation ...") + inst, err := auto.NewInstrumentation(ctx, auto.WithEnv()) + if err != nil { + logger.Error(err, "failed to create instrumentation") + return + } + logger.Info("starting instrumentation...") if err = inst.Run(ctx); err != nil && !errors.Is(err, process.ErrInterrupted) { logger.Error(err, "instrumentation crashed") diff --git a/docs/getting-started/emojivoto-instrumented.yaml b/docs/getting-started/emojivoto-instrumented.yaml index e63923d84..84e6b3a1f 100644 --- a/docs/getting-started/emojivoto-instrumented.yaml +++ b/docs/getting-started/emojivoto-instrumented.yaml @@ -45,7 +45,7 @@ spec: - name: OTEL_GO_AUTO_TARGET_EXE value: /usr/local/bin/emojivoto-emoji-svc - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: "http://jaeger:4317" + value: "http://jaeger:4318" - name: OTEL_SERVICE_NAME value: "emojivoto-emoji" securityContext: @@ -99,7 +99,7 @@ spec: - name: OTEL_GO_AUTO_TARGET_EXE value: /usr/local/bin/emojivoto-voting-svc - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: "http://jaeger:4317" + value: "http://jaeger:4318" - name: OTEL_SERVICE_NAME value: "emojivoto-voting" securityContext: @@ -155,7 +155,7 @@ spec: - name: OTEL_GO_AUTO_TARGET_EXE value: /usr/local/bin/emojivoto-web - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: "http://jaeger:4317" + value: "http://jaeger:4318" - name: OTEL_SERVICE_NAME value: "emojivoto-web" securityContext: diff --git a/docs/getting-started/jaeger.yaml b/docs/getting-started/jaeger.yaml index 86fb3fa36..0a4938ebf 100644 --- a/docs/getting-started/jaeger.yaml +++ b/docs/getting-started/jaeger.yaml @@ -26,9 +26,9 @@ metadata: name: jaeger spec: ports: - - name: grpc - port: 4317 - targetPort: 4317 + - name: http + port: 4318 + targetPort: 4318 - name: ui port: 16686 targetPort: 16686 diff --git a/examples/httpPlusdb/docker-compose.yaml b/examples/httpPlusdb/docker-compose.yaml index f764897ed..f33e86c64 100644 --- a/examples/httpPlusdb/docker-compose.yaml +++ b/examples/httpPlusdb/docker-compose.yaml @@ -27,7 +27,7 @@ services: privileged: true pid: "host" environment: - - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317 + - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318 - OTEL_GO_AUTO_TARGET_EXE=/app/main - OTEL_GO_AUTO_INCLUDE_DB_STATEMENT=true - OTEL_SERVICE_NAME=httpPlusdb diff --git a/examples/rolldice/docker-compose.yaml b/examples/rolldice/docker-compose.yaml index 7aac3684b..ecbf2431e 100644 --- a/examples/rolldice/docker-compose.yaml +++ b/examples/rolldice/docker-compose.yaml @@ -27,7 +27,7 @@ services: privileged: true pid: "host" environment: - - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317 + - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318 - OTEL_GO_AUTO_TARGET_EXE=/app/main - OTEL_SERVICE_NAME=rolldice - OTEL_PROPAGATORS=tracecontext,baggage diff --git a/go.mod b/go.mod index 12396a38d..61eedb6a1 100644 --- a/go.mod +++ b/go.mod @@ -27,15 +27,14 @@ require ( github.com/mattn/go-sqlite3 v1.14.17 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.8.4 + go.opentelemetry.io/contrib/exporters/autoexport v0.45.0 go.opentelemetry.io/otel v1.19.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 go.opentelemetry.io/otel/sdk v1.19.0 go.opentelemetry.io/otel/trace v1.19.0 go.uber.org/zap v1.26.0 golang.org/x/arch v0.5.0 golang.org/x/sys v0.13.0 - google.golang.org/grpc v1.59.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -62,14 +61,17 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.12.0 // indirect + golang.org/x/crypto v0.13.0 // indirect golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.59.0 // indirect google.golang.org/protobuf v1.31.0 // indirect ) diff --git a/go.sum b/go.sum index 8638bc529..7d036e75d 100644 --- a/go.sum +++ b/go.sum @@ -92,12 +92,17 @@ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2 github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/contrib/exporters/autoexport v0.45.0 h1:KU3hwb3O+fc2F15lltmDgtH/QNfXZ7fvYGrZcKFDHxw= +go.opentelemetry.io/contrib/exporters/autoexport v0.45.0/go.mod h1:9hFI4YY6Ehe9enzw9qGlKAjJGQAtEo75Ysrb3byOZtI= go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 h1:Nw7Dv4lwvGrI68+wULbcq7su9K2cebeCUrDjVrUJHxM= go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= @@ -120,8 +125,8 @@ golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -130,8 +135,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -146,8 +151,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/instrumentation.go b/instrumentation.go index 459676079..0186d085d 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -16,15 +16,21 @@ package auto import ( "context" + "errors" "fmt" "log" "os" "path/filepath" + "runtime" "strings" "github.com/go-logr/logr" "github.com/go-logr/stdr" "github.com/go-logr/zapr" + "go.opentelemetry.io/contrib/exporters/autoexport" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/sdk/resource" + "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "go.uber.org/zap" @@ -42,6 +48,9 @@ const ( // envResourceAttrKey is the key for the environment variable value containing // OpenTelemetry Resource attributes. envResourceAttrKey = "OTEL_RESOURCE_ATTRIBUTES" + // envTracesExportersKey is the key for the environment variable value + // containing what OpenTelemetry trace exporter to use. + envTracesExportersKey = "OTEL_TRACES_EXPORTER" ) // Instrumentation manages and controls all OpenTelemetry Go @@ -75,7 +84,7 @@ func newLogger() logr.Logger { // // If conflicting or duplicate options are provided, the last one will have // precedence and be used. -func NewInstrumentation(opts ...InstrumentationOption) (*Instrumentation, error) { +func NewInstrumentation(ctx context.Context, opts ...InstrumentationOption) (*Instrumentation, error) { // TODO: pass this in as an option. // // We likely want to use slog instead of logr in the longterm. Wait until @@ -84,7 +93,10 @@ func NewInstrumentation(opts ...InstrumentationOption) (*Instrumentation, error) logger := newLogger() logger = logger.WithName("Instrumentation") - c := newInstConfig(opts) + c, err := newInstConfig(ctx, opts) + if err != nil { + return nil, err + } if err := c.validate(); err != nil { return nil, err } @@ -95,7 +107,7 @@ func NewInstrumentation(opts ...InstrumentationOption) (*Instrumentation, error) return nil, err } - ctrl, err := opentelemetry.NewController(logger, Version(), c.serviceName) + ctrl, err := opentelemetry.NewController(logger, c.tracerProvider()) if err != nil { return nil, err } @@ -147,19 +159,25 @@ func (i *Instrumentation) Close() error { // InstrumentationOption applies a configuration option to [Instrumentation]. type InstrumentationOption interface { - apply(instConfig) instConfig + apply(context.Context, instConfig) (instConfig, error) } type instConfig struct { + traceExp trace.SpanExporter target process.TargetArgs serviceName string } -func newInstConfig(opts []InstrumentationOption) instConfig { - var c instConfig +func newInstConfig(ctx context.Context, opts []InstrumentationOption) (instConfig, error) { + var ( + c instConfig + err error + ) for _, opt := range opts { if opt != nil { - c = opt.apply(c) + var e error + c, e = opt.apply(ctx, c) + err = errors.Join(err, e) } } @@ -167,8 +185,14 @@ func newInstConfig(opts []InstrumentationOption) instConfig { if c.serviceName == "" { c.serviceName = c.defualtServiceName() } + if c.traceExp == nil { + var e error + // This is the OTel recommended default. + c.traceExp, e = otlptracehttp.New(ctx) + err = errors.Join(err, e) + } - return c + return c, err } func (c instConfig) defualtServiceName() string { @@ -184,12 +208,46 @@ func (c instConfig) validate() error { if c.target == zero { return errUndefinedTarget } + if c.traceExp == nil { + return errors.New("undefined trace exporter") + } return c.target.Validate() } -type fnOpt func(instConfig) instConfig +func (c instConfig) tracerProvider() *trace.TracerProvider { + return trace.NewTracerProvider( + trace.WithSampler(trace.AlwaysSample()), + trace.WithResource(c.res()), + trace.WithBatcher(c.traceExp), + trace.WithIDGenerator(opentelemetry.NewEBPFSourceIDGenerator()), + ) +} + +func (c instConfig) res() *resource.Resource { + runVer := strings.TrimPrefix(runtime.Version(), "go") + runName := runtime.Compiler + if runName == "gc" { + runName = "go" + } + runDesc := fmt.Sprintf( + "go version %s %s/%s", + runVer, runtime.GOOS, runtime.GOARCH, + ) + + return resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(c.serviceName), + semconv.TelemetrySDKLanguageGo, + semconv.TelemetryAutoVersionKey.String(Version()), + semconv.ProcessRuntimeName(runName), + semconv.ProcessRuntimeVersion(runVer), + semconv.ProcessRuntimeDescription(runDesc), + ) +} + +type fnOpt func(context.Context, instConfig) (instConfig, error) -func (o fnOpt) apply(c instConfig) instConfig { return o(c) } +func (o fnOpt) apply(ctx context.Context, c instConfig) (instConfig, error) { return o(ctx, c) } // WithTarget returns an [InstrumentationOption] defining the target binary for // [Instrumentation] that is being executed at the provided path. @@ -204,9 +262,9 @@ func (o fnOpt) apply(c instConfig) instConfig { return o(c) } // [WithEnv]. If both are used, the last one provided to an [Instrumentation] // will be used. func WithTarget(path string) InstrumentationOption { - return fnOpt(func(c instConfig) instConfig { + return fnOpt(func(_ context.Context, c instConfig) (instConfig, error) { c.target = process.TargetArgs{ExePath: path} - return c + return c, nil }) } @@ -219,9 +277,9 @@ func WithTarget(path string) InstrumentationOption { // OTEL_RESOURCE_ATTRIBUTES, this option will conflict with [WithEnv]. If both // are used, the last one provided to an [Instrumentation] will be used. func WithServiceName(serviceName string) InstrumentationOption { - return fnOpt(func(c instConfig) instConfig { + return fnOpt(func(_ context.Context, c instConfig) (instConfig, error) { c.serviceName = serviceName - return c + return c, nil }) } @@ -238,9 +296,9 @@ func WithServiceName(serviceName string) InstrumentationOption { // [WithEnv]. If both are used, the last one provided to an [Instrumentation] // will be used. func WithPID(pid int) InstrumentationOption { - return fnOpt(func(c instConfig) instConfig { + return fnOpt(func(_ context.Context, c instConfig) (instConfig, error) { c.target = process.TargetArgs{Pid: pid} - return c + return c, nil }) } @@ -252,20 +310,33 @@ var lookupEnv = os.LookupEnv // // - OTEL_GO_AUTO_TARGET_EXE: sets the target binary // - OTEL_SERVICE_NAME (or OTEL_RESOURCE_ATTRIBUTES): sets the service name +// - OTEL_TRACES_EXPORTER: sets the trace exporter // -// This option may conflict with [WithTarget], [WithPID], and [WithServiceName] -// if their respective environment variable is defined. If more than one of -// these options are used, the last one provided to an [Instrumentation] will -// be used. +// This option may conflict with [WithTarget], [WithPID], [WithTraceExporter], +// and [WithServiceName] if their respective environment variable is defined. +// If more than one of these options are used, the last one provided to an +// [Instrumentation] will be used. func WithEnv() InstrumentationOption { - return fnOpt(func(c instConfig) instConfig { + return fnOpt(func(ctx context.Context, c instConfig) (instConfig, error) { + var err error if v, ok := lookupEnv(envTargetExeKey); ok { c.target = process.TargetArgs{ExePath: v} } + if _, ok := lookupEnv(envTracesExportersKey); ok { + // Don't track the lookup value because autoexport does not provide + // a way to just pass the envoriment value currently. Just use + // NewSpanExporter which will re-read this value. + + var e error + // NewSpanExporter will use an OTLP (HTTP/protobuf) exporter as the + // default. This is the OTel recommended default. + c.traceExp, e = autoexport.NewSpanExporter(ctx) + err = errors.Join(err, e) + } if v, ok := lookupServiceName(); ok { c.serviceName = v } - return c + return c, err }) } @@ -293,3 +364,17 @@ func lookupServiceName() (string, bool) { return "", false } + +// WithTraceExporter returns an [InstrumentationOption] that will configure an +// [Instrumentation] to use the provided exp to export OpenTelemetry tracing +// telemetry. +// +// If OTEL_TRACES_EXPORTER is defined, this option will conflict with +// [WithEnv]. If both are used, the last one provided to an [Instrumentation] +// will be used. +func WithTraceExporter(exp trace.SpanExporter) InstrumentationOption { + return fnOpt(func(_ context.Context, c instConfig) (instConfig, error) { + c.traceExp = exp + return c, nil + }) +} diff --git a/instrumentation_test.go b/instrumentation_test.go index b92d3e3fd..50961f360 100644 --- a/instrumentation_test.go +++ b/instrumentation_test.go @@ -15,32 +15,41 @@ package auto import ( + "context" "fmt" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) func TestWithServiceName(t *testing.T) { + ctx := context.Background() testServiceName := "test_serviceName" // Use WithServiceName to config the service name - c := newInstConfig([]InstrumentationOption{WithServiceName((testServiceName))}) + c, err := newInstConfig(ctx, []InstrumentationOption{WithServiceName((testServiceName))}) + require.NoError(t, err) assert.Equal(t, testServiceName, c.serviceName) // No service name provided - check for default value - c = newInstConfig([]InstrumentationOption{}) + c, err = newInstConfig(ctx, []InstrumentationOption{}) + require.NoError(t, err) assert.Equal(t, c.defualtServiceName(), c.serviceName) } func TestWithPID(t *testing.T) { - c := newInstConfig([]InstrumentationOption{WithPID(1)}) + ctx := context.Background() + + c, err := newInstConfig(ctx, []InstrumentationOption{WithPID(1)}) + require.NoError(t, err) assert.Equal(t, 1, c.target.Pid) const exe = "./test/path/program/run.go" // PID should override valid target exe - c = newInstConfig([]InstrumentationOption{WithTarget(exe), WithPID(1)}) + c, err = newInstConfig(ctx, []InstrumentationOption{WithTarget(exe), WithPID(1)}) + require.NoError(t, err) assert.Equal(t, 1, c.target.Pid) assert.Equal(t, "", c.target.ExePath) } @@ -49,7 +58,8 @@ func TestWithEnv(t *testing.T) { t.Run("OTEL_GO_AUTO_TARGET_EXE", func(t *testing.T) { const path = "./test/path/program/run.go" mockEnv(t, map[string]string{"OTEL_GO_AUTO_TARGET_EXE": path}) - c := newInstConfig([]InstrumentationOption{WithEnv()}) + c, err := newInstConfig(context.Background(), []InstrumentationOption{WithEnv()}) + require.NoError(t, err) assert.Equal(t, path, c.target.ExePath) assert.Equal(t, 0, c.target.Pid) }) @@ -57,7 +67,8 @@ func TestWithEnv(t *testing.T) { t.Run("OTEL_SERVICE_NAME", func(t *testing.T) { const name = "test_service" mockEnv(t, map[string]string{"OTEL_SERVICE_NAME": name}) - c := newInstConfig([]InstrumentationOption{WithEnv()}) + c, err := newInstConfig(context.Background(), []InstrumentationOption{WithEnv()}) + require.NoError(t, err) assert.Equal(t, name, c.serviceName) }) @@ -65,7 +76,8 @@ func TestWithEnv(t *testing.T) { const name = "test_service" val := fmt.Sprintf("a=b,fubar,%s=%s,foo=bar", semconv.ServiceNameKey, name) mockEnv(t, map[string]string{"OTEL_RESOURCE_ATTRIBUTES": val}) - c := newInstConfig([]InstrumentationOption{WithEnv()}) + c, err := newInstConfig(context.Background(), []InstrumentationOption{WithEnv()}) + require.NoError(t, err) assert.Equal(t, name, c.serviceName) }) } @@ -88,7 +100,8 @@ func TestOptionPrecedence(t *testing.T) { WithServiceName("wrong"), WithEnv(), } - c := newInstConfig(opts) + c, err := newInstConfig(context.Background(), opts) + require.NoError(t, err) assert.Equal(t, path, c.target.ExePath) assert.Equal(t, 0, c.target.Pid) assert.Equal(t, name, c.serviceName) @@ -106,7 +119,8 @@ func TestOptionPrecedence(t *testing.T) { WithPID(1), WithServiceName(name), } - c := newInstConfig(opts) + c, err := newInstConfig(context.Background(), opts) + require.NoError(t, err) assert.Equal(t, "", c.target.ExePath) assert.Equal(t, 1, c.target.Pid) assert.Equal(t, name, c.serviceName) diff --git a/internal/pkg/opentelemetry/controller.go b/internal/pkg/opentelemetry/controller.go index 67544101f..5f8d7eb68 100644 --- a/internal/pkg/opentelemetry/controller.go +++ b/internal/pkg/opentelemetry/controller.go @@ -16,28 +16,16 @@ package opentelemetry import ( "context" - "fmt" "runtime" - "strings" "time" "github.com/go-logr/logr" - "golang.org/x/sys/unix" - "google.golang.org/grpc" - - "go.opentelemetry.io/otel/exporters/otlp/otlptrace" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" - "go.opentelemetry.io/otel/sdk/resource" - sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.18.0" "go.opentelemetry.io/otel/trace" + "golang.org/x/sys/unix" "go.opentelemetry.io/auto/internal/pkg/instrumentation/events" ) -// 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) - // Controller handles OpenTelemetry telemetry generation for events. type Controller struct { logger logr.Logger @@ -86,42 +74,9 @@ func (c *Controller) convertTime(t int64) time.Time { } // NewController returns a new initialized [Controller]. -func NewController(logger logr.Logger, version string, serviceName string) (*Controller, error) { +func NewController(logger logr.Logger, tracerProvider trace.TracerProvider) (*Controller, error) { logger = logger.WithName("Controller") - ctx := context.Background() - res, err := resource.New(ctx, - resource.WithAttributes( - semconv.ServiceNameKey.String(serviceName), - semconv.TelemetrySDKLanguageGo, - semconv.TelemetryAutoVersionKey.String(version), - ), - ) - if err != nil { - return nil, err - } - - logger.Info("Establishing connection to OTLP receiver ...") - // Controller-local reference to the auto-instrumentation release version. - // Start of this auto-instrumentation's exporter User-Agent header, e.g. ""OTel-Go-Auto-Instrumentation/1.2.3". - baseUserAgent := fmt.Sprintf("OTel-Go-Auto-Instrumentation/%s", version) - autoinstUserAgent := fmt.Sprintf("%s %s", baseUserAgent, runtimeInfo) - otlpTraceClient := otlptracegrpc.NewClient( - otlptracegrpc.WithDialOption(grpc.WithUserAgent(autoinstUserAgent)), - ) - traceExporter, err := otlptrace.New(ctx, otlpTraceClient) - if err != nil { - return nil, err - } - - bsp := sdktrace.NewBatchSpanProcessor(traceExporter) - tracerProvider := sdktrace.NewTracerProvider( - sdktrace.WithSampler(sdktrace.AlwaysSample()), - sdktrace.WithResource(res), - sdktrace.WithSpanProcessor(bsp), - sdktrace.WithIDGenerator(newEBPFSourceIDGenerator()), - ) - bt, err := estimateBootTimeOffset() if err != nil { return nil, err diff --git a/internal/pkg/opentelemetry/id_generator.go b/internal/pkg/opentelemetry/id_generator.go index 64faff2ec..28e0d8a30 100644 --- a/internal/pkg/opentelemetry/id_generator.go +++ b/internal/pkg/opentelemetry/id_generator.go @@ -22,12 +22,12 @@ import ( "go.opentelemetry.io/auto/internal/pkg/instrumentation/events" ) -type eBPFSourceIDGenerator struct{} +type EBPFSourceIDGenerator struct{} type eBPFEventKey struct{} -func newEBPFSourceIDGenerator() *eBPFSourceIDGenerator { - return &eBPFSourceIDGenerator{} +func NewEBPFSourceIDGenerator() *EBPFSourceIDGenerator { + return &EBPFSourceIDGenerator{} } // ContextWithEBPFEvent returns a copy of parent in which event is stored. @@ -50,7 +50,7 @@ func EventFromContext(ctx context.Context) *events.Event { return &event } -func (e *eBPFSourceIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) { +func (e *EBPFSourceIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) { event := EventFromContext(ctx) if event == nil || event.SpanContext == nil { return trace.TraceID{}, trace.SpanID{} @@ -59,7 +59,7 @@ func (e *eBPFSourceIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trac return event.SpanContext.TraceID(), event.SpanContext.SpanID() } -func (e *eBPFSourceIDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID { +func (e *EBPFSourceIDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID { event := EventFromContext(ctx) if event == nil { return trace.SpanID{} diff --git a/internal/test/e2e/databasesql/traces.json b/internal/test/e2e/databasesql/traces.json index 9078456e9..eeb45afaa 100644 --- a/internal/test/e2e/databasesql/traces.json +++ b/internal/test/e2e/databasesql/traces.json @@ -3,6 +3,24 @@ { "resource": { "attributes": [ + { + "key": "process.runtime.description", + "value": { + "stringValue": "go version 1.21.3 linux/amd64" + } + }, + { + "key": "process.runtime.name", + "value": { + "stringValue": "go" + } + }, + { + "key": "process.runtime.version", + "value": { + "stringValue": "1.21.3" + } + }, { "key": "service.name", "value": { @@ -23,6 +41,7 @@ } ] }, + "schemaUrl": "https://opentelemetry.io/schemas/1.21.0", "scopeSpans": [ { "scope": { diff --git a/internal/test/e2e/gin/traces.json b/internal/test/e2e/gin/traces.json index 2d2c7bceb..0df4f1f9d 100644 --- a/internal/test/e2e/gin/traces.json +++ b/internal/test/e2e/gin/traces.json @@ -3,6 +3,24 @@ { "resource": { "attributes": [ + { + "key": "process.runtime.description", + "value": { + "stringValue": "go version 1.21.3 linux/amd64" + } + }, + { + "key": "process.runtime.name", + "value": { + "stringValue": "go" + } + }, + { + "key": "process.runtime.version", + "value": { + "stringValue": "1.21.3" + } + }, { "key": "service.name", "value": { @@ -23,6 +41,7 @@ } ] }, + "schemaUrl": "https://opentelemetry.io/schemas/1.21.0", "scopeSpans": [ { "scope": { diff --git a/internal/test/e2e/nethttp/traces.json b/internal/test/e2e/nethttp/traces.json index 73a9ff4db..ce1ab0c20 100644 --- a/internal/test/e2e/nethttp/traces.json +++ b/internal/test/e2e/nethttp/traces.json @@ -3,6 +3,24 @@ { "resource": { "attributes": [ + { + "key": "process.runtime.description", + "value": { + "stringValue": "go version 1.21.3 linux/amd64" + } + }, + { + "key": "process.runtime.name", + "value": { + "stringValue": "go" + } + }, + { + "key": "process.runtime.version", + "value": { + "stringValue": "1.21.3" + } + }, { "key": "service.name", "value": { @@ -23,6 +41,7 @@ } ] }, + "schemaUrl": "https://opentelemetry.io/schemas/1.21.0", "scopeSpans": [ { "scope": {