Skip to content

Commit 378efdb

Browse files
authored
fix: import path may have alias name (#172)
* fix: import path may have alias name * fix once again * better * preclean should not remove deps * better logo * use %q format specifier * fine grind log
1 parent bd75fa4 commit 378efdb

File tree

8 files changed

+138
-76
lines changed

8 files changed

+138
-76
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
This project provides an automatic solution for Golang applications that want to
88
leverage OpenTelemetry to enable effective observability. No code changes are
9-
required in the target application, and the instrumentation is done at compile
10-
time. Simply replace `go build` with `otelbuild` to get started.
9+
required in the target application, the instrumentation is done at compile
10+
time. Simply replacing `go build` with `otelbuild` to get started :rocket:
1111

1212
# Installation
1313

1414
### Install via Bash
15-
For **Linux and MacOS** users, install the tool by running the following command :rocket:
15+
For **Linux and MacOS** users, install the tool by running the following command
1616
```bash
1717
$ sudo curl -fsSL https://cdn.jsdelivr.net/gh/alibaba/opentelemetry-go-auto-instrumentation@main/install.sh | sudo bash
1818
```

docs/anim-logo.svg

+2-2
Loading

pkg/data/test_error.json

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
"OnEnter": "onEnterTestSkip",
1313
"Path": "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/rules/test/error2"
1414
},
15+
{
16+
"ImportPath": "errors",
17+
"Function": "New",
18+
"OnEnter": "onEnterErrorsNewAlias",
19+
"Path": "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/rules/test/error3"
20+
},
1521
{
1622
"ImportPath": "errorstest/auxiliary",
1723
"Function": "TestSkip",

pkg/rules/test/error3/hook.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2024 Alibaba Group Holding Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package error2
16+
17+
import (
18+
erralias "errors"
19+
20+
"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/api"
21+
)
22+
23+
func onEnterErrorsNewAlias(call api.CallContext, text string) {
24+
// Check if alias name confuses compilation
25+
_ = erralias.ErrUnsupported
26+
}

tool/preprocess/pkgdep.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func replaceImport(importPath string, code string) string {
3333
return code
3434
}
3535

36-
func (dp *DepProcessor) replaceOtelImports(compileCmds []string) error {
36+
func (dp *DepProcessor) replaceOtelImports() error {
3737
moduleName, err := dp.getImportPathOf(OtelPkgDepsDir)
3838
if err != nil {
3939
return fmt.Errorf("failed to get import path of otel_pkg: %w", err)
@@ -42,7 +42,7 @@ func (dp *DepProcessor) replaceOtelImports(compileCmds []string) error {
4242
for _, dep := range []string{OtelRules, OtelPkgDepsDir} {
4343
files, err := util.ListFiles(dep)
4444
if err != nil {
45-
return fmt.Errorf("failed to list files during replacement: %w", err)
45+
return fmt.Errorf("failed to list files: %w", err)
4646
}
4747
for _, file := range files {
4848
// Skip non-go files as no imports within them

tool/preprocess/preprocess.go

+9-16
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const (
5555
DryRunLog = "dry_run.log"
5656
StdRulesPrefix = "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/"
5757
StdRulesPath = "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/rules"
58-
apiImport = "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/api"
58+
ApiPath = "github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/api"
5959
)
6060

6161
// @@ Change should sync with trampoline template
@@ -312,7 +312,9 @@ func (dp *DepProcessor) addExplicitImport(importPaths ...string) (err error) {
312312
// Prepend import path to the file
313313
for _, importPath := range importPaths {
314314
shared.AddImportForcely(astRoot, importPath)
315-
log.Printf("Add %s import to %v", importPath, file)
315+
if shared.Verbose {
316+
log.Printf("Add %s import to %v", importPath, file)
317+
}
316318
}
317319
addImport = true
318320

@@ -410,23 +412,12 @@ func (dp *DepProcessor) preclean() {
410412
if astRoot == nil {
411413
continue
412414
}
413-
if shared.RemoveImport(astRoot, ruleImport) {
415+
if shared.RemoveImport(astRoot, ruleImport) != nil {
414416
if shared.Verbose {
415417
log.Printf("Remove obsolete import %v from %v",
416418
ruleImport, file)
417419
}
418420
}
419-
for _, dep := range fixedDeps {
420-
if !dep.addImport {
421-
continue
422-
}
423-
if shared.RemoveImport(astRoot, dep.dep) {
424-
if shared.Verbose {
425-
log.Printf("Remove obsolete import %v from %v",
426-
dep, file)
427-
}
428-
}
429-
}
430421
_, err := shared.WriteAstToFile(astRoot, file)
431422
if err != nil {
432423
log.Printf("Failed to write ast to %v: %v", file, err)
@@ -552,7 +543,9 @@ func (dp *DepProcessor) pinDepVersion() error {
552543
for _, dep := range fixedDeps {
553544
p := dep.dep
554545
v := dep.version
555-
log.Printf("Pin dependency version %v@%v", p, v)
546+
if shared.Verbose {
547+
log.Printf("Pin dependency version %v@%v", p, v)
548+
}
556549
err := fetchDep(p + "@" + v)
557550
if err != nil {
558551
if dep.fallible {
@@ -664,7 +657,7 @@ func (dp *DepProcessor) setupDeps() error {
664657
return fmt.Errorf("failed to setup dependencies: %w", err)
665658
}
666659

667-
err = dp.replaceOtelImports(compileCmds)
660+
err = dp.replaceOtelImports()
668661
if err != nil {
669662
return fmt.Errorf("failed to replace otel imports: %w", err)
670663
}

tool/preprocess/setup.go

+59-18
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package preprocess
1616

1717
import (
1818
"fmt"
19+
"go/token"
1920
"log"
2021
"os"
2122
"path/filepath"
@@ -101,23 +102,24 @@ func (dp *DepProcessor) copyRules(target string) (err error) {
101102
}
102103
}
103104

104-
// for path, bundle := range uniqueResources {
105-
// dir := res2Dir[path]
106-
// ruleDir := filepath.Join(target, dir)
107-
// err = os.MkdirAll(ruleDir, 0777)
108-
// if err != nil {
109-
// return fmt.Errorf("failed to create directory %v: %w", ruleDir, err)
110-
// }
111-
// ruleFile := filepath.Join(ruleDir, filepath.Base(path))
112-
// err = dp.copyRule(path, ruleFile, bundle)
113-
// if err != nil {
114-
// return fmt.Errorf("failed to copy rule %v: %w", path, err)
115-
// }
116-
// }
117105
return nil
118106
}
119107

120-
func renameCallContext(astRoot *dst.File, pkgName string) {
108+
func renameCallContext(astRoot *dst.File, bundle *resource.RuleBundle) {
109+
pkgName := bundle.PackageName
110+
// Find out if the target import path is aliased to another name
111+
// if so, we need to rename api.CallContext to the alias name
112+
// instead of the package name
113+
for _, spec := range astRoot.Imports {
114+
// Same import path and alias name is not null?
115+
// One exception is the alias name is "_", we should ignore it
116+
if shared.IsStringLit(spec.Path, bundle.ImportPath) &&
117+
spec.Name != nil &&
118+
spec.Name.Name != shared.IdentIgnore {
119+
pkgName = spec.Name.Name
120+
break
121+
}
122+
}
121123
for _, decl := range astRoot.Decls {
122124
if f, ok := decl.(*dst.FuncDecl); ok {
123125
params := f.Type.Params.List
@@ -165,6 +167,45 @@ func makeHookPublic(astRoot *dst.File, bundle *resource.RuleBundle) {
165167
}
166168
}
167169

170+
func renameImport(root *dst.File, oldPath, newPath string) bool {
171+
// Find out if the old import and replace it with new one. Why we dont
172+
// remove old import and add new one? Because we are not sure if the
173+
// new import will be used, it's a compilation error if we import it
174+
// but never use it.
175+
for _, decl := range root.Decls {
176+
if genDecl, ok := decl.(*dst.GenDecl); ok &&
177+
genDecl.Tok == token.IMPORT {
178+
for _, spec := range genDecl.Specs {
179+
if importSpec, ok := spec.(*dst.ImportSpec); ok {
180+
if importSpec.Path.Value == fmt.Sprintf("%q", oldPath) {
181+
// In case the new import is already present, try to
182+
// remove it first
183+
oldSpec := shared.RemoveImport(root, newPath)
184+
// Replace old with new one
185+
importSpec.Path.Value = fmt.Sprintf("%q", newPath)
186+
// Respect alias name of old import, if any
187+
if oldSpec != nil {
188+
importSpec.Name = oldSpec.Name
189+
190+
// Unless the alias name is "_", we should keep it
191+
// For "_" alias, we should add additional normal
192+
// variant for CallContext usage, i.e. keep both
193+
// imports, one for existing usages, one for
194+
// CallContext usage
195+
if oldSpec.Name != nil &&
196+
oldSpec.Name.Name == shared.IdentIgnore {
197+
shared.AddImport(root, newPath)
198+
}
199+
}
200+
return true
201+
}
202+
}
203+
}
204+
}
205+
}
206+
return false
207+
}
208+
168209
func (dp *DepProcessor) copyRule(path, target string,
169210
bundle *resource.RuleBundle) error {
170211
text, err := util.ReadFile(path)
@@ -180,13 +221,13 @@ func (dp *DepProcessor) copyRule(path, target string,
180221
astRoot.Name.Name = filepath.Base(filepath.Dir(target))
181222

182223
// Rename api.CallContext to correct package name if present
183-
renameCallContext(astRoot, bundle.PackageName)
224+
renameCallContext(astRoot, bundle)
184225

185226
// Make hook functions public
186227
makeHookPublic(astRoot, bundle)
187228

188-
// Remove "api" import if present
189-
shared.ReplaceImport(astRoot, apiImport, bundle.ImportPath)
229+
// Rename "api" import to the correct package prefix
230+
renameImport(astRoot, ApiPath, bundle.ImportPath)
190231

191232
// Copy used rule into project
192233
_, err = shared.WriteAstToFile(astRoot, target)
@@ -287,7 +328,7 @@ func (dp *DepProcessor) initRules(pkgName, target string) (err error) {
287328
imports[OtelGetStackImportPath] = OtelGetStackAliasPkg
288329
}
289330
for k, v := range imports {
290-
c += fmt.Sprintf("import %s \"%s\"\n", v, k)
331+
c += fmt.Sprintf("import %s %q\n", v, k)
291332
}
292333

293334
// Assignments

tool/shared/ast.go

+31-35
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ func StringLit(value string) *dst.BasicLit {
6868
}
6969
}
7070

71+
func IsStringLit(expr dst.Expr, val string) bool {
72+
lit, ok := expr.(*dst.BasicLit)
73+
return ok &&
74+
lit.Kind == token.STRING &&
75+
lit.Value == fmt.Sprintf("%q", val)
76+
}
77+
7178
func IntLit(value int) *dst.BasicLit {
7279
return &dst.BasicLit{
7380
Kind: token.INT,
@@ -235,63 +242,52 @@ func AddStructField(decl dst.Decl, name string, typ string) {
235242
st.Fields.List = append(st.Fields.List, fd)
236243
}
237244

238-
func AddImportForcely(root *dst.File, path string) {
239-
importStmt := &dst.GenDecl{
240-
Tok: token.IMPORT,
241-
Specs: []dst.Spec{
242-
&dst.ImportSpec{
243-
Name: &dst.Ident{Name: IdentIgnore},
244-
Path: &dst.BasicLit{
245-
Kind: token.STRING,
246-
Value: fmt.Sprintf("\"%s\"", path),
247-
},
248-
},
245+
func addImport(root *dst.File, path string, force bool) {
246+
spec := &dst.ImportSpec{
247+
Path: &dst.BasicLit{
248+
Kind: token.STRING,
249+
Value: fmt.Sprintf("%q", path),
249250
},
250251
}
252+
if force {
253+
spec.Name = &dst.Ident{Name: IdentIgnore}
254+
}
255+
importStmt := &dst.GenDecl{
256+
Tok: token.IMPORT,
257+
Specs: []dst.Spec{spec},
258+
}
251259
root.Decls = append([]dst.Decl{importStmt}, root.Decls...)
252260
}
253261

254-
func RemoveImport(root *dst.File, path string) bool {
262+
func AddImportForcely(root *dst.File, path string) {
263+
addImport(root, path, true)
264+
}
265+
266+
func AddImport(root *dst.File, path string) {
267+
addImport(root, path, false)
268+
}
269+
270+
func RemoveImport(root *dst.File, path string) *dst.ImportSpec {
255271
for j, decl := range root.Decls {
256272
if genDecl, ok := decl.(*dst.GenDecl); ok &&
257273
genDecl.Tok == token.IMPORT {
258274
for i, spec := range genDecl.Specs {
259275
if importSpec, ok := spec.(*dst.ImportSpec); ok {
260-
if importSpec.Path.Value == fmt.Sprintf("\"%s\"", path) {
276+
if importSpec.Path.Value == fmt.Sprintf("%q", path) {
261277
genDecl.Specs =
262278
append(genDecl.Specs[:i],
263279
genDecl.Specs[i+1:]...)
264280
if len(genDecl.Specs) == 0 {
265281
root.Decls =
266282
append(root.Decls[:j], root.Decls[j+1:]...)
267283
}
268-
return true
284+
return importSpec
269285
}
270286
}
271287
}
272288
}
273289
}
274-
return false
275-
}
276-
277-
func ReplaceImport(root *dst.File, oldPath, newPath string) bool {
278-
for _, decl := range root.Decls {
279-
if genDecl, ok := decl.(*dst.GenDecl); ok &&
280-
genDecl.Tok == token.IMPORT {
281-
for _, spec := range genDecl.Specs {
282-
if importSpec, ok := spec.(*dst.ImportSpec); ok {
283-
if importSpec.Path.Value == fmt.Sprintf("\"%s\"", oldPath) {
284-
// In case the import is already present, try to remove
285-
// it first
286-
RemoveImport(root, newPath)
287-
importSpec.Path.Value = fmt.Sprintf("\"%s\"", newPath)
288-
return true
289-
}
290-
}
291-
}
292-
}
293-
}
294-
return false
290+
return nil
295291
}
296292

297293
func NewVarDecl(name string, paramTypes *dst.FieldList) *dst.GenDecl {

0 commit comments

Comments
 (0)