├── pkg ├── go.sum ├── go.mod ├── instrumentation │ ├── helloworld │ │ ├── NOTICE │ │ ├── go.mod │ │ ├── helloworld_instrumenter.go │ │ └── go.sum │ ├── runtime │ │ ├── go.mod │ │ └── runtime_gls.go │ ├── nethttp │ │ ├── go.mod │ │ ├── server │ │ │ └── response_writer.go │ │ ├── go.sum │ │ └── client │ │ │ └── go.mod │ ├── grpc │ │ ├── go.mod │ │ ├── semconv │ │ │ ├── util.go │ │ │ ├── semconv_test.go │ │ │ ├── util_test.go │ │ │ └── semconv.go │ │ ├── go.sum │ │ ├── client │ │ │ └── go.mod │ │ └── server │ │ │ └── go.mod │ └── shared │ │ ├── otel_setup_test.go │ │ └── go.mod └── inst │ └── context.go ├── demo ├── http │ ├── client │ │ ├── go.sum │ │ ├── .gitignore │ │ ├── go.mod │ │ └── Dockerfile │ └── server │ │ ├── go.sum │ │ ├── .gitignore │ │ ├── go.mod │ │ └── Dockerfile ├── grpc │ ├── client │ │ ├── .gitignore │ │ ├── go.mod │ │ └── Dockerfile │ └── server │ │ ├── .gitignore │ │ ├── generate.sh │ │ ├── go.mod │ │ ├── greeter.proto │ │ ├── main_test.go │ │ ├── Dockerfile │ │ └── main.go ├── infrastructure │ ├── kubernetes │ │ └── .gitkeep │ └── docker-compose │ │ ├── jaeger │ │ ├── ui-config.json │ │ └── config.yaml │ │ ├── grafana │ │ ├── dashboards │ │ │ └── dashboard.yaml │ │ └── datasources │ │ │ └── datasources.yaml │ │ ├── prometheus │ │ └── prometheus.yml │ │ └── otel-collector │ │ └── config.yaml └── basic │ ├── go.mod │ ├── go.sum │ └── main.go ├── docs ├── assets │ └── otel-logo.png ├── go.mod └── getting-started.md ├── tool ├── internal │ ├── instrument │ │ ├── testdata │ │ │ ├── golden │ │ │ │ ├── raw-rule-only │ │ │ │ │ ├── rules.yml │ │ │ │ │ ├── raw_rule_only.main.go.golden │ │ │ │ │ └── raw_rule_only.otel.globals.go.golden │ │ │ │ ├── file-rule-only │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── file_rule_only.otel.newfile.go.golden │ │ │ │ ├── before-only │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── before_only.otel.globals.go.golden │ │ │ │ ├── ellipsis-syntax │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── ellipsis_syntax.otel.globals.go.golden │ │ │ │ ├── func-rule-only │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── func_rule_only.otel.globals.go.golden │ │ │ │ ├── underscore-syntax │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── underscore_syntax.otel.globals.go.golden │ │ │ │ ├── struct-rule-only │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── struct_rule_only.main.go.golden │ │ │ │ ├── method-receiver │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── method_receiver.otel.globals.go.golden │ │ │ │ ├── invalid-receiver │ │ │ │ │ └── rules.yml │ │ │ │ ├── combined-rules │ │ │ │ │ ├── combined_rules.otel.newfile.go.golden │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── combined_rules.otel.globals.go.golden │ │ │ │ ├── func-and-raw-rules │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── func_and_raw_rules.otel.globals.go.golden │ │ │ │ ├── multiple-struct-fields │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── multiple_struct_fields.main.go.golden │ │ │ │ ├── after-only │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── after_only.otel.globals.go.golden │ │ │ │ ├── multiple-func-rules │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── multiple_func_rules.otel.globals.go.golden │ │ │ │ ├── opt-multiple-funcs │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── opt_multiple_funcs.otel.globals.go.golden │ │ │ │ └── generic-functions │ │ │ │ │ ├── rules.yml │ │ │ │ │ └── generic_functions.otel.globals.go.golden │ │ │ ├── newfile.go │ │ │ ├── source.go │ │ │ └── hook.go │ │ ├── apply_struct.go │ │ ├── match.go │ │ ├── api.tmpl │ │ ├── apply_raw.go │ │ ├── apply_file.go │ │ └── impl.tmpl │ ├── ast │ │ └── ast_test.go │ ├── setup │ │ ├── testdata │ │ │ ├── single_func_rule.otel.runtime.go.golden │ │ │ └── multiple_rule_sets.otel.runtime.go.golden │ │ └── store.go │ └── rule │ │ ├── file_rule.go │ │ ├── raw_rule.go │ │ ├── struct_rule.go │ │ └── func_rule.go ├── data │ ├── nethttp.yaml │ ├── runtime.yaml │ ├── grpc.yaml │ ├── export.go │ └── helloworld.yaml ├── util │ ├── log.go │ ├── assert.go │ ├── shared.go │ ├── go.go │ └── sys.go ├── cmd │ ├── cmd_setup.go │ ├── cmd_go.go │ ├── cmd_toolexec.go │ ├── version.go │ ├── cmd_version.go │ └── main.go └── ex │ └── ex_test.go ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ ├── bug_report.md │ └── new_instrumentation.yaml ├── tools │ ├── go.mod │ ├── go.sum │ └── conventionalcommit │ │ └── errors.go ├── workflows │ ├── check-typos.yaml │ ├── fossa.yml │ ├── lint-markdown.yaml │ ├── check-yaml-format.yaml │ ├── check-makefile.yaml │ ├── lint-dockerfile.yaml │ ├── lint-actionlint.yaml │ ├── ossf-scorecard.yml │ ├── check-license-headers.yaml │ ├── lint-golang.yaml │ ├── test-e2e.yaml │ └── test-integration.yaml ├── codeconv.yaml ├── renovate.json5 ├── PULL_REQUEST_TEMPLATE.md └── scripts │ └── license-check.sh ├── .config ├── typos.toml ├── markdownlint.yaml ├── yamlfmt ├── checkmake └── hadolint.yaml ├── .gitignore ├── .semconv-version ├── .editorconfig ├── go.mod ├── test ├── integration │ ├── http_client_test.go │ ├── basic_test.go │ └── grpc_shutdown_test.go └── e2e │ ├── http_test.go │ └── grpc_test.go ├── README.md └── go.sum /pkg/go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/http/client/go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/http/server/go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/grpc/client/.gitignore: -------------------------------------------------------------------------------- 1 | client 2 | -------------------------------------------------------------------------------- /demo/grpc/server/.gitignore: -------------------------------------------------------------------------------- 1 | server 2 | -------------------------------------------------------------------------------- /demo/http/client/.gitignore: -------------------------------------------------------------------------------- 1 | client 2 | -------------------------------------------------------------------------------- /demo/http/server/.gitignore: -------------------------------------------------------------------------------- 1 | server 2 | -------------------------------------------------------------------------------- /demo/infrastructure/kubernetes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pkg/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg 2 | 3 | go 1.23.0 4 | -------------------------------------------------------------------------------- /demo/infrastructure/docker-compose/jaeger/ui-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "monitor": { 3 | "menuEnabled": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /demo/http/client/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/demo/http/client 2 | 3 | go 1.23.0 4 | -------------------------------------------------------------------------------- /demo/http/server/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/demo/http/server 2 | 3 | go 1.23.0 4 | -------------------------------------------------------------------------------- /docs/assets/otel-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-telemetry/opentelemetry-go-compile-instrumentation/HEAD/docs/assets/otel-logo.png -------------------------------------------------------------------------------- /pkg/instrumentation/helloworld/NOTICE: -------------------------------------------------------------------------------- 1 | This is the simplest demo for instrumenting the user main function to generate trace and metrics data. 2 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/raw-rule-only/rules.yml: -------------------------------------------------------------------------------- 1 | add_raw_code: 2 | target: main 3 | func: Func1 4 | raw: "_ = 123" 5 | 6 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/file-rule-only/rules.yml: -------------------------------------------------------------------------------- 1 | add_new_file: 2 | target: main 3 | file: newfile.go 4 | path: testdata 5 | 6 | -------------------------------------------------------------------------------- /pkg/instrumentation/runtime/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/runtime 2 | 3 | go 1.23 4 | -------------------------------------------------------------------------------- /demo/basic/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/demo/basic 2 | 3 | go 1.24.0 4 | 5 | require golang.org/x/time v0.14.0 6 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/before-only/rules.yml: -------------------------------------------------------------------------------- 1 | hook_before_only: 2 | target: main 3 | func: Func1 4 | before: H1Before 5 | path: testdata 6 | 7 | -------------------------------------------------------------------------------- /demo/basic/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= 2 | golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= 3 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/ellipsis-syntax/rules.yml: -------------------------------------------------------------------------------- 1 | hook_before_only: 2 | target: main 3 | func: EllipsisFunc 4 | before: H9Before 5 | path: testdata 6 | 7 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/func-rule-only/rules.yml: -------------------------------------------------------------------------------- 1 | hook_func: 2 | target: main 3 | func: Func1 4 | before: H1Before 5 | after: H1After 6 | path: testdata 7 | 8 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/underscore-syntax/rules.yml: -------------------------------------------------------------------------------- 1 | hook_underscore: 2 | target: main 3 | func: UnderscoreFunc 4 | before: H10Before 5 | path: testdata 6 | 7 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/struct-rule-only/rules.yml: -------------------------------------------------------------------------------- 1 | add_new_field: 2 | target: main 3 | struct: T 4 | new_field: 5 | - name: NewField 6 | type: string 7 | 8 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/method-receiver/rules.yml: -------------------------------------------------------------------------------- 1 | hook_method: 2 | target: main 3 | func: Func1 4 | recv: "*T" 5 | before: H3Before 6 | after: H3After 7 | path: testdata 8 | 9 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/newfile.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | func func2() { 7 | println("func2") 8 | } 9 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/invalid-receiver/rules.yml: -------------------------------------------------------------------------------- 1 | hook_invalid_receiver: 2 | target: main 3 | func: Func1 4 | recv: "*NonExistent" 5 | before: H1Before 6 | after: H1After 7 | path: testdata 8 | 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owners file. 2 | # This file controls who is tagged for review for any given pull request. 3 | 4 | # For anything not explicitly taken by someone else: 5 | * @open-telemetry/go-compile-instrumentation-approvers 6 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/combined-rules/combined_rules.otel.newfile.go.golden: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | func func2() { 7 | println("func2") 8 | } 9 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/file-rule-only/file_rule_only.otel.newfile.go.golden: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | func func2() { 7 | println("func2") 8 | } 9 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/func-and-raw-rules/rules.yml: -------------------------------------------------------------------------------- 1 | hook_func: 2 | target: main 3 | func: Func1 4 | before: H1Before 5 | after: H1After 6 | path: testdata 7 | 8 | add_raw_code: 9 | target: main 10 | func: Func1 11 | raw: "_ = 456" 12 | 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | 3 | contact_links: 4 | - name: Have you read the tutorials? 5 | url: https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation/blob/main/README.md 6 | about: Much help can be found in the docs 7 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/multiple-struct-fields/rules.yml: -------------------------------------------------------------------------------- 1 | add_multiple_fields: 2 | target: main 3 | struct: T 4 | new_field: 5 | - name: Field1 6 | type: string 7 | - name: Field2 8 | type: int 9 | - name: Field3 10 | type: bool 11 | 12 | -------------------------------------------------------------------------------- /.config/typos.toml: -------------------------------------------------------------------------------- 1 | # For more information about this file, 2 | # please refer to https://github.com/crate-ci/typos/blob/master/docs/reference.md 3 | [files] 4 | extend-exclude = [ 5 | # Write here the files and directories you want to exclude from the typos check. 6 | "go.mod", 7 | "go.sum", 8 | ] 9 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/after-only/rules.yml: -------------------------------------------------------------------------------- 1 | hook_after_only: 2 | target: main 3 | func: Func1 4 | after: H1After 5 | path: testdata 6 | 7 | hook_with_receiver_after_only: 8 | target: main 9 | func: Func1 10 | recv: "*T" 11 | after: H8After 12 | path: testdata 13 | 14 | -------------------------------------------------------------------------------- /.github/tools/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/.github/tools 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/google/go-github/v79 v79.0.0 7 | github.com/google/go-github/v80 v80.0.0 8 | ) 9 | 10 | require github.com/google/go-querystring v1.1.0 // indirect 11 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/multiple-func-rules/rules.yml: -------------------------------------------------------------------------------- 1 | hook_func_1: 2 | target: main 3 | func: Func1 4 | before: H1Before 5 | after: H1After 6 | path: testdata 7 | 8 | hook_func_2: 9 | target: main 10 | func: Func1 11 | before: H2Before 12 | after: H2After 13 | path: testdata 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | opentelemetry-go-compile-instrumentation 3 | otel 4 | otel.exe 5 | demo/basic/basic 6 | .idea 7 | tmp 8 | 9 | .otel-build 10 | **/.DS_Store 11 | demo/demo 12 | 13 | otel.runtime.go 14 | otel-pkg.gz 15 | pkg_temp 16 | coverage.txt 17 | 18 | go.work 19 | go.work.sum 20 | 21 | coverage*.txt 22 | gotest*.log 23 | -------------------------------------------------------------------------------- /tool/internal/ast/ast_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package ast 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestParseAst(t *testing.T) { 13 | _, err := ParseFile("ast_test.go") 14 | require.NoError(t, err) 15 | } 16 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/opt-multiple-funcs/rules.yml: -------------------------------------------------------------------------------- 1 | opt_good: 2 | target: main 3 | func: OptGood 4 | before: H5Before 5 | path: testdata 6 | 7 | opt_bad: 8 | target: main 9 | func: OptBad 10 | before: H6Before 11 | path: testdata 12 | 13 | opt_bad2: 14 | target: main 15 | func: OptBad2 16 | before: H7Before 17 | after: H7After 18 | path: testdata 19 | 20 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/generic-functions/rules.yml: -------------------------------------------------------------------------------- 1 | generic_func_rule: 2 | target: main 3 | func: GenericFunc 4 | before: GenericFuncBefore 5 | after: GenericFuncAfter 6 | path: testdata 7 | 8 | generic_method_rule: 9 | target: main 10 | func: GenericMethod 11 | recv: "*GenStruct" 12 | before: GenericMethodBefore 13 | after: GenericMethodAfter 14 | path: testdata 15 | 16 | -------------------------------------------------------------------------------- /.semconv-version: -------------------------------------------------------------------------------- 1 | # OpenTelemetry Semantic Conventions Version 2 | # This file specifies the semantic conventions version that this project intends to abide by. 3 | # The version should match the semconv imports used in pkg/instrumentation/**/semconv/**. 4 | # Format: vMAJOR.MINOR.PATCH (e.g., v1.30.0) 5 | # Latest available version: v1.38.0 (as of 2025-11-19) 6 | # Note: Run 'make registry-diff' to see what's new in the latest version 7 | v1.37.0 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | # Use unix-style newlines with a new line at EOF. No trailing whitespace. 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [{*.go,go.mod,go.sum}] 13 | indent_style = tab 14 | 15 | [*.json] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.{yml,yaml}] 20 | indent_style = space 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /docs/go.mod: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // This module is not intended to be published nor used. This go.mod file is 5 | // only present to make sure the go toolchain does not bring this directory 6 | // into the GOCACHE servers, and never accounts for it when computing codebase 7 | // checksums. 8 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/_docs 9 | 10 | go 1.23.0 11 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/combined-rules/rules.yml: -------------------------------------------------------------------------------- 1 | hook_func: 2 | target: main 3 | func: Func1 4 | before: H1Before 5 | after: H1After 6 | path: testdata 7 | 8 | add_field: 9 | target: main 10 | struct: T 11 | new_field: 12 | - name: NewField 13 | type: string 14 | 15 | add_raw: 16 | target: main 17 | func: Func1 18 | raw: "_ = 789" 19 | 20 | add_file: 21 | target: main 22 | file: newfile.go 23 | path: testdata 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/check-typos.yaml: -------------------------------------------------------------------------------- 1 | name: Check Typos 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | check-typos: 13 | name: Check Typos 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 17 | - uses: crate-ci/typos@2d0ce569feab1f8752f1dde43cc2f2aa53236e06 # v1.40.0 18 | with: 19 | config: .config/typos.toml 20 | -------------------------------------------------------------------------------- /demo/infrastructure/docker-compose/grafana/dashboards/dashboard.yaml: -------------------------------------------------------------------------------- 1 | # Grafana dashboard provisioning 2 | # Automatically loads dashboards from the dashboards directory 3 | 4 | apiVersion: 1 5 | 6 | providers: 7 | - name: Default 8 | orgId: 1 9 | folder: OpenTelemetry Demo 10 | type: file 11 | disableDeletion: false 12 | updateIntervalSeconds: 10 13 | allowUiUpdates: true 14 | options: 15 | path: /etc/grafana/provisioning/dashboards/dashboards 16 | foldersFromFilesStructure: true 17 | -------------------------------------------------------------------------------- /.github/codeconv.yaml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: true 3 | 4 | coverage: 5 | status: 6 | project: 7 | default: 8 | target: auto # compare to base 9 | threshold: 1% # allow small drops 10 | informational: true # don't fail PRs on project drop 11 | patch: 12 | default: 13 | target: auto 14 | threshold: 0.1% # require the change itself to be covered 15 | 16 | comment: 17 | layout: "reach,diff,flags,tree" 18 | behavior: default 19 | require_changes: yes 20 | -------------------------------------------------------------------------------- /.github/workflows/fossa.yml: -------------------------------------------------------------------------------- 1 | name: FOSSA scanning 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | fossa: 13 | name: FOSSA Scanning 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 17 | - uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0 18 | with: 19 | api-key: ${{secrets.FOSSA_API_KEY}} 20 | team: OpenTelemetry 21 | -------------------------------------------------------------------------------- /tool/data/nethttp.yaml: -------------------------------------------------------------------------------- 1 | server_hook: 2 | target: net/http 3 | func: ServeHTTP 4 | recv: serverHandler 5 | before: BeforeServeHTTP 6 | after: AfterServeHTTP 7 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/nethttp/server" 8 | 9 | client_hook: 10 | target: net/http 11 | func: RoundTrip 12 | recv: "*Transport" 13 | before: BeforeRoundTrip 14 | after: AfterRoundTrip 15 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/nethttp/client" 16 | -------------------------------------------------------------------------------- /tool/internal/setup/testdata/single_func_rule.otel.runtime.go.golden: -------------------------------------------------------------------------------- 1 | // This file is generated by the opentelemetry-go-compile-instrumentation tool. DO NOT EDIT. 2 | package main 3 | 4 | import _ "github.com/example/pkg" 5 | import _otel_log "log" 6 | import _otel_debug "runtime/debug" 7 | import _ "unsafe" 8 | 9 | //go:linkname _getstatck0 github.com/example/pkg.OtelGetStackImpl 10 | var _getstatck0 = _otel_debug.Stack 11 | 12 | //go:linkname _printstack0 github.com/example/pkg.OtelPrintStackImpl 13 | var _printstack0 = func(bt []byte) { _otel_log.Printf(string(bt)) } 14 | -------------------------------------------------------------------------------- /.github/workflows/lint-markdown.yaml: -------------------------------------------------------------------------------- 1 | name: Check Markdown Files 2 | on: 3 | pull_request: 4 | paths: 5 | - '**/*.md' 6 | push: 7 | branches: [main] 8 | paths: 9 | - '**/*.md' 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | markdown-lint: 16 | name: Run Markdown Lint 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 20 | - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 21 | - run: make lint/markdown 22 | -------------------------------------------------------------------------------- /.config/markdownlint.yaml: -------------------------------------------------------------------------------- 1 | # See: https://github.com/DavidAnson/markdownlint/blob/main/README.md 2 | # Default state for all rules 3 | default: true 4 | 5 | # ul-style 6 | MD004: false 7 | 8 | # hard-tabs 9 | MD010: false 10 | 11 | # line-length 12 | MD013: false 13 | 14 | # no-duplicate-header 15 | MD024: 16 | siblings_only: true 17 | 18 | #single-title 19 | MD025: false 20 | 21 | # ol-prefix 22 | MD029: 23 | style: ordered 24 | 25 | # no-inline-html 26 | MD033: false 27 | 28 | # fenced-code-language 29 | MD040: false 30 | 31 | # first-line-heading 32 | MD041: false 33 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:best-practices", 5 | "helpers:pinGitHubActionDigestsToSemver" 6 | ], 7 | "packageRules": [ 8 | { 9 | "groupName": "all non-major versions", 10 | "matchUpdateTypes": ["patch", "minor", "digest"], 11 | "schedule": ["before 8am on Monday"] 12 | }, 13 | { 14 | "matchUpdateTypes": ["major"], 15 | "schedule": ["before 8am on Monday"] 16 | } 17 | ], 18 | "labels": [ 19 | "dependencies" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tool/util/log.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package util 5 | 6 | import ( 7 | "context" 8 | "log/slog" 9 | ) 10 | 11 | type contextKeyLogger struct{} 12 | 13 | func ContextWithLogger(ctx context.Context, logger *slog.Logger) context.Context { 14 | return context.WithValue(ctx, contextKeyLogger{}, logger) 15 | } 16 | 17 | func LoggerFromContext(ctx context.Context) *slog.Logger { 18 | logger, ok := ctx.Value(contextKeyLogger{}).(*slog.Logger) 19 | if !ok { 20 | return slog.Default() 21 | } 22 | return logger 23 | } 24 | -------------------------------------------------------------------------------- /.config/yamlfmt: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # yamlfmt configuration file 5 | # See: https://github.com/google/yamlfmt/blob/main/docs/config-file.md 6 | 7 | formatter: 8 | type: basic 9 | retain_line_breaks: true 10 | max_line_length: 120 11 | indentless_arrays: false 12 | drop_merge_tag: false 13 | pad_line_comments: 2 14 | include_document_start: false 15 | 16 | # Exclude patterns for files/directories to ignore 17 | exclude: 18 | - testdata/ 19 | - vendor/ 20 | - .git/ 21 | - node_modules/ 22 | - pb/ 23 | - .otel-build/ 24 | -------------------------------------------------------------------------------- /.config/checkmake: -------------------------------------------------------------------------------- 1 | [default] 2 | # Configuration for checkmake linter 3 | 4 | [uniquetargets] 5 | # Allow duplicate target definitions (one for comment, one for dependencies) 6 | # This improves readability by separating comments from dependencies 7 | disabled = true 8 | 9 | [maxbodylength] 10 | # Allow longer target bodies for complex build tasks 11 | # Package and clean targets naturally have multiple steps 12 | disabled = true 13 | 14 | [phonydeclared] 15 | # We declare all phony targets at the top of the Makefile in a single .PHONY declaration 16 | # This is a valid approach and more maintainable than declaring each target separately 17 | disabled = true 18 | -------------------------------------------------------------------------------- /tool/cmd/cmd_setup.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/urfave/cli/v3" 10 | 11 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/setup" 12 | ) 13 | 14 | //nolint:gochecknoglobals // Implementation of a CLI command 15 | var commandSetup = cli.Command{ 16 | Name: "setup", 17 | Description: "Set up the environment for instrumentation", 18 | Before: addLoggerPhaseAttribute, 19 | Action: func(ctx context.Context, cmd *cli.Command) error { 20 | return setup.Setup(ctx, cmd.Args().Slice()) 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /demo/grpc/server/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright The OpenTelemetry Authors 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | 7 | # Generate Go code from proto file 8 | # Note: This requires protoc to be installed on the system 9 | # On macOS: brew install protobuf 10 | # On Linux: apt-get install -y protobuf-compiler 11 | 12 | # Create the pb directory if it doesn't exist 13 | mkdir -p pb 14 | 15 | # Generate protobuf and gRPC code in the pb directory 16 | protoc --go_out=pb --go_opt=paths=source_relative \ 17 | --go-grpc_out=pb --go-grpc_opt=paths=source_relative \ 18 | greeter.proto 19 | 20 | echo "Generated files in pb/ directory:" 21 | ls -la pb/*.go -------------------------------------------------------------------------------- /tool/ex/ex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package ex 5 | 6 | import ( 7 | "errors" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestError(t *testing.T) { 14 | err := Newf("a") 15 | err = Wrapf(err, "b") 16 | err = Wrap(Wrap(Wrap(err))) // make no sense 17 | require.Contains(t, err.Error(), "a") 18 | require.Contains(t, err.Error(), "b") 19 | 20 | err = errors.New("c") 21 | err = Wrapf(err, "d") 22 | err = Wrapf(err, "e") 23 | err = Wrap(Wrap(Wrap(err))) // make no sense 24 | require.Contains(t, err.Error(), "c") 25 | require.Contains(t, err.Error(), "d") 26 | } 27 | -------------------------------------------------------------------------------- /demo/grpc/server/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/demo/grpc/server 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/stretchr/testify v1.11.1 7 | google.golang.org/grpc v1.77.0 8 | google.golang.org/protobuf v1.36.10 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/pmezard/go-difflib v1.0.0 // indirect 14 | golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect 15 | golang.org/x/sys v0.37.0 // indirect 16 | golang.org/x/text v0.30.0 // indirect 17 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect 18 | gopkg.in/yaml.v3 v3.0.1 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /.github/workflows/check-yaml-format.yaml: -------------------------------------------------------------------------------- 1 | name: Check YAML Formatting 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "**/*.yml" 7 | - "**/*.yaml" 8 | push: 9 | branches: [main] 10 | paths: 11 | - "**/*.yml" 12 | - "**/*.yaml" 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | yaml-format: 19 | name: Check YAML Formatting 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 23 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 24 | with: 25 | go-version-file: go.mod 26 | cache: false 27 | - run: make lint/yaml 28 | -------------------------------------------------------------------------------- /demo/grpc/server/greeter.proto: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | syntax = "proto3"; 5 | 6 | package greeter; 7 | 8 | option go_package = "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/demo/grpc/server/pb"; 9 | 10 | service Greeter { 11 | rpc SayHello (HelloRequest) returns (HelloReply) {} 12 | rpc SayHelloStream (stream HelloRequest) returns (stream HelloReply) {} 13 | rpc Shutdown (ShutdownRequest) returns (ShutdownReply) {} 14 | } 15 | 16 | message HelloRequest { 17 | string name = 1; 18 | } 19 | 20 | message HelloReply { 21 | string message = 1; 22 | } 23 | 24 | message ShutdownRequest {} 25 | 26 | message ShutdownReply { 27 | string message = 1; 28 | } -------------------------------------------------------------------------------- /tool/cmd/cmd_go.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/setup" 10 | "github.com/urfave/cli/v3" 11 | ) 12 | 13 | //nolint:gochecknoglobals // Implementation of a CLI command 14 | var commandGo = cli.Command{ 15 | Name: "go", 16 | Description: "Invoke the go toolchain with toolexec mode", 17 | ArgsUsage: "[go toolchain flags]", 18 | SkipFlagParsing: true, 19 | Before: addLoggerPhaseAttribute, 20 | Action: func(ctx context.Context, cmd *cli.Command) error { 21 | return setup.GoBuild(ctx, cmd.Args().Slice()) 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /tool/data/runtime.yaml: -------------------------------------------------------------------------------- 1 | add_gls_field: 2 | target: "runtime" 3 | struct: "g" 4 | new_field: 5 | - name: "otel_trace_context" 6 | type: "interface{}" 7 | - name: "otel_baggage_container" 8 | type: "interface{}" 9 | 10 | gls_linker: 11 | target: "runtime" 12 | file: "runtime_gls.go" 13 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/runtime" 14 | 15 | goroutine_propagate: 16 | target: "runtime" 17 | func: "newproc1" 18 | raw: | 19 | defer func(){ 20 | _unnamedRetVal0.otel_trace_context = propagateOtelContext(callergp.otel_trace_context); 21 | _unnamedRetVal0.otel_baggage_container = propagateOtelContext(callergp.otel_baggage_container); 22 | }() 23 | -------------------------------------------------------------------------------- /tool/cmd/cmd_toolexec.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/urfave/cli/v3" 10 | 11 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/instrument" 12 | ) 13 | 14 | //nolint:gochecknoglobals // Implementation of a CLI command 15 | var commandToolexec = cli.Command{ 16 | Name: "toolexec", 17 | Description: "Wrap a command run by the go toolchain", 18 | SkipFlagParsing: true, 19 | Hidden: true, 20 | Before: addLoggerPhaseAttribute, 21 | Action: func(ctx context.Context, cmd *cli.Command) error { 22 | return instrument.Toolexec(ctx, cmd.Args().Slice()) 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /pkg/instrumentation/nethttp/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/nethttp 2 | 3 | go 1.23.0 4 | 5 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.11.1 9 | go.opentelemetry.io/otel v1.38.0 10 | go.opentelemetry.io/otel/metric v1.38.0 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/go-logr/logr v1.4.3 // indirect 16 | github.com/go-logr/stdr v1.2.2 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 19 | go.opentelemetry.io/otel/trace v1.38.0 // indirect 20 | gopkg.in/yaml.v3 v3.0.1 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /pkg/instrumentation/grpc/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/grpc 2 | 3 | go 1.24.0 4 | 5 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.11.1 9 | go.opentelemetry.io/otel v1.38.0 10 | google.golang.org/grpc v1.77.0 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | go.opentelemetry.io/otel/trace v1.38.0 // indirect 17 | golang.org/x/sys v0.37.0 // indirect 18 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect 19 | google.golang.org/protobuf v1.36.10 // indirect 20 | gopkg.in/yaml.v3 v3.0.1 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation 5 | 6 | go 1.24.0 7 | 8 | require ( 9 | github.com/dave/dst v0.27.3 10 | github.com/stretchr/testify v1.11.1 11 | github.com/urfave/cli/v3 v3.6.1 12 | golang.org/x/mod v0.30.0 13 | golang.org/x/sync v0.18.0 14 | golang.org/x/tools v0.38.0 15 | gopkg.in/yaml.v3 v3.0.1 16 | gotest.tools/v3 v3.5.2 17 | ) 18 | 19 | require ( 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/google/go-cmp v0.7.0 // indirect 22 | github.com/kr/pretty v0.3.1 // indirect 23 | github.com/pmezard/go-difflib v1.0.0 // indirect 24 | github.com/rogpeppe/go-internal v1.13.1 // indirect 25 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /.github/tools/go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 2 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 3 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 4 | github.com/google/go-github/v79 v79.0.0 h1:MdodQojuFPBhmtwHiBcIGLw/e/wei2PvFX9ndxK0X4Y= 5 | github.com/google/go-github/v79 v79.0.0/go.mod h1:OAFbNhq7fQwohojb06iIIQAB9CBGYLq999myfUFnrS4= 6 | github.com/google/go-github/v80 v80.0.0/go.mod h1:pRo4AIMdHW83HNMGfNysgSAv0vmu+/pkY8nZO9FT9Yo= 7 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 8 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 9 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 10 | -------------------------------------------------------------------------------- /.github/workflows/check-makefile.yaml: -------------------------------------------------------------------------------- 1 | name: Check Makefiles 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "**/*Makefile*" 9 | - ".config/checkmake" 10 | - ".github/workflows/checkmake.yaml" 11 | pull_request: 12 | paths: 13 | - "**/*Makefile*" 14 | - ".config/checkmake" 15 | - ".github/workflows/checkmake.yaml" 16 | 17 | jobs: 18 | checkmake: 19 | name: Check Makefile Formatting 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 23 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 24 | with: 25 | go-version: "1.25" 26 | - run: go install github.com/checkmake/checkmake/cmd/checkmake@latest 27 | - run: checkmake --config .config/checkmake Makefile 28 | -------------------------------------------------------------------------------- /.github/workflows/lint-dockerfile.yaml: -------------------------------------------------------------------------------- 1 | name: Check Dockerfiles 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**/*Dockerfile*' 7 | - '.config/hadolint.yaml' 8 | - '.github/workflows/lint-dockerfile.yaml' 9 | push: 10 | branches: 11 | - main 12 | paths: 13 | - '**/*Dockerfile*' 14 | - '.config/hadolint.yaml' 15 | - '.github/workflows/lint-dockerfile.yaml' 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | hadolint: 22 | name: Lint Dockerfiles 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 26 | - uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 27 | with: 28 | config: .config/hadolint.yaml 29 | failure-threshold: warning 30 | recursive: true 31 | -------------------------------------------------------------------------------- /tool/data/grpc.yaml: -------------------------------------------------------------------------------- 1 | # Copyright The OpenTelemetry Authors 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | server_hook: 5 | target: google.golang.org/grpc 6 | func: NewServer 7 | before: BeforeNewServer 8 | after: AfterNewServer 9 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/grpc/server" 10 | 11 | client_hook_newclient: 12 | target: google.golang.org/grpc 13 | func: NewClient 14 | before: BeforeNewClient 15 | after: AfterNewClient 16 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/grpc/client" 17 | 18 | client_hook_dialcontext: 19 | target: google.golang.org/grpc 20 | func: DialContext 21 | before: BeforeDialContext 22 | after: AfterDialContext 23 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/grpc/client" 24 | -------------------------------------------------------------------------------- /.config/hadolint.yaml: -------------------------------------------------------------------------------- 1 | # Hadolint configuration for OpenTelemetry Go compile-time instrumentation 2 | # See https://github.com/hadolint/hadolint for more information 3 | 4 | # Rules to ignore 5 | ignored: 6 | # DL3018: Pin versions in apk add. 7 | # Pinning apk package versions is impractical as they change between Alpine releases. 8 | # We pin the Alpine base image version instead for reproducibility. 9 | - DL3018 10 | # DL3059: Multiple consecutive RUN instructions. 11 | # We intentionally separate RUN commands for different build phases to optimize 12 | # Docker layer caching and improve readability. 13 | - DL3059 14 | 15 | # Trusted registries for base images 16 | trustedRegistries: 17 | - docker.io 18 | - gcr.io 19 | # Override specific rules 20 | # override: 21 | # error: 22 | # - DL3008 23 | # warning: 24 | # - DL3015 25 | # info: 26 | # - DL3059 27 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/source.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | type T struct{} 7 | 8 | func (t *T) Func1(p1 string, p2 int) (float32, error) { 9 | return 0.0, nil 10 | } 11 | 12 | func Func1(p1 string, p2 int) (float32, error) { 13 | println("Hello, World!") 14 | return 0.0, nil 15 | } 16 | 17 | func Func2(p1 string, _ int) {} 18 | 19 | func OptGood() {} 20 | func OptBad() {} 21 | func OptBad2() {} 22 | 23 | func GenericFunc[T any](p1 T, p2 int) (T, error) { 24 | return p1, nil 25 | } 26 | 27 | type GenStruct[T any] struct { 28 | value T 29 | } 30 | 31 | func (g *GenStruct[T]) GenericMethod(p1 T, p2 string) (T, error) { 32 | return p1, nil 33 | } 34 | 35 | func EllipsisFunc(p1 ...string) {} 36 | 37 | func UnderscoreFunc(_ int, _ float32) {} 38 | 39 | func main() { Func1("hello", 123) } 40 | -------------------------------------------------------------------------------- /tool/util/assert.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package util 5 | 6 | import ( 7 | "reflect" 8 | 9 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 10 | ) 11 | 12 | func Assert(condition bool, message string) { 13 | if !condition { 14 | ex.Fatalf("Assertion failed: %s", message) 15 | } 16 | } 17 | 18 | func AssertType[T any](v any) T { 19 | value, ok := v.(T) 20 | if !ok { 21 | actualType := reflect.TypeOf(v).Name() 22 | var zero T 23 | expectType := reflect.TypeOf(zero).String() 24 | ex.Fatalf("Type assertion failed: %s, expected %s", 25 | actualType, expectType) 26 | } 27 | return value 28 | } 29 | 30 | func ShouldNotReachHere() { 31 | ex.Fatalf("Should not reach here!") 32 | } 33 | 34 | func Unimplemented(message string) { 35 | ex.Fatalf("Unimplemented: %s", message) 36 | } 37 | -------------------------------------------------------------------------------- /tool/internal/instrument/apply_struct.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package instrument 5 | 6 | import ( 7 | "github.com/dave/dst" 8 | 9 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 10 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/ast" 11 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/rule" 12 | ) 13 | 14 | func (ip *InstrumentPhase) applyStructRule(rule *rule.InstStructRule, root *dst.File) error { 15 | structDecl := ast.FindStructDecl(root, rule.Struct) 16 | if structDecl == nil { 17 | return ex.Newf("can not find struct %s", rule.Struct) 18 | } 19 | for _, field := range rule.NewField { 20 | ast.AddStructField(structDecl, field.Name, field.Type) 21 | } 22 | ip.Info("Apply struct rule", "rule", rule) 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /demo/grpc/client/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/demo/grpc/client 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/open-telemetry/opentelemetry-go-compile-instrumentation/demo/grpc/server v0.0.0 7 | github.com/stretchr/testify v1.11.1 8 | google.golang.org/grpc v1.77.0 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/pmezard/go-difflib v1.0.0 // indirect 14 | golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect 15 | golang.org/x/sys v0.37.0 // indirect 16 | golang.org/x/text v0.30.0 // indirect 17 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect 18 | google.golang.org/protobuf v1.36.10 // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | 22 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/demo/grpc/server => ../server 23 | -------------------------------------------------------------------------------- /tool/internal/setup/testdata/multiple_rule_sets.otel.runtime.go.golden: -------------------------------------------------------------------------------- 1 | // This file is generated by the opentelemetry-go-compile-instrumentation tool. DO NOT EDIT. 2 | package main 3 | 4 | import _ "github.com/example/pkg" 5 | import _ "github.com/example/pkg1" 6 | import _ "github.com/example/pkg2" 7 | import _otel_log "log" 8 | import _otel_debug "runtime/debug" 9 | import _ "unsafe" 10 | 11 | //go:linkname _getstatck0 github.com/example/pkg1.OtelGetStackImpl 12 | var _getstatck0 = _otel_debug.Stack 13 | 14 | //go:linkname _printstack0 github.com/example/pkg1.OtelPrintStackImpl 15 | var _printstack0 = func(bt []byte) { _otel_log.Printf(string(bt)) } 16 | 17 | //go:linkname _getstatck1 github.com/example/pkg2.OtelGetStackImpl 18 | var _getstatck1 = _otel_debug.Stack 19 | 20 | //go:linkname _printstack1 github.com/example/pkg2.OtelPrintStackImpl 21 | var _printstack1 = func(bt []byte) { _otel_log.Printf(string(bt)) } 22 | -------------------------------------------------------------------------------- /tool/cmd/version.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "runtime/debug" 8 | ) 9 | 10 | // These variables are set by the linker. Changes should sync with the Makefile. 11 | // 12 | //nolint:gochecknoglobals // these variables are set by the linker 13 | var ( 14 | Version = "v0.0.0" 15 | CommitHash = "unknown" 16 | BuildTime = "unknown" 17 | ) 18 | 19 | func init() { 20 | if Version != "v0.0.0" { 21 | // Was set at build time 22 | return 23 | } 24 | 25 | bi, ok := debug.ReadBuildInfo() 26 | if !ok { 27 | return 28 | } 29 | 30 | if version := bi.Main.Version; version != "" { 31 | Version = version 32 | } 33 | 34 | for _, setting := range bi.Settings { 35 | switch setting.Key { 36 | case "vcs.revision": 37 | CommitHash = setting.Value 38 | case "vcs.time": 39 | BuildTime = setting.Value 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/lint-actionlint.yaml: -------------------------------------------------------------------------------- 1 | name: Check GitHub Actions 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".github/workflows/*.yml" 7 | - ".github/workflows/*.yaml" 8 | - ".github/actions/**/action.yml" 9 | - ".github/actions/**/action.yaml" 10 | push: 11 | branches: [main] 12 | paths: 13 | - ".github/workflows/*.yml" 14 | - ".github/workflows/*.yaml" 15 | - ".github/actions/**/action.yml" 16 | - ".github/actions/**/action.yaml" 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | actionlint: 23 | name: Lint GitHub Actions 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 27 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 28 | with: 29 | go-version-file: go.mod 30 | cache: false 31 | - run: make lint/action 32 | -------------------------------------------------------------------------------- /pkg/instrumentation/runtime/runtime_gls.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package runtime 5 | 6 | func GetTraceContextFromGLS() interface{} { 7 | return getg().m.curg.otel_trace_context 8 | } 9 | 10 | func GetBaggageContainerFromGLS() interface{} { 11 | return getg().m.curg.otel_baggage_container 12 | } 13 | 14 | func SetTraceContextToGLS(traceContext interface{}) { 15 | getg().m.curg.otel_trace_context = traceContext 16 | } 17 | 18 | func SetBaggageContainerToGLS(baggageContainer interface{}) { 19 | getg().m.curg.otel_baggage_container = baggageContainer 20 | } 21 | 22 | type OtelContextCloner interface { 23 | Clone() interface{} 24 | } 25 | 26 | func propagateOtelContext(context interface{}) interface{} { 27 | if context == nil { 28 | return nil 29 | } 30 | if cloner, ok := context.(OtelContextCloner); ok { 31 | return cloner.Clone() 32 | } 33 | return context 34 | } 35 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/struct-rule-only/struct_rule_only.main.go.golden: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | type T struct{ NewField string } 7 | 8 | func (t *T) Func1(p1 string, p2 int) (float32, error) { 9 | return 0.0, nil 10 | } 11 | 12 | func Func1(p1 string, p2 int) (float32, error) { 13 | println("Hello, World!") 14 | return 0.0, nil 15 | } 16 | 17 | func Func2(p1 string, _ int) {} 18 | 19 | func OptGood() {} 20 | func OptBad() {} 21 | func OptBad2() {} 22 | 23 | func GenericFunc[T any](p1 T, p2 int) (T, error) { 24 | return p1, nil 25 | } 26 | 27 | type GenStruct[T any] struct { 28 | value T 29 | } 30 | 31 | func (g *GenStruct[T]) GenericMethod(p1 T, p2 string) (T, error) { 32 | return p1, nil 33 | } 34 | 35 | func EllipsisFunc(p1 ...string) {} 36 | 37 | func UnderscoreFunc(_ int, _ float32) {} 38 | 39 | func main() { Func1("hello", 123) } 40 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/raw-rule-only/raw_rule_only.main.go.golden: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | type T struct{} 7 | 8 | func (t *T) Func1(p1 string, p2 int) (float32, error) { 9 | return 0.0, nil 10 | } 11 | 12 | func Func1(p1 string, p2 int) (_unnamedRetVal0 float32, _unnamedRetVal1 error) { 13 | _ = 123 14 | println("Hello, World!") 15 | return 0.0, nil 16 | } 17 | 18 | func Func2(p1 string, _ int) {} 19 | 20 | func OptGood() {} 21 | func OptBad() {} 22 | func OptBad2() {} 23 | 24 | func GenericFunc[T any](p1 T, p2 int) (T, error) { 25 | return p1, nil 26 | } 27 | 28 | type GenStruct[T any] struct { 29 | value T 30 | } 31 | 32 | func (g *GenStruct[T]) GenericMethod(p1 T, p2 string) (T, error) { 33 | return p1, nil 34 | } 35 | 36 | func EllipsisFunc(p1 ...string) {} 37 | 38 | func UnderscoreFunc(_ int, _ float32) {} 39 | 40 | func main() { Func1("hello", 123) } 41 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/multiple-struct-fields/multiple_struct_fields.main.go.golden: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | type T struct { 7 | Field1 string 8 | Field2 int 9 | Field3 bool 10 | } 11 | 12 | func (t *T) Func1(p1 string, p2 int) (float32, error) { 13 | return 0.0, nil 14 | } 15 | 16 | func Func1(p1 string, p2 int) (float32, error) { 17 | println("Hello, World!") 18 | return 0.0, nil 19 | } 20 | 21 | func Func2(p1 string, _ int) {} 22 | 23 | func OptGood() {} 24 | func OptBad() {} 25 | func OptBad2() {} 26 | 27 | func GenericFunc[T any](p1 T, p2 int) (T, error) { 28 | return p1, nil 29 | } 30 | 31 | type GenStruct[T any] struct { 32 | value T 33 | } 34 | 35 | func (g *GenStruct[T]) GenericMethod(p1 T, p2 string) (T, error) { 36 | return p1, nil 37 | } 38 | 39 | func EllipsisFunc(p1 ...string) {} 40 | 41 | func UnderscoreFunc(_ int, _ float32) {} 42 | 43 | func main() { Func1("hello", 123) } 44 | -------------------------------------------------------------------------------- /pkg/instrumentation/helloworld/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld 2 | 3 | go 1.23.0 4 | 5 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg => ../.. 6 | 7 | require ( 8 | github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg v0.0.0-20251208011108-ac0fa4a155e3 9 | go.opentelemetry.io/otel v1.38.0 10 | go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 11 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 12 | go.opentelemetry.io/otel/sdk v1.38.0 13 | go.opentelemetry.io/otel/sdk/metric v1.38.0 14 | go.opentelemetry.io/otel/trace v1.38.0 15 | ) 16 | 17 | require ( 18 | github.com/go-logr/logr v1.4.3 // indirect 19 | github.com/go-logr/stdr v1.2.2 // indirect 20 | github.com/google/uuid v1.6.0 // indirect 21 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 22 | go.opentelemetry.io/otel/metric v1.38.0 // indirect 23 | golang.org/x/sys v0.35.0 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /tool/data/export.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | package data 4 | 5 | import ( 6 | "embed" 7 | "strings" 8 | 9 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 10 | ) 11 | 12 | //go:embed * 13 | var dataFs embed.FS 14 | 15 | // ListEmbedFiles lists all the files in the embedded data 16 | func ListEmbedFiles() ([]string, error) { 17 | rules, err := dataFs.ReadDir(".") 18 | if err != nil { 19 | return nil, ex.Wrapf(err, "failed to read directory") 20 | } 21 | 22 | var ruleFiles []string 23 | for _, rule := range rules { 24 | if !rule.IsDir() && strings.HasSuffix(rule.Name(), ".yaml") { 25 | ruleFiles = append(ruleFiles, rule.Name()) 26 | } 27 | } 28 | return ruleFiles, nil 29 | } 30 | 31 | // ReadEmbedFile reads a file from the embedded data 32 | func ReadEmbedFile(path string) ([]byte, error) { 33 | bs, err := dataFs.ReadFile(path) 34 | if err != nil { 35 | return nil, ex.Wrapf(err, "failed to read file") 36 | } 37 | return bs, nil 38 | } 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | 22 | **Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the bug 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | ### Environment 15 | 16 | - OS: [e.g. iOS, Windows] 17 | - Go Version: [e.g. 1.6] 18 | - Version: [e.g. 1295520, v0.1.0] 19 | 20 | ### To Reproduce 21 | 22 | Steps to reproduce the behavior: 23 | 1. Go to '...' 24 | 2. Click on '....' 25 | 3. Scroll down to '....' 26 | 4. See error 27 | 28 | ### Expected behavior 29 | 30 | A clear and concise description of what you expected to happen. 31 | 32 | ### Additional context 33 | 34 | Add any other context about the problem here. 35 | 36 | **Tip**: [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). 37 | -------------------------------------------------------------------------------- /tool/internal/setup/store.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package setup 5 | 6 | import ( 7 | "encoding/json" 8 | "os" 9 | 10 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 11 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/rule" 12 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/util" 13 | ) 14 | 15 | // store stores the matched rules to the file 16 | // It's the pair of the InstrumentPhase.load 17 | func (sp *SetupPhase) store(matched []*rule.InstRuleSet) error { 18 | f := util.GetMatchedRuleFile() 19 | file, err := os.Create(f) 20 | if err != nil { 21 | return ex.Wrapf(err, "failed to create file %s", f) 22 | } 23 | defer file.Close() 24 | 25 | bs, err := json.Marshal(matched) 26 | if err != nil { 27 | return ex.Wrapf(err, "failed to marshal rules to JSON") 28 | } 29 | 30 | _, err = file.Write(bs) 31 | if err != nil { 32 | return ex.Wrapf(err, "failed to write JSON to file %s", f) 33 | } 34 | sp.Info("Stored matched sets", "path", f) 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /pkg/instrumentation/grpc/semconv/util.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package semconv 5 | 6 | import ( 7 | "net" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | // splitHostPort splits a network address hostport of the form "host:port" into host and port. 13 | // Returns host and port (or -1 if port not found/invalid). 14 | func splitHostPort(hostport string) (host string, port int) { 15 | port = -1 16 | 17 | if strings.HasPrefix(hostport, "[") { 18 | // IPv6 address 19 | addrEnd := strings.LastIndexByte(hostport, ']') 20 | if addrEnd < 0 { 21 | // Invalid hostport 22 | return "", port 23 | } 24 | if i := strings.LastIndexByte(hostport[addrEnd:], ':'); i < 0 { 25 | host = hostport[1:addrEnd] 26 | return host, port 27 | } 28 | } else { 29 | if i := strings.LastIndexByte(hostport, ':'); i < 0 { 30 | host = hostport 31 | return host, port 32 | } 33 | } 34 | 35 | var pStr string 36 | var err error 37 | host, pStr, err = net.SplitHostPort(hostport) 38 | if err != nil { 39 | return "", port 40 | } 41 | 42 | p, err := strconv.ParseUint(pStr, 10, 16) 43 | if err != nil { 44 | return host, port 45 | } 46 | return host, int(p) 47 | } 48 | -------------------------------------------------------------------------------- /demo/infrastructure/docker-compose/grafana/datasources/datasources.yaml: -------------------------------------------------------------------------------- 1 | # Grafana datasources provisioning 2 | # Automatically configures Prometheus and Jaeger datasources on startup 3 | 4 | apiVersion: 1 5 | 6 | datasources: 7 | # Prometheus datasource for metrics 8 | - name: Prometheus 9 | type: prometheus 10 | uid: prometheus 11 | access: proxy 12 | url: http://prometheus:9090 13 | isDefault: true 14 | editable: true 15 | jsonData: 16 | httpMethod: POST 17 | # Enable exemplar support for metric-to-trace correlation 18 | exemplarTraceIdDestinations: 19 | - name: traceID 20 | datasourceUid: jaeger 21 | # Enable Prometheus query suggestions 22 | timeInterval: 15s 23 | 24 | # Jaeger datasource for distributed tracing 25 | - name: Jaeger 26 | type: jaeger 27 | uid: jaeger 28 | access: proxy 29 | url: http://jaeger:16686 30 | editable: true 31 | jsonData: 32 | # Trace to metrics correlation 33 | tracesToMetrics: 34 | datasourceUid: prometheus 35 | tags: 36 | - key: service.name 37 | value: job 38 | # Node graph support for service dependencies 39 | nodeGraph: 40 | enabled: true 41 | -------------------------------------------------------------------------------- /tool/internal/rule/file_rule.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package rule 5 | 6 | import ( 7 | "strings" 8 | 9 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 10 | "gopkg.in/yaml.v3" 11 | ) 12 | 13 | // InstFileRule represents a rule that allows adding a new file to the target 14 | // package. For example, if we want to add a new file to the target package, 15 | // we can define a rule: 16 | // 17 | // rule: 18 | // name: "newrule" 19 | // target: "main" 20 | // file: "newfile.go" 21 | // path: "github.com/foo/bar/newfile" 22 | type InstFileRule struct { 23 | InstBaseRule `yaml:",inline"` 24 | 25 | File string `json:"file" yaml:"file"` // The name of the file to be added to the target package 26 | Path string `json:"path" yaml:"path"` // The module path where the file is located 27 | } 28 | 29 | // NewInstFileRule loads and validates an InstFileRule from YAML data. 30 | func NewInstFileRule(data []byte, name string) (*InstFileRule, error) { 31 | var r InstFileRule 32 | if err := yaml.Unmarshal(data, &r); err != nil { 33 | return nil, ex.Wrap(err) 34 | } 35 | if r.Name == "" { 36 | r.Name = name 37 | } 38 | if err := r.validate(); err != nil { 39 | return nil, ex.Wrapf(err, "invalid file rule %q", name) 40 | } 41 | return &r, nil 42 | } 43 | 44 | func (r *InstFileRule) validate() error { 45 | if strings.TrimSpace(r.File) == "" { 46 | return ex.Newf("file cannot be empty") 47 | } 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/hook.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package testdata 5 | 6 | import ( 7 | _ "unsafe" 8 | 9 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/inst" 10 | ) 11 | 12 | func H1Before(ctx inst.HookContext, p1 string, p2 int) { 13 | println("H1Before") 14 | } 15 | 16 | func H1After(ctx inst.HookContext, r1 float32, r2 error) {} 17 | 18 | func H2Before(ctx inst.HookContext, p1 string, p2 int) {} 19 | 20 | func H2After(ctx inst.HookContext, r1 float32, r2 error) {} 21 | 22 | func H3Before(ctx inst.HookContext, recv interface{}, p1 string, p2 int) {} 23 | 24 | func H3After(ctx inst.HookContext, r1 float32, r2 error) {} 25 | 26 | func H4Before(ctx inst.HookContext, p1 string, _ int) {} 27 | 28 | func H5Before(ctx inst.HookContext) {} 29 | 30 | func H6Before(ctx inst.HookContext) { _ = ctx } 31 | 32 | func H7Before(ctx inst.HookContext) { ctx.SetSkipCall(true) } 33 | 34 | func H7After(ctx inst.HookContext) { _ = ctx } 35 | 36 | func H8After(ctx inst.HookContext, ret1 float32, ret2 error) {} 37 | 38 | func H9Before(ctx inst.HookContext, p1 []string) {} 39 | 40 | func H10Before(ctx inst.HookContext, _ int, _ float32) {} 41 | 42 | func GenericFuncBefore(ctx inst.HookContext, p1 interface{}, p2 int) {} 43 | 44 | func GenericFuncAfter(ctx inst.HookContext, r1 interface{}, r2 error) {} 45 | 46 | func GenericMethodBefore(ctx inst.HookContext, recv interface{}, p1 interface{}, p2 string) {} 47 | 48 | func GenericMethodAfter(ctx inst.HookContext, r1 interface{}, r2 error) {} 49 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 29 | 30 | ## Description 31 | 32 | 33 | 34 | ## Motivation 35 | 36 | 37 | 38 | Fixes # 39 | 40 | --- 41 | 42 | ## Checklist 43 | 44 | - [ ] PR title follows [conventional commits](https://www.conventionalcommits.org/) format 45 | - [ ] Code formatted: `make format` 46 | - [ ] Linters pass: `make lint` 47 | - [ ] Tests pass: `make test` 48 | - [ ] Tests added for new functionality 49 | - [ ] Documentation updated (if applicable) 50 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/raw-rule-only/raw_rule_only.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 4 | type HookContext interface { 5 | // Set the skip call flag, can be used to skip the original function call 6 | SetSkipCall(bool) 7 | // Get the skip call flag, can be used to skip the original function call 8 | IsSkipCall() bool 9 | // Set the data field, can be used to pass information between Before and After hooks 10 | SetData(interface{}) 11 | // Get the data field, can be used to pass information between Before and After hooks 12 | GetData() interface{} 13 | // Get a value from the data field by key 14 | GetKeyData(key string) interface{} 15 | // Set a key-value pair in the data field 16 | SetKeyData(key string, val interface{}) 17 | // Check if a key exists in the data field 18 | HasKeyData(key string) bool 19 | // Number of original function parameters 20 | GetParamCount() int 21 | // Get the original function parameter at index idx 22 | GetParam(idx int) interface{} 23 | // Change the original function parameter at index idx 24 | SetParam(idx int, val interface{}) 25 | // Number of original function return values 26 | GetReturnValCount() int 27 | // Get the original function return value at index idx 28 | GetReturnVal(idx int) interface{} 29 | // Change the original function return value at index idx 30 | SetReturnVal(idx int, val interface{}) 31 | // Get the original function name 32 | GetFuncName() string 33 | // Get the package name of the original function 34 | GetPackageName() string 35 | } 36 | -------------------------------------------------------------------------------- /tool/internal/rule/raw_rule.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package rule 5 | 6 | import ( 7 | "strings" 8 | 9 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 10 | "gopkg.in/yaml.v3" 11 | ) 12 | 13 | // InstRawRule represents a rule that allows raw Go source code injection into 14 | // appropriate target function locations. For example, if we want to inject 15 | // raw code at the entry of target function Bar, we can define a rule: 16 | // 17 | // rule: 18 | // name: "newrule" 19 | // target: "main" 20 | // func: "Bar" 21 | // recv: "*Recv" 22 | // raw: "println(\"Hello, World!\")" 23 | type InstRawRule struct { 24 | InstBaseRule `yaml:",inline"` 25 | 26 | Func string `json:"func" yaml:"func"` // The name of the target func to be instrumented 27 | Recv string `json:"recv" yaml:"recv"` // The name of the receiver type 28 | Raw string `json:"raw" yaml:"raw"` // The raw code to be injected 29 | } 30 | 31 | // NewInstRawRule loads and validates an InstRawRule from YAML data. 32 | func NewInstRawRule(data []byte, name string) (*InstRawRule, error) { 33 | var r InstRawRule 34 | if err := yaml.Unmarshal(data, &r); err != nil { 35 | return nil, ex.Wrap(err) 36 | } 37 | if r.Name == "" { 38 | r.Name = name 39 | } 40 | if err := r.validate(); err != nil { 41 | return nil, ex.Wrapf(err, "invalid raw rule %q", name) 42 | } 43 | return &r, nil 44 | } 45 | 46 | func (r *InstRawRule) validate() error { 47 | if strings.TrimSpace(r.Raw) == "" { 48 | return ex.Newf("raw cannot be empty") 49 | } 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/inst/context.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package inst 5 | 6 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 7 | type HookContext interface { 8 | // Set the skip call flag, can be used to skip the original function call 9 | SetSkipCall(bool) 10 | // Get the skip call flag, can be used to skip the original function call 11 | IsSkipCall() bool 12 | // Set the data field, can be used to pass information between Before and After hooks 13 | SetData(interface{}) 14 | // Get the data field, can be used to pass information between Before and After hooks 15 | GetData() interface{} 16 | // Get a value from the data field by key 17 | GetKeyData(key string) interface{} 18 | // Set a key-value pair in the data field 19 | SetKeyData(key string, val interface{}) 20 | // Check if a key exists in the data field 21 | HasKeyData(key string) bool 22 | // Number of original function parameters 23 | GetParamCount() int 24 | // Get the original function parameter at index idx 25 | GetParam(idx int) interface{} 26 | // Change the original function parameter at index idx 27 | SetParam(idx int, val interface{}) 28 | // Number of original function return values 29 | GetReturnValCount() int 30 | // Get the original function return value at index idx 31 | GetReturnVal(idx int) interface{} 32 | // Change the original function return value at index idx 33 | SetReturnVal(idx int, val interface{}) 34 | // Get the original function name 35 | GetFuncName() string 36 | // Get the package name of the original function 37 | GetPackageName() string 38 | } 39 | -------------------------------------------------------------------------------- /tool/internal/instrument/match.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package instrument 5 | 6 | import ( 7 | "encoding/json" 8 | "os" 9 | 10 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 11 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/rule" 12 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/util" 13 | ) 14 | 15 | // load loads the matched rules from the build temp directory. 16 | // TODO: Shared memory across all sub-processes is possible 17 | func (ip *InstrumentPhase) load() ([]*rule.InstRuleSet, error) { 18 | f := util.GetMatchedRuleFile() 19 | content, err := os.ReadFile(f) 20 | if err != nil { 21 | return nil, ex.Wrapf(err, "failed to read file %s", f) 22 | } 23 | rset := make([]*rule.InstRuleSet, 0) 24 | err = json.Unmarshal(content, &rset) 25 | if err != nil { 26 | return nil, ex.Wrapf(err, "failed to unmarshal JSON") 27 | } 28 | 29 | ip.Debug("Load matched rule sets", "path", f) 30 | return rset, nil 31 | } 32 | 33 | // match matches the rules with the compile command. 34 | func (ip *InstrumentPhase) match(allSet []*rule.InstRuleSet, args []string) *rule.InstRuleSet { 35 | // One package can only be matched with one rule set, so it's safe to return 36 | // the first matched rule set. 37 | importPath := util.FindFlagValue(args, "-p") 38 | util.Assert(importPath != "", "sanity check") 39 | for _, rset := range allSet { 40 | if rset.ModulePath == importPath { 41 | ip.Debug("Match rule set", "set", rset) 42 | return rset 43 | } 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /tool/internal/instrument/api.tmpl: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package inst 5 | 6 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 7 | type HookContext interface { 8 | // Set the skip call flag, can be used to skip the original function call 9 | SetSkipCall(bool) 10 | // Get the skip call flag, can be used to skip the original function call 11 | IsSkipCall() bool 12 | // Set the data field, can be used to pass information between Before and After hooks 13 | SetData(interface{}) 14 | // Get the data field, can be used to pass information between Before and After hooks 15 | GetData() interface{} 16 | // Get a value from the data field by key 17 | GetKeyData(key string) interface{} 18 | // Set a key-value pair in the data field 19 | SetKeyData(key string, val interface{}) 20 | // Check if a key exists in the data field 21 | HasKeyData(key string) bool 22 | // Number of original function parameters 23 | GetParamCount() int 24 | // Get the original function parameter at index idx 25 | GetParam(idx int) interface{} 26 | // Change the original function parameter at index idx 27 | SetParam(idx int, val interface{}) 28 | // Number of original function return values 29 | GetReturnValCount() int 30 | // Get the original function return value at index idx 31 | GetReturnVal(idx int) interface{} 32 | // Change the original function return value at index idx 33 | SetReturnVal(idx int, val interface{}) 34 | // Get the original function name 35 | GetFuncName() string 36 | // Get the package name of the original function 37 | GetPackageName() string 38 | } 39 | -------------------------------------------------------------------------------- /tool/cmd/cmd_version.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "runtime" 10 | 11 | "github.com/urfave/cli/v3" 12 | 13 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 14 | ) 15 | 16 | //nolint:gochecknoglobals // Implementation of a CLI command 17 | var commandVersion = cli.Command{ 18 | Name: "version", 19 | Description: "Print the version of the tool", 20 | Flags: []cli.Flag{ 21 | &cli.BoolFlag{ 22 | Name: "verbose", 23 | Usage: "Print additional information about the tool", 24 | }, 25 | }, 26 | Before: addLoggerPhaseAttribute, 27 | Action: func(_ context.Context, cmd *cli.Command) error { 28 | _, err := fmt.Fprintf(cmd.Writer, "otel version %s", Version) 29 | if err != nil { 30 | return ex.Wrapf(err, "failed to print version") 31 | } 32 | 33 | if CommitHash != "unknown" { 34 | _, err = fmt.Fprintf(cmd.Writer, "+%s", CommitHash) 35 | if err != nil { 36 | return ex.Wrapf(err, "failed to print commit hash") 37 | } 38 | } 39 | 40 | if BuildTime != "unknown" { 41 | _, err = fmt.Fprintf(cmd.Writer, " (%s)", BuildTime) 42 | if err != nil { 43 | return ex.Wrapf(err, "failed to print build time") 44 | } 45 | } 46 | 47 | _, err = fmt.Fprint(cmd.Writer, "\n") 48 | if err != nil { 49 | return ex.Wrapf(err, "failed to print newline") 50 | } 51 | 52 | if cmd.Bool("verbose") { 53 | _, err = fmt.Fprintf(cmd.Writer, "%s\n", runtime.Version()) 54 | if err != nil { 55 | return ex.Wrapf(err, "failed to print runtime version") 56 | } 57 | } 58 | 59 | return nil 60 | }, 61 | } 62 | -------------------------------------------------------------------------------- /demo/infrastructure/docker-compose/jaeger/config.yaml: -------------------------------------------------------------------------------- 1 | # Jaeger v2 configuration with Service Performance Monitoring (SPM) enabled 2 | # Based on: https://www.jaegertracing.io/docs/2.11/architecture/spm/ 3 | 4 | extensions: 5 | jaeger_storage: 6 | backends: 7 | memory: 8 | memory: 9 | max_traces: 100000 10 | # Configure metric backends for SPM 11 | metric_backends: 12 | prometheus_metrics: 13 | prometheus: 14 | endpoint: http://prometheus:9090 15 | # Normalize metric names to match OTel Collector spanmetrics output 16 | normalize_calls: true 17 | normalize_duration: true 18 | 19 | jaeger_query: 20 | storage: 21 | traces: memory 22 | metrics: prometheus_metrics 23 | ui: 24 | config_file: /etc/jaeger/ui-config.json 25 | 26 | healthcheckv2: 27 | http: 28 | endpoint: 0.0.0.0:13133 29 | 30 | receivers: 31 | otlp: 32 | protocols: 33 | grpc: 34 | endpoint: 0.0.0.0:4317 35 | http: 36 | endpoint: 0.0.0.0:4318 37 | 38 | processors: 39 | batch: 40 | timeout: 5s 41 | send_batch_size: 1024 42 | 43 | exporters: 44 | jaeger_storage_exporter: 45 | trace_storage: memory 46 | 47 | # Export traces to OTel Collector for spanmetrics generation 48 | otlp: 49 | endpoint: otel-collector:4317 50 | tls: 51 | insecure: true 52 | 53 | service: 54 | extensions: [jaeger_storage, jaeger_query, healthcheckv2] 55 | 56 | pipelines: 57 | traces: 58 | receivers: [otlp] 59 | processors: [batch] 60 | # Export to both local storage AND OTel Collector for spanmetrics 61 | exporters: [jaeger_storage_exporter, otlp] 62 | -------------------------------------------------------------------------------- /.github/tools/conventionalcommit/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type ( 13 | ConventionalCommitError interface { 14 | error 15 | ReviewComment() string 16 | } 17 | 18 | InvalidFormatError struct{} 19 | InvalidCommitType struct{ Type string } 20 | ) 21 | 22 | func (InvalidFormatError) Error() string { 23 | return "title does not match expected conventional commit format" 24 | } 25 | 26 | func (InvalidFormatError) ReviewComment() string { 27 | return "The title of this pull request does not match the [conventional commits][cc-spec] format.\n" + 28 | "Please update the title, as apprioriate.\n\n" + 29 | "Refer to the [CONTRIBUTING.md][contributing] file for more information.\n\n" + 30 | "[cc-spec]: https://www.conventionalcommits.org/en/v1.0.0/\n" + 31 | "[contributing]: https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#conventional-commits\n" 32 | } 33 | 34 | func (e InvalidCommitType) Error() string { 35 | return fmt.Sprintf("unsupported conventional commit type: %q", e.Type) 36 | } 37 | 38 | func (e InvalidCommitType) ReviewComment() string { 39 | var msg strings.Builder 40 | msg.WriteString("The title of this pull request uses an unsupported conventional commit type: ") 41 | msg.WriteString(strconv.Quote(e.Type)) 42 | msg.WriteString(".\n") 43 | 44 | msg.WriteString("Please update the title to use one of the supported types:\n") 45 | for ctype := range conventionalLabels { 46 | msg.WriteString("- `") 47 | msg.WriteString(ctype) 48 | msg.WriteString("`\n") 49 | } 50 | 51 | return msg.String() 52 | } 53 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/after-only/after_only.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Variable Template 4 | var ( 5 | OtelGetStackImpl func() []byte = nil 6 | OtelPrintStackImpl func([]byte) = nil 7 | ) 8 | 9 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 10 | type HookContext interface { 11 | // Set the skip call flag, can be used to skip the original function call 12 | SetSkipCall(bool) 13 | // Get the skip call flag, can be used to skip the original function call 14 | IsSkipCall() bool 15 | // Set the data field, can be used to pass information between Before and After hooks 16 | SetData(interface{}) 17 | // Get the data field, can be used to pass information between Before and After hooks 18 | GetData() interface{} 19 | // Get a value from the data field by key 20 | GetKeyData(key string) interface{} 21 | // Set a key-value pair in the data field 22 | SetKeyData(key string, val interface{}) 23 | // Check if a key exists in the data field 24 | HasKeyData(key string) bool 25 | // Number of original function parameters 26 | GetParamCount() int 27 | // Get the original function parameter at index idx 28 | GetParam(idx int) interface{} 29 | // Change the original function parameter at index idx 30 | SetParam(idx int, val interface{}) 31 | // Number of original function return values 32 | GetReturnValCount() int 33 | // Get the original function return value at index idx 34 | GetReturnVal(idx int) interface{} 35 | // Change the original function return value at index idx 36 | SetReturnVal(idx int, val interface{}) 37 | // Get the original function name 38 | GetFuncName() string 39 | // Get the package name of the original function 40 | GetPackageName() string 41 | } 42 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/before-only/before_only.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Variable Template 4 | var ( 5 | OtelGetStackImpl func() []byte = nil 6 | OtelPrintStackImpl func([]byte) = nil 7 | ) 8 | 9 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 10 | type HookContext interface { 11 | // Set the skip call flag, can be used to skip the original function call 12 | SetSkipCall(bool) 13 | // Get the skip call flag, can be used to skip the original function call 14 | IsSkipCall() bool 15 | // Set the data field, can be used to pass information between Before and After hooks 16 | SetData(interface{}) 17 | // Get the data field, can be used to pass information between Before and After hooks 18 | GetData() interface{} 19 | // Get a value from the data field by key 20 | GetKeyData(key string) interface{} 21 | // Set a key-value pair in the data field 22 | SetKeyData(key string, val interface{}) 23 | // Check if a key exists in the data field 24 | HasKeyData(key string) bool 25 | // Number of original function parameters 26 | GetParamCount() int 27 | // Get the original function parameter at index idx 28 | GetParam(idx int) interface{} 29 | // Change the original function parameter at index idx 30 | SetParam(idx int, val interface{}) 31 | // Number of original function return values 32 | GetReturnValCount() int 33 | // Get the original function return value at index idx 34 | GetReturnVal(idx int) interface{} 35 | // Change the original function return value at index idx 36 | SetReturnVal(idx int, val interface{}) 37 | // Get the original function name 38 | GetFuncName() string 39 | // Get the package name of the original function 40 | GetPackageName() string 41 | } 42 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/combined-rules/combined_rules.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Variable Template 4 | var ( 5 | OtelGetStackImpl func() []byte = nil 6 | OtelPrintStackImpl func([]byte) = nil 7 | ) 8 | 9 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 10 | type HookContext interface { 11 | // Set the skip call flag, can be used to skip the original function call 12 | SetSkipCall(bool) 13 | // Get the skip call flag, can be used to skip the original function call 14 | IsSkipCall() bool 15 | // Set the data field, can be used to pass information between Before and After hooks 16 | SetData(interface{}) 17 | // Get the data field, can be used to pass information between Before and After hooks 18 | GetData() interface{} 19 | // Get a value from the data field by key 20 | GetKeyData(key string) interface{} 21 | // Set a key-value pair in the data field 22 | SetKeyData(key string, val interface{}) 23 | // Check if a key exists in the data field 24 | HasKeyData(key string) bool 25 | // Number of original function parameters 26 | GetParamCount() int 27 | // Get the original function parameter at index idx 28 | GetParam(idx int) interface{} 29 | // Change the original function parameter at index idx 30 | SetParam(idx int, val interface{}) 31 | // Number of original function return values 32 | GetReturnValCount() int 33 | // Get the original function return value at index idx 34 | GetReturnVal(idx int) interface{} 35 | // Change the original function return value at index idx 36 | SetReturnVal(idx int, val interface{}) 37 | // Get the original function name 38 | GetFuncName() string 39 | // Get the package name of the original function 40 | GetPackageName() string 41 | } 42 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/ellipsis-syntax/ellipsis_syntax.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Variable Template 4 | var ( 5 | OtelGetStackImpl func() []byte = nil 6 | OtelPrintStackImpl func([]byte) = nil 7 | ) 8 | 9 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 10 | type HookContext interface { 11 | // Set the skip call flag, can be used to skip the original function call 12 | SetSkipCall(bool) 13 | // Get the skip call flag, can be used to skip the original function call 14 | IsSkipCall() bool 15 | // Set the data field, can be used to pass information between Before and After hooks 16 | SetData(interface{}) 17 | // Get the data field, can be used to pass information between Before and After hooks 18 | GetData() interface{} 19 | // Get a value from the data field by key 20 | GetKeyData(key string) interface{} 21 | // Set a key-value pair in the data field 22 | SetKeyData(key string, val interface{}) 23 | // Check if a key exists in the data field 24 | HasKeyData(key string) bool 25 | // Number of original function parameters 26 | GetParamCount() int 27 | // Get the original function parameter at index idx 28 | GetParam(idx int) interface{} 29 | // Change the original function parameter at index idx 30 | SetParam(idx int, val interface{}) 31 | // Number of original function return values 32 | GetReturnValCount() int 33 | // Get the original function return value at index idx 34 | GetReturnVal(idx int) interface{} 35 | // Change the original function return value at index idx 36 | SetReturnVal(idx int, val interface{}) 37 | // Get the original function name 38 | GetFuncName() string 39 | // Get the package name of the original function 40 | GetPackageName() string 41 | } 42 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/func-rule-only/func_rule_only.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Variable Template 4 | var ( 5 | OtelGetStackImpl func() []byte = nil 6 | OtelPrintStackImpl func([]byte) = nil 7 | ) 8 | 9 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 10 | type HookContext interface { 11 | // Set the skip call flag, can be used to skip the original function call 12 | SetSkipCall(bool) 13 | // Get the skip call flag, can be used to skip the original function call 14 | IsSkipCall() bool 15 | // Set the data field, can be used to pass information between Before and After hooks 16 | SetData(interface{}) 17 | // Get the data field, can be used to pass information between Before and After hooks 18 | GetData() interface{} 19 | // Get a value from the data field by key 20 | GetKeyData(key string) interface{} 21 | // Set a key-value pair in the data field 22 | SetKeyData(key string, val interface{}) 23 | // Check if a key exists in the data field 24 | HasKeyData(key string) bool 25 | // Number of original function parameters 26 | GetParamCount() int 27 | // Get the original function parameter at index idx 28 | GetParam(idx int) interface{} 29 | // Change the original function parameter at index idx 30 | SetParam(idx int, val interface{}) 31 | // Number of original function return values 32 | GetReturnValCount() int 33 | // Get the original function return value at index idx 34 | GetReturnVal(idx int) interface{} 35 | // Change the original function return value at index idx 36 | SetReturnVal(idx int, val interface{}) 37 | // Get the original function name 38 | GetFuncName() string 39 | // Get the package name of the original function 40 | GetPackageName() string 41 | } 42 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/method-receiver/method_receiver.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Variable Template 4 | var ( 5 | OtelGetStackImpl func() []byte = nil 6 | OtelPrintStackImpl func([]byte) = nil 7 | ) 8 | 9 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 10 | type HookContext interface { 11 | // Set the skip call flag, can be used to skip the original function call 12 | SetSkipCall(bool) 13 | // Get the skip call flag, can be used to skip the original function call 14 | IsSkipCall() bool 15 | // Set the data field, can be used to pass information between Before and After hooks 16 | SetData(interface{}) 17 | // Get the data field, can be used to pass information between Before and After hooks 18 | GetData() interface{} 19 | // Get a value from the data field by key 20 | GetKeyData(key string) interface{} 21 | // Set a key-value pair in the data field 22 | SetKeyData(key string, val interface{}) 23 | // Check if a key exists in the data field 24 | HasKeyData(key string) bool 25 | // Number of original function parameters 26 | GetParamCount() int 27 | // Get the original function parameter at index idx 28 | GetParam(idx int) interface{} 29 | // Change the original function parameter at index idx 30 | SetParam(idx int, val interface{}) 31 | // Number of original function return values 32 | GetReturnValCount() int 33 | // Get the original function return value at index idx 34 | GetReturnVal(idx int) interface{} 35 | // Change the original function return value at index idx 36 | SetReturnVal(idx int, val interface{}) 37 | // Get the original function name 38 | GetFuncName() string 39 | // Get the package name of the original function 40 | GetPackageName() string 41 | } 42 | -------------------------------------------------------------------------------- /tool/util/shared.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package util 5 | 6 | import ( 7 | "errors" 8 | "os" 9 | "path/filepath" 10 | ) 11 | 12 | const ( 13 | EnvOtelWorkDir = "OTEL_WORK_DIR" 14 | BuildTempDir = ".otel-build" 15 | OtelRoot = "github.com/open-telemetry/opentelemetry-go-compile-instrumentation" 16 | ) 17 | 18 | func GetMatchedRuleFile() string { 19 | const matchedRuleFile = "matched.json" 20 | return GetBuildTemp(matchedRuleFile) 21 | } 22 | 23 | func GetOtelWorkDir() string { 24 | wd := os.Getenv(EnvOtelWorkDir) 25 | if wd == "" { 26 | wd, _ = os.Getwd() 27 | return wd 28 | } 29 | return wd 30 | } 31 | 32 | // GetBuildTemp returns the path to the build temp directory $BUILD_TEMP/name 33 | func GetBuildTempDir() string { 34 | return filepath.Join(GetOtelWorkDir(), BuildTempDir) 35 | } 36 | 37 | // GetBuildTemp returns the path to the build temp directory $BUILD_TEMP/name 38 | func GetBuildTemp(name string) string { 39 | return filepath.Join(GetOtelWorkDir(), BuildTempDir, name) 40 | } 41 | 42 | func copyBackupFiles(names []string, src, dst string) error { 43 | var err error 44 | for _, name := range names { 45 | srcFile := filepath.Join(src, name) 46 | dstFile := filepath.Join(dst, name) 47 | err = errors.Join(err, CopyFile(srcFile, dstFile)) 48 | } 49 | return err 50 | } 51 | 52 | // BackupFile backups the source file to $BUILD_TEMP/backup/name. 53 | func BackupFile(names []string) error { 54 | return copyBackupFiles(names, ".", GetBuildTemp("backup")) 55 | } 56 | 57 | // RestoreFile restores the source file from $BUILD_TEMP/backup/name. 58 | func RestoreFile(names []string) error { 59 | return copyBackupFiles(names, GetBuildTemp("backup"), ".") 60 | } 61 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/func-and-raw-rules/func_and_raw_rules.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Variable Template 4 | var ( 5 | OtelGetStackImpl func() []byte = nil 6 | OtelPrintStackImpl func([]byte) = nil 7 | ) 8 | 9 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 10 | type HookContext interface { 11 | // Set the skip call flag, can be used to skip the original function call 12 | SetSkipCall(bool) 13 | // Get the skip call flag, can be used to skip the original function call 14 | IsSkipCall() bool 15 | // Set the data field, can be used to pass information between Before and After hooks 16 | SetData(interface{}) 17 | // Get the data field, can be used to pass information between Before and After hooks 18 | GetData() interface{} 19 | // Get a value from the data field by key 20 | GetKeyData(key string) interface{} 21 | // Set a key-value pair in the data field 22 | SetKeyData(key string, val interface{}) 23 | // Check if a key exists in the data field 24 | HasKeyData(key string) bool 25 | // Number of original function parameters 26 | GetParamCount() int 27 | // Get the original function parameter at index idx 28 | GetParam(idx int) interface{} 29 | // Change the original function parameter at index idx 30 | SetParam(idx int, val interface{}) 31 | // Number of original function return values 32 | GetReturnValCount() int 33 | // Get the original function return value at index idx 34 | GetReturnVal(idx int) interface{} 35 | // Change the original function return value at index idx 36 | SetReturnVal(idx int, val interface{}) 37 | // Get the original function name 38 | GetFuncName() string 39 | // Get the package name of the original function 40 | GetPackageName() string 41 | } 42 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/generic-functions/generic_functions.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Variable Template 4 | var ( 5 | OtelGetStackImpl func() []byte = nil 6 | OtelPrintStackImpl func([]byte) = nil 7 | ) 8 | 9 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 10 | type HookContext interface { 11 | // Set the skip call flag, can be used to skip the original function call 12 | SetSkipCall(bool) 13 | // Get the skip call flag, can be used to skip the original function call 14 | IsSkipCall() bool 15 | // Set the data field, can be used to pass information between Before and After hooks 16 | SetData(interface{}) 17 | // Get the data field, can be used to pass information between Before and After hooks 18 | GetData() interface{} 19 | // Get a value from the data field by key 20 | GetKeyData(key string) interface{} 21 | // Set a key-value pair in the data field 22 | SetKeyData(key string, val interface{}) 23 | // Check if a key exists in the data field 24 | HasKeyData(key string) bool 25 | // Number of original function parameters 26 | GetParamCount() int 27 | // Get the original function parameter at index idx 28 | GetParam(idx int) interface{} 29 | // Change the original function parameter at index idx 30 | SetParam(idx int, val interface{}) 31 | // Number of original function return values 32 | GetReturnValCount() int 33 | // Get the original function return value at index idx 34 | GetReturnVal(idx int) interface{} 35 | // Change the original function return value at index idx 36 | SetReturnVal(idx int, val interface{}) 37 | // Get the original function name 38 | GetFuncName() string 39 | // Get the package name of the original function 40 | GetPackageName() string 41 | } 42 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/opt-multiple-funcs/opt_multiple_funcs.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Variable Template 4 | var ( 5 | OtelGetStackImpl func() []byte = nil 6 | OtelPrintStackImpl func([]byte) = nil 7 | ) 8 | 9 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 10 | type HookContext interface { 11 | // Set the skip call flag, can be used to skip the original function call 12 | SetSkipCall(bool) 13 | // Get the skip call flag, can be used to skip the original function call 14 | IsSkipCall() bool 15 | // Set the data field, can be used to pass information between Before and After hooks 16 | SetData(interface{}) 17 | // Get the data field, can be used to pass information between Before and After hooks 18 | GetData() interface{} 19 | // Get a value from the data field by key 20 | GetKeyData(key string) interface{} 21 | // Set a key-value pair in the data field 22 | SetKeyData(key string, val interface{}) 23 | // Check if a key exists in the data field 24 | HasKeyData(key string) bool 25 | // Number of original function parameters 26 | GetParamCount() int 27 | // Get the original function parameter at index idx 28 | GetParam(idx int) interface{} 29 | // Change the original function parameter at index idx 30 | SetParam(idx int, val interface{}) 31 | // Number of original function return values 32 | GetReturnValCount() int 33 | // Get the original function return value at index idx 34 | GetReturnVal(idx int) interface{} 35 | // Change the original function return value at index idx 36 | SetReturnVal(idx int, val interface{}) 37 | // Get the original function name 38 | GetFuncName() string 39 | // Get the package name of the original function 40 | GetPackageName() string 41 | } 42 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/underscore-syntax/underscore_syntax.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Variable Template 4 | var ( 5 | OtelGetStackImpl func() []byte = nil 6 | OtelPrintStackImpl func([]byte) = nil 7 | ) 8 | 9 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 10 | type HookContext interface { 11 | // Set the skip call flag, can be used to skip the original function call 12 | SetSkipCall(bool) 13 | // Get the skip call flag, can be used to skip the original function call 14 | IsSkipCall() bool 15 | // Set the data field, can be used to pass information between Before and After hooks 16 | SetData(interface{}) 17 | // Get the data field, can be used to pass information between Before and After hooks 18 | GetData() interface{} 19 | // Get a value from the data field by key 20 | GetKeyData(key string) interface{} 21 | // Set a key-value pair in the data field 22 | SetKeyData(key string, val interface{}) 23 | // Check if a key exists in the data field 24 | HasKeyData(key string) bool 25 | // Number of original function parameters 26 | GetParamCount() int 27 | // Get the original function parameter at index idx 28 | GetParam(idx int) interface{} 29 | // Change the original function parameter at index idx 30 | SetParam(idx int, val interface{}) 31 | // Number of original function return values 32 | GetReturnValCount() int 33 | // Get the original function return value at index idx 34 | GetReturnVal(idx int) interface{} 35 | // Change the original function return value at index idx 36 | SetReturnVal(idx int, val interface{}) 37 | // Get the original function name 38 | GetFuncName() string 39 | // Get the package name of the original function 40 | GetPackageName() string 41 | } 42 | -------------------------------------------------------------------------------- /tool/internal/instrument/testdata/golden/multiple-func-rules/multiple_func_rules.otel.globals.go.golden: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Variable Template 4 | var ( 5 | OtelGetStackImpl func() []byte = nil 6 | OtelPrintStackImpl func([]byte) = nil 7 | ) 8 | 9 | // !!! pkg/inst/context.go will auto-sync to tool/internal/instrument/api.tmpl 10 | type HookContext interface { 11 | // Set the skip call flag, can be used to skip the original function call 12 | SetSkipCall(bool) 13 | // Get the skip call flag, can be used to skip the original function call 14 | IsSkipCall() bool 15 | // Set the data field, can be used to pass information between Before and After hooks 16 | SetData(interface{}) 17 | // Get the data field, can be used to pass information between Before and After hooks 18 | GetData() interface{} 19 | // Get a value from the data field by key 20 | GetKeyData(key string) interface{} 21 | // Set a key-value pair in the data field 22 | SetKeyData(key string, val interface{}) 23 | // Check if a key exists in the data field 24 | HasKeyData(key string) bool 25 | // Number of original function parameters 26 | GetParamCount() int 27 | // Get the original function parameter at index idx 28 | GetParam(idx int) interface{} 29 | // Change the original function parameter at index idx 30 | SetParam(idx int, val interface{}) 31 | // Number of original function return values 32 | GetReturnValCount() int 33 | // Get the original function return value at index idx 34 | GetReturnVal(idx int) interface{} 35 | // Change the original function return value at index idx 36 | SetReturnVal(idx int, val interface{}) 37 | // Get the original function name 38 | GetFuncName() string 39 | // Get the package name of the original function 40 | GetPackageName() string 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/ossf-scorecard.yml: -------------------------------------------------------------------------------- 1 | name: OSSF Scorecard 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | schedule: 8 | - cron: "13 5 * * 4" # once a week 9 | workflow_dispatch: 10 | 11 | permissions: read-all 12 | 13 | jobs: 14 | analysis: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | # Needed for Code scanning upload 18 | security-events: write 19 | # Needed for GitHub OIDC token if publish_results is true 20 | id-token: write 21 | steps: 22 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 23 | with: 24 | persist-credentials: false 25 | 26 | - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 27 | with: 28 | results_file: results.sarif 29 | results_format: sarif 30 | publish_results: true 31 | 32 | # Upload the results as artifacts (optional). Commenting out will disable 33 | # uploads of run results in SARIF format to the repository Actions tab. 34 | # https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts 35 | - name: "Upload artifact" 36 | uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 37 | with: 38 | name: SARIF file 39 | path: results.sarif 40 | retention-days: 5 41 | 42 | # Upload the results to GitHub's code scanning dashboard (optional). 43 | # Commenting out will disable upload of results to your repo's Code Scanning dashboard 44 | - name: "Upload to code-scanning" 45 | uses: github/codeql-action/upload-sarif@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7 46 | with: 47 | sarif_file: results.sarif 48 | -------------------------------------------------------------------------------- /pkg/instrumentation/grpc/semconv/semconv_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package semconv 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | "go.opentelemetry.io/otel/propagation" 13 | "google.golang.org/grpc/metadata" 14 | ) 15 | 16 | func TestMetadataSupplier(t *testing.T) { 17 | md := metadata.MD{} 18 | supplier := NewMetadataSupplier(&md) 19 | 20 | // Test Set and Get 21 | supplier.Set("key1", "value1") 22 | assert.Equal(t, "value1", supplier.Get("key1")) 23 | 24 | // Test Get non-existent key 25 | assert.Empty(t, supplier.Get("non-existent")) 26 | 27 | // Test Keys 28 | supplier.Set("key2", "value2") 29 | keys := supplier.Keys() 30 | assert.Len(t, keys, 2) 31 | assert.Contains(t, keys, "key1") 32 | assert.Contains(t, keys, "key2") 33 | } 34 | 35 | func TestInject(t *testing.T) { 36 | propagator := propagation.TraceContext{} 37 | ctx := context.Background() 38 | 39 | // Create a context with trace info 40 | // Note: In a real scenario, you'd have an active span 41 | ctx = Inject(ctx, propagator) 42 | 43 | // Verify metadata was added to context 44 | md, ok := metadata.FromOutgoingContext(ctx) 45 | require.True(t, ok) 46 | require.NotNil(t, md) 47 | } 48 | 49 | func TestExtract(t *testing.T) { 50 | propagator := propagation.TraceContext{} 51 | 52 | // Create incoming context with metadata 53 | md := metadata.MD{ 54 | "traceparent": []string{"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0aa902b7-01"}, 55 | } 56 | ctx := metadata.NewIncomingContext(context.Background(), md) 57 | 58 | // Extract should work 59 | extractedCtx := Extract(ctx, propagator) 60 | require.NotNil(t, extractedCtx) 61 | } 62 | -------------------------------------------------------------------------------- /tool/internal/rule/struct_rule.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package rule 5 | 6 | import ( 7 | "strings" 8 | 9 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 10 | "gopkg.in/yaml.v3" 11 | ) 12 | 13 | // InstStructRule represents a rule that guides hook struct field injection into 14 | // appropriate target struct locations. For example, if we want to inject 15 | // custom Foo field at the target struct Bar, we can define a rule: 16 | // 17 | // rule: 18 | // name: "rule" 19 | // target: "main" 20 | // struct: "Bar" 21 | // field_name: "Foo" 22 | // field_type: "int" 23 | type InstStructField struct { 24 | Name string `json:"name" yaml:"name"` // The name of the field to be added 25 | Type string `json:"type" yaml:"type"` // The type of the field to be added 26 | } 27 | 28 | type InstStructRule struct { 29 | InstBaseRule `yaml:",inline"` 30 | 31 | Struct string `json:"struct" yaml:"struct"` // The type name of the struct to be instrumented 32 | NewField []*InstStructField `json:"new_field" yaml:"new_field"` // The new fields to be added 33 | } 34 | 35 | // NewInstStructRule loads and validates an InstStructRule from YAML data. 36 | func NewInstStructRule(data []byte, name string) (*InstStructRule, error) { 37 | var r InstStructRule 38 | if err := yaml.Unmarshal(data, &r); err != nil { 39 | return nil, ex.Wrap(err) 40 | } 41 | if r.Name == "" { 42 | r.Name = name 43 | } 44 | if err := r.validate(); err != nil { 45 | return nil, ex.Wrapf(err, "invalid struct rule %q", name) 46 | } 47 | return &r, nil 48 | } 49 | 50 | func (r *InstStructRule) validate() error { 51 | if strings.TrimSpace(r.Struct) == "" { 52 | return ex.Newf("struct cannot be empty") 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/instrumentation/nethttp/server/response_writer.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package server 5 | 6 | import ( 7 | "bufio" 8 | "fmt" 9 | "net" 10 | "net/http" 11 | ) 12 | 13 | // writerWrapper wraps http.ResponseWriter to capture the status code 14 | type writerWrapper struct { 15 | http.ResponseWriter 16 | statusCode int 17 | wroteHeader bool 18 | } 19 | 20 | // WriteHeader captures the status code and forwards to the underlying ResponseWriter 21 | func (w *writerWrapper) WriteHeader(statusCode int) { 22 | // Prevent duplicate header writes 23 | if w.wroteHeader { 24 | return 25 | } 26 | w.statusCode = statusCode 27 | w.wroteHeader = true 28 | w.ResponseWriter.WriteHeader(statusCode) 29 | } 30 | 31 | // Write implements http.ResponseWriter.Write and ensures WriteHeader is called 32 | func (w *writerWrapper) Write(b []byte) (int, error) { 33 | // If WriteHeader wasn't called yet, call it with 200 OK (default HTTP behavior) 34 | if !w.wroteHeader { 35 | w.WriteHeader(http.StatusOK) 36 | } 37 | return w.ResponseWriter.Write(b) 38 | } 39 | 40 | // Hijack implements the http.Hijacker interface 41 | func (w *writerWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) { 42 | if h, ok := w.ResponseWriter.(http.Hijacker); ok { 43 | return h.Hijack() 44 | } 45 | return nil, nil, fmt.Errorf("responseWriter does not implement http.Hijacker") 46 | } 47 | 48 | // Flush implements the http.Flusher interface 49 | func (w *writerWrapper) Flush() { 50 | if f, ok := w.ResponseWriter.(http.Flusher); ok { 51 | f.Flush() 52 | } 53 | } 54 | 55 | // Pusher implements the http.Pusher interface 56 | func (w *writerWrapper) Pusher() http.Pusher { 57 | if pusher, ok := w.ResponseWriter.(http.Pusher); ok { 58 | return pusher 59 | } 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/instrumentation/grpc/semconv/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package semconv 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestSplitHostPort(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | hostport string 16 | expectedHost string 17 | expectedPort int 18 | }{ 19 | { 20 | name: "IPv4 with port", 21 | hostport: "192.168.1.1:8080", 22 | expectedHost: "192.168.1.1", 23 | expectedPort: 8080, 24 | }, 25 | { 26 | name: "IPv6 with port", 27 | hostport: "[::1]:8080", 28 | expectedHost: "::1", 29 | expectedPort: 8080, 30 | }, 31 | { 32 | name: "IPv6 full with port", 33 | hostport: "[2001:db8::1]:50051", 34 | expectedHost: "2001:db8::1", 35 | expectedPort: 50051, 36 | }, 37 | { 38 | name: "hostname with port", 39 | hostport: "localhost:50051", 40 | expectedHost: "localhost", 41 | expectedPort: 50051, 42 | }, 43 | { 44 | name: "hostname without port", 45 | hostport: "localhost", 46 | expectedHost: "localhost", 47 | expectedPort: -1, 48 | }, 49 | { 50 | name: "IPv6 without port", 51 | hostport: "[::1]", 52 | expectedHost: "::1", 53 | expectedPort: -1, 54 | }, 55 | { 56 | name: "invalid IPv6", 57 | hostport: "[::1", 58 | expectedHost: "", 59 | expectedPort: -1, 60 | }, 61 | { 62 | name: "empty", 63 | hostport: "", 64 | expectedHost: "", 65 | expectedPort: -1, 66 | }, 67 | } 68 | 69 | for _, tt := range tests { 70 | t.Run(tt.name, func(t *testing.T) { 71 | host, port := splitHostPort(tt.hostport) 72 | assert.Equal(t, tt.expectedHost, host) 73 | assert.Equal(t, tt.expectedPort, port) 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /demo/grpc/server/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "log/slog" 9 | "os" 10 | "testing" 11 | 12 | pb "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/demo/grpc/server/pb" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func init() { 18 | // Initialize logger for tests 19 | logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ 20 | Level: slog.LevelError, // Quiet during tests 21 | })) 22 | } 23 | 24 | func TestSayHello(t *testing.T) { 25 | s := &server{} 26 | ctx := context.Background() 27 | 28 | tests := []struct { 29 | name string 30 | request string 31 | expected string 32 | }{ 33 | { 34 | name: "simple greeting", 35 | request: "world", 36 | expected: "Hello world", 37 | }, 38 | { 39 | name: "named greeting", 40 | request: "Alice", 41 | expected: "Hello Alice", 42 | }, 43 | { 44 | name: "empty name", 45 | request: "", 46 | expected: "Hello ", 47 | }, 48 | { 49 | name: "special characters", 50 | request: "世界", 51 | expected: "Hello 世界", 52 | }, 53 | } 54 | 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | resp, err := s.SayHello(ctx, &pb.HelloRequest{Name: tt.request}) 58 | require.NoError(t, err) 59 | assert.Equal(t, tt.expected, resp.GetMessage()) 60 | }) 61 | } 62 | } 63 | 64 | func TestShutdown(t *testing.T) { 65 | s := &server{} 66 | ctx := context.Background() 67 | 68 | // Note: We can't test the os.Exit() behavior in a unit test, 69 | // but we can test that the RPC returns the correct response 70 | resp, err := s.Shutdown(ctx, &pb.ShutdownRequest{}) 71 | require.NoError(t, err) 72 | assert.Equal(t, "Server is shutting down", resp.GetMessage()) 73 | } 74 | -------------------------------------------------------------------------------- /pkg/instrumentation/grpc/semconv/semconv.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package semconv 5 | 6 | import ( 7 | "context" 8 | 9 | "go.opentelemetry.io/otel/propagation" 10 | "google.golang.org/grpc/metadata" 11 | ) 12 | 13 | // MetadataSupplier is a TextMapCarrier for gRPC metadata 14 | type MetadataSupplier struct { 15 | metadata *metadata.MD 16 | } 17 | 18 | // NewMetadataSupplier creates a new MetadataSupplier 19 | func NewMetadataSupplier(md *metadata.MD) MetadataSupplier { 20 | return MetadataSupplier{metadata: md} 21 | } 22 | 23 | // Get returns the value for a key from metadata 24 | func (s MetadataSupplier) Get(key string) string { 25 | values := s.metadata.Get(key) 26 | if len(values) == 0 { 27 | return "" 28 | } 29 | return values[0] 30 | } 31 | 32 | // Set sets a key-value pair in metadata 33 | func (s MetadataSupplier) Set(key, value string) { 34 | s.metadata.Set(key, value) 35 | } 36 | 37 | // Keys returns all keys in metadata 38 | func (s MetadataSupplier) Keys() []string { 39 | out := make([]string, 0, len(*s.metadata)) 40 | for key := range *s.metadata { 41 | out = append(out, key) 42 | } 43 | return out 44 | } 45 | 46 | // Inject injects the context into outgoing gRPC metadata 47 | func Inject(ctx context.Context, propagators propagation.TextMapPropagator) context.Context { 48 | md, ok := metadata.FromOutgoingContext(ctx) 49 | if !ok { 50 | md = metadata.MD{} 51 | } 52 | propagators.Inject(ctx, NewMetadataSupplier(&md)) 53 | return metadata.NewOutgoingContext(ctx, md) 54 | } 55 | 56 | // Extract extracts the context from incoming gRPC metadata 57 | func Extract(ctx context.Context, propagators propagation.TextMapPropagator) context.Context { 58 | md, ok := metadata.FromIncomingContext(ctx) 59 | if !ok { 60 | md = metadata.MD{} 61 | } 62 | return propagators.Extract(ctx, NewMetadataSupplier(&md)) 63 | } 64 | -------------------------------------------------------------------------------- /tool/internal/rule/func_rule.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package rule 5 | 6 | import ( 7 | "strings" 8 | 9 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 10 | "gopkg.in/yaml.v3" 11 | ) 12 | 13 | // InstFuncRule represents a rule that guides hook function injection into 14 | // appropriate target function locations. For example, if we want to inject 15 | // custom Foo function at the entry of target function Bar, we can define a rule: 16 | // 17 | // rule: 18 | // name: "newrule" 19 | // target: "main" 20 | // func: "Bar" 21 | // recv: "*RecvType" 22 | // before: "Foo" 23 | // path: "github.com/foo/bar/hook_rule" 24 | type InstFuncRule struct { 25 | InstBaseRule `yaml:",inline"` 26 | 27 | Func string `json:"func" yaml:"func"` // The name of the target func to be instrumented 28 | Recv string `json:"recv" yaml:"recv"` // The name of the receiver type 29 | Before string `json:"before" yaml:"before"` // The function we inject at the target function entry 30 | After string `json:"after" yaml:"after"` // The function we inject at the target function exit 31 | Path string `json:"path" yaml:"path"` // The module path where hook code is located 32 | } 33 | 34 | // NewInstFuncRule loads and validates an InstFuncRule from YAML data. 35 | func NewInstFuncRule(data []byte, name string) (*InstFuncRule, error) { 36 | var r InstFuncRule 37 | if err := yaml.Unmarshal(data, &r); err != nil { 38 | return nil, ex.Wrap(err) 39 | } 40 | if r.Name == "" { 41 | r.Name = name 42 | } 43 | if err := r.validate(); err != nil { 44 | return nil, ex.Wrapf(err, "invalid func rule %q", name) 45 | } 46 | return &r, nil 47 | } 48 | 49 | func (r *InstFuncRule) validate() error { 50 | if strings.TrimSpace(r.Func) == "" { 51 | return ex.Newf("func cannot be empty") 52 | } 53 | if r.Before == "" && r.After == "" { 54 | return ex.Newf("before or after must be set") 55 | } 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /demo/http/client/Dockerfile: -------------------------------------------------------------------------------- 1 | # Multi-stage Dockerfile for HTTP client with OpenTelemetry compile-time instrumentation 2 | # Stage 1: Build the otel tool 3 | FROM golang:1.25-alpine@sha256:26111811bc967321e7b6f852e914d14bede324cd1accb7f81811929a6a57fea9 AS otel-builder 4 | 5 | WORKDIR /build 6 | 7 | # Install build dependencies 8 | RUN apk add --no-cache git make gcc musl-dev 9 | 10 | # Copy the entire project to build the otel tool 11 | COPY go.mod go.sum ./ 12 | COPY . . 13 | 14 | # Build the otel tool 15 | RUN go build -o /otel ./tool/cmd 16 | 17 | # Stage 2: Build the HTTP client with instrumentation 18 | FROM golang:1.25-alpine@sha256:26111811bc967321e7b6f852e914d14bede324cd1accb7f81811929a6a57fea9 AS app-builder 19 | 20 | WORKDIR /app 21 | 22 | # Install git for go mod download 23 | RUN apk add --no-cache git 24 | 25 | # Copy the otel tool from previous stage 26 | COPY --from=otel-builder /otel /usr/local/bin/otel 27 | 28 | # Copy HTTP client source 29 | COPY demo/http/client /app 30 | 31 | # Download dependencies 32 | RUN go mod download 33 | 34 | # Build the app with otel instrumentation 35 | RUN otel go build -o client . 36 | 37 | # Stage 3: Runtime image 38 | FROM alpine:3.23@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 39 | 40 | # Install ca-certificates for TLS and timezone data 41 | RUN apk --no-cache add ca-certificates tzdata 42 | 43 | WORKDIR /app 44 | 45 | # Copy the instrumented binary 46 | COPY --from=app-builder /app/client . 47 | 48 | # Create non-root user 49 | RUN addgroup -g 1000 appuser && \ 50 | adduser -D -u 1000 -G appuser appuser && \ 51 | chown -R appuser:appuser /app 52 | 53 | USER appuser 54 | 55 | # Environment variables for OpenTelemetry 56 | ENV OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 \ 57 | OTEL_EXPORTER_OTLP_PROTOCOL=grpc \ 58 | OTEL_SERVICE_NAME=http-client \ 59 | OTEL_RESOURCE_ATTRIBUTES="service.namespace=demo,service.version=1.0.0" \ 60 | OTEL_LOG_LEVEL=info 61 | 62 | ENTRYPOINT ["./client"] 63 | CMD ["-addr", "http://http-server:8080"] 64 | -------------------------------------------------------------------------------- /pkg/instrumentation/helloworld/helloworld_instrumenter.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package helloworld 5 | 6 | import ( 7 | "context" 8 | 9 | "go.opentelemetry.io/otel" 10 | "go.opentelemetry.io/otel/attribute" 11 | "go.opentelemetry.io/otel/trace" 12 | ) 13 | 14 | const ( 15 | instrumentationName = "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld" 16 | instrumentationVersion = "0.1.0" 17 | ) 18 | 19 | // HelloWorldRequest represents a simple request for demonstration purposes 20 | type HelloWorldRequest struct { 21 | Path string 22 | Params map[string]string 23 | } 24 | 25 | // HelloWorldResponse represents a simple response for demonstration purposes 26 | type HelloWorldResponse struct { 27 | Status int 28 | } 29 | 30 | var tracer trace.Tracer 31 | 32 | func init() { 33 | tracer = otel.GetTracerProvider().Tracer( 34 | instrumentationName, 35 | trace.WithInstrumentationVersion(instrumentationVersion), 36 | ) 37 | } 38 | 39 | // StartInstrumentation starts a span for the hello world operation 40 | func StartInstrumentation(ctx context.Context, req HelloWorldRequest) (context.Context, trace.Span) { 41 | // Create attributes from request 42 | attrs := []attribute.KeyValue{ 43 | attribute.String("hello.path", req.Path), 44 | } 45 | 46 | // Add params as attributes if present 47 | for key, value := range req.Params { 48 | attrs = append(attrs, attribute.String("hello.param."+key, value)) 49 | } 50 | 51 | // Start span with attributes 52 | ctx, span := tracer.Start(ctx, 53 | "hello-world", 54 | trace.WithSpanKind(trace.SpanKindInternal), 55 | trace.WithAttributes(attrs...), 56 | ) 57 | 58 | return ctx, span 59 | } 60 | 61 | // EndInstrumentation ends the span and records response attributes 62 | func EndInstrumentation(span trace.Span, resp HelloWorldResponse) { 63 | // Add response attributes 64 | span.SetAttributes( 65 | attribute.Int("hello.status", resp.Status), 66 | ) 67 | 68 | span.End() 69 | } 70 | -------------------------------------------------------------------------------- /demo/http/server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Multi-stage Dockerfile for HTTP server with OpenTelemetry compile-time instrumentation 2 | # Stage 1: Build the otel tool 3 | FROM golang:1.25-alpine@sha256:26111811bc967321e7b6f852e914d14bede324cd1accb7f81811929a6a57fea9 AS otel-builder 4 | 5 | WORKDIR /build 6 | 7 | # Install build dependencies 8 | RUN apk add --no-cache git make gcc musl-dev 9 | 10 | # Copy the entire project to build the otel tool 11 | COPY go.mod go.sum ./ 12 | COPY . . 13 | 14 | # Build the otel tool 15 | RUN go build -o /otel ./tool/cmd 16 | 17 | # Stage 2: Build the HTTP server with instrumentation 18 | FROM golang:1.25-alpine@sha256:26111811bc967321e7b6f852e914d14bede324cd1accb7f81811929a6a57fea9 AS app-builder 19 | 20 | WORKDIR /app 21 | 22 | # Install git for go mod download 23 | RUN apk add --no-cache git 24 | 25 | # Copy the otel tool from previous stage 26 | COPY --from=otel-builder /otel /usr/local/bin/otel 27 | 28 | # Copy HTTP server source 29 | COPY demo/http/server /app 30 | 31 | # Download dependencies 32 | RUN go mod download 33 | 34 | # Build the app with otel instrumentation 35 | RUN otel go build -o server . 36 | 37 | # Stage 3: Runtime image 38 | FROM alpine:3.23@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 39 | 40 | # Install ca-certificates for TLS and timezone data 41 | RUN apk --no-cache add ca-certificates tzdata 42 | 43 | WORKDIR /app 44 | 45 | # Copy the instrumented binary 46 | COPY --from=app-builder /app/server . 47 | 48 | # Create non-root user 49 | RUN addgroup -g 1000 appuser && \ 50 | adduser -D -u 1000 -G appuser appuser && \ 51 | chown -R appuser:appuser /app 52 | 53 | USER appuser 54 | 55 | # Expose HTTP port 56 | EXPOSE 8080 57 | 58 | # Environment variables for OpenTelemetry 59 | ENV OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 \ 60 | OTEL_EXPORTER_OTLP_PROTOCOL=grpc \ 61 | OTEL_SERVICE_NAME=http-server \ 62 | OTEL_RESOURCE_ATTRIBUTES="service.namespace=demo,service.version=1.0.0" \ 63 | OTEL_LOG_LEVEL=info 64 | 65 | ENTRYPOINT ["./server"] 66 | CMD ["-port", "8080"] 67 | -------------------------------------------------------------------------------- /.github/workflows/check-license-headers.yaml: -------------------------------------------------------------------------------- 1 | name: Check License Headers 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | # This is a required workflow, so it must always run and report a status. 14 | # We use dorny/paths-filter at the job level instead of workflow-level path filters 15 | # to ensure the workflow runs and the "done" job reports success even when no relevant files change. 16 | # This prevents PRs from being blocked by missing status checks. 17 | changes: 18 | name: Detect Changes 19 | runs-on: ubuntu-latest 20 | permissions: 21 | pull-requests: read 22 | outputs: 23 | code: ${{ steps.filter.outputs.code }} 24 | steps: 25 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 26 | - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 27 | id: filter 28 | with: 29 | filters: | 30 | code: 31 | - "**/*.go" 32 | - "**/*.sh" 33 | - "Makefile" 34 | - ".github/workflows/check-license-headers.yaml" 35 | 36 | check-license-headers: 37 | name: Check License Headers 38 | runs-on: ubuntu-latest 39 | needs: [changes] 40 | if: needs.changes.outputs.code == 'true' 41 | steps: 42 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 43 | - run: make lint/license-header 44 | 45 | done: 46 | name: Done (Check License Headers) 47 | runs-on: ubuntu-latest 48 | needs: [changes, check-license-headers] 49 | if: always() 50 | steps: 51 | - name: Check result 52 | run: | 53 | result="${{ needs.check-license-headers.result }}" 54 | echo "Test result: $result" 55 | if [[ "$result" == "success" || "$result" == "skipped" ]]; then 56 | echo "✅ Tests passed or were skipped" 57 | exit 0 58 | else 59 | echo "❌ Tests failed" 60 | exit 1 61 | fi 62 | -------------------------------------------------------------------------------- /demo/grpc/client/Dockerfile: -------------------------------------------------------------------------------- 1 | # Multi-stage Dockerfile for gRPC client with OpenTelemetry compile-time instrumentation 2 | # Stage 1: Build the otel tool 3 | FROM golang:1.25-alpine@sha256:26111811bc967321e7b6f852e914d14bede324cd1accb7f81811929a6a57fea9 AS otel-builder 4 | 5 | WORKDIR /build 6 | 7 | # Install build dependencies 8 | RUN apk add --no-cache git make gcc musl-dev 9 | 10 | # Copy the entire project to build the otel tool 11 | COPY go.mod go.sum ./ 12 | COPY . . 13 | 14 | # Build the otel tool 15 | RUN go build -o /otel ./tool/cmd 16 | 17 | # Stage 2: Build the gRPC client with instrumentation 18 | FROM golang:1.25-alpine@sha256:26111811bc967321e7b6f852e914d14bede324cd1accb7f81811929a6a57fea9 AS app-builder 19 | 20 | WORKDIR /app 21 | 22 | # Install git for go mod download 23 | RUN apk add --no-cache git 24 | 25 | # Copy the otel tool from previous stage 26 | COPY --from=otel-builder /otel /usr/local/bin/otel 27 | 28 | # Copy both client and server to maintain the go.mod replace directive structure 29 | COPY demo/grpc/server /server 30 | COPY demo/grpc/client /app 31 | 32 | # Download dependencies 33 | RUN go mod download 34 | 35 | # Build the app with otel instrumentation 36 | RUN otel go build -o client . 37 | 38 | # Stage 3: Runtime image 39 | FROM alpine:3.23@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 40 | 41 | # Install ca-certificates for TLS and timezone data 42 | RUN apk --no-cache add ca-certificates tzdata 43 | 44 | WORKDIR /app 45 | 46 | # Copy the instrumented binary 47 | COPY --from=app-builder /app/client . 48 | 49 | # Create non-root user 50 | RUN addgroup -g 1000 appuser && \ 51 | adduser -D -u 1000 -G appuser appuser && \ 52 | chown -R appuser:appuser /app 53 | 54 | USER appuser 55 | 56 | # Environment variables for OpenTelemetry 57 | ENV OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 \ 58 | OTEL_EXPORTER_OTLP_PROTOCOL=grpc \ 59 | OTEL_SERVICE_NAME=grpc-client \ 60 | OTEL_RESOURCE_ATTRIBUTES="service.namespace=demo,service.version=1.0.0" \ 61 | OTEL_LOG_LEVEL=info 62 | 63 | ENTRYPOINT ["./client"] 64 | -------------------------------------------------------------------------------- /demo/infrastructure/docker-compose/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | # Prometheus configuration for demo environment 2 | # Scrapes metrics from OpenTelemetry Collector 3 | # Supports native OTLP ingestion (Prometheus v3.0+) 4 | 5 | global: 6 | scrape_interval: 15s 7 | evaluation_interval: 15s 8 | external_labels: 9 | environment: demo 10 | cluster: local 11 | 12 | # Tracing configuration - send Prometheus spans to OTel Collector 13 | tracing: 14 | endpoint: otel-collector:4317 15 | insecure: true 16 | tls_config: 17 | insecure_skip_verify: true 18 | # Enable sampling of queries to generate traces 19 | sampling_fraction: 1.0 # Sample 100% of queries for demo purposes 20 | 21 | # OTLP configuration for native OTLP receiver (Prometheus v3.0+) 22 | otlp: 23 | # Promote resource attributes to labels 24 | promote_resource_attributes: 25 | - service.name 26 | - service.namespace 27 | - service.instance.id 28 | # Use NoTranslation to preserve original metric names (requires UTF-8) 29 | translation_strategy: NoTranslation 30 | 31 | # Scrape configurations 32 | scrape_configs: 33 | # Scrape Prometheus itself 34 | - job_name: prometheus 35 | static_configs: 36 | - targets: ['localhost:9090'] 37 | labels: 38 | service: prometheus 39 | 40 | # Scrape OpenTelemetry Collector internal metrics 41 | - job_name: otel-collector 42 | scrape_interval: 10s 43 | static_configs: 44 | - targets: ['otel-collector:8888'] 45 | labels: 46 | service: otel-collector 47 | component: collector 48 | 49 | # Scrape OpenTelemetry Collector Prometheus exporter 50 | # This includes: application metrics, spanmetrics, AND Jaeger metrics 51 | # (OTel Collector scrapes Jaeger and re-exports via this endpoint) 52 | - job_name: otel-metrics 53 | scrape_interval: 15s 54 | static_configs: 55 | - targets: ['otel-collector:8889'] 56 | labels: 57 | component: applications 58 | 59 | # Storage configuration 60 | storage: 61 | tsdb: 62 | # Out-of-order ingestion window for distributed collectors 63 | out_of_order_time_window: 30m 64 | -------------------------------------------------------------------------------- /tool/internal/instrument/apply_raw.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package instrument 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/dave/dst" 10 | 11 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 12 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/ast" 13 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/rule" 14 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/util" 15 | ) 16 | 17 | const ( 18 | unnamedRetValName = "_unnamedRetVal" 19 | ignoredParam = "_ignoredParam" 20 | ) 21 | 22 | func renameReturnValues(funcDecl *dst.FuncDecl) { 23 | if retList := funcDecl.Type.Results; retList != nil { 24 | idx := 0 25 | for _, field := range retList.List { 26 | if field.Names == nil { 27 | name := fmt.Sprintf("%s%d", unnamedRetValName, idx) 28 | field.Names = []*dst.Ident{ast.Ident(name)} 29 | idx++ 30 | } 31 | } 32 | } 33 | } 34 | 35 | func insertRaw(r *rule.InstRawRule, decl *dst.FuncDecl) error { 36 | util.Assert(decl.Name.Name == r.Func, "sanity check") 37 | 38 | // Rename the unnamed return values so that the raw code can reference them 39 | renameReturnValues(decl) 40 | // Parse the raw code into AST statements 41 | p := ast.NewAstParser() 42 | stmts, err := p.ParseSnippet(r.Raw) 43 | if err != nil { 44 | return err 45 | } 46 | // Insert the raw code into target function body 47 | decl.Body.List = append(stmts, decl.Body.List...) 48 | return nil 49 | } 50 | 51 | // applyRawRule injects the raw code into the target function at the beginning 52 | // of the function. 53 | func (ip *InstrumentPhase) applyRawRule(rule *rule.InstRawRule, root *dst.File) error { 54 | // Find the target function to be instrumented 55 | funcDecl := ast.FindFuncDecl(root, rule.Func, rule.Recv) 56 | if funcDecl == nil { 57 | return ex.Newf("can not find function %s", rule.Func) 58 | } 59 | // Insert the raw code into the target function 60 | err := insertRaw(rule, funcDecl) 61 | if err != nil { 62 | return err 63 | } 64 | ip.Info("Apply raw rule", "rule", rule) 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/lint-golang.yaml: -------------------------------------------------------------------------------- 1 | name: Check Go Code 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | env: 11 | # TODO: Configure dependabot or renovate to update this version. 12 | GOLANGCI_LINT_VERSION: v2.1.6 13 | 14 | jobs: 15 | # This is a required workflow, so it must always run and report a status. 16 | # We use dorny/paths-filter at the job level instead of workflow-level path filters 17 | # to ensure the workflow runs and the "done" job reports success even when no relevant files change. 18 | # This prevents PRs from being blocked by missing status checks. 19 | changes: 20 | name: Detect Changes 21 | runs-on: ubuntu-latest 22 | permissions: 23 | pull-requests: read 24 | outputs: 25 | code: ${{ steps.filter.outputs.code }} 26 | steps: 27 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 28 | - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 29 | id: filter 30 | with: 31 | filters: | 32 | code: 33 | - '**/*.go' 34 | - '.config/golangci.yml' 35 | - '.github/workflows/lint-golang.yaml' 36 | 37 | golangci-lint: 38 | name: Lint Go Code 39 | runs-on: ubuntu-latest 40 | needs: [changes] 41 | if: needs.changes.outputs.code == 'true' 42 | steps: 43 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 44 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 45 | with: 46 | go-version-file: go.mod 47 | - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 48 | with: 49 | version: ${{ env.GOLANGCI_LINT_VERSION }} 50 | args: --config .config/golangci.yml 51 | 52 | done: 53 | name: Done (Check Go Code) 54 | runs-on: ubuntu-latest 55 | needs: [changes, golangci-lint] 56 | if: always() 57 | steps: 58 | - name: Check result 59 | run: | 60 | result="${{ needs.golangci-lint.result }}" 61 | echo "Test result: $result" 62 | if [[ "$result" == "success" || "$result" == "skipped" ]]; then 63 | echo "✅ Tests passed or were skipped" 64 | exit 0 65 | else 66 | echo "❌ Tests failed" 67 | exit 1 68 | fi 69 | -------------------------------------------------------------------------------- /tool/internal/instrument/apply_file.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package instrument 5 | 6 | import ( 7 | "fmt" 8 | "path/filepath" 9 | "slices" 10 | "strings" 11 | 12 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 13 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/ast" 14 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/internal/rule" 15 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/util" 16 | ) 17 | 18 | func listRuleFiles(path string) ([]string, error) { 19 | // If the path is a local path, use it directly, otherwise, it's a module 20 | // path, we need to convert it to a local path and find the module files 21 | var p string 22 | if util.PathExists(path) { 23 | p = path 24 | } else { 25 | p = strings.TrimPrefix(path, util.OtelRoot) 26 | p = filepath.Join(util.GetBuildTempDir(), p) 27 | } 28 | return util.ListFiles(p) 29 | } 30 | 31 | // applyFileRule introduces the new file to the target package at compile time. 32 | func (ip *InstrumentPhase) applyFileRule(rule *rule.InstFileRule, pkgName string) error { 33 | // List all files in the rule module path 34 | files, err := listRuleFiles(rule.Path) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | // Find the new file we want to introduce 40 | index := slices.IndexFunc(files, func(file string) bool { 41 | return strings.HasSuffix(file, rule.File) 42 | }) 43 | if index == -1 { 44 | return ex.Newf("file %s not found", rule.File) 45 | } 46 | file := files[index] 47 | 48 | // Parse the new file into AST nodes and modify it as needed 49 | root, err := ip.parseFile(file) 50 | if err != nil { 51 | return err 52 | } 53 | // Always rename the package name to the target package name 54 | root.Name.Name = pkgName 55 | 56 | // Write back the modified AST to a new file in the working directory 57 | base := filepath.Base(rule.File) 58 | ext := filepath.Ext(base) 59 | newName := strings.TrimSuffix(base, ext) 60 | newFile := filepath.Join(ip.workDir, fmt.Sprintf("otel.%s.go", newName)) 61 | err = ast.WriteFile(newFile, root) 62 | if err != nil { 63 | return err 64 | } 65 | ip.Info("Apply file rule", "rule", rule) 66 | 67 | // Add the new file as part of the source files to be compiled 68 | ip.addCompileArg(newFile) 69 | ip.keepForDebug(newFile) 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /test/integration/http_client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build integration 5 | 6 | package test 7 | 8 | import ( 9 | "path/filepath" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | 14 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/test/app" 15 | ) 16 | 17 | func TestHTTPClientIntegration(t *testing.T) { 18 | clientDir := filepath.Join("..", "..", "demo", "http", "client") 19 | serverDir := filepath.Join("..", "..", "demo", "http", "server") 20 | 21 | // Enable debug logging 22 | t.Setenv("OTEL_LOG_LEVEL", "debug") 23 | 24 | // Build both client and server with instrumentation 25 | t.Log("Building instrumented HTTP client and server...") 26 | app.Build(t, clientDir, "go", "build", "-a") 27 | app.Build(t, serverDir, "go", "build", "-a") 28 | 29 | // Start the server 30 | t.Log("Starting HTTP server...") 31 | serverCmd, outputPipe := app.Start(t, serverDir, "-port=8083", "-no-faults", "-no-latency") 32 | waitUntilDone, err := app.WaitForServerReady(t, serverCmd, outputPipe) 33 | require.NoError(t, err, "server should start successfully") 34 | 35 | // Run the client (makes request and exits) 36 | t.Log("Running HTTP client...") 37 | clientOutput := app.Run(t, clientDir, "-addr=http://localhost:8083", "-count=1") 38 | 39 | // Shutdown the server 40 | t.Log("Shutting down server...") 41 | app.Run(t, clientDir, "-addr=http://localhost:8083", "-shutdown") 42 | 43 | // Wait for server to exit and get output 44 | serverOutput := waitUntilDone() 45 | 46 | // Verify client instrumentation 47 | t.Log("Verifying client instrumentation...") 48 | require.Contains(t, clientOutput, 49 | "HTTP client instrumentation initialized", "client should initialize instrumentation") 50 | require.Contains(t, clientOutput, "BeforeRoundTrip called", "client before hook should be called") 51 | require.Contains(t, clientOutput, "AfterRoundTrip called", "client after hook should be called") 52 | 53 | // Verify server instrumentation 54 | t.Log("Verifying server instrumentation...") 55 | require.Contains(t, serverOutput, 56 | "HTTP server instrumentation initialized", "server should initialize instrumentation") 57 | require.Contains(t, serverOutput, "BeforeServeHTTP called", "server before hook should be called") 58 | require.Contains(t, serverOutput, "AfterServeHTTP called", "server after hook should be called") 59 | 60 | t.Log("HTTP client integration test passed!") 61 | } 62 | -------------------------------------------------------------------------------- /test/e2e/http_test.go: -------------------------------------------------------------------------------- 1 | //go:build e2e 2 | 3 | // Copyright The OpenTelemetry Authors 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package test 7 | 8 | import ( 9 | "bufio" 10 | "io" 11 | "os/exec" 12 | "path/filepath" 13 | "strings" 14 | "testing" 15 | "time" 16 | 17 | "github.com/stretchr/testify/require" 18 | 19 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/test/app" 20 | ) 21 | 22 | func waitUntilReady(t *testing.T, serverApp *exec.Cmd, outputPipe io.ReadCloser) func() string { 23 | t.Helper() 24 | 25 | readyChan := make(chan struct{}) 26 | doneChan := make(chan struct{}) 27 | output := strings.Builder{} 28 | const readyMsg = "server started" 29 | go func() { 30 | // Scan will return false when the application exits. 31 | defer close(doneChan) 32 | scanner := bufio.NewScanner(outputPipe) 33 | for scanner.Scan() { 34 | line := scanner.Text() 35 | output.WriteString(line + "\n") 36 | if strings.Contains(line, readyMsg) { 37 | close(readyChan) 38 | } 39 | } 40 | }() 41 | 42 | select { 43 | case <-readyChan: 44 | t.Logf("Server is ready!") 45 | case <-time.After(10 * time.Second): 46 | t.Fatal("timeout waiting for server to be ready") 47 | } 48 | 49 | return func() string { 50 | // Wait for the server to exit 51 | serverApp.Wait() 52 | // Wait for the output goroutine to finish 53 | <-doneChan 54 | // Return the complete output 55 | return output.String() 56 | } 57 | } 58 | 59 | func TestHttp(t *testing.T) { 60 | serverDir := filepath.Join("..", "..", "demo", "http", "server") 61 | clientDir := filepath.Join("..", "..", "demo", "http", "client") 62 | 63 | // Build the server and client applications with the instrumentation tool. 64 | app.Build(t, serverDir, "go", "build", "-a") 65 | app.Build(t, clientDir, "go", "build", "-a") 66 | 67 | // Start the server and wait for it to be ready. 68 | serverApp, outputPipe := app.Start(t, serverDir) 69 | waitUntilDone := waitUntilReady(t, serverApp, outputPipe) 70 | 71 | // Run the client, it will send a shutdown request to the server. 72 | app.Run(t, clientDir, "-shutdown") 73 | 74 | // Wait for the server to exit and return the output. 75 | output := waitUntilDone() 76 | 77 | // Verify that server instrumentation was initialized 78 | // Note: Client instrumentation is lazily initialized on first use, 79 | // so it won't appear in the server output unless the server makes HTTP client requests 80 | require.Contains(t, output, "HTTP server instrumentation initialized") 81 | 82 | // Note: Full trace validation would require parsing OTel spans 83 | // For now, we verify that instrumentation hooks are in place and executing 84 | } 85 | -------------------------------------------------------------------------------- /tool/cmd/main.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "log/slog" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/urfave/cli/v3" 13 | 14 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 15 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/util" 16 | ) 17 | 18 | const ( 19 | debugLogFilename = "debug.log" 20 | ) 21 | 22 | func main() { 23 | app := cli.Command{ 24 | Name: "otel", 25 | Usage: "OpenTelemetry Go Compile-Time Instrumentation Tool", 26 | HideVersion: true, 27 | Flags: []cli.Flag{ 28 | &cli.StringFlag{ 29 | Name: "work-dir", 30 | Aliases: []string{"w"}, 31 | Usage: "The path to a directory where working files will be written", 32 | TakesFile: true, 33 | Value: util.GetBuildTempDir(), 34 | }, 35 | &cli.BoolFlag{ 36 | Name: "-debug", 37 | Aliases: []string{"d"}, 38 | Usage: "Enable debug mode", 39 | Value: false, 40 | }, 41 | }, 42 | Commands: []*cli.Command{ 43 | &commandSetup, 44 | &commandGo, 45 | &commandToolexec, 46 | &commandVersion, 47 | }, 48 | Before: initLogger, 49 | } 50 | 51 | err := app.Run(context.Background(), os.Args) 52 | if err != nil { 53 | ex.Fatal(err) 54 | } 55 | } 56 | 57 | func initLogger(ctx context.Context, cmd *cli.Command) (context.Context, error) { 58 | buildTempDir := cmd.String("work-dir") 59 | err := os.MkdirAll(buildTempDir, 0o755) 60 | if err != nil { 61 | return ctx, ex.Wrapf(err, "failed to create work directory %q", buildTempDir) 62 | } 63 | 64 | logFilename := filepath.Join(buildTempDir, debugLogFilename) 65 | writer, err := os.OpenFile(logFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) 66 | if err != nil { 67 | return ctx, ex.Wrapf(err, "failed to open log file %q", buildTempDir) 68 | } 69 | 70 | // Create a custom handler with shorter time format 71 | // Remove time and level keys as they make no sense for debugging 72 | handler := slog.NewTextHandler(writer, &slog.HandlerOptions{ 73 | ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { 74 | if a.Key == slog.TimeKey || a.Key == slog.LevelKey { 75 | return slog.Attr{} 76 | } 77 | return a 78 | }, 79 | Level: slog.LevelInfo, 80 | }) 81 | logger := slog.New(handler) 82 | ctx = util.ContextWithLogger(ctx, logger) 83 | 84 | return ctx, nil 85 | } 86 | 87 | func addLoggerPhaseAttribute(ctx context.Context, cmd *cli.Command) (context.Context, error) { 88 | logger := util.LoggerFromContext(ctx) 89 | logger = logger.With("phase", cmd.Name) 90 | return util.ContextWithLogger(ctx, logger), nil 91 | } 92 | -------------------------------------------------------------------------------- /demo/grpc/server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Multi-stage Dockerfile for gRPC server with OpenTelemetry compile-time instrumentation 2 | # Stage 1: Build the otel tool 3 | FROM golang:1.24-alpine@sha256:06545cc1ff10ddf04aebe20db1352ec7c96d1e43135767931c473557d0378202 AS otel-builder 4 | 5 | WORKDIR /build 6 | 7 | # Install build dependencies 8 | RUN apk add --no-cache git make gcc musl-dev 9 | 10 | # Copy the entire project to build the otel tool 11 | COPY go.mod go.sum ./ 12 | COPY . . 13 | 14 | # Build the otel tool 15 | RUN go build -o /otel ./tool/cmd 16 | 17 | # Stage 2: Build the gRPC server with instrumentation 18 | FROM golang:1.24-alpine@sha256:06545cc1ff10ddf04aebe20db1352ec7c96d1e43135767931c473557d0378202 AS app-builder 19 | 20 | WORKDIR /app 21 | 22 | # Install protobuf compiler and dependencies 23 | RUN apk add --no-cache git protobuf-dev protoc 24 | 25 | # Install protoc-gen-go and protoc-gen-go-grpc 26 | RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.4 && \ 27 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 28 | 29 | # Copy the otel tool from previous stage 30 | COPY --from=otel-builder /otel /usr/local/bin/otel 31 | 32 | # Copy demo app source 33 | COPY demo/grpc/server /app 34 | 35 | # Generate protobuf code 36 | RUN mkdir -p pb && \ 37 | protoc --go_out=pb --go_opt=paths=source_relative \ 38 | --go-grpc_out=pb --go-grpc_opt=paths=source_relative \ 39 | greeter.proto 40 | 41 | # Download dependencies 42 | RUN go mod download 43 | 44 | # Build the app with otel instrumentation 45 | RUN otel go build -o server . 46 | 47 | # Stage 3: Runtime image 48 | FROM alpine:3.23@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 49 | 50 | # Install ca-certificates for TLS and timezone data 51 | RUN apk --no-cache add ca-certificates tzdata 52 | 53 | WORKDIR /app 54 | 55 | # Copy the instrumented binary 56 | COPY --from=app-builder /app/server . 57 | 58 | # Create non-root user 59 | RUN addgroup -g 1000 appuser && \ 60 | adduser -D -u 1000 -G appuser appuser && \ 61 | chown -R appuser:appuser /app 62 | 63 | USER appuser 64 | 65 | # Expose gRPC port 66 | EXPOSE 50051 67 | 68 | # Environment variables for OpenTelemetry 69 | ENV OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 \ 70 | OTEL_EXPORTER_OTLP_PROTOCOL=grpc \ 71 | OTEL_SERVICE_NAME=grpc-server \ 72 | OTEL_RESOURCE_ATTRIBUTES="service.namespace=demo,service.version=1.0.0" \ 73 | OTEL_LOG_LEVEL=info 74 | 75 | # Health check (if health endpoint is implemented) 76 | # HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ 77 | # CMD grpc_health_probe -addr=:50051 || exit 1 78 | 79 | ENTRYPOINT ["./server"] 80 | CMD ["-port", "50051"] 81 | -------------------------------------------------------------------------------- /tool/data/helloworld.yaml: -------------------------------------------------------------------------------- 1 | hook_helloworld: 2 | target: main 3 | func: Example 4 | before: MyHookBefore 5 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld" 6 | 7 | add_new_field: 8 | target: main 9 | struct: MyStruct 10 | new_field: 11 | - name: NewField 12 | type: string 13 | 14 | raw_helloworld: 15 | target: main 16 | func: Example 17 | raw: | 18 | go func(){ println("RawCode") }() 19 | 20 | hook_recv: 21 | target: main 22 | func: Example 23 | recv: "*MyStruct" 24 | before: MyHook1Before 25 | after: MyHook1After 26 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld" 27 | 28 | version_range: 29 | target: golang.org/x/time/rate 30 | version: "v0.14.0,v0.15.0" 31 | func: Every 32 | raw: | 33 | println("Every1") 34 | 35 | version_min_bad: 36 | target: golang.org/x/time/rate 37 | version: "v0.15.0" 38 | func: Every 39 | raw: | 40 | println("Every2") 41 | 42 | version_min_good: 43 | target: golang.org/x/time/rate 44 | version: "v0.11.0" 45 | func: Every 46 | raw: | 47 | println("Every3") 48 | 49 | only_after: 50 | target: main 51 | func: Example 52 | after: MyHookAfter 53 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld" 54 | 55 | underscore_param: 56 | target: main 57 | func: Underscore 58 | before: BeforeUnderscore 59 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld" 60 | 61 | hook_generic: 62 | target: main 63 | func: GenericExample 64 | before: MyHookGenericBefore 65 | after: MyHookGenericAfter 66 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld" 67 | 68 | hook_generic_recv: 69 | target: main 70 | func: GenericRecvExample 71 | recv: "*GenStruct" 72 | before: MyHookRecvBefore 73 | after: MyHookRecvAfter 74 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld" 75 | 76 | hook_ellipsis: 77 | target: main 78 | func: Ellipsis 79 | before: MyHookEllipsisBefore 80 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld" 81 | 82 | hook_function_a: 83 | target: main 84 | func: FunctionA 85 | before: FunctionABefore 86 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld" 87 | 88 | hook_function_b: 89 | target: main 90 | func: FunctionB 91 | before: FunctionBBefore 92 | path: "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/helloworld" 93 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new_instrumentation.yaml: -------------------------------------------------------------------------------- 1 | name: New Instrumentation 2 | description: Request new instrumentation be added to the project 3 | title: 'New Instrumentation Request for ' 4 | labels: 5 | - enhancement 6 | - new instrumentation 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this new instrumentation request! 12 | - type: checkboxes 13 | attributes: 14 | label: Is there an existing issue for this? 15 | description: | 16 | Please search to see if an issue already exists for the instrumentation. 17 | 18 | If one already exists, add a 👍 to that issue and your use-case if it is not already captured. 19 | options: 20 | - label: I have searched the existing issues 21 | - type: textarea 22 | attributes: 23 | label: Package 24 | description: What is the name of the Go package? 25 | validations: 26 | required: true 27 | - type: textarea 28 | attributes: 29 | label: Version 30 | description: What version of the package do you use? 31 | validations: 32 | required: false 33 | - type: textarea 34 | attributes: 35 | label: Environment 36 | description: How do you normally run applications built with the package? 37 | placeholder: As a Deployment on Kubernetes 1.30 38 | validations: 39 | required: true 40 | - type: textarea 41 | attributes: 42 | label: Use-case 43 | description: What kind of applications do you build with the package? 44 | validations: 45 | required: false 46 | - type: textarea 47 | attributes: 48 | label: Telemetry 49 | description: What telemetry you would like to see for for the package? 50 | validations: 51 | required: false 52 | - type: textarea 53 | attributes: 54 | label: Anything else? 55 | description: | 56 | Links? References? Anything that will give us more context! 57 | 58 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 59 | validations: 60 | required: false 61 | - type: dropdown 62 | attributes: 63 | label: Tip 64 | description: This element is static, used to render a helpful sub-heading for end-users and community members to help 65 | prioritize issues. Please leave as is. 66 | options: 67 | - [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) 68 | with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, 69 | to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). 70 | default: 0 71 | -------------------------------------------------------------------------------- /test/e2e/grpc_test.go: -------------------------------------------------------------------------------- 1 | //go:build e2e 2 | 3 | // Copyright The OpenTelemetry Authors 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package test 7 | 8 | import ( 9 | "bufio" 10 | "io" 11 | "os/exec" 12 | "path/filepath" 13 | "strings" 14 | "testing" 15 | "time" 16 | 17 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/test/app" 18 | "github.com/stretchr/testify/require" 19 | ) 20 | 21 | func waitUntilGrpcReady(t *testing.T, serverApp *exec.Cmd, outputPipe io.ReadCloser) func() string { 22 | t.Helper() 23 | 24 | readyChan := make(chan struct{}) 25 | doneChan := make(chan struct{}) 26 | output := strings.Builder{} 27 | const readyMsg = "server started" 28 | go func() { 29 | // Scan will return false when the application exits. 30 | defer close(doneChan) 31 | scanner := bufio.NewScanner(outputPipe) 32 | for scanner.Scan() { 33 | line := scanner.Text() 34 | output.WriteString(line + "\n") 35 | if strings.Contains(line, readyMsg) { 36 | close(readyChan) 37 | } 38 | } 39 | }() 40 | 41 | select { 42 | case <-readyChan: 43 | t.Logf("gRPC Server is ready!") 44 | case <-time.After(10 * time.Second): 45 | t.Fatal("timeout waiting for gRPC server to be ready") 46 | } 47 | 48 | return func() string { 49 | // Wait for the server to exit 50 | serverApp.Wait() 51 | // Wait for the output goroutine to finish 52 | <-doneChan 53 | // Return the complete output 54 | return output.String() 55 | } 56 | } 57 | 58 | func TestGrpc(t *testing.T) { 59 | serverDir := filepath.Join("..", "..", "demo", "grpc", "server") 60 | clientDir := filepath.Join("..", "..", "demo", "grpc", "client") 61 | 62 | // Build the server and client applications with the instrumentation tool. 63 | app.Build(t, serverDir, "go", "build", "-a") 64 | app.Build(t, clientDir, "go", "build", "-a") 65 | 66 | // Start the server and wait for it to be ready. 67 | serverApp, outputPipe := app.Start(t, serverDir) 68 | waitUntilDone := waitUntilGrpcReady(t, serverApp, outputPipe) 69 | 70 | // Run the client to make a unary RPC call 71 | app.Run(t, clientDir, "-name", "OpenTelemetry") 72 | 73 | // Run the client again for streaming RPC 74 | app.Run(t, clientDir, "-stream") 75 | 76 | // Finally, send shutdown request to the server 77 | app.Run(t, clientDir, "-shutdown") 78 | 79 | // Wait for the server to exit and return the output. 80 | output := waitUntilDone() 81 | 82 | // Verify that the instrumentation was initialized 83 | require.Contains(t, output, "gRPC server instrumentation initialized", "instrumentation should be initialized") 84 | 85 | // Verify that the server started (JSON format) 86 | require.Contains(t, output, `"msg":"server listening"`) 87 | 88 | // The output should show that the gRPC server received requests (JSON format) 89 | require.Contains(t, output, `"msg":"received request"`) 90 | } 91 | -------------------------------------------------------------------------------- /pkg/instrumentation/nethttp/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 4 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 5 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 6 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 7 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 8 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 9 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 10 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 11 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 12 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 13 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 17 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 18 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 19 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 20 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 21 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 22 | go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= 23 | go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= 24 | go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= 25 | go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= 26 | go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= 27 | go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= 28 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 29 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 30 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 31 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 32 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 33 | -------------------------------------------------------------------------------- /test/integration/basic_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration 2 | 3 | // Copyright The OpenTelemetry Authors 4 | // SPDX-License-Identifier: Apache-2.0 5 | 6 | package test 7 | 8 | import ( 9 | "path/filepath" 10 | "regexp" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/require" 14 | 15 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/test/app" 16 | ) 17 | 18 | func TestBasic(t *testing.T) { 19 | appDir := filepath.Join("..", "..", "demo", "basic") 20 | 21 | app.Build(t, appDir, "go", "build", "-a") 22 | output := app.Run(t, appDir) 23 | expect := []string{ 24 | "Every1", 25 | "Every3", 26 | "MyStruct.Example", 27 | "GenericExample before hook", 28 | "Hello, Generic World! 1 2", 29 | "GenericExample after hook", 30 | "traceID: 123, spanID: 456", 31 | "GenericRecvExample before hook", 32 | "Hello, Generic Recv World!", 33 | "GenericRecvExample after hook", 34 | "traceID: 123, spanID: 456", 35 | "[MyHook]", 36 | "=setupOpenTelemetry=", 37 | "RawCode", 38 | "funcName:Example", 39 | "packageName:main", 40 | "paramCount:1", 41 | "returnValCount:0", 42 | "isSkipCall:false", 43 | "Ellipsis", 44 | "Hello from stdio", 45 | "Underscore", 46 | } 47 | for _, e := range expect { 48 | require.Contains(t, output, e) 49 | } 50 | 51 | verifyGenericHookContextLogs(t, output) 52 | verifyTracePropagationBetweenFunctionAAndB(t, output) 53 | } 54 | 55 | func verifyGenericHookContextLogs(t *testing.T, output string) { 56 | expectedGenericLogs := []string{ 57 | "[Generic] Function: main.GenericExample", 58 | "[Generic] Param count: 2", 59 | "[Generic] Skip call: false", 60 | "[Generic] Data from Before: test-data", 61 | "[Generic] Return value count: 1", 62 | "[Generic] SetParam panic (expected): SetParam is unsupported for generic functions", 63 | "[Generic] SetReturnVal panic (expected): SetReturnVal is unsupported for generic functions", 64 | } 65 | for _, log := range expectedGenericLogs { 66 | require.Contains(t, output, log, "Expected generic HookContext log: %s", log) 67 | } 68 | } 69 | 70 | func verifyTracePropagationBetweenFunctionAAndB(t *testing.T, output string) { 71 | traceA, spanA := extractSpanInfo(t, output, "FunctionABefore") 72 | traceB, spanB := extractSpanInfo(t, output, "FunctionBBefore") 73 | 74 | require.Equal(t, traceA, traceB, "expected FunctionA and FunctionB to share the same trace ID") 75 | require.NotEqual(t, spanA, spanB, "expected FunctionA and FunctionB to have different span IDs") 76 | } 77 | 78 | //nolint:revive // just a helper function to extract span info from the output 79 | func extractSpanInfo(t *testing.T, output, funcName string) (string, string) { 80 | re := regexp.MustCompile(funcName + `: TraceID: ([0-9a-f]{32}), SpanID: ([0-9a-f]{16})`) 81 | match := re.FindStringSubmatch(output) 82 | require.Len(t, match, 3, "expected log line for %s with trace and span IDs", funcName) 83 | return match[1], match[2] 84 | } 85 | -------------------------------------------------------------------------------- /demo/basic/main.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | // #include 7 | // #include 8 | // 9 | // static void myCprint(char* s) { 10 | // printf("%s\n", s); 11 | // fflush(stdout); 12 | // } 13 | import "C" 14 | 15 | import ( 16 | "context" 17 | "fmt" 18 | "runtime" 19 | "time" 20 | "unsafe" 21 | 22 | "golang.org/x/time/rate" 23 | ) 24 | 25 | type traceContext struct { 26 | traceID string 27 | spanID string 28 | } 29 | 30 | func (tc *traceContext) String() string { 31 | return fmt.Sprintf("traceID: %s, spanID: %s", tc.traceID, tc.spanID) 32 | } 33 | 34 | func (tc *traceContext) Clone() interface{} { 35 | return &traceContext{ 36 | traceID: tc.traceID, 37 | spanID: tc.spanID, 38 | } 39 | } 40 | 41 | type MyStruct struct{} 42 | 43 | func (m *MyStruct) Example() { println("MyStruct.Example") } 44 | 45 | type GenStruct[T any] struct { 46 | Value T 47 | } 48 | 49 | func (m *GenStruct[T]) GenericRecvExample(t T) T { 50 | fmt.Printf("%s%s\n", m.Value, t) 51 | return t 52 | } 53 | 54 | func GenericExample[K comparable, V any](key K, value V) V { 55 | println("Hello, Generic World!", key, value) 56 | return value 57 | } 58 | 59 | // Example demonstrates how to use the instrumenter. 60 | func Example() { 61 | // Output: 62 | // [MyHook] start to instrument hello world! 63 | // [MyHook] hello world is instrumented! 64 | } 65 | 66 | func Underscore(_ int, _ float32) {} 67 | 68 | func Ellipsis(p1 ...string) {} 69 | 70 | // FunctionA is the parent function that calls FunctionB. 71 | // It receives a context as the first parameter, which will be instrumented. 72 | func FunctionA(ctx context.Context) { 73 | FunctionB(ctx) 74 | } 75 | 76 | // FunctionB is the child function called by FunctionA. 77 | // It receives a context as the first parameter, which will be instrumented. 78 | func FunctionB(ctx context.Context) {} 79 | 80 | func main() { 81 | ctx := &traceContext{ 82 | traceID: "123", 83 | spanID: "456", 84 | } 85 | runtime.SetTraceContextToGLS(ctx) 86 | 87 | go func() { 88 | fmt.Printf("traceContext from parent goroutine: %s\n", runtime.GetTraceContextFromGLS()) 89 | }() 90 | 91 | // Call the Example function to trigger the instrumentation 92 | Example() 93 | m := &MyStruct{} 94 | // Add a new field to the struct 95 | m.NewField = "abc" 96 | m.Example() 97 | 98 | _ = GenericExample(1, 2) 99 | g := &GenStruct[string]{Value: "Hello"} 100 | _ = g.GenericRecvExample(", Generic Recv World!") 101 | 102 | // Call real module function 103 | println(rate.Every(time.Duration(1))) 104 | 105 | // Support cgo 106 | cs := C.CString("Hello from stdio") 107 | C.myCprint(cs) 108 | C.free(unsafe.Pointer(cs)) 109 | 110 | Ellipsis("a", "b") 111 | 112 | Underscore(1, 2) 113 | 114 | FunctionA(context.Background()) 115 | } 116 | -------------------------------------------------------------------------------- /tool/util/go.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package util 5 | 6 | import ( 7 | "bufio" 8 | "os" 9 | "strings" 10 | 11 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 12 | ) 13 | 14 | // IsCompileCommand checks if the line is a compile command. 15 | func IsCompileCommand(line string) bool { 16 | check := []string{"-o", "-p", "-buildid"} 17 | if IsWindows() { 18 | check = append(check, "compile.exe") 19 | } else { 20 | check = append(check, "compile") 21 | } 22 | 23 | // Check if the line contains all the required fields 24 | for _, id := range check { 25 | if !strings.Contains(line, id) { 26 | return false 27 | } 28 | } 29 | 30 | // @@PGO compile command is different from normal compile command, we 31 | // should skip it, otherwise the same package will be find twice 32 | // (one for PGO and one for normal) 33 | if strings.Contains(line, "-pgoprofile") { 34 | return false 35 | } 36 | return true 37 | } 38 | 39 | // isCgoCommand checks if the line is a cgo tool invocation with -objdir and -importpath flags. 40 | func IsCgoCommand(line string) bool { 41 | return strings.Contains(line, "cgo") && 42 | strings.Contains(line, "-objdir") && 43 | strings.Contains(line, "-importpath") && 44 | !strings.Contains(line, "-dynimport") 45 | } 46 | 47 | // FindFlagValue finds the value of a flag in the command line. 48 | func FindFlagValue(cmd []string, flag string) string { 49 | for i, v := range cmd { 50 | if v == flag { 51 | return cmd[i+1] 52 | } 53 | } 54 | return "" 55 | } 56 | 57 | // SplitCompileCmds splits the command line by space, but keep the quoted part 58 | // as a whole. For example, "a b" c will be split into ["a b", "c"]. 59 | func SplitCompileCmds(input string) []string { 60 | var args []string 61 | var inQuotes bool 62 | var arg strings.Builder 63 | 64 | for i := range len(input) { 65 | c := input[i] 66 | 67 | if c == '"' { 68 | inQuotes = !inQuotes 69 | continue 70 | } 71 | 72 | if c == ' ' && !inQuotes { 73 | if arg.Len() > 0 { 74 | args = append(args, arg.String()) 75 | arg.Reset() 76 | } 77 | continue 78 | } 79 | 80 | err := arg.WriteByte(c) 81 | if err != nil { 82 | ex.Fatal(err) 83 | } 84 | } 85 | 86 | if arg.Len() > 0 { 87 | args = append(args, arg.String()) 88 | } 89 | 90 | // Fix the escaped backslashes on Windows 91 | if IsWindows() { 92 | for i, arg := range args { 93 | args[i] = strings.ReplaceAll(arg, `\\`, `\`) 94 | } 95 | } 96 | return args 97 | } 98 | 99 | func IsGoFile(path string) bool { 100 | return strings.HasSuffix(strings.ToLower(path), ".go") 101 | } 102 | 103 | func NewFileScanner(file *os.File, size int) (*bufio.Scanner, error) { 104 | if _, err := file.Seek(0, 0); err != nil { 105 | return nil, ex.Wrapf(err, "failed to seek file") 106 | } 107 | scanner := bufio.NewScanner(file) 108 | scanner.Buffer(make([]byte, 0, size), size) 109 | return scanner, nil 110 | } 111 | -------------------------------------------------------------------------------- /test/integration/grpc_shutdown_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:build integration 5 | 6 | package test 7 | 8 | import ( 9 | "path/filepath" 10 | "testing" 11 | "time" 12 | 13 | "github.com/stretchr/testify/require" 14 | 15 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/test/app" 16 | ) 17 | 18 | // TestGRPCClientTelemetryFlushOnExit verifies that telemetry is properly flushed 19 | // when the client application exits, without needing an explicit sleep. 20 | // This test validates that the signal-based shutdown handler in the instrumentation 21 | // layer works correctly. 22 | func TestGRPCClientTelemetryFlushOnExit(t *testing.T) { 23 | serverDir := filepath.Join("..", "..", "demo", "grpc", "server") 24 | clientDir := filepath.Join("..", "..", "demo", "grpc", "client") 25 | 26 | // Enable debug logging to verify shutdown behavior 27 | t.Setenv("OTEL_LOG_LEVEL", "debug") 28 | // Use stdout exporter for easy verification 29 | t.Setenv("OTEL_TRACES_EXPORTER", "console") 30 | 31 | t.Log("Building instrumented gRPC applications...") 32 | 33 | // Build server and client 34 | app.Build(t, serverDir, "go", "build", "-a") 35 | app.Build(t, clientDir, "go", "build", "-a") 36 | 37 | t.Log("Starting gRPC server...") 38 | 39 | // Start the server 40 | serverApp, outputPipe := app.Start(t, serverDir) 41 | defer func() { 42 | if serverApp.Process != nil { 43 | _ = serverApp.Process.Kill() 44 | } 45 | }() 46 | _, err := app.WaitForServerReady(t, serverApp, outputPipe) 47 | require.NoError(t, err, "server should start successfully") 48 | 49 | t.Log("Running gRPC client and monitoring shutdown...") 50 | 51 | // Run client with a single request 52 | // The client should exit cleanly and export telemetry WITHOUT the 6s sleep 53 | start := time.Now() 54 | clientOutput := app.Run(t, clientDir, "-name", "ShutdownTest", "-log-level", "debug") 55 | duration := time.Since(start) 56 | 57 | t.Logf("Client completed in %v", duration) 58 | 59 | // Verify the client ran successfully 60 | require.Contains(t, clientOutput, `"msg":"greeting"`, "Expected greeting response") 61 | require.Contains(t, clientOutput, `"msg":"client finished"`, "Expected client finished log") 62 | 63 | // Verify instrumentation was active 64 | require.Contains( 65 | t, 66 | clientOutput, 67 | "gRPC client instrumentation initialized", 68 | "Expected instrumentation to be initialized", 69 | ) 70 | 71 | // Verify signal handler was setup (debug log) 72 | // Note: Since we're using app.Run which waits for process completion, 73 | // the signal handler won't be triggered by SIGINT/SIGTERM. 74 | // Instead, we verify the normal exit path works without requiring explicit sleep. 75 | 76 | require.Less(t, duration, 3*time.Second, 77 | "Client should complete quickly without explicit sleep - signal handler handles flush") 78 | 79 | t.Log("Shutting down server...") 80 | app.Run(t, clientDir, "-shutdown") 81 | _ = serverApp.Wait() 82 | 83 | t.Log("Telemetry flush test passed - no explicit sleep needed!") 84 | } 85 | -------------------------------------------------------------------------------- /demo/grpc/server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:generate mkdir -p pb 5 | //go:generate protoc --go_out=pb --go_opt=paths=source_relative --go-grpc_out=pb --go-grpc_opt=paths=source_relative greeter.proto 6 | 7 | package main 8 | 9 | import ( 10 | "context" 11 | "flag" 12 | "fmt" 13 | "io" 14 | "log/slog" 15 | "net" 16 | "os" 17 | "time" 18 | 19 | pb "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/demo/grpc/server/pb" 20 | "google.golang.org/grpc" 21 | ) 22 | 23 | var ( 24 | port = flag.Int("port", 50051, "The server port") 25 | logLevel = flag.String("log-level", "info", "Log level (debug, info, warn, error)") 26 | logger *slog.Logger 27 | ) 28 | 29 | type server struct { 30 | pb.UnimplementedGreeterServer 31 | } 32 | 33 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 34 | logger.Info("received request", "name", in.GetName()) 35 | return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil 36 | } 37 | 38 | func (s *server) SayHelloStream(stream pb.Greeter_SayHelloStreamServer) error { 39 | for { 40 | in, err := stream.Recv() 41 | if err == io.EOF { 42 | return nil 43 | } 44 | if err != nil { 45 | return err 46 | } 47 | logger.Info("received stream request", "name", in.GetName()) 48 | if err := stream.Send(&pb.HelloReply{Message: "Hello " + in.GetName()}); err != nil { 49 | return err 50 | } 51 | } 52 | } 53 | 54 | func (s *server) Shutdown(ctx context.Context, in *pb.ShutdownRequest) (*pb.ShutdownReply, error) { 55 | logger.Info("shutdown request received") 56 | go func() { 57 | time.Sleep(100 * time.Millisecond) // Give time to send response 58 | os.Exit(0) 59 | }() 60 | return &pb.ShutdownReply{Message: "Server is shutting down"}, nil 61 | } 62 | 63 | func main() { 64 | flag.Parse() 65 | 66 | // Initialize logger with appropriate level 67 | var level slog.Level 68 | switch *logLevel { 69 | case "debug": 70 | level = slog.LevelDebug 71 | case "info": 72 | level = slog.LevelInfo 73 | case "warn": 74 | level = slog.LevelWarn 75 | case "error": 76 | level = slog.LevelError 77 | default: 78 | level = slog.LevelInfo 79 | } 80 | 81 | opts := &slog.HandlerOptions{ 82 | Level: level, 83 | } 84 | logger = slog.New(slog.NewJSONHandler(os.Stdout, opts)) 85 | 86 | addr := fmt.Sprintf(":%d", *port) 87 | logger.Info("server starting", "address", addr, "log_level", *logLevel) 88 | 89 | lis, err := net.Listen("tcp", addr) 90 | if err != nil { 91 | logger.Error("failed to listen", "error", err) 92 | os.Exit(1) 93 | } 94 | defer lis.Close() 95 | 96 | runServer(lis) 97 | } 98 | 99 | func runServer(lis net.Listener) { 100 | s := grpc.NewServer() 101 | pb.RegisterGreeterServer(s, &server{}) 102 | logger.Info("server listening", "address", lis.Addr()) 103 | logger.Info("server started") 104 | if err := s.Serve(lis); err != nil { 105 | logger.Error("failed to serve", "error", err) 106 | os.Exit(1) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /pkg/instrumentation/shared/otel_setup_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package shared 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestGetLogger(t *testing.T) { 14 | logger1 := Logger() 15 | require.NotNil(t, logger1) 16 | 17 | // Should return the same instance (singleton) 18 | logger2 := Logger() 19 | assert.Equal(t, logger1, logger2) 20 | } 21 | 22 | func TestSetupOTelSDK(t *testing.T) { 23 | var ( 24 | instrumentationName = "github.com/open-telemetry/opentelemetry-go-compile-instrumentation" 25 | instrumentationVersion = "0.1.0" 26 | ) 27 | err := SetupOTelSDK(instrumentationName, instrumentationVersion) 28 | require.NoError(t, err) 29 | 30 | // Should be idempotent 31 | err = SetupOTelSDK(instrumentationName, instrumentationVersion) 32 | require.NoError(t, err) 33 | } 34 | 35 | func TestInstrumented(t *testing.T) { 36 | tests := []struct { 37 | name string 38 | enabledList string 39 | disabledList string 40 | instrumentationName string 41 | expected bool 42 | }{ 43 | { 44 | name: "default enabled", 45 | enabledList: "", 46 | disabledList: "", 47 | instrumentationName: "nethttp", 48 | expected: true, 49 | }, 50 | { 51 | name: "explicitly enabled", 52 | enabledList: "nethttp,grpc", 53 | disabledList: "", 54 | instrumentationName: "nethttp", 55 | expected: true, 56 | }, 57 | { 58 | name: "not in enabled list", 59 | enabledList: "grpc", 60 | disabledList: "", 61 | instrumentationName: "nethttp", 62 | expected: false, 63 | }, 64 | { 65 | name: "explicitly disabled", 66 | enabledList: "", 67 | disabledList: "nethttp", 68 | instrumentationName: "nethttp", 69 | expected: false, 70 | }, 71 | { 72 | name: "enabled then disabled", 73 | enabledList: "nethttp,grpc", 74 | disabledList: "nethttp", 75 | instrumentationName: "nethttp", 76 | expected: false, 77 | }, 78 | { 79 | name: "case insensitive", 80 | enabledList: "NETHTTP,GRPC", 81 | disabledList: "", 82 | instrumentationName: "NetHTTP", 83 | expected: true, 84 | }, 85 | { 86 | name: "with spaces", 87 | enabledList: " nethttp , grpc ", 88 | disabledList: "", 89 | instrumentationName: "nethttp", 90 | expected: true, 91 | }, 92 | } 93 | 94 | for _, tt := range tests { 95 | t.Run(tt.name, func(t *testing.T) { 96 | if tt.enabledList != "" { 97 | t.Setenv("OTEL_GO_ENABLED_INSTRUMENTATIONS", tt.enabledList) 98 | } 99 | if tt.disabledList != "" { 100 | t.Setenv("OTEL_GO_DISABLED_INSTRUMENTATIONS", tt.disabledList) 101 | } 102 | 103 | result := Instrumented(tt.instrumentationName) 104 | assert.Equal(t, tt.expected, result) 105 | }) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /pkg/instrumentation/shared/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/shared 2 | 3 | go 1.24.0 4 | 5 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.11.1 9 | go.opentelemetry.io/contrib/exporters/autoexport v0.63.0 10 | go.opentelemetry.io/contrib/instrumentation/runtime v0.64.0 11 | go.opentelemetry.io/otel v1.39.0 12 | go.opentelemetry.io/otel/sdk v1.39.0 13 | go.opentelemetry.io/otel/sdk/metric v1.39.0 14 | ) 15 | 16 | require ( 17 | github.com/beorn7/perks v1.0.1 // indirect 18 | github.com/cenkalti/backoff/v5 v5.0.3 // indirect 19 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/go-logr/logr v1.4.3 // indirect 22 | github.com/go-logr/stdr v1.2.2 // indirect 23 | github.com/google/uuid v1.6.0 // indirect 24 | github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect 25 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect 26 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 27 | github.com/pmezard/go-difflib v1.0.0 // indirect 28 | github.com/prometheus/client_golang v1.23.0 // indirect 29 | github.com/prometheus/client_model v0.6.2 // indirect 30 | github.com/prometheus/common v0.65.0 // indirect 31 | github.com/prometheus/otlptranslator v0.0.2 // indirect 32 | github.com/prometheus/procfs v0.17.0 // indirect 33 | go.opentelemetry.io/auto/sdk v1.2.1 // indirect 34 | go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 // indirect 35 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect 36 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect 37 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect 38 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect 39 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect 40 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect 41 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect 42 | go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect 43 | go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect 44 | go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect 45 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect 46 | go.opentelemetry.io/otel/log v0.14.0 // indirect 47 | go.opentelemetry.io/otel/metric v1.39.0 // indirect 48 | go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect 49 | go.opentelemetry.io/otel/trace v1.39.0 // indirect 50 | go.opentelemetry.io/proto/otlp v1.7.1 // indirect 51 | golang.org/x/net v0.43.0 // indirect 52 | golang.org/x/sys v0.39.0 // indirect 53 | golang.org/x/text v0.28.0 // indirect 54 | google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect 55 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect 56 | google.golang.org/grpc v1.75.0 // indirect 57 | google.golang.org/protobuf v1.36.8 // indirect 58 | gopkg.in/yaml.v3 v3.0.1 // indirect 59 | ) 60 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | OpenTelemetry Go Compile-Time Instrumentation is a tool that automatically instruments your Go applications with [OpenTelemetry](https://opentelemetry.io/) at compile-time. 4 | No manual code changes required. 5 | 6 | ## Why Use This Tool? 7 | 8 | - **Zero-code instrumentation** - Automatically instrument your entire application without modifying source code 9 | - **Third-party library support** - Instrument dependencies and libraries you don't control 10 | - **Complete decoupling** - Keep your codebase free from instrumentation concerns 11 | - **Flexible deployment** - Integrate at development time or in your CI/CD pipeline 12 | 13 | ## Quick Start 14 | 15 | 1. **Clone and build the tool** 16 | 17 | ```bash 18 | git clone https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation.git 19 | cd opentelemetry-go-compile-instrumentation 20 | make build 21 | ``` 22 | 23 | 2. **Try the demo** 24 | 25 | ```bash 26 | make demo 27 | ``` 28 | 29 | 3. **Use with your application** 30 | 31 | ```bash 32 | # Option 1: Direct build 33 | ./otel go build -o myapp . 34 | 35 | # Option 2: Install as tool dependency (Go 1.24+) 36 | go get -tool github.com/open-telemetry/opentelemetry-go-compile-instrumentation/cmd/otel 37 | go tool otel go build -o myapp . 38 | ``` 39 | 40 | ## How It Works 41 | 42 | The tool uses compile-time instrumentation through: 43 | 44 | 1. **Trampoline Code Injection** - Injects lightweight hook points into target functions 45 | 2. **Function Pointer Redirection** - Automatically generates and links hooks to monitoring code via `//go:linkname` 46 | 3. **Custom Toolchain Integration** - Intercepts compilation using `-toolexec` flag 47 | 48 | This approach provides dynamic instrumentation without runtime overhead or invasive code modifications. 49 | 50 | ## Learn More 51 | 52 | - [User Experience Design](./ux-design.md) - Detailed UX documentation and configuration options 53 | - [Implementation Details](./implementation.md) - Technical architecture and internals 54 | - [API Design](./api-design-and-project-structure.md) - API structure and project organization 55 | - [Contributing Guide](../CONTRIBUTING.md) - How to contribute to the project 56 | 57 | ### Video Talks 58 | 59 | Learn more about the project from these presentations: 60 | 61 | - [OpenTelemetry Go Compile-Time Instrumentation Overview](https://www.youtube.com/watch?v=xEsVOhBdlZY) 62 | - [Deep Dive: Conceptual details](https://www.youtube.com/watch?v=8Rw-fVEjihw&list=PLDWZ5uzn69ewrYyHTNrXlrWVDjLiOX0Yb&index=19) 63 | 64 | ## Community 65 | 66 | - **Slack**: Join [#otel-go-compt-instr-sig](https://cloud-native.slack.com/archives/C088D8GSSSF) 67 | - **Meetings**: Check the [meeting notes](https://docs.google.com/document/d/1XkVahJfhf482d3WVHsvUUDaGzHc8TO3sqQlSS80mpGY/edit) for SIG schedules 68 | - **GitHub**: [open-telemetry/opentelemetry-go-compile-instrumentation](https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation) 69 | 70 | ## Status 71 | 72 | > **Note**: This project is currently in active development and not yet ready for production use. 73 | 74 | For the latest updates and development progress, follow the project on GitHub and join the community discussions. 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | OpenTelemetry Logo 3 |
4 | Go 5 | License 6 | Status 7 | Slack 8 |
9 | 10 | > [!IMPORTANT] 11 | > This is a work in progress and not ready for production use. 🚨 12 | 13 | ## Overview 14 | 15 | This project provides a tool to automatically instrument Go applications with [OpenTelemetry](https://opentelemetry.io/) at compile-time. 16 | It modifies the Go build process to inject OpenTelemetry code into the application **without requiring manual changes to the source code**. 17 | 18 | Highlights: 19 | 20 | - **Zero Runtime Overhead** - Instrumentation is baked into your binary at compile time 21 | - **Zero Code Changes** - Automatically instrument entire applications and dependencies 22 | - **Third-Party Library Support** - Instrument libraries you don't control 23 | - **Complete Decoupling** - Keep your codebase free from instrumentation concerns 24 | - **Flexible Deployment** - Integrate at development time or in your CI/CD pipeline 25 | 26 | ## Quick Start 27 | 28 | ### 1. Build the Tool 29 | 30 | ```bash 31 | git clone https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation.git 32 | cd opentelemetry-go-compile-instrumentation 33 | make build 34 | ``` 35 | 36 | The `otel` binary will be built in the root directory. 37 | 38 | ### 2. Try the Demo 39 | 40 | Just prefix the original `go build` command with `otel`. 41 | 42 | ```bash 43 | cd demo/basic 44 | ../../otel go build 45 | ./basic 46 | [... output ...] 47 | ``` 48 | 49 | ### 3. Run the Tests 50 | 51 | ```bash 52 | make test 53 | ``` 54 | 55 | ## Community 56 | 57 | ### Documentation 58 | 59 | - [Getting Started Guide](./docs/getting-started.md) - Setup and usage 60 | - [UX Design](./docs/ux-design.md) - Configuration options 61 | - [Implementation Details](./docs/implementation.md) - Technical architecture 62 | - [API Design](./docs/api-design-and-project-structure.md) - API structure 63 | - [Semantic Conventions](./docs/semantic-conventions.md) - Managing semantic conventions 64 | 65 | ### Video Talks 66 | 67 | - [Project Overview](https://www.youtube.com/watch?v=xEsVOhBdlZY) 68 | - [Deep Dive Details](https://www.youtube.com/watch?v=8Rw-fVEjihw&list=PLDWZ5uzn69ewrYyHTNrXlrWVDjLiOX0Yb&index=19) 69 | 70 | ### Get Help 71 | 72 | - [GitHub Discussions](https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation/discussions) - Ask questions 73 | - [GitHub Issues](https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation/issues) - Report bugs 74 | - [Slack Channel](https://cloud-native.slack.com/archives/C088D8GSSSF) - Real-time chat 75 | 76 | ### Contributing 77 | 78 | We welcome contributions! See our [contributing guide](CONTRIBUTING.md) and [development docs](./docs/). 79 | 80 | This project follows the [OpenTelemetry Code of Conduct](https://github.com/open-telemetry/community/blob/main/code-of-conduct.md). 81 | -------------------------------------------------------------------------------- /pkg/instrumentation/helloworld/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 4 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 5 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 6 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 7 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 8 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 9 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 10 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 11 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 15 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 16 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 17 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 18 | go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= 19 | go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= 20 | go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI= 21 | go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w= 22 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= 23 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= 24 | go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= 25 | go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= 26 | go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= 27 | go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= 28 | go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= 29 | go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= 30 | go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= 31 | go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= 32 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 33 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 34 | golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 35 | golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 36 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 37 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 38 | -------------------------------------------------------------------------------- /.github/scripts/license-check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright The OpenTelemetry Authors 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | set -euo pipefail 7 | 8 | # License header check exclusions 9 | EXCLUDE_PATHS=( 10 | -path '**/vendor/*' 11 | -o -path './.git/*' 12 | -o -path './tmp/*' 13 | -o -path './.otel-build/*' 14 | -o -path '**/pkg_temp/*' 15 | -o -path '**/pb/*' 16 | -o -path './LICENSE' 17 | -o -path './LICENSES/*' 18 | -o -path './.github/workflows/*' 19 | -o -path './scripts/*' 20 | -o -path '**/.otel-build/*' 21 | ) 22 | 23 | # File patterns that require license headers (source code files only) 24 | FILE_PATTERNS=('*.go' '*.sh') 25 | 26 | # Parse arguments 27 | FIX_MODE=false 28 | if [[ "${1:-}" == "--fix" ]]; then 29 | FIX_MODE=true 30 | fi 31 | 32 | if [ "$FIX_MODE" = true ]; then 33 | echo "Adding missing license headers..." 34 | 35 | # Fix Go files 36 | while IFS= read -r file; do 37 | if ! grep -q "Copyright The OpenTelemetry Authors" "$file"; then 38 | echo "Adding license header to $file" 39 | tmpfile=$(mktemp) 40 | { 41 | echo "// Copyright The OpenTelemetry Authors" 42 | echo "// SPDX-License-Identifier: Apache-2.0" 43 | echo "" 44 | cat "$file" 45 | } > "$tmpfile" 46 | mv "$tmpfile" "$file" 47 | fi 48 | done < <(find . -type f -iname '*.go' ! \( "${EXCLUDE_PATHS[@]}" \) 2>/dev/null) 49 | 50 | # Fix Shell files 51 | while IFS= read -r file; do 52 | if ! grep -q "Copyright The OpenTelemetry Authors" "$file"; then 53 | echo "Adding license header to $file" 54 | tmpfile=$(mktemp) 55 | if head -n 1 "$file" | grep -q '^#!/'; then 56 | { 57 | head -n 1 "$file" 58 | echo "" 59 | echo "# Copyright The OpenTelemetry Authors" 60 | echo "# SPDX-License-Identifier: Apache-2.0" 61 | echo "" 62 | tail -n +2 "$file" 63 | } > "$tmpfile" 64 | else 65 | { 66 | echo "# Copyright The OpenTelemetry Authors" 67 | echo "# SPDX-License-Identifier: Apache-2.0" 68 | echo "" 69 | cat "$file" 70 | } > "$tmpfile" 71 | fi 72 | mv "$tmpfile" "$file" 73 | fi 74 | done < <(find . -type f -iname '*.sh' ! \( "${EXCLUDE_PATHS[@]}" \) 2>/dev/null) 75 | 76 | echo "License headers added successfully" 77 | else 78 | echo "Checking license headers..." 79 | 80 | missing_files=() 81 | 82 | for pattern in "${FILE_PATTERNS[@]}"; do 83 | while IFS= read -r file; do 84 | if ! grep -q "Copyright The OpenTelemetry Authors" "$file" || \ 85 | ! grep -q "SPDX-License-Identifier: Apache-2.0" "$file"; then 86 | missing_files+=("$file") 87 | fi 88 | done < <(find . -type f -iname "$pattern" ! \( "${EXCLUDE_PATHS[@]}" \) 2>/dev/null) 89 | done 90 | 91 | if [ ${#missing_files[@]} -gt 0 ]; then 92 | echo "Missing license header in the following files:" 93 | printf '%s\n' "${missing_files[@]}" 94 | exit 1 95 | fi 96 | 97 | echo "All files have proper license headers" 98 | fi 99 | -------------------------------------------------------------------------------- /pkg/instrumentation/grpc/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 4 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 5 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 6 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 7 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 8 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 9 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 10 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 11 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 14 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 15 | go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= 16 | go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= 17 | go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= 18 | go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= 19 | go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= 20 | go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= 21 | go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= 22 | go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= 23 | golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 h1:6/3JGEh1C88g7m+qzzTbl3A0FtsLguXieqofVLU/JAo= 24 | golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= 25 | golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= 26 | golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 27 | golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= 28 | golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= 29 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk= 30 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= 31 | google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= 32 | google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= 33 | google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= 34 | google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 35 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 36 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 37 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 38 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 39 | -------------------------------------------------------------------------------- /.github/workflows/test-e2e.yaml: -------------------------------------------------------------------------------- 1 | name: E2E Tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | # Declare default permissions as read only. 8 | permissions: read-all 9 | jobs: 10 | # This is a required workflow, so it must always run and report a status. 11 | # We use dorny/paths-filter at the job level instead of workflow-level path filters 12 | # to ensure the workflow runs and the "done" job reports success even when no relevant files change. 13 | # This prevents PRs from being blocked by missing status checks. 14 | changes: 15 | name: Detect Changes 16 | runs-on: ubuntu-latest 17 | permissions: 18 | pull-requests: read 19 | outputs: 20 | code: ${{ steps.filter.outputs.code }} 21 | steps: 22 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 23 | - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 24 | id: filter 25 | with: 26 | filters: | 27 | code: 28 | - '**/*.go' 29 | - '**/go.mod' 30 | - '**/go.sum' 31 | - 'Makefile' 32 | - 'tool/**' 33 | - 'pkg/**' 34 | - 'test/**' 35 | - 'demo/**' 36 | - '.github/workflows/test-e2e.yaml' 37 | 38 | test-e2e-coverage: 39 | name: Coverage E2E Tests 40 | runs-on: ubuntu-latest 41 | needs: [changes] 42 | if: needs.changes.outputs.code == 'true' 43 | steps: 44 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 45 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 46 | with: 47 | go-version-file: go.mod 48 | - run: make test-e2e/coverage 49 | - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 50 | 51 | test-e2e: 52 | name: Test (go ${{ matrix.platform.os }} ${{ matrix.platform.arch }}) 53 | needs: [changes] 54 | if: needs.changes.outputs.code == 'true' 55 | strategy: 56 | matrix: 57 | platform: 58 | - os: ubuntu-latest 59 | arch: amd64 60 | - os: ubuntu-22.04-arm 61 | arch: arm64 62 | - os: macos-latest 63 | arch: arm64 64 | - os: windows-latest 65 | arch: amd64 66 | runs-on: ${{ matrix.platform.os }} 67 | steps: 68 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 69 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 70 | with: 71 | go-version-file: go.mod 72 | cache-dependency-path: "**/go.mod" 73 | - name: Install protoc 74 | uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # ratchet:arduino/setup-protoc@v3 75 | with: 76 | repo-token: ${{ secrets.GITHUB_TOKEN }} 77 | - run: make test-e2e 78 | env: 79 | GOARCH: ${{ matrix.platform.arch }} 80 | 81 | done: 82 | name: Done (E2E Tests) 83 | runs-on: ubuntu-latest 84 | needs: [changes, test-e2e-coverage, test-e2e] 85 | if: always() 86 | steps: 87 | - name: Check result 88 | run: | 89 | result="${{ needs.test-e2e.result }}" 90 | echo "Test result: $result" 91 | if [[ "$result" == "success" || "$result" == "skipped" ]]; then 92 | echo "✅ Tests passed or were skipped" 93 | exit 0 94 | else 95 | echo "❌ Tests failed" 96 | exit 1 97 | fi 98 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/dave/dst v0.27.3 h1:P1HPoMza3cMEquVf9kKy8yXsFirry4zEnWOdYPOoIzY= 3 | github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= 4 | github.com/dave/jennifer v1.5.0 h1:HmgPN93bVDpkQyYbqhCHj5QlgvUkvEOzMyEvKLgCRrg= 5 | github.com/dave/jennifer v1.5.0/go.mod h1:4MnyiFIlZS3l5tSDn8VnzE6ffAhYBMB2SZntBsZGUok= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 9 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 10 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 11 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 12 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 13 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 14 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 15 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 16 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 17 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 21 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 22 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 23 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 24 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 25 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 26 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 27 | github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo= 28 | github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= 29 | golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= 30 | golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= 31 | golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= 32 | golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 33 | golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= 34 | golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= 35 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 36 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 37 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 38 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 39 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= 41 | gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= 42 | -------------------------------------------------------------------------------- /tool/internal/instrument/impl.tmpl: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package instrument 5 | 6 | //line :1 7 | type HookContextImpl struct { 8 | params []interface{} 9 | returnVals []interface{} 10 | skipCall bool 11 | data interface{} 12 | funcName string 13 | packageName string 14 | } 15 | 16 | func (c *HookContextImpl) SetSkipCall(skip bool) { c.skipCall = skip } 17 | func (c *HookContextImpl) IsSkipCall() bool { return c.skipCall } 18 | func (c *HookContextImpl) SetData(data interface{}) { c.data = data } 19 | func (c *HookContextImpl) GetData() interface{} { return c.data } 20 | func (c *HookContextImpl) GetKeyData(key string) interface{} { 21 | if c.data == nil { 22 | return nil 23 | } 24 | return c.data.(map[string]interface{})[key] 25 | } 26 | 27 | func (c *HookContextImpl) SetKeyData(key string, val interface{}) { 28 | if c.data == nil { 29 | c.data = make(map[string]interface{}) 30 | } 31 | c.data.(map[string]interface{})[key] = val 32 | } 33 | 34 | func (c *HookContextImpl) HasKeyData(key string) bool { 35 | if c.data == nil { 36 | return false 37 | } 38 | _, ok := c.data.(map[string]interface{})[key] 39 | return ok 40 | } 41 | 42 | func (c *HookContextImpl) GetParam(idx int) interface{} { 43 | switch idx { 44 | } 45 | return nil 46 | } 47 | 48 | func (c *HookContextImpl) SetParam(idx int, val interface{}) { 49 | if val == nil { 50 | c.params[idx] = nil 51 | return 52 | } 53 | switch idx { 54 | } 55 | } 56 | 57 | func (c *HookContextImpl) GetReturnVal(idx int) interface{} { 58 | switch idx { 59 | } 60 | return nil 61 | } 62 | 63 | func (c *HookContextImpl) SetReturnVal(idx int, val interface{}) { 64 | if val == nil { 65 | c.returnVals[idx] = nil 66 | return 67 | } 68 | switch idx { 69 | } 70 | } 71 | func (c *HookContextImpl) GetParamCount() int { return len(c.params) } 72 | func (c *HookContextImpl) GetReturnValCount() int { return len(c.returnVals) } 73 | func (c *HookContextImpl) GetFuncName() string { return c.funcName } 74 | func (c *HookContextImpl) GetPackageName() string { return c.packageName } 75 | 76 | // Variable Template 77 | var ( 78 | OtelGetStackImpl func() []byte = nil 79 | OtelPrintStackImpl func([]byte) = nil 80 | ) 81 | 82 | // Trampoline Template 83 | func OtelBeforeTrampoline() (hookContext *HookContextImpl, skipCall bool) { 84 | defer func() { 85 | if err := recover(); err != nil { 86 | println("failed to exec Before hook", "OtelBeforeNamePlaceholder") 87 | if e, ok := err.(error); ok { 88 | println(e.Error()) 89 | } 90 | fetchStack, printStack := OtelGetStackImpl, OtelPrintStackImpl 91 | if fetchStack != nil && printStack != nil { 92 | printStack(fetchStack()) 93 | } 94 | } 95 | }() 96 | hookContext = &HookContextImpl{} 97 | hookContext.params = []interface{}{} 98 | hookContext.funcName = "" 99 | hookContext.packageName = "" 100 | return hookContext, hookContext.skipCall 101 | } 102 | 103 | func OtelAfterTrampoline(hookContext HookContext) { 104 | defer func() { 105 | if err := recover(); err != nil { 106 | println("failed to exec After hook", "OtelAfterNamePlaceholder") 107 | if e, ok := err.(error); ok { 108 | println(e.Error()) 109 | } 110 | fetchStack, printStack := OtelGetStackImpl, OtelPrintStackImpl 111 | if fetchStack != nil && printStack != nil { 112 | printStack(fetchStack()) 113 | } 114 | } 115 | }() 116 | hookContext.(*HookContextImpl).returnVals = []interface{}{} 117 | } 118 | -------------------------------------------------------------------------------- /demo/infrastructure/docker-compose/otel-collector/config.yaml: -------------------------------------------------------------------------------- 1 | # OpenTelemetry Collector configuration for demo environment 2 | # Receives traces and metrics from instrumented Go applications 3 | # Exports traces to Jaeger and exposes metrics for Prometheus 4 | 5 | extensions: 6 | # Health check extension for container healthcheck 7 | health_check: 8 | endpoint: 0.0.0.0:13133 9 | 10 | connectors: 11 | # Spanmetrics connector - generates metrics from spans 12 | # Generates standard OpenTelemetry metrics with traces_span_ namespace (v0.109.0+) 13 | spanmetrics: 14 | histogram: 15 | explicit: 16 | buckets: [100us, 1ms, 2ms, 6ms, 10ms, 100ms, 250ms] 17 | dimensions: 18 | - name: http.method 19 | default: GET 20 | - name: http.status_code 21 | - name: http.route 22 | default: "" # Optional - empty for services without route instrumentation 23 | - name: service.version 24 | exemplars: 25 | enabled: true 26 | metrics_flush_interval: 15s 27 | 28 | receivers: 29 | # OTLP receiver for traces and metrics 30 | otlp: 31 | protocols: 32 | grpc: 33 | # Must bind to 0.0.0.0 for Docker networking (required in v0.116.0+) 34 | endpoint: 0.0.0.0:4317 35 | http: 36 | endpoint: 0.0.0.0:4318 37 | 38 | # Prometheus receiver to scrape Jaeger metrics 39 | # Jaeger v2 exposes internal telemetry on port 8888 (same as OTel Collector) 40 | # Mapped to host port 8889 to avoid conflict 41 | prometheus: 42 | config: 43 | scrape_configs: 44 | - job_name: jaeger 45 | scrape_interval: 15s 46 | static_configs: 47 | - targets: ["jaeger:8888"] # Jaeger internal telemetry port 48 | labels: 49 | service: jaeger 50 | component: tracing 51 | 52 | processors: 53 | # Batch processor for improved performance 54 | batch: 55 | send_batch_size: 1024 56 | timeout: 5s 57 | send_batch_max_size: 2048 58 | 59 | # Memory limiter to prevent OOM 60 | memory_limiter: 61 | check_interval: 1s 62 | limit_mib: 512 63 | 64 | exporters: 65 | # Debug exporter for troubleshooting 66 | debug: 67 | verbosity: detailed 68 | sampling_initial: 5 69 | sampling_thereafter: 200 70 | 71 | # OTLP exporter for Jaeger v2 (native OTLP support) 72 | otlp/jaeger: 73 | endpoint: jaeger:4317 74 | tls: 75 | insecure: true 76 | 77 | # Prometheus exporter for metrics 78 | prometheus: 79 | endpoint: 0.0.0.0:8889 80 | # namespace removed to avoid breaking dashboard queries 81 | # metrics will use their original names without prefix 82 | const_labels: 83 | environment: demo 84 | 85 | service: 86 | extensions: [health_check] 87 | 88 | pipelines: 89 | # Traces pipeline - exports to Jaeger and spanmetrics connector 90 | traces: 91 | receivers: [otlp] 92 | processors: [memory_limiter, batch] 93 | exporters: [debug, otlp/jaeger, spanmetrics] 94 | 95 | # Metrics pipeline - receives from applications via OTLP and scrapes Jaeger 96 | metrics: 97 | receivers: [otlp, prometheus] 98 | processors: [memory_limiter, batch] 99 | exporters: [debug, prometheus] 100 | 101 | # Spanmetrics pipeline - receives span-derived metrics from connector 102 | metrics/spanmetrics: 103 | receivers: [spanmetrics] 104 | processors: [batch] 105 | exporters: [prometheus] 106 | 107 | # Telemetry configuration for the collector itself 108 | telemetry: 109 | logs: 110 | level: info 111 | -------------------------------------------------------------------------------- /tool/util/sys.go: -------------------------------------------------------------------------------- 1 | // Copyright The OpenTelemetry Authors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package util 5 | 6 | import ( 7 | "context" 8 | "hash/crc32" 9 | "io" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "runtime" 14 | "strconv" 15 | "strings" 16 | 17 | "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/tool/ex" 18 | ) 19 | 20 | func runCmd(ctx context.Context, dir string, env []string, args ...string) error { 21 | path := args[0] 22 | args = args[1:] 23 | cmd := exec.CommandContext(ctx, path, args...) 24 | cmd.Stdin = os.Stdin 25 | cmd.Stdout = os.Stdout 26 | cmd.Stderr = os.Stderr 27 | 28 | if dir != "" { 29 | cmd.Dir = dir 30 | } 31 | if env != nil { 32 | cmd.Env = env 33 | } 34 | 35 | err := cmd.Run() 36 | if err != nil { 37 | return ex.Wrapf(err, "failed to run command %q in dir '%q' with args: %v", path, dir, args) 38 | } 39 | return nil 40 | } 41 | 42 | // RunCmdWithEnv executes a command with custom environment variables. 43 | func RunCmdWithEnv(ctx context.Context, env []string, args ...string) error { 44 | return runCmd(ctx, "", env, args...) 45 | } 46 | 47 | // RunCmd executes a command with the default environment. 48 | func RunCmd(ctx context.Context, args ...string) error { 49 | return runCmd(ctx, "", nil, args...) 50 | } 51 | 52 | // RunCmdInDir executes a command in a specific directory. 53 | func RunCmdInDir(ctx context.Context, dir string, args ...string) error { 54 | return runCmd(ctx, dir, nil, args...) 55 | } 56 | 57 | func IsWindows() bool { 58 | return runtime.GOOS == "windows" 59 | } 60 | 61 | func IsUnix() bool { 62 | return runtime.GOOS == "linux" || runtime.GOOS == "darwin" 63 | } 64 | 65 | func CopyFile(src, dst string) error { 66 | _, err := os.Stat(filepath.Dir(dst)) 67 | if os.IsNotExist(err) { 68 | err = os.MkdirAll(filepath.Dir(dst), 0o755) 69 | if err != nil { 70 | return ex.Wrap(err) 71 | } 72 | } 73 | 74 | srcFile, err := os.Open(src) 75 | if err != nil { 76 | return ex.Wrap(err) 77 | } 78 | defer srcFile.Close() 79 | 80 | dstFile, err := os.Create(dst) 81 | if err != nil { 82 | return ex.Wrap(err) 83 | } 84 | defer dstFile.Close() 85 | 86 | _, err = io.Copy(dstFile, srcFile) 87 | if err != nil { 88 | return ex.Wrap(err) 89 | } 90 | return nil 91 | } 92 | 93 | func CRC32(s string) string { 94 | crc32Hash := crc32.ChecksumIEEE([]byte(s)) 95 | return strconv.FormatUint(uint64(crc32Hash), 10) 96 | } 97 | 98 | func ListFiles(dir string) ([]string, error) { 99 | var files []string 100 | walkFn := func(path string, info os.FileInfo, err error) error { 101 | if err != nil { 102 | return ex.Wrap(err) 103 | } 104 | // Don't list files under hidden directories 105 | if strings.HasPrefix(info.Name(), ".") { 106 | return filepath.SkipDir 107 | } 108 | if !info.IsDir() { 109 | files = append(files, path) 110 | } 111 | return nil 112 | } 113 | err := filepath.Walk(dir, walkFn) 114 | if err != nil { 115 | return nil, ex.Wrap(err) 116 | } 117 | return files, nil 118 | } 119 | 120 | func WriteFile(filePath, content string) error { 121 | file, err := os.Create(filePath) 122 | if err != nil { 123 | return ex.Wrap(err) 124 | } 125 | defer func(file *os.File) { 126 | err = file.Close() 127 | if err != nil { 128 | ex.Fatal(err) 129 | } 130 | }(file) 131 | 132 | _, err = file.WriteString(content) 133 | if err != nil { 134 | return ex.Wrap(err) 135 | } 136 | return nil 137 | } 138 | 139 | func PathExists(path string) bool { 140 | _, err := os.Stat(path) 141 | return err == nil 142 | } 143 | 144 | func NormalizePath(path string) string { 145 | return filepath.ToSlash(filepath.Clean(path)) 146 | } 147 | -------------------------------------------------------------------------------- /.github/workflows/test-integration.yaml: -------------------------------------------------------------------------------- 1 | name: Integration Tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | # Declare default permissions as read only. 8 | permissions: read-all 9 | jobs: 10 | # This is a required workflow, so it must always run and report a status. 11 | # We use dorny/paths-filter at the job level instead of workflow-level path filters 12 | # to ensure the workflow runs and the "done" job reports success even when no relevant files change. 13 | # This prevents PRs from being blocked by missing status checks. 14 | changes: 15 | name: Detect Changes 16 | runs-on: ubuntu-latest 17 | permissions: 18 | pull-requests: read 19 | outputs: 20 | code: ${{ steps.filter.outputs.code }} 21 | steps: 22 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 23 | - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 24 | id: filter 25 | with: 26 | filters: | 27 | code: 28 | - '**/*.go' 29 | - '**/go.mod' 30 | - '**/go.sum' 31 | - 'Makefile' 32 | - 'tool/**' 33 | - 'pkg/**' 34 | - 'test/**' 35 | - '.github/workflows/test-integration.yaml' 36 | 37 | test-integration-coverage: 38 | name: Coverage Integration Tests 39 | runs-on: ubuntu-latest 40 | needs: [changes] 41 | if: needs.changes.outputs.code == 'true' 42 | steps: 43 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 44 | - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 45 | with: 46 | go-version-file: go.mod 47 | - run: make test-integration/coverage 48 | - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 49 | 50 | test-integration: 51 | name: Integration Tests (go ${{ matrix.platform.os }} ${{ matrix.platform.arch }}) 52 | needs: [changes] 53 | if: needs.changes.outputs.code == 'true' 54 | strategy: 55 | matrix: 56 | platform: 57 | - os: ubuntu-latest 58 | arch: amd64 59 | - os: ubuntu-22.04-arm 60 | arch: arm64 61 | - os: macos-latest 62 | arch: arm64 63 | - os: windows-latest 64 | arch: amd64 65 | runs-on: ${{ matrix.platform.os }} 66 | steps: 67 | - name: Checkout code 68 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 69 | - name: Install Go 70 | uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 71 | with: 72 | go-version-file: go.mod 73 | cache-dependency-path: "**/go.mod" 74 | - name: Install protoc 75 | uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # ratchet:arduino/setup-protoc@v3 76 | with: 77 | repo-token: ${{ secrets.GITHUB_TOKEN }} 78 | - name: Build and test 79 | env: 80 | GOARCH: ${{ matrix.platform.arch }} 81 | run: make test-integration 82 | 83 | done: 84 | name: Done (Integration Tests) 85 | runs-on: ubuntu-latest 86 | needs: [changes, test-integration-coverage, test-integration] 87 | if: always() 88 | steps: 89 | - name: Check result 90 | run: | 91 | result="${{ needs.test-integration.result }}" 92 | echo "Test result: $result" 93 | if [[ "$result" == "success" || "$result" == "skipped" ]]; then 94 | echo "✅ Tests passed or were skipped" 95 | exit 0 96 | else 97 | echo "❌ Tests failed" 98 | exit 1 99 | fi 100 | -------------------------------------------------------------------------------- /pkg/instrumentation/grpc/client/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/grpc/client 2 | 3 | go 1.24.0 4 | 5 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg => ../../.. 6 | 7 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/shared => ../../shared 8 | 9 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/grpc => .. 10 | 11 | require ( 12 | github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg v0.0.0 13 | github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/grpc v0.0.0-00010101000000-000000000000 14 | github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/shared v0.0.0-00010101000000-000000000000 15 | github.com/stretchr/testify v1.11.1 16 | go.opentelemetry.io/otel v1.39.0 17 | go.opentelemetry.io/otel/metric v1.39.0 18 | go.opentelemetry.io/otel/sdk v1.39.0 19 | go.opentelemetry.io/otel/trace v1.39.0 20 | google.golang.org/grpc v1.77.0 21 | ) 22 | 23 | require ( 24 | github.com/beorn7/perks v1.0.1 // indirect 25 | github.com/cenkalti/backoff/v5 v5.0.3 // indirect 26 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 27 | github.com/davecgh/go-spew v1.1.1 // indirect 28 | github.com/go-logr/logr v1.4.3 // indirect 29 | github.com/go-logr/stdr v1.2.2 // indirect 30 | github.com/google/uuid v1.6.0 // indirect 31 | github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect 32 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect 33 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 34 | github.com/pmezard/go-difflib v1.0.0 // indirect 35 | github.com/prometheus/client_golang v1.23.0 // indirect 36 | github.com/prometheus/client_model v0.6.2 // indirect 37 | github.com/prometheus/common v0.65.0 // indirect 38 | github.com/prometheus/otlptranslator v0.0.2 // indirect 39 | github.com/prometheus/procfs v0.17.0 // indirect 40 | go.opentelemetry.io/auto/sdk v1.2.1 // indirect 41 | go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 // indirect 42 | go.opentelemetry.io/contrib/exporters/autoexport v0.63.0 // indirect 43 | go.opentelemetry.io/contrib/instrumentation/runtime v0.64.0 // indirect 44 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect 45 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect 46 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect 47 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect 48 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect 49 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect 50 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect 51 | go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect 52 | go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect 53 | go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect 54 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect 55 | go.opentelemetry.io/otel/log v0.14.0 // indirect 56 | go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect 57 | go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect 58 | go.opentelemetry.io/proto/otlp v1.7.1 // indirect 59 | golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect 60 | golang.org/x/sys v0.39.0 // indirect 61 | golang.org/x/text v0.30.0 // indirect 62 | google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect 63 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect 64 | google.golang.org/protobuf v1.36.10 // indirect 65 | gopkg.in/yaml.v3 v3.0.1 // indirect 66 | ) 67 | -------------------------------------------------------------------------------- /pkg/instrumentation/grpc/server/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/grpc/server 2 | 3 | go 1.24.0 4 | 5 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg => ../../.. 6 | 7 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/shared => ../../shared 8 | 9 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/grpc => .. 10 | 11 | require ( 12 | github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg v0.0.0 13 | github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/grpc v0.0.0-00010101000000-000000000000 14 | github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/shared v0.0.0-00010101000000-000000000000 15 | github.com/stretchr/testify v1.11.1 16 | go.opentelemetry.io/otel v1.39.0 17 | go.opentelemetry.io/otel/metric v1.39.0 18 | go.opentelemetry.io/otel/sdk v1.39.0 19 | go.opentelemetry.io/otel/trace v1.39.0 20 | google.golang.org/grpc v1.77.0 21 | ) 22 | 23 | require ( 24 | github.com/beorn7/perks v1.0.1 // indirect 25 | github.com/cenkalti/backoff/v5 v5.0.3 // indirect 26 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 27 | github.com/davecgh/go-spew v1.1.1 // indirect 28 | github.com/go-logr/logr v1.4.3 // indirect 29 | github.com/go-logr/stdr v1.2.2 // indirect 30 | github.com/google/uuid v1.6.0 // indirect 31 | github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect 32 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect 33 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 34 | github.com/pmezard/go-difflib v1.0.0 // indirect 35 | github.com/prometheus/client_golang v1.23.0 // indirect 36 | github.com/prometheus/client_model v0.6.2 // indirect 37 | github.com/prometheus/common v0.65.0 // indirect 38 | github.com/prometheus/otlptranslator v0.0.2 // indirect 39 | github.com/prometheus/procfs v0.17.0 // indirect 40 | go.opentelemetry.io/auto/sdk v1.2.1 // indirect 41 | go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 // indirect 42 | go.opentelemetry.io/contrib/exporters/autoexport v0.63.0 // indirect 43 | go.opentelemetry.io/contrib/instrumentation/runtime v0.64.0 // indirect 44 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect 45 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect 46 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect 47 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect 48 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect 49 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect 50 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect 51 | go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect 52 | go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect 53 | go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect 54 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect 55 | go.opentelemetry.io/otel/log v0.14.0 // indirect 56 | go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect 57 | go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect 58 | go.opentelemetry.io/proto/otlp v1.7.1 // indirect 59 | golang.org/x/net v0.46.1-0.20251013234738-63d1a5100f82 // indirect 60 | golang.org/x/sys v0.39.0 // indirect 61 | golang.org/x/text v0.30.0 // indirect 62 | google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect 63 | google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect 64 | google.golang.org/protobuf v1.36.10 // indirect 65 | gopkg.in/yaml.v3 v3.0.1 // indirect 66 | ) 67 | -------------------------------------------------------------------------------- /pkg/instrumentation/nethttp/client/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/nethttp/client 2 | 3 | go 1.24.0 4 | 5 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg => ../../.. 6 | 7 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/shared => ../../shared 8 | 9 | replace github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/nethttp => .. 10 | 11 | require ( 12 | github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg v0.0.0-20251208011108-ac0fa4a155e3 13 | github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/nethttp v0.0.0-20251208011108-ac0fa4a155e3 14 | github.com/open-telemetry/opentelemetry-go-compile-instrumentation/pkg/instrumentation/shared v0.0.0-20251208011108-ac0fa4a155e3 15 | github.com/stretchr/testify v1.11.1 16 | go.opentelemetry.io/otel v1.39.0 17 | go.opentelemetry.io/otel/sdk v1.39.0 18 | go.opentelemetry.io/otel/trace v1.39.0 19 | ) 20 | 21 | require ( 22 | github.com/beorn7/perks v1.0.1 // indirect 23 | github.com/cenkalti/backoff/v5 v5.0.3 // indirect 24 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 25 | github.com/davecgh/go-spew v1.1.1 // indirect 26 | github.com/go-logr/logr v1.4.3 // indirect 27 | github.com/go-logr/stdr v1.2.2 // indirect 28 | github.com/google/uuid v1.6.0 // indirect 29 | github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect 30 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect 31 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 32 | github.com/pmezard/go-difflib v1.0.0 // indirect 33 | github.com/prometheus/client_golang v1.23.0 // indirect 34 | github.com/prometheus/client_model v0.6.2 // indirect 35 | github.com/prometheus/common v0.65.0 // indirect 36 | github.com/prometheus/otlptranslator v0.0.2 // indirect 37 | github.com/prometheus/procfs v0.17.0 // indirect 38 | go.opentelemetry.io/auto/sdk v1.2.1 // indirect 39 | go.opentelemetry.io/contrib/bridges/prometheus v0.63.0 // indirect 40 | go.opentelemetry.io/contrib/exporters/autoexport v0.63.0 // indirect 41 | go.opentelemetry.io/contrib/instrumentation/runtime v0.64.0 // indirect 42 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 // indirect 43 | go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 // indirect 44 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect 45 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 // indirect 46 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect 47 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect 48 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect 49 | go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect 50 | go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.14.0 // indirect 51 | go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 // indirect 52 | go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 // indirect 53 | go.opentelemetry.io/otel/log v0.14.0 // indirect 54 | go.opentelemetry.io/otel/metric v1.39.0 // indirect 55 | go.opentelemetry.io/otel/sdk/log v0.14.0 // indirect 56 | go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect 57 | go.opentelemetry.io/proto/otlp v1.7.1 // indirect 58 | golang.org/x/net v0.43.0 // indirect 59 | golang.org/x/sys v0.39.0 // indirect 60 | golang.org/x/text v0.28.0 // indirect 61 | google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect 62 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect 63 | google.golang.org/grpc v1.75.0 // indirect 64 | google.golang.org/protobuf v1.36.8 // indirect 65 | gopkg.in/yaml.v3 v3.0.1 // indirect 66 | ) 67 | --------------------------------------------------------------------------------