├── 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 |

3 |
4 |

5 |

6 |

7 |

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 |
--------------------------------------------------------------------------------