You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2023/04/21 10:29:40 UTC
[skywalking-go] branch main updated: Add the plugin API (#9)
This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-go.git
The following commit(s) were added to refs/heads/main by this push:
new 3bd9f26 Add the plugin API (#9)
3bd9f26 is described below
commit 3bd9f2606a87ab0daa738f7241b0f59d3b518fd4
Author: mrproliu <74...@qq.com>
AuthorDate: Fri Apr 21 18:29:34 2023 +0800
Add the plugin API (#9)
---
.licenserc.yaml | 1 +
Makefile | 1 +
agent/core/compile.go | 3 +
imports.go => agent/core/operator/compile.go | 12 +-
agent/core/tracing/compile.go | 2 +-
imports.go | 1 +
plugins/core/context.go | 60 ++--
plugins/core/go.mod | 3 +
plugins/core/go.sum | 9 +
imports.go => plugins/core/instrument/declare.go | 24 +-
plugins/core/instrument/enhance.go | 74 ++++
plugins/core/instrument/method.go | 56 +++
plugins/core/instrument/method_test.go | 127 +++++++
.../files.go => plugins/core/instrument/struct.go | 34 +-
plugins/core/instrument/struct_test.go | 93 +++++
plugins/core/{intercepter.go => log/logger.go} | 34 +-
.../flags.go => plugins/core/operator/common.go | 23 +-
plugins/core/{ => operator}/intercepter.go | 2 +-
imports.go => plugins/core/operator/logger.go | 16 +-
.../core/{intercepter.go => operator/tracing.go} | 26 +-
plugins/core/span.go | 31 +-
plugins/core/span_default.go | 28 +-
plugins/core/span_noop.go | 12 +-
plugins/core/span_tracing.go | 19 +-
plugins/core/test_base.go | 20 +-
plugins/core/tracer.go | 2 +
plugins/core/tracing.go | 241 +++++++++++++
plugins/core/tracing/api.go | 254 ++++----------
plugins/core/tracing/api_test.go | 346 -------------------
.../compile.go => plugins/core/tracing/bridge.go | 63 +++-
plugins/core/tracing/span.go | 111 ++++++
plugins/core/tracing_test.go | 364 +++++++++++++++++++
plugins/ginv2/go.mod | 39 +++
plugins/ginv2/go.sum | 91 +++++
plugins/{core/tracer.go => ginv2/instrument.go} | 53 ++-
plugins/ginv2/intercepter.go | 60 ++++
tools/go-agent/cmd/helper.go | 3 +-
tools/go-agent/cmd/main.go | 49 ++-
tools/go-agent/go.mod | 29 +-
tools/go-agent/go.sum | 51 +++
tools/go-agent/instrument/agentcore/instrument.go | 98 ++++--
tools/go-agent/instrument/api/flags.go | 7 +-
tools/go-agent/instrument/api/instrument.go | 5 +-
.../instrument/framework/enhance_instance.go | 100 ++++++
.../instrument/framework/enhance_method.go | 230 ++++++++++++
tools/go-agent/instrument/framework/instrument.go | 384 +++++++++++++++++++++
.../go-agent/instrument/framework/register.go | 25 +-
.../instrument/framework/rewrite/context.go | 298 ++++++++++++++++
.../go-agent/instrument/framework/rewrite/func.go | 205 +++++++++++
.../instrument/framework/rewrite/import.go | 57 +++
.../instrument/framework/rewrite/rewrite.go | 115 ++++++
.../go-agent/instrument/framework/rewrite/type.go | 56 +++
.../framework/rewrite/var.go} | 33 +-
.../framework/templates/method_inserts.tmpl | 11 +
.../templates/method_intercept_after.tmpl | 17 +
.../templates/method_intercept_before.tmpl | 33 ++
tools/go-agent/instrument/instrument.go | 25 +-
tools/go-agent/instrument/runtime/instrument.go | 106 ++++--
tools/go-agent/tools/dst.go | 142 +++++++-
tools/go-agent/tools/enhancement.go | 11 +
tools/go-agent/tools/files.go | 8 +
tools/go-agent/tools/flags.go | 44 ++-
62 files changed, 3608 insertions(+), 869 deletions(-)
diff --git a/.licenserc.yaml b/.licenserc.yaml
index 1a02b30..9c588cd 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -31,6 +31,7 @@ header:
- 'NOTICE'
- '.gitignore'
- 'dist/LICENSE.tpl'
+ - '**/*.tmpl'
comment: on-failure
diff --git a/Makefile b/Makefile
index 4494050..790f1d8 100644
--- a/Makefile
+++ b/Makefile
@@ -41,6 +41,7 @@ test:
echo "mode: atomic" > ${REPODIR}/coverage.txt;
@for dir in $$(find . -name go.mod -exec dirname {} \; ); do \
cd $$dir; \
+ echo "Testing $$dir"; \
go test -v -coverprofile=module_coverage.txt -covermode=atomic ./...; \
test_status=$$?; \
if [ -f module_coverage.txt ]; then \
diff --git a/agent/core/compile.go b/agent/core/compile.go
index c6b078a..9cbc9e3 100644
--- a/agent/core/compile.go
+++ b/agent/core/compile.go
@@ -23,6 +23,7 @@ import (
_ "fmt"
_ "math"
_ "math/rand"
+ _ "reflect"
_ "strconv"
_ "strings"
_ "sync"
@@ -30,6 +31,8 @@ import (
_ "time"
//go:nolint
+ _ "github.com/apache/skywalking-go/agent/core/operator"
+ _ "github.com/apache/skywalking-go/agent/core/tracing"
_ "github.com/apache/skywalking-go/reporter"
//go:nolint
diff --git a/imports.go b/agent/core/operator/compile.go
similarity index 76%
copy from imports.go
copy to agent/core/operator/compile.go
index ae50979..be5da0e 100644
--- a/imports.go
+++ b/agent/core/operator/compile.go
@@ -15,14 +15,4 @@
// specific language governing permissions and limitations
// under the License.
-package skywalking
-
-import (
- // base module imports
- _ "github.com/google/uuid"
- _ "github.com/pkg/errors"
-
- // force compile for the agent core copied
- _ "github.com/apache/skywalking-go/agent/core"
- _ "github.com/apache/skywalking-go/agent/core/tracing"
-)
+package operator
diff --git a/agent/core/tracing/compile.go b/agent/core/tracing/compile.go
index 5ccde8b..a7a5458 100644
--- a/agent/core/tracing/compile.go
+++ b/agent/core/tracing/compile.go
@@ -26,7 +26,7 @@ import (
_ "github.com/pkg/errors"
//go:nolint
- _ "github.com/apache/skywalking-go/agent/core"
+ _ "github.com/apache/skywalking-go/agent/core/operator"
_ "github.com/apache/skywalking-go/reporter"
//go:nolint
diff --git a/imports.go b/imports.go
index ae50979..5f0f76b 100644
--- a/imports.go
+++ b/imports.go
@@ -24,5 +24,6 @@ import (
// force compile for the agent core copied
_ "github.com/apache/skywalking-go/agent/core"
+ _ "github.com/apache/skywalking-go/agent/core/operator"
_ "github.com/apache/skywalking-go/agent/core/tracing"
)
diff --git a/plugins/core/context.go b/plugins/core/context.go
index 87b7f6e..cc8ddf4 100644
--- a/plugins/core/context.go
+++ b/plugins/core/context.go
@@ -17,16 +17,47 @@
package core
+import "reflect"
+
var (
- GetGLS = func() interface{} { return nil }
- SetGLS = func(interface{}) {}
+ GetGLS = func() interface{} { return nil }
+ SetGLS = func(interface{}) {}
+ SetGlobalOperator = func(interface{}) {}
+ GetGlobalOperator = func() interface{} { return nil }
)
+type ContextSnapshoter interface {
+ TakeSnapShot(val interface{}) interface{}
+}
+
type TracingContext struct {
- ActiveSpan Span
+ activeSpan TracingSpan
Runtime *RuntimeContext
}
+func (t *TracingContext) TakeSnapShot(val interface{}) interface{} {
+ snapshot := newSnapshotSpan(t.ActiveSpan())
+ return &TracingContext{
+ activeSpan: snapshot,
+ Runtime: t.Runtime.clone(),
+ }
+}
+
+func (t *TracingContext) ActiveSpan() TracingSpan {
+ if t.activeSpan == nil || reflect.ValueOf(t.activeSpan).IsZero() {
+ return nil
+ }
+ return t.activeSpan
+}
+
+func (t *TracingContext) SaveActiveSpan(s TracingSpan) {
+ t.activeSpan = s
+}
+
+func (t *TracingContext) RuntimeContext() *RuntimeContext {
+ return t.Runtime
+}
+
type RuntimeContext struct {
data map[string]interface{}
}
@@ -49,14 +80,6 @@ func (r *RuntimeContext) clone() *RuntimeContext {
}
}
-func GetTracingContext() *TracingContext {
- gls := GetGLS()
- if gls == nil {
- return nil
- }
- return gls.(*TracingContext)
-}
-
func (r *RuntimeContext) Get(key string) interface{} {
return r.data[key]
}
@@ -64,18 +87,3 @@ func (r *RuntimeContext) Get(key string) interface{} {
func (r *RuntimeContext) Set(key string, value interface{}) {
r.data[key] = value
}
-
-func TaskTracingContextSnapshot(val interface{}) interface{} {
- if val == nil {
- return nil
- }
- context, ok := val.(*TracingContext)
- if !ok {
- return nil
- }
- snapshot := newSnapshotSpan(context.ActiveSpan)
- return &TracingContext{
- ActiveSpan: snapshot,
- Runtime: context.Runtime.clone(),
- }
-}
diff --git a/plugins/core/go.mod b/plugins/core/go.mod
index 2ade84b..4700958 100644
--- a/plugins/core/go.mod
+++ b/plugins/core/go.mod
@@ -4,6 +4,7 @@ go 1.18
require (
github.com/apache/skywalking-go v0.0.0-20230411034404-b9270e98036b
+ github.com/dave/dst v0.27.2
github.com/google/uuid v1.3.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.2
@@ -14,9 +15,11 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
+ golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
+ golang.org/x/tools v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.29.0 // indirect
diff --git a/plugins/core/go.sum b/plugins/core/go.sum
index 7003eeb..c7e5d5a 100644
--- a/plugins/core/go.sum
+++ b/plugins/core/go.sum
@@ -9,6 +9,9 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/dave/dst v0.27.2 h1:4Y5VFTkhGLC1oddtNwuxxe36pnyLxMFXT51FOzH8Ekc=
+github.com/dave/dst v0.27.2/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
+github.com/dave/jennifer v1.5.0 h1:HmgPN93bVDpkQyYbqhCHj5QlgvUkvEOzMyEvKLgCRrg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -54,6 +57,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -78,6 +82,8 @@ golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -100,6 +106,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -132,6 +139,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/imports.go b/plugins/core/instrument/declare.go
similarity index 75%
copy from imports.go
copy to plugins/core/instrument/declare.go
index ae50979..79a8709 100644
--- a/imports.go
+++ b/plugins/core/instrument/declare.go
@@ -15,14 +15,20 @@
// specific language governing permissions and limitations
// under the License.
-package skywalking
+package instrument
-import (
- // base module imports
- _ "github.com/google/uuid"
- _ "github.com/pkg/errors"
+import "embed"
- // force compile for the agent core copied
- _ "github.com/apache/skywalking-go/agent/core"
- _ "github.com/apache/skywalking-go/agent/core/tracing"
-)
+type Instrument interface {
+ Name() string
+ BasePackage() string
+ VersionChecker(version string) bool
+ Points() []*Point
+ FS() *embed.FS
+}
+
+type Point struct {
+ PackagePath string
+ At *EnhanceMatcher
+ Interceptor string
+}
diff --git a/plugins/core/instrument/enhance.go b/plugins/core/instrument/enhance.go
new file mode 100644
index 0000000..cf24b86
--- /dev/null
+++ b/plugins/core/instrument/enhance.go
@@ -0,0 +1,74 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package instrument
+
+import "github.com/dave/dst"
+
+type EnhanceType int
+
+var (
+ EnhanceTypeMethod EnhanceType = 1
+ EnhanceTypeStruct EnhanceType = 2
+)
+
+type MethodFilterOption func(decl *dst.FuncDecl, files []*dst.File) bool
+type StructFilterOption func(structType *dst.TypeSpec, files []*dst.File) bool
+
+type EnhanceMatcher struct {
+ Type EnhanceType
+ Name string
+ Receiver string
+ MethodFilters []MethodFilterOption
+ StructFilters []StructFilterOption
+}
+
+func NewStaticMethodEnhance(name string, filters ...MethodFilterOption) *EnhanceMatcher {
+ return &EnhanceMatcher{Type: EnhanceTypeMethod, Name: name, MethodFilters: filters}
+}
+
+func NewMethodEnhance(receiver, name string, filters ...MethodFilterOption) *EnhanceMatcher {
+ return &EnhanceMatcher{Type: EnhanceTypeMethod, Name: name, Receiver: receiver, MethodFilters: filters}
+}
+
+func NewStructEnhance(name string, filters ...StructFilterOption) *EnhanceMatcher {
+ return &EnhanceMatcher{Type: EnhanceTypeStruct, Name: name, StructFilters: filters}
+}
+
+func verifyTypeName(exp dst.Expr, val string) bool {
+ data := generateTypeNameByExp(exp)
+ return data == val
+}
+
+func generateTypeNameByExp(exp dst.Expr) string {
+ var data string
+ switch n := exp.(type) {
+ case *dst.StarExpr:
+ data = "*" + generateTypeNameByExp(n.X)
+ case *dst.TypeAssertExpr:
+ data = generateTypeNameByExp(n.X)
+ case *dst.InterfaceType:
+ data = "interface{}"
+ case *dst.Ident:
+ data = n.Name
+ case *dst.SelectorExpr:
+ data = generateTypeNameByExp(n.X) + "." + generateTypeNameByExp(n.Sel)
+ default:
+ return ""
+ }
+ return data
+}
diff --git a/plugins/core/instrument/method.go b/plugins/core/instrument/method.go
new file mode 100644
index 0000000..f4706bb
--- /dev/null
+++ b/plugins/core/instrument/method.go
@@ -0,0 +1,56 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package instrument
+
+import "github.com/dave/dst"
+
+func WithArgsCount(argsCount int) MethodFilterOption {
+ return func(method *dst.FuncDecl, files []*dst.File) bool {
+ return (method.Type.Params == nil && len(method.Type.Params.List) == argsCount) || (len(method.Type.Params.List) == argsCount)
+ }
+}
+
+func WithResultCount(resultCount int) MethodFilterOption {
+ return func(decl *dst.FuncDecl, files []*dst.File) bool {
+ return (decl.Type.Results == nil && resultCount == 0) || (len(decl.Type.Results.List) == resultCount)
+ }
+}
+
+func WithArgType(argIndex int, dataType string) MethodFilterOption {
+ return func(decl *dst.FuncDecl, files []*dst.File) bool {
+ if len(decl.Type.Params.List) <= argIndex {
+ return false
+ }
+ return verifyTypeName(decl.Type.Params.List[argIndex].Type, dataType)
+ }
+}
+
+func WithStaticMethod() MethodFilterOption {
+ return func(decl *dst.FuncDecl, files []*dst.File) bool {
+ return decl.Recv == nil || len(decl.Recv.List) == 0
+ }
+}
+
+func WithReceiverType(dataType string) MethodFilterOption {
+ return func(decl *dst.FuncDecl, files []*dst.File) bool {
+ if decl.Recv == nil || len(decl.Recv.List) == 0 {
+ return false
+ }
+ return verifyTypeName(decl.Recv.List[0].Type, dataType)
+ }
+}
diff --git a/plugins/core/instrument/method_test.go b/plugins/core/instrument/method_test.go
new file mode 100644
index 0000000..b545a6d
--- /dev/null
+++ b/plugins/core/instrument/method_test.go
@@ -0,0 +1,127 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package instrument
+
+import (
+ "embed"
+ "go/parser"
+ "testing"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/decorator"
+ "github.com/dave/dst/dstutil"
+
+ "github.com/stretchr/testify/assert"
+)
+
+//go:embed method_test.go
+var methodFS embed.FS
+
+// nolint
+func testStaticMethod(i int) *TestStruct {
+ return nil
+}
+
+// nolint
+func (t *TestStruct) testMethod(d interface{}) {
+
+}
+
+func TestStaticMethodFilter(t *testing.T) {
+ var tests = []struct {
+ filter MethodFilterOption
+ found bool
+ }{
+ {
+ filter: WithArgsCount(1),
+ found: true,
+ },
+ {
+ filter: WithResultCount(1),
+ found: true,
+ },
+ {
+ filter: WithArgType(0, "int"),
+ found: true,
+ },
+ {
+ filter: WithStaticMethod(),
+ found: true,
+ },
+ }
+
+ file, err := methodFS.ReadFile("method_test.go")
+ assert.Nil(t, err, "reading file error: %v", err)
+ f, err := decorator.ParseFile(nil, "method.go", file, parser.ParseComments)
+ assert.Nil(t, err, "parse file error: %v", err)
+ var funcDecl *dst.FuncDecl
+ dstutil.Apply(f, func(cursor *dstutil.Cursor) bool {
+ if s, ok := cursor.Node().(*dst.FuncDecl); ok && s.Name.Name == "testStaticMethod" {
+ funcDecl = s
+ return false
+ }
+ return true
+ }, func(cursor *dstutil.Cursor) bool {
+ return true
+ })
+ assert.NotNil(t, funcDecl, "cannot found the function")
+ for i, tt := range tests {
+ got := tt.filter(funcDecl, []*dst.File{f})
+ assert.Equal(t, tt.found, got, "not correct with case: %d", i)
+ }
+}
+
+func TestReceiverMethodFilter(t *testing.T) {
+ var tests = []struct {
+ filter MethodFilterOption
+ found bool
+ }{
+ {
+ filter: WithArgType(0, "interface{}"),
+ found: true,
+ },
+ {
+ filter: WithResultCount(0),
+ found: true,
+ },
+ {
+ filter: WithReceiverType("*TestStruct"),
+ found: true,
+ },
+ }
+
+ file, err := methodFS.ReadFile("method_test.go")
+ assert.Nil(t, err, "reading file error: %v", err)
+ f, err := decorator.ParseFile(nil, "method.go", file, parser.ParseComments)
+ assert.Nil(t, err, "parse file error: %v", err)
+ var funcDecl *dst.FuncDecl
+ dstutil.Apply(f, func(cursor *dstutil.Cursor) bool {
+ if s, ok := cursor.Node().(*dst.FuncDecl); ok && s.Name.Name == "testMethod" {
+ funcDecl = s
+ return false
+ }
+ return true
+ }, func(cursor *dstutil.Cursor) bool {
+ return true
+ })
+ assert.NotNil(t, funcDecl, "cannot found the function")
+ for i, tt := range tests {
+ got := tt.filter(funcDecl, []*dst.File{f})
+ assert.Equal(t, tt.found, got, "not correct with case: %d", i)
+ }
+}
diff --git a/tools/go-agent/tools/files.go b/plugins/core/instrument/struct.go
similarity index 55%
copy from tools/go-agent/tools/files.go
copy to plugins/core/instrument/struct.go
index 99ba059..cd35aac 100644
--- a/tools/go-agent/tools/files.go
+++ b/plugins/core/instrument/struct.go
@@ -15,22 +15,30 @@
// specific language governing permissions and limitations
// under the License.
-package tools
+package instrument
-import (
- "os"
- "path/filepath"
-)
+import "github.com/dave/dst"
-func WriteMultipleFile(baseDir string, nameWithData map[string]string) ([]string, error) {
- paths := make([]string, 0)
- for name, data := range nameWithData {
- fileName := filepath.Join(baseDir, name)
- if err := os.WriteFile(fileName, []byte(data), 0o600); err != nil {
- return nil, err
+func WithFieldExists(fieldName string) StructFilterOption {
+ return func(structType *dst.TypeSpec, files []*dst.File) bool {
+ st := structType.Type.(*dst.StructType)
+ for _, field := range st.Fields.List {
+ if field.Names[0].Name == fieldName {
+ return true
+ }
}
- paths = append(paths, fileName)
+ return false
}
+}
- return paths, nil
+func WithFiledType(filedName, filedType string) StructFilterOption {
+ return func(structType *dst.TypeSpec, files []*dst.File) bool {
+ st := structType.Type.(*dst.StructType)
+ for _, field := range st.Fields.List {
+ if field.Names[0].Name == filedName {
+ return verifyTypeName(field.Type, filedType)
+ }
+ }
+ return false
+ }
}
diff --git a/plugins/core/instrument/struct_test.go b/plugins/core/instrument/struct_test.go
new file mode 100644
index 0000000..2c3e1c1
--- /dev/null
+++ b/plugins/core/instrument/struct_test.go
@@ -0,0 +1,93 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package instrument
+
+import (
+ "embed"
+ "go/parser"
+ "testing"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/decorator"
+ "github.com/dave/dst/dstutil"
+
+ "github.com/stretchr/testify/assert"
+)
+
+//go:embed struct_test.go
+var structFS embed.FS
+
+// nolint
+type TestStruct struct {
+ field1 interface{}
+ field2 int
+ field3 *TestStruct
+ field4 *embed.FS
+}
+
+func TestStructFilter(t *testing.T) {
+ var tests = []struct {
+ filter StructFilterOption
+ found bool
+ }{
+ {
+ filter: WithFieldExists("field1"),
+ found: true,
+ },
+ {
+ filter: WithFieldExists("field5"),
+ found: false,
+ },
+ {
+ filter: WithFiledType("field1", "interface{}"),
+ found: true,
+ },
+ {
+ filter: WithFiledType("field3", "*TestStruct"),
+ found: true,
+ },
+ {
+ filter: WithFiledType("field4", "*embed.FS"),
+ found: true,
+ },
+ {
+ filter: WithFiledType("field2", "string"),
+ found: false,
+ },
+ }
+
+ file, err := structFS.ReadFile("struct_test.go")
+ assert.Nil(t, err, "reading file error: %v", err)
+ f, err := decorator.ParseFile(nil, "struct.go", file, parser.ParseComments)
+ assert.Nil(t, err, "parse file error: %v", err)
+ var typeSpec *dst.TypeSpec
+ dstutil.Apply(f, func(cursor *dstutil.Cursor) bool {
+ if s, ok := cursor.Node().(*dst.TypeSpec); ok {
+ typeSpec = s
+ return false
+ }
+ return true
+ }, func(cursor *dstutil.Cursor) bool {
+ return true
+ })
+ assert.NotNil(t, typeSpec, "cannot found the structure")
+ for i, tt := range tests {
+ got := tt.filter(typeSpec, []*dst.File{f})
+ assert.Equal(t, tt.found, got, "not correct with case: %d", i)
+ }
+}
diff --git a/plugins/core/intercepter.go b/plugins/core/log/logger.go
similarity index 59%
copy from plugins/core/intercepter.go
copy to plugins/core/log/logger.go
index 8efa903..3e9388f 100644
--- a/plugins/core/intercepter.go
+++ b/plugins/core/log/logger.go
@@ -15,24 +15,30 @@
// specific language governing permissions and limitations
// under the License.
-package core
+package log
-type Invocation struct {
- CallerInstance interface{}
- Args []interface{}
+import "github.com/apache/skywalking-go/plugins/core/operator"
- Continue bool
- Return []interface{}
-
- Context interface{}
+func Infof(format string, args ...interface{}) {
+ op := operator.GetOperator()
+ if op == nil {
+ return
+ }
+ op.Logger().(operator.LogOperator).Infof(format, args...)
}
-type EnhancedInstance interface {
- GetSkyWalkingDynamicField() interface{}
- SetSkyWalkingDynamicField(interface{})
+func Warnf(format string, args ...interface{}) {
+ op := operator.GetOperator()
+ if op == nil {
+ return
+ }
+ op.Logger().(operator.LogOperator).Warnf(format, args...)
}
-type Interceptor interface {
- BeforeInvoke(invocation *Invocation) error
- AfterInvoke(invocation *Invocation, result ...interface{}) error
+func Errorf(format string, args ...interface{}) {
+ op := operator.GetOperator()
+ if op == nil {
+ return
+ }
+ op.Logger().(operator.LogOperator).Errorf(format, args...)
}
diff --git a/tools/go-agent/instrument/api/flags.go b/plugins/core/operator/common.go
similarity index 67%
copy from tools/go-agent/instrument/api/flags.go
copy to plugins/core/operator/common.go
index 0a39718..24d4e18 100644
--- a/tools/go-agent/instrument/api/flags.go
+++ b/plugins/core/operator/common.go
@@ -15,19 +15,24 @@
// specific language governing permissions and limitations
// under the License.
-package api
+package operator
-import "path/filepath"
+var GetOperator = func() Operator { return nil }
-type CompileOptions struct {
- Package string `skyflag:"-p"`
- Output string `skyflag:"-o"`
+type Operator interface {
+ Tracing() interface{} // to TracingOperator
+ Logger() interface{} // to LogOperator
}
-func (c *CompileOptions) IsValid() bool {
- return c.Package != "" && c.Output != ""
+// OperateError reduce the "fmt" package import
+type OperateError struct {
+ message string
}
-func (c *CompileOptions) CompileBaseDir() string {
- return filepath.Dir(filepath.Dir(c.Output))
+func (e OperateError) Error() string {
+ return e.message
+}
+
+func NewError(message string) error {
+ return OperateError{message: message}
}
diff --git a/plugins/core/intercepter.go b/plugins/core/operator/intercepter.go
similarity index 98%
copy from plugins/core/intercepter.go
copy to plugins/core/operator/intercepter.go
index 8efa903..fe3146a 100644
--- a/plugins/core/intercepter.go
+++ b/plugins/core/operator/intercepter.go
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-package core
+package operator
type Invocation struct {
CallerInstance interface{}
diff --git a/imports.go b/plugins/core/operator/logger.go
similarity index 76%
copy from imports.go
copy to plugins/core/operator/logger.go
index ae50979..3534b1e 100644
--- a/imports.go
+++ b/plugins/core/operator/logger.go
@@ -15,14 +15,10 @@
// specific language governing permissions and limitations
// under the License.
-package skywalking
+package operator
-import (
- // base module imports
- _ "github.com/google/uuid"
- _ "github.com/pkg/errors"
-
- // force compile for the agent core copied
- _ "github.com/apache/skywalking-go/agent/core"
- _ "github.com/apache/skywalking-go/agent/core/tracing"
-)
+type LogOperator interface {
+ Infof(format string, args ...interface{})
+ Warnf(format string, args ...interface{})
+ Errorf(format string, args ...interface{})
+}
diff --git a/plugins/core/intercepter.go b/plugins/core/operator/tracing.go
similarity index 62%
rename from plugins/core/intercepter.go
rename to plugins/core/operator/tracing.go
index 8efa903..c98a45a 100644
--- a/plugins/core/intercepter.go
+++ b/plugins/core/operator/tracing.go
@@ -15,24 +15,14 @@
// specific language governing permissions and limitations
// under the License.
-package core
+package operator
-type Invocation struct {
- CallerInstance interface{}
- Args []interface{}
+type TracingOperator interface {
+ CreateEntrySpan(operationName string, extractor interface{}, opts ...interface{}) (s interface{}, err error)
+ CreateLocalSpan(operationName string, opts ...interface{}) (s interface{}, err error)
+ CreateExitSpan(operationName, peer string, injector interface{}, opts ...interface{}) (s interface{}, err error)
+ ActiveSpan() interface{} // to Span
- Continue bool
- Return []interface{}
-
- Context interface{}
-}
-
-type EnhancedInstance interface {
- GetSkyWalkingDynamicField() interface{}
- SetSkyWalkingDynamicField(interface{})
-}
-
-type Interceptor interface {
- BeforeInvoke(invocation *Invocation) error
- AfterInvoke(invocation *Invocation, result ...interface{}) error
+ GetRuntimeContextValue(key string) interface{}
+ SetRuntimeContextValue(key string, value interface{})
}
diff --git a/plugins/core/span.go b/plugins/core/span.go
index 4d797d5..0099ef2 100644
--- a/plugins/core/span.go
+++ b/plugins/core/span.go
@@ -18,7 +18,7 @@
package core
import (
- "time"
+ "github.com/apache/skywalking-go/plugins/core/tracing"
agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
)
@@ -26,11 +26,6 @@ import (
// SpanType is used to identify entry, exit and local
type SpanType int32
-// Tag are supported by sky-walking engine.
-// As default, all Tags will be stored, but these ones have
-// particular meanings.
-type Tag string
-
const (
// SpanTypeEntry is a entry span, eg http server
SpanTypeEntry SpanType = 0
@@ -40,35 +35,19 @@ const (
SpanTypeLocal SpanType = 2
)
-const (
- TagURL Tag = "url"
- TagStatusCode Tag = "status_code"
- TagHTTPMethod Tag = "http.method"
- TagDBType Tag = "db.type"
- TagDBInstance Tag = "db.instance"
- TagDBStatement Tag = "db.statement"
- TagDBSqlParameters Tag = "db.sql.parameters"
- TagMQQueue Tag = "mq.queue"
- TagMQBroker Tag = "mq.broker"
- TagMQTopic Tag = "mq.topic"
-)
-
-// Span interface as commonv3 span specification
-type Span interface {
+// TracingSpan interface as commonv3 span specification
+type TracingSpan interface {
+ tracing.AdaptSpan
SetOperationName(string)
GetOperationName() string
SetPeer(string)
GetPeer() string
- SetSpanLayer(agentv3.SpanLayer)
GetSpanLayer() agentv3.SpanLayer
SetComponent(int32)
GetComponent() int32
- Tag(Tag, string)
- Log(time.Time, ...string)
- Error(time.Time, ...string)
End()
IsEntry() bool
IsExit() bool
IsValid() bool
- ParentSpan() Span
+ ParentSpan() TracingSpan
}
diff --git a/plugins/core/span_default.go b/plugins/core/span_default.go
index ad9db39..f887883 100644
--- a/plugins/core/span_default.go
+++ b/plugins/core/span_default.go
@@ -40,10 +40,10 @@ type DefaultSpan struct {
Logs []*agentv3.Log
IsError bool
SpanType SpanType
- Parent Span
+ Parent TracingSpan
}
-func NewDefaultSpan(tracer *Tracer, parent Span) *DefaultSpan {
+func NewDefaultSpan(tracer *Tracer, parent TracingSpan) *DefaultSpan {
return &DefaultSpan{
tracer: tracer,
StartTime: time.Now(),
@@ -52,7 +52,7 @@ func NewDefaultSpan(tracer *Tracer, parent Span) *DefaultSpan {
}
}
-// For Span
+// For TracingSpan
func (ds *DefaultSpan) SetOperationName(name string) {
ds.OperationName = name
}
@@ -69,8 +69,8 @@ func (ds *DefaultSpan) GetPeer() string {
return ds.Peer
}
-func (ds *DefaultSpan) SetSpanLayer(layer agentv3.SpanLayer) {
- ds.Layer = layer
+func (ds *DefaultSpan) SetSpanLayer(layer int32) {
+ ds.Layer = agentv3.SpanLayer(layer)
}
func (ds *DefaultSpan) GetSpanLayer() agentv3.SpanLayer {
@@ -85,11 +85,11 @@ func (ds *DefaultSpan) GetComponent() int32 {
return ds.ComponentID
}
-func (ds *DefaultSpan) Tag(key Tag, value string) {
- ds.Tags = append(ds.Tags, &commonv3.KeyStringValuePair{Key: string(key), Value: value})
+func (ds *DefaultSpan) Tag(key, value string) {
+ ds.Tags = append(ds.Tags, &commonv3.KeyStringValuePair{Key: key, Value: value})
}
-func (ds *DefaultSpan) Log(t time.Time, ll ...string) {
+func (ds *DefaultSpan) Log(ll ...string) {
data := make([]*commonv3.KeyStringValuePair, 0, int32(math.Ceil(float64(len(ll))/2.0)))
var kvp *commonv3.KeyStringValuePair
for i, l := range ll {
@@ -101,18 +101,18 @@ func (ds *DefaultSpan) Log(t time.Time, ll ...string) {
kvp.Value = l
}
}
- ds.Logs = append(ds.Logs, &agentv3.Log{Time: Millisecond(t), Data: data})
+ ds.Logs = append(ds.Logs, &agentv3.Log{Time: Millisecond(time.Now()), Data: data})
}
-func (ds *DefaultSpan) Error(t time.Time, ll ...string) {
+func (ds *DefaultSpan) Error(ll ...string) {
ds.IsError = true
- ds.Log(t, ll...)
+ ds.Log(ll...)
}
func (ds *DefaultSpan) End() {
ds.EndTime = time.Now()
- if ctx := GetTracingContext(); ctx != nil {
- ctx.ActiveSpan = ds.Parent
+ if ctx := getTracingContext(); ctx != nil {
+ ctx.SaveActiveSpan(ds.Parent)
}
}
@@ -128,6 +128,6 @@ func (ds *DefaultSpan) IsValid() bool {
return ds.EndTime.IsZero()
}
-func (ds *DefaultSpan) ParentSpan() Span {
+func (ds *DefaultSpan) ParentSpan() TracingSpan {
return ds.Parent
}
diff --git a/plugins/core/span_noop.go b/plugins/core/span_noop.go
index 9810228..30062f9 100644
--- a/plugins/core/span_noop.go
+++ b/plugins/core/span_noop.go
@@ -18,8 +18,6 @@
package core
import (
- "time"
-
agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
)
@@ -40,7 +38,7 @@ func (*NoopSpan) GetPeer() string {
return ""
}
-func (*NoopSpan) SetSpanLayer(agentv3.SpanLayer) {
+func (*NoopSpan) SetSpanLayer(layer int32) {
}
func (*NoopSpan) GetSpanLayer() agentv3.SpanLayer {
@@ -54,13 +52,13 @@ func (*NoopSpan) GetComponent() int32 {
return 0
}
-func (*NoopSpan) Tag(Tag, string) {
+func (*NoopSpan) Tag(string, string) {
}
-func (*NoopSpan) Log(time.Time, ...string) {
+func (*NoopSpan) Log(...string) {
}
-func (*NoopSpan) Error(time.Time, ...string) {
+func (*NoopSpan) Error(...string) {
}
func (*NoopSpan) End() {
@@ -78,6 +76,6 @@ func (*NoopSpan) IsValid() bool {
return true
}
-func (n *NoopSpan) ParentSpan() Span {
+func (n *NoopSpan) ParentSpan() TracingSpan {
return nil
}
diff --git a/plugins/core/span_tracing.go b/plugins/core/span_tracing.go
index 129b02d..27a530b 100644
--- a/plugins/core/span_tracing.go
+++ b/plugins/core/span_tracing.go
@@ -20,7 +20,6 @@ package core
import (
"fmt"
"sync/atomic"
- "time"
"github.com/apache/skywalking-go/reporter"
@@ -59,7 +58,7 @@ type SegmentContext struct {
collect chan<- reporter.ReportedSpan
refNum *int32
spanIDGenerator *int32
- FirstSpan Span `json:"-"`
+ FirstSpan TracingSpan `json:"-"`
CorrelationContext map[string]string
}
@@ -84,7 +83,7 @@ func (c *SegmentContext) GetParentSegmentID() string {
}
type SegmentSpan interface {
- Span
+ TracingSpan
GetSegmentContext() SegmentContext
tracer() *Tracer
segmentRegister() bool
@@ -95,7 +94,7 @@ type SegmentSpanImpl struct {
SegmentContext
}
-// For Span
+// For TracingSpan
func (s *SegmentSpanImpl) End() {
if !s.IsValid() {
return
@@ -110,7 +109,7 @@ func (s *SegmentSpanImpl) GetDefaultSpan() DefaultSpan {
return s.DefaultSpan
}
-// For Reported Span
+// For Reported TracingSpan
func (s *SegmentSpanImpl) Context() reporter.SegmentContext {
return &s.SegmentContext
@@ -251,7 +250,7 @@ func (s *SnapshotSpan) SetOperationName(_ string) {
panic(fmt.Errorf("cannot update the operation name of span in other goroutine"))
}
-func (s *SnapshotSpan) SetSpanLayer(_ agentv3.SpanLayer) {
+func (s *SnapshotSpan) SetSpanLayer(_ int32) {
panic(fmt.Errorf("cannot update the layer of span in other goroutine"))
}
@@ -259,15 +258,15 @@ func (s *SnapshotSpan) SetComponent(_ int32) {
panic(fmt.Errorf("cannot update the compoenent of span in other goroutine"))
}
-func (s *SnapshotSpan) Tag(key Tag, value string) {
+func (s *SnapshotSpan) Tag(key, value string) {
panic(fmt.Errorf("cannot add tag of span in other goroutine"))
}
-func (s *SnapshotSpan) Log(_ time.Time, _ ...string) {
+func (s *SnapshotSpan) Log(_ ...string) {
panic(fmt.Errorf("cannot add log of span in other goroutine"))
}
-func (s *SnapshotSpan) Error(_ time.Time, _ ...string) {
+func (s *SnapshotSpan) Error(_ ...string) {
panic(fmt.Errorf("cannot add error of span in other goroutine"))
}
@@ -322,7 +321,7 @@ func newSegmentRoot(segmentSpan *SegmentSpanImpl) *RootSegmentSpan {
return s
}
-func newSnapshotSpan(current Span) *SnapshotSpan {
+func newSnapshotSpan(current TracingSpan) *SnapshotSpan {
if current == nil {
return nil
}
diff --git a/plugins/core/test_base.go b/plugins/core/test_base.go
index 2825e6c..8561ede 100644
--- a/plugins/core/test_base.go
+++ b/plugins/core/test_base.go
@@ -18,10 +18,12 @@
package core
import (
+ "github.com/apache/skywalking-go/plugins/core/operator"
"github.com/apache/skywalking-go/reporter"
)
var tlsData interface{}
+var Tracing *Tracer
func init() {
SetGLS = func(i interface{}) {
@@ -30,16 +32,30 @@ func init() {
GetGLS = func() interface{} {
return tlsData
}
+ operator.GetOperator = func() operator.Operator {
+ return Tracing
+ }
ResetTracingContext()
}
func ResetTracingContext() {
SetGLS(nil)
- SetGlobalTracer(&Tracer{initFlag: 1, Sampler: NewConstSampler(true), Reporter: &StoreReporter{}})
+ Tracing = &Tracer{initFlag: 1, Sampler: NewConstSampler(true), Reporter: &StoreReporter{}}
+ SetAsNewGoroutine()
+}
+
+func SetAsNewGoroutine() {
+ gls := GetGLS()
+ if gls == nil {
+ return
+ }
+ if e := gls.(ContextSnapshoter); e != nil {
+ SetGLS(e.TakeSnapShot(GetGLS()))
+ }
}
func GetReportedSpans() []reporter.ReportedSpan {
- return GetGlobalTracer().Reporter.(*StoreReporter).Spans
+ return Tracing.Reporter.(*StoreReporter).Spans
}
type StoreReporter struct {
diff --git a/plugins/core/tracer.go b/plugins/core/tracer.go
index 9890dfc..3331a85 100644
--- a/plugins/core/tracer.go
+++ b/plugins/core/tracer.go
@@ -18,6 +18,7 @@
package core
import (
+ "github.com/apache/skywalking-go/plugins/core/operator"
"github.com/apache/skywalking-go/reporter"
)
@@ -33,6 +34,7 @@ type Tracer struct {
// 0 not init 1 init
initFlag int32
Sampler Sampler
+ Log operator.LogOperator
// correlation *CorrelationConfig // temporarily disable, because haven't been implemented yet
cdsWatchers []reporter.AgentConfigChangeWatcher
}
diff --git a/plugins/core/tracing.go b/plugins/core/tracing.go
new file mode 100644
index 0000000..1f4c383
--- /dev/null
+++ b/plugins/core/tracing.go
@@ -0,0 +1,241 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package core
+
+import (
+ "reflect"
+
+ "github.com/pkg/errors"
+
+ "github.com/apache/skywalking-go/plugins/core/tracing"
+ "github.com/apache/skywalking-go/reporter"
+)
+
+func (t *Tracer) Test() bool {
+ return true
+}
+
+func (t *Tracer) Tracing() interface{} {
+ return t
+}
+
+func (t *Tracer) Logger() interface{} {
+ return t.Log
+}
+
+func (t *Tracer) CreateEntrySpan(operationName string, extractor interface{}, opts ...interface{}) (s interface{}, err error) {
+ ctx, tracingSpan, noop := t.createNoop()
+ if noop {
+ return s, nil
+ }
+ defer func() {
+ saveSpanToActiveIfNotError(ctx, s, err)
+ }()
+ var ref = &SpanContext{}
+ if err := ref.Decode(extractor.(tracing.ExtractorWrapper).Fun()); err != nil {
+ return nil, err
+ }
+ if !ref.Valid {
+ ref = nil
+ }
+
+ return t.createSpan0(tracingSpan, append(opts, withRef(ref), withSpanType(SpanTypeEntry), withOperationName(operationName))...)
+}
+
+func (t *Tracer) CreateLocalSpan(operationName string, opts ...interface{}) (s interface{}, err error) {
+ ctx, tracingSpan, noop := t.createNoop()
+ if noop {
+ return tracingSpan, nil
+ }
+ defer func() {
+ saveSpanToActiveIfNotError(ctx, s, err)
+ }()
+
+ return t.createSpan0(tracingSpan, append(opts, withSpanType(SpanTypeLocal), withOperationName(operationName))...)
+}
+
+func (t *Tracer) CreateExitSpan(operationName, peer string, injector interface{}, opts ...interface{}) (s interface{}, err error) {
+ ctx, tracingSpan, noop := t.createNoop()
+ if noop {
+ return tracingSpan, nil
+ }
+ defer func() {
+ saveSpanToActiveIfNotError(ctx, s, err)
+ }()
+
+ span, err := t.createSpan0(tracingSpan, append(opts, withSpanType(SpanTypeExit), withOperationName(operationName), withPeer(peer))...)
+ if err != nil {
+ return nil, err
+ }
+ spanContext := &SpanContext{}
+ reportedSpan, ok := span.(SegmentSpan)
+ if !ok {
+ return nil, errors.New("span type is wrong")
+ }
+
+ firstSpan := reportedSpan.GetSegmentContext().FirstSpan
+ spanContext.Sample = 1
+ spanContext.TraceID = reportedSpan.GetSegmentContext().TraceID
+ spanContext.ParentSegmentID = reportedSpan.GetSegmentContext().SegmentID
+ spanContext.ParentSpanID = reportedSpan.GetSegmentContext().SpanID
+ spanContext.ParentService = t.Service
+ spanContext.ParentServiceInstance = t.Instance
+ spanContext.ParentEndpoint = firstSpan.GetOperationName()
+ spanContext.AddressUsedAtClient = peer
+ spanContext.CorrelationContext = reportedSpan.GetSegmentContext().CorrelationContext
+
+ err = spanContext.Encode(injector.(tracing.InjectorWrapper).Fun())
+ if err != nil {
+ return nil, err
+ }
+ return span, nil
+}
+
+func (t *Tracer) ActiveSpan() interface{} {
+ ctx := getTracingContext()
+ if ctx == nil || ctx.ActiveSpan() == nil {
+ return nil
+ }
+ span := ctx.ActiveSpan()
+ if _, ok := span.(*SnapshotSpan); ok {
+ return nil
+ }
+ return span
+}
+
+func (t *Tracer) GetRuntimeContextValue(key string) interface{} {
+ context := getTracingContext()
+ if context == nil {
+ return nil
+ }
+ return context.Runtime.Get(key)
+}
+
+func (t *Tracer) SetRuntimeContextValue(key string, value interface{}) {
+ context := getTracingContext()
+ if context == nil {
+ context = NewTracingContext()
+ SetGLS(context)
+ }
+ context.Runtime.Set(key, value)
+}
+
+func (t *Tracer) createNoop() (*TracingContext, TracingSpan, bool) {
+ if !t.InitSuccess() {
+ return nil, &NoopSpan{}, true
+ }
+ ctx := getTracingContext()
+ if ctx != nil {
+ span := ctx.ActiveSpan()
+ _, ok := span.(*NoopSpan)
+ return ctx, span, ok
+ }
+ return nil, nil, false
+}
+
+func (t *Tracer) createSpan0(parent TracingSpan, opts ...interface{}) (s TracingSpan, err error) {
+ ds := NewDefaultSpan(t, parent)
+ for _, opt := range opts {
+ opt.(tracing.SpanOption).Apply(ds)
+ }
+ var parentSpan SegmentSpan
+ if parent != nil {
+ tmpSpan, ok := parent.(SegmentSpan)
+ if ok {
+ parentSpan = tmpSpan
+ }
+ }
+ isForceSample := len(ds.Refs) > 0
+ // Try to sample when it is not force sample
+ if parentSpan == nil && !isForceSample {
+ // Force sample
+ sampled := t.Sampler.IsSampled(ds.OperationName)
+ if !sampled {
+ // Filter by sample just return noop span
+ s = &NoopSpan{}
+ return s, nil
+ }
+ }
+ s, err = NewSegmentSpan(ds, parentSpan)
+ if err != nil {
+ return nil, err
+ }
+ return s, nil
+}
+
+func withSpanType(spanType SpanType) tracing.SpanOption {
+ return buildSpanOption(func(span *DefaultSpan) {
+ span.SpanType = spanType
+ })
+}
+
+func withOperationName(opName string) tracing.SpanOption {
+ return buildSpanOption(func(span *DefaultSpan) {
+ span.OperationName = opName
+ })
+}
+
+func withRef(sc reporter.SpanContext) tracing.SpanOption {
+ return buildSpanOption(func(span *DefaultSpan) {
+ if sc == nil {
+ return
+ }
+ v := reflect.ValueOf(sc)
+ if v.Interface() == reflect.Zero(v.Type()).Interface() {
+ return
+ }
+ span.Refs = append(span.Refs, sc)
+ })
+}
+
+func withPeer(peer string) tracing.SpanOption {
+ return buildSpanOption(func(span *DefaultSpan) {
+ span.Peer = peer
+ })
+}
+
+type spanOpImpl struct {
+ exe func(s *DefaultSpan)
+}
+
+func (s *spanOpImpl) Apply(span interface{}) {
+ s.exe(span.(*DefaultSpan))
+}
+
+func buildSpanOption(e func(s *DefaultSpan)) tracing.SpanOption {
+ return &spanOpImpl{exe: e}
+}
+
+func getTracingContext() *TracingContext {
+ gls := GetGLS()
+ if gls == nil {
+ return nil
+ }
+ return gls.(*TracingContext)
+}
+
+func saveSpanToActiveIfNotError(ctx *TracingContext, span interface{}, err error) {
+ if err != nil || span == nil {
+ return
+ }
+ if ctx == nil {
+ ctx = NewTracingContext()
+ }
+ ctx.SaveActiveSpan(span.(TracingSpan))
+ SetGLS(ctx)
+}
diff --git a/plugins/core/tracing/api.go b/plugins/core/tracing/api.go
index a53c086..0c07a41 100644
--- a/plugins/core/tracing/api.go
+++ b/plugins/core/tracing/api.go
@@ -18,246 +18,132 @@
package tracing
import (
- "fmt"
- "reflect"
-
- "github.com/apache/skywalking-go/plugins/core"
- "github.com/apache/skywalking-go/reporter"
-
- "github.com/pkg/errors"
-
- commonv3 "skywalking.apache.org/repo/goapi/collect/common/v3"
- agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
+ "github.com/apache/skywalking-go/plugins/core/operator"
)
var (
- errParameter = fmt.Errorf("parameter are nil")
+ errParameter = operator.NewError("parameter are nil")
)
-// Extractor is a tool specification which define how to
-// extract trace parent context from propagation context
-type Extractor func(headerKey string) (string, error)
-
-// Injector is a tool specification which define how to
-// inject trace context into propagation context
-type Injector func(headerKey, headerValue string) error
-
-func CreateEntrySpan(operationName string, extractor Extractor, opts ...SpanOption) (s core.Span, err error) {
+func CreateEntrySpan(operationName string, extractor Extractor, opts ...SpanOption) (s Span, err error) {
if operationName == "" || extractor == nil {
return nil, errParameter
}
- tracer, ctx, s, noop := createNoop()
- if noop {
- return s, nil
+ op := operator.GetOperator()
+ if op == nil {
+ return &NoopSpan{}, nil
}
- defer func() {
- saveSpanToActiveIfNotError(ctx, s, err)
- }()
- var ref = &core.SpanContext{}
- if err := ref.Decode(extractor); err != nil {
+ span, err := op.Tracing().(operator.TracingOperator).CreateEntrySpan(operationName, extractorWrapper(extractor), copyOptsAsInterface(opts)...)
+ if err != nil {
return nil, err
}
- if !ref.Valid {
- ref = nil
- }
-
- return createSpan0(tracer, s, append(opts, withRef(ref), withSpanType(core.SpanTypeEntry), withOperationName(operationName))...)
+ return newSpanAdapter(span.(AdaptSpan)), nil
}
-func CreateLocalSpan(operationName string, opts ...SpanOption) (s core.Span, err error) {
+func CreateLocalSpan(operationName string, opts ...SpanOption) (s Span, err error) {
if operationName == "" {
return nil, errParameter
}
- tracer, ctx, s, noop := createNoop()
- if noop {
- return s, nil
+ op := operator.GetOperator()
+ if op == nil {
+ return &NoopSpan{}, nil
}
- defer func() {
- saveSpanToActiveIfNotError(ctx, s, err)
- }()
-
- return createSpan0(tracer, s, append(opts, withSpanType(core.SpanTypeLocal), withOperationName(operationName))...)
+ span, err := op.Tracing().(operator.TracingOperator).CreateLocalSpan(operationName, copyOptsAsInterface(opts)...)
+ if err != nil {
+ return nil, err
+ }
+ return newSpanAdapter(span.(AdaptSpan)), nil
}
-func CreateExitSpan(operationName, peer string, injector Injector, opts ...SpanOption) (s core.Span, err error) {
+func CreateExitSpan(operationName, peer string, injector Injector, opts ...SpanOption) (s Span, err error) {
if operationName == "" || peer == "" || injector == nil {
return nil, errParameter
}
- tracer, ctx, s, noop := createNoop()
- if noop {
- return s, nil
+ op := operator.GetOperator()
+ if op == nil {
+ return &NoopSpan{}, nil
}
- defer func() {
- saveSpanToActiveIfNotError(ctx, s, err)
- }()
-
- span, err := createSpan0(tracer, s, append(opts, withSpanType(core.SpanTypeExit), withOperationName(operationName), withPeer(peer))...)
+ span, err := op.Tracing().(operator.TracingOperator).CreateExitSpan(operationName, peer, injectorWrapper(injector), copyOptsAsInterface(opts)...)
if err != nil {
return nil, err
}
- spanContext := &core.SpanContext{}
- reportedSpan, ok := span.(core.SegmentSpan)
- if !ok {
- return nil, errors.New("span type is wrong")
- }
-
- firstSpan := reportedSpan.GetSegmentContext().FirstSpan
- spanContext.Sample = 1
- spanContext.TraceID = reportedSpan.GetSegmentContext().TraceID
- spanContext.ParentSegmentID = reportedSpan.GetSegmentContext().SegmentID
- spanContext.ParentSpanID = reportedSpan.GetSegmentContext().SpanID
- spanContext.ParentService = tracer.Service
- spanContext.ParentServiceInstance = tracer.Instance
- spanContext.ParentEndpoint = firstSpan.GetOperationName()
- spanContext.AddressUsedAtClient = peer
- spanContext.CorrelationContext = reportedSpan.GetSegmentContext().CorrelationContext
-
- err = spanContext.Encode(injector)
- if err != nil {
- return nil, err
- }
- return span, nil
+ return newSpanAdapter(span.(AdaptSpan)), nil
}
-func ActiveSpan() core.Span {
- ctx := getTracingContext()
- if ctx == nil || ctx.ActiveSpan == nil {
+func ActiveSpan() Span {
+ op := operator.GetOperator()
+ if op == nil {
return nil
}
- if _, ok := ctx.ActiveSpan.(*core.SnapshotSpan); ok {
- return nil
+ if span, ok := op.Tracing().(operator.TracingOperator).ActiveSpan().(AdaptSpan); ok {
+ return newSpanAdapter(span)
}
- return ctx.ActiveSpan
+ return nil
}
-// SpanOption allows for functional options to adjust behavior
-// of a Span to be created by CreateLocalSpan
-type SpanOption func(s *core.DefaultSpan)
-
-func WithLayer(layer agentv3.SpanLayer) SpanOption {
- return func(s *core.DefaultSpan) {
- s.Layer = layer
+func GetRuntimeContextValue(key string) interface{} {
+ op := operator.GetOperator()
+ if op == nil {
+ return nil
}
+ return op.Tracing().(operator.TracingOperator).GetRuntimeContextValue(key)
}
-func WithComponent(componentID int32) SpanOption {
- return func(s *core.DefaultSpan) {
- s.ComponentID = componentID
+func SetRuntimeContextValue(key string, val interface{}) {
+ op := operator.GetOperator()
+ if op != nil {
+ op.Tracing().(operator.TracingOperator).SetRuntimeContextValue(key, val)
}
}
-func WithTag(key, value string) SpanOption {
- return func(s *core.DefaultSpan) {
- s.Tags = append(s.Tags, &commonv3.KeyStringValuePair{Key: key, Value: value})
+func copyOptsAsInterface(opts []SpanOption) []interface{} {
+ optsVal := make([]interface{}, len(opts))
+ for i := range opts {
+ optsVal[i] = opts[i]
}
+ return optsVal
}
-func GetRuntimeContextValue(key string) interface{} {
- context := core.GetTracingContext()
- if context == nil {
- return nil
- }
- return context.Runtime.Get(key)
+type extractorWrapperImpl struct {
+ extractor Extractor
}
-func SetRuntimeContextValue(key string, val interface{}) {
- context := core.GetTracingContext()
- if context == nil {
- context = core.NewTracingContext()
- core.SetGLS(context)
- }
- context.Runtime.Set(key, val)
+func (e *extractorWrapperImpl) Fun() func(headerKey string) (string, error) {
+ return e.extractor
}
-func createNoop() (*core.Tracer, *core.TracingContext, core.Span, bool) {
- tracer := core.GetGlobalTracer()
- if tracer == nil || !tracer.InitSuccess() {
- return nil, nil, &core.NoopSpan{}, true
- }
- ctx := getTracingContext()
- if ctx != nil {
- _, ok := ctx.ActiveSpan.(*core.NoopSpan)
- return tracer, ctx, ctx.ActiveSpan, ok
- }
- return tracer, nil, nil, false
+func extractorWrapper(extractor Extractor) *extractorWrapperImpl {
+ return &extractorWrapperImpl{extractor: extractor}
}
-func createSpan0(tracer *core.Tracer, parent core.Span, opts ...SpanOption) (s core.Span, err error) {
- ds := core.NewDefaultSpan(tracer, parent)
- for _, opt := range opts {
- opt(ds)
- }
- var parentSpan core.SegmentSpan
- if parent != nil {
- tmpSpan, ok := parent.(core.SegmentSpan)
- if ok {
- parentSpan = tmpSpan
- }
- }
- isForceSample := len(ds.Refs) > 0
- // Try to sample when it is not force sample
- if parentSpan == nil && !isForceSample {
- // Force sample
- sampled := tracer.Sampler.IsSampled(ds.OperationName)
- if !sampled {
- // Filter by sample just return noop span
- s = &core.NoopSpan{}
- return s, nil
- }
- }
- s, err = core.NewSegmentSpan(ds, parentSpan)
- if err != nil {
- return nil, err
- }
- return s, nil
+type injectorWrapperImpl struct {
+ injector Injector
}
-func withSpanType(spanType core.SpanType) SpanOption {
- return func(span *core.DefaultSpan) {
- span.SpanType = spanType
- }
+func (i *injectorWrapperImpl) Fun() func(headerKey, headerValue string) error {
+ return i.injector
}
-func withOperationName(opName string) SpanOption {
- return func(span *core.DefaultSpan) {
- span.OperationName = opName
- }
+func injectorWrapper(injector Injector) *injectorWrapperImpl {
+ return &injectorWrapperImpl{injector: injector}
}
-func withRef(sc reporter.SpanContext) SpanOption {
- return func(span *core.DefaultSpan) {
- if sc == nil {
- return
- }
- v := reflect.ValueOf(sc)
- if v.Interface() == reflect.Zero(v.Type()).Interface() {
- return
- }
- span.Refs = append(span.Refs, sc)
- }
+type NoopSpan struct {
}
-func withPeer(peer string) SpanOption {
- return func(span *core.DefaultSpan) {
- span.Peer = peer
- }
+func (n *NoopSpan) SetOperationName(string) {
}
-
-func getTracingContext() *core.TracingContext {
- ctx := core.GetGLS()
- if ctx == nil {
- return nil
- }
- return ctx.(*core.TracingContext)
+func (n *NoopSpan) SetPeer(string) {
}
-
-func saveSpanToActiveIfNotError(ctx *core.TracingContext, span core.Span, err error) {
- if err != nil {
- return
- }
- if ctx == nil {
- ctx = core.NewTracingContext()
- }
- ctx.ActiveSpan = span
- core.SetGLS(ctx)
+func (n *NoopSpan) SetSpanLayer(SpanLayer) {
+}
+func (n *NoopSpan) SetComponent(int32) {
+}
+func (n *NoopSpan) Tag(Tag, string) {
+}
+func (n *NoopSpan) Log(...string) {
+}
+func (n *NoopSpan) Error(...string) {
+}
+func (n *NoopSpan) End() {
}
diff --git a/plugins/core/tracing/api_test.go b/plugins/core/tracing/api_test.go
deleted file mode 100644
index 2560042..0000000
--- a/plugins/core/tracing/api_test.go
+++ /dev/null
@@ -1,346 +0,0 @@
-// Licensed to Apache Software Foundation (ASF) under one or more contributor
-// license agreements. See the NOTICE file distributed with
-// this work for additional information regarding copyright
-// ownership. Apache Software Foundation (ASF) licenses this file to you under
-// the Apache License, Version 2.0 (the "License"); you may
-// not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package tracing
-
-import (
- "testing"
- "time"
-
- "github.com/apache/skywalking-go/plugins/core"
-
- "github.com/stretchr/testify/assert"
-
- agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
-)
-
-const (
- sample = 1
- traceID = "1f2d4bf47bf711eab794acde48001122"
- parentSegmentID = "1e7c204a7bf711eab858acde48001122"
- parentSpanID = 0
- parentService = "service"
- parentServiceInstance = "instance"
- parentEndpoint = "/foo/bar"
- addressUsedAtClient = "foo.svc:8787"
-)
-
-var header string
-
-func init() {
- scx := core.SpanContext{
- Sample: sample,
- TraceID: traceID,
- ParentSegmentID: parentSegmentID,
- ParentSpanID: parentSpanID,
- ParentService: parentService,
- ParentServiceInstance: parentServiceInstance,
- ParentEndpoint: parentEndpoint,
- AddressUsedAtClient: addressUsedAtClient,
- }
- header = scx.EncodeSW8()
-}
-
-type spanOperationTestCase struct {
- operations []func(existingSpans []core.Span) (core.Span, error)
- exceptedSpans []struct {
- spanType core.SpanType
- operationName string
- parentSpanOpName string
- peer string
- }
-}
-
-func TestCreateSpanInSingleGoroutine(t *testing.T) {
- defer core.ResetTracingContext()
- validateSpanOperation(t, []spanOperationTestCase{
- {
- operations: []func(existingSpans []core.Span) (core.Span, error){
- func(existingSpans []core.Span) (core.Span, error) {
- return CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
- },
- func(existingSpans []core.Span) (core.Span, error) {
- return CreateLocalSpan("/local1")
- },
- func(existingSpans []core.Span) (core.Span, error) {
- return CreateLocalSpan("/local1-1")
- },
- func(existingSpans []core.Span) (core.Span, error) {
- return CreateExitSpan("/local1-1-exit", "localhost:8080", func(key, value string) error { return nil })
- },
- func(existingSpans []core.Span) (core.Span, error) { existingSpans[3].End(); return nil, nil },
- func(existingSpans []core.Span) (core.Span, error) { existingSpans[2].End(); return nil, nil },
- func(existingSpans []core.Span) (core.Span, error) {
- return CreateExitSpan("/local1-exit", "localhost:8080", func(key, value string) error { return nil })
- },
- func(existingSpans []core.Span) (core.Span, error) { existingSpans[4].End(); return nil, nil },
- func(existingSpans []core.Span) (core.Span, error) { existingSpans[1].End(); return nil, nil },
- func(existingSpans []core.Span) (core.Span, error) {
- return CreateExitSpan("/entry-exit", "localhost:8080", func(key, value string) error { return nil })
- },
- func(existingSpans []core.Span) (core.Span, error) { existingSpans[5].End(); return nil, nil },
- func(existingSpans []core.Span) (core.Span, error) { existingSpans[0].End(); return nil, nil },
- },
- exceptedSpans: []struct {
- spanType core.SpanType
- operationName string
- parentSpanOpName string
- peer string
- }{
- {core.SpanTypeEntry, "/entry", "", ""},
- {core.SpanTypeLocal, "/local1", "/entry", ""},
- {core.SpanTypeLocal, "/local1-1", "/local1", ""},
- {core.SpanTypeExit, "/local1-1-exit", "/local1-1", "localhost:8080"},
- {core.SpanTypeExit, "/local1-exit", "/local1", "localhost:8080"},
- {core.SpanTypeExit, "/entry-exit", "/entry", "localhost:8080"},
- },
- },
- })
-}
-
-func TestCreateSpanInDifferenceGoroutine(t *testing.T) {
- defer core.ResetTracingContext()
- validateSpanOperation(t, []spanOperationTestCase{
- {
- operations: []func(existingSpans []core.Span) (core.Span, error){
- func(existingSpans []core.Span) (core.Span, error) {
- return CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
- },
- func(existingSpans []core.Span) (core.Span, error) { // new goroutine
- core.SetGLS(core.TaskTracingContextSnapshot(core.GetGLS()))
- return nil, nil
- },
- func(existingSpans []core.Span) (core.Span, error) {
- return CreateLocalSpan("/local")
- },
- func(existingSpans []core.Span) (core.Span, error) { // new goroutine
- core.SetGLS(core.TaskTracingContextSnapshot(core.GetGLS()))
- return nil, nil
- },
- func(existingSpans []core.Span) (core.Span, error) {
- return CreateExitSpan("/local-exit", "localhost:8080", func(key, value string) error { return nil })
- },
- func(existingSpans []core.Span) (core.Span, error) {
- existingSpans[2].End()
- return nil, nil
- },
- func(existingSpans []core.Span) (core.Span, error) {
- existingSpans[1].End()
- return nil, nil
- },
- func(existingSpans []core.Span) (core.Span, error) {
- existingSpans[0].End()
- return nil, nil
- },
- },
- exceptedSpans: []struct {
- spanType core.SpanType
- operationName string
- parentSpanOpName string
- peer string
- }{
- {core.SpanTypeEntry, "/entry", "", ""},
- {core.SpanTypeLocal, "/local", "/entry", ""},
- {core.SpanTypeExit, "/local-exit", "/local", "localhost:8080"},
- },
- },
- })
-}
-
-func TestSpanContextWriting(t *testing.T) {
- defer core.ResetTracingContext()
- s, err := CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
- assert.NoError(t, err)
- s.End()
- s, err = CreateExitSpan("/exit", "localhost:8080", func(key, value string) error {
- ctx := core.SpanContext{}
- if key == core.Header {
- assert.NoError(t, ctx.DecodeSW8(value))
- }
- if key == core.HeaderCorrelation {
- assert.NoError(t, ctx.DecodeSW8Correlation(value))
- }
- return nil
- })
- assert.NoError(t, err)
- s.End()
-}
-
-func TestSpanContextReading(t *testing.T) {
- defer core.ResetTracingContext()
- s, err := CreateEntrySpan("/entry", func(key string) (string, error) {
- if key == core.Header {
- return header, nil
- }
- return "", nil
- })
- assert.NoError(t, err)
- s.End()
- time.Sleep(time.Millisecond * 50)
- spans := core.GetReportedSpans()
- assert.Equal(t, 1, len(spans), "span count not correct")
- tracingContext := spans[0].Context()
- assert.Equal(t, traceID, tracingContext.GetTraceID(), "trace id not correct")
- assert.Equal(t, 1, len(spans[0].Refs()), "refs not correct")
- refCtx := spans[0].Refs()[0]
- assert.Equal(t, traceID, refCtx.GetTraceID(), "ref trace id not correct")
- assert.Equal(t, parentSegmentID, refCtx.GetParentSegmentID(), "ref segment id not correct")
- assert.Equal(t, parentEndpoint, refCtx.GetParentEndpoint(), "ref endpoint not correct")
- assert.Equal(t, int32(parentSpanID), refCtx.GetParentSpanID(), "ref span id not correct")
- assert.Equal(t, parentService, refCtx.GetParentService(), "ref service not correct")
- assert.Equal(t, parentServiceInstance, refCtx.GetParentServiceInstance(), "ref service instance not correct")
- assert.Equal(t, parentEndpoint, refCtx.GetParentEndpoint(), "ref endpoint not correct")
-}
-
-func TestSpanOperation(t *testing.T) {
- defer core.ResetTracingContext()
- spanCreations := []func(op SpanOption) (core.Span, error){
- func(op SpanOption) (core.Span, error) {
- return CreateEntrySpan("test", func(headerKey string) (string, error) {
- return "", nil
- }, op)
- },
- func(op SpanOption) (core.Span, error) {
- return CreateLocalSpan("test", op)
- },
- func(op SpanOption) (core.Span, error) {
- return CreateExitSpan("test", "localhost:8080", func(headerKey, headerValue string) error {
- return nil
- }, op)
- },
- }
-
- spanOptions := []struct {
- op SpanOption
- validate func(s *core.RootSegmentSpan) bool
- }{
- {WithLayer(agentv3.SpanLayer_Http), func(s *core.RootSegmentSpan) bool {
- return s.DefaultSpan.Layer == agentv3.SpanLayer_Http
- }},
- {WithComponent(1), func(s *core.RootSegmentSpan) bool {
- return s.DefaultSpan.ComponentID == 1
- }},
- {WithTag("test", "test1"), func(s *core.RootSegmentSpan) bool {
- for _, k := range s.DefaultSpan.Tags {
- if k.Key == "test" {
- return k.Value == "test1"
- }
- }
- return false
- }},
- }
-
- for createInx, spanCreate := range spanCreations {
- for _, op := range spanOptions {
- create, err := spanCreate(op.op)
- if err != nil {
- assert.Nil(t, err, "create span error")
- }
- span := create.(*core.RootSegmentSpan)
- assert.Truef(t, op.validate(span), "span validation failed, create index: %d, option index: %d", createInx, op)
- create.End()
- }
- }
-}
-
-func TestActiveSpan(t *testing.T) {
- defer core.ResetTracingContext()
- // active span in same goroutine
- span, err := CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
- assert.NoError(t, err)
- assert.Equal(t, span, ActiveSpan(), "active span not correct")
- oldGLS := core.GetGLS()
- // change goroutine
- core.SetGLS(core.TaskTracingContextSnapshot(oldGLS))
- assert.Nil(t, ActiveSpan(), "active span should be nil when cross goroutine")
- // change back
- core.SetGLS(oldGLS)
- span.End()
- assert.Nil(t, ActiveSpan(), "active span not correct")
-}
-
-func TestRuntimeContext(t *testing.T) {
- defer core.ResetTracingContext()
- assert.Nilf(t, GetRuntimeContextValue("test"), "runtime context data should be nil")
- SetRuntimeContextValue("test", "test")
- assert.Equal(t, "test", GetRuntimeContextValue("test"), "runtime context data should be \"test\"")
- // switch to the new goroutine
- oldGLS := core.GetGLS()
- core.SetGLS(core.TaskTracingContextSnapshot(oldGLS))
- assert.Equal(t, "test", GetRuntimeContextValue("test"), "runtime context data should be \"test\"")
- assert.Nilf(t, GetRuntimeContextValue("test1"), "runtime context data should be nil")
- SetRuntimeContextValue("test1", "test1")
- assert.Equal(t, "test1", GetRuntimeContextValue("test1"), "runtime context data should be \"test1\"")
- // switch back to the old goroutine
- core.SetGLS(oldGLS)
- assert.Nilf(t, GetRuntimeContextValue("test1"), "runtime context data should be nil")
-}
-
-func validateSpanOperation(t *testing.T, cases []spanOperationTestCase) {
- for _, tt := range cases {
- spans := make([]core.Span, 0)
- for i, op := range tt.operations {
- span, err := op(spans)
- assert.Nilf(t, err, "create span error, operation index: %d", i)
- time.Sleep(time.Millisecond * 50)
- if span != nil {
- spans = append(spans, span)
- }
- }
-
- time.Sleep(time.Millisecond * 100)
- assert.Equal(t, len(tt.exceptedSpans), len(core.GetReportedSpans()), "span count not equal")
- for i, exceptedSpan := range tt.exceptedSpans {
- var span core.DefaultSpan
- if i == 0 {
- tmp, ok := core.GetReportedSpans()[len(core.GetReportedSpans())-1-i].(*core.RootSegmentSpan)
- assert.True(t, ok, "first span is not root segment span")
- span = tmp.DefaultSpan
- } else {
- found := false
- for _, s := range core.GetReportedSpans() {
- if s.OperationName() != exceptedSpan.operationName {
- continue
- }
- tmp, ok := s.(*core.SegmentSpanImpl)
- assert.Truef(t, ok, "span is not segment span, span index: %d", i)
- span = tmp.DefaultSpan
- found = true
- break
- }
- assert.Truef(t, found, "span not found, span index: %d, name: %s", i, exceptedSpan.operationName)
- }
- assert.Equalf(t, exceptedSpan.spanType, span.SpanType, "span type not equal, span index: %d", i)
- assert.Equalf(t, exceptedSpan.operationName, span.OperationName, "operation name not equal, span index: %d", i)
- if exceptedSpan.parentSpanOpName != "" {
- assert.Equalf(t, exceptedSpan.parentSpanOpName, span.Parent.GetOperationName(), "parent operation name not equal, span index: %d", i)
- } else {
- assert.Nilf(t, span.Parent, "parent span not nil, span index: %d", i)
- }
- if exceptedSpan.peer != "" {
- assert.Equalf(t, exceptedSpan.peer, span.Peer, "span peer not equal, span index: %d", i)
- } else {
- assert.Truef(t, span.Peer == "", "span peer not empty, span index: %d", i)
- }
-
- assert.Greaterf(t, core.Millisecond(span.StartTime), int64(0), "start time not greater than 0, span index: %d", i)
- assert.Greaterf(t, core.Millisecond(span.EndTime), core.Millisecond(span.StartTime), "end time not greater than 0, span indeX: %d", i)
- }
-
- core.GetGlobalTracer().Reporter = core.NewStoreReporter()
- }
-}
diff --git a/agent/core/tracing/compile.go b/plugins/core/tracing/bridge.go
similarity index 50%
copy from agent/core/tracing/compile.go
copy to plugins/core/tracing/bridge.go
index 5ccde8b..356c2b0 100644
--- a/agent/core/tracing/compile.go
+++ b/plugins/core/tracing/bridge.go
@@ -17,19 +17,50 @@
package tracing
-import (
- //go:nolint
- _ "fmt"
- _ "reflect"
-
- //go:nolint
- _ "github.com/pkg/errors"
-
- //go:nolint
- _ "github.com/apache/skywalking-go/agent/core"
- _ "github.com/apache/skywalking-go/reporter"
-
- //go:nolint
- _ "skywalking.apache.org/repo/goapi/collect/common/v3"
- _ "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
-)
+// AdaptSpan for adapt with agent core
+type AdaptSpan interface {
+ SetOperationName(string)
+ SetPeer(string)
+ SetSpanLayer(int32)
+ SetComponent(int32)
+ Tag(string, string)
+ Log(...string)
+ Error(...string)
+ End()
+}
+
+type SpanWrapper struct {
+ Span AdaptSpan
+}
+
+func newSpanAdapter(s AdaptSpan) Span {
+ return &SpanWrapper{Span: s}
+}
+
+func (s *SpanWrapper) Tag(k Tag, v string) {
+ s.Span.Tag(string(k), v)
+}
+
+func (s *SpanWrapper) SetSpanLayer(l SpanLayer) {
+ s.Span.SetSpanLayer(int32(l))
+}
+
+func (s *SpanWrapper) SetOperationName(name string) {
+ s.Span.SetOperationName(name)
+}
+
+func (s *SpanWrapper) SetPeer(v string) {
+ s.Span.SetPeer(v)
+}
+
+func (s *SpanWrapper) Log(v ...string) {
+ s.Span.Log(v...)
+}
+
+func (s *SpanWrapper) Error(v ...string) {
+ s.Span.Error(v...)
+}
+
+func (s *SpanWrapper) End() {
+ s.Span.End()
+}
diff --git a/plugins/core/tracing/span.go b/plugins/core/tracing/span.go
new file mode 100644
index 0000000..9b6d022
--- /dev/null
+++ b/plugins/core/tracing/span.go
@@ -0,0 +1,111 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package tracing
+
+// Extractor is a tool specification which define how to
+// extract trace parent context from propagation context
+type Extractor func(headerKey string) (string, error)
+
+// Injector is a tool specification which define how to
+// inject trace context into propagation context
+type Injector func(headerKey, headerValue string) error
+
+// SpanOption allows for functional options to adjust behavior
+// of a AdaptSpan to be created by CreateLocalSpan
+type SpanOption interface {
+ Apply(interface{})
+}
+
+// SpanLayer define the Span belong to which layer
+type SpanLayer int32
+
+var (
+ SpanLayerDatabase SpanLayer = 1
+ SpanLayerRPCFramework SpanLayer = 2
+ SpanLayerHTTP SpanLayer = 3
+ SpanLayerMQ SpanLayer = 4
+ SpanLayerCache SpanLayer = 5
+ SpanLayerFAAS SpanLayer = 6
+)
+
+// Tag are supported by sky-walking engine.
+// As default, all Tags will be stored, but these ones have
+// particular meanings.
+type Tag string
+
+const (
+ TagURL Tag = "url"
+ TagStatusCode Tag = "status_code"
+ TagHTTPMethod Tag = "http.method"
+ TagDBType Tag = "db.type"
+ TagDBInstance Tag = "db.instance"
+ TagDBStatement Tag = "db.statement"
+ TagDBSqlParameters Tag = "db.sql.parameters"
+ TagMQQueue Tag = "mq.queue"
+ TagMQBroker Tag = "mq.broker"
+ TagMQTopic Tag = "mq.topic"
+)
+
+func WithLayer(layer SpanLayer) SpanOption {
+ return buildSpanOption(func(s AdaptSpan) {
+ s.SetSpanLayer(int32(layer))
+ })
+}
+
+func WithComponent(componentID int32) SpanOption {
+ return buildSpanOption(func(s AdaptSpan) {
+ s.SetComponent(componentID)
+ })
+}
+
+func WithTag(key Tag, value string) SpanOption {
+ return buildSpanOption(func(s AdaptSpan) {
+ s.Tag(string(key), value)
+ })
+}
+
+type spanOpImpl struct {
+ exe func(s AdaptSpan)
+}
+
+func (s *spanOpImpl) Apply(span interface{}) {
+ s.exe(span.(AdaptSpan))
+}
+
+func buildSpanOption(e func(s AdaptSpan)) SpanOption {
+ return &spanOpImpl{exe: e}
+}
+
+type ExtractorWrapper interface {
+ Fun() func(headerKey string) (string, error)
+}
+
+type InjectorWrapper interface {
+ Fun() func(headerKey, headerValue string) error
+}
+
+// Span for plugin API
+type Span interface {
+ Tag(Tag, string)
+ SetSpanLayer(SpanLayer)
+ SetOperationName(string)
+ SetPeer(string)
+ Log(...string)
+ Error(...string)
+ End()
+}
diff --git a/plugins/core/tracing_test.go b/plugins/core/tracing_test.go
new file mode 100644
index 0000000..3356326
--- /dev/null
+++ b/plugins/core/tracing_test.go
@@ -0,0 +1,364 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package core
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+
+ agentv3 "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
+
+ "github.com/apache/skywalking-go/plugins/core/tracing"
+)
+
+const (
+ sample = 1
+ traceID = "1f2d4bf47bf711eab794acde48001122"
+ parentSegmentID = "1e7c204a7bf711eab858acde48001122"
+ parentSpanID = 0
+ parentService = "service"
+ parentServiceInstance = "instance"
+ parentEndpoint = "/foo/bar"
+ addressUsedAtClient = "foo.svc:8787"
+)
+
+var header string
+
+func init() {
+ scx := SpanContext{
+ Sample: sample,
+ TraceID: traceID,
+ ParentSegmentID: parentSegmentID,
+ ParentSpanID: parentSpanID,
+ ParentService: parentService,
+ ParentServiceInstance: parentServiceInstance,
+ ParentEndpoint: parentEndpoint,
+ AddressUsedAtClient: addressUsedAtClient,
+ }
+ header = scx.EncodeSW8()
+}
+
+type spanOperationTestCase struct {
+ operations []func(existingSpans []tracing.Span) (tracing.Span, error)
+ exceptedSpans []struct {
+ spanType SpanType
+ operationName string
+ parentSpanOpName string
+ peer string
+ }
+}
+
+func TestCreateSpanInSingleGoroutine(t *testing.T) {
+ defer ResetTracingContext()
+ validateSpanOperation(t, []spanOperationTestCase{
+ {
+ operations: []func(existingSpans []tracing.Span) (tracing.Span, error){
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ return tracing.CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ return tracing.CreateLocalSpan("/local1")
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ return tracing.CreateLocalSpan("/local1-1")
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ return tracing.CreateExitSpan("/local1-1-exit", "localhost:8080", func(key, value string) error { return nil })
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ existingSpans[3].End()
+ return nil, nil
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ existingSpans[2].End()
+ return nil, nil
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ return tracing.CreateExitSpan("/local1-exit", "localhost:8080", func(key, value string) error { return nil })
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ existingSpans[4].End()
+ return nil, nil
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ existingSpans[1].End()
+ return nil, nil
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ return tracing.CreateExitSpan("/entry-exit", "localhost:8080", func(key, value string) error { return nil })
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ existingSpans[5].End()
+ return nil, nil
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ existingSpans[0].End()
+ return nil, nil
+ },
+ },
+ exceptedSpans: []struct {
+ spanType SpanType
+ operationName string
+ parentSpanOpName string
+ peer string
+ }{
+ {SpanTypeEntry, "/entry", "", ""},
+ {SpanTypeLocal, "/local1", "/entry", ""},
+ {SpanTypeLocal, "/local1-1", "/local1", ""},
+ {SpanTypeExit, "/local1-1-exit", "/local1-1", "localhost:8080"},
+ {SpanTypeExit, "/local1-exit", "/local1", "localhost:8080"},
+ {SpanTypeExit, "/entry-exit", "/entry", "localhost:8080"},
+ },
+ },
+ })
+}
+
+func TestCreateSpanInDifferenceGoroutine(t *testing.T) {
+ defer ResetTracingContext()
+ validateSpanOperation(t, []spanOperationTestCase{
+ {
+ operations: []func(existingSpans []tracing.Span) (tracing.Span, error){
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ return tracing.CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) { // new goroutine
+ SetAsNewGoroutine()
+ return nil, nil
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ return tracing.CreateLocalSpan("/local")
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) { // new goroutine
+ SetAsNewGoroutine()
+ return nil, nil
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ return tracing.CreateExitSpan("/local-exit", "localhost:8080", func(key, value string) error { return nil })
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ existingSpans[2].End()
+ return nil, nil
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ existingSpans[1].End()
+ return nil, nil
+ },
+ func(existingSpans []tracing.Span) (tracing.Span, error) {
+ existingSpans[0].End()
+ return nil, nil
+ },
+ },
+ exceptedSpans: []struct {
+ spanType SpanType
+ operationName string
+ parentSpanOpName string
+ peer string
+ }{
+ {SpanTypeEntry, "/entry", "", ""},
+ {SpanTypeLocal, "/local", "/entry", ""},
+ {SpanTypeExit, "/local-exit", "/local", "localhost:8080"},
+ },
+ },
+ })
+}
+
+func TestSpanContextWriting(t *testing.T) {
+ defer ResetTracingContext()
+ s, err := tracing.CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
+ assert.NoError(t, err)
+ s.End()
+ s, err = tracing.CreateExitSpan("/exit", "localhost:8080", func(key, value string) error {
+ ctx := SpanContext{}
+ if key == Header {
+ assert.NoError(t, ctx.DecodeSW8(value))
+ }
+ if key == HeaderCorrelation {
+ assert.NoError(t, ctx.DecodeSW8Correlation(value))
+ }
+ return nil
+ })
+ assert.NoError(t, err)
+ s.End()
+}
+
+func TestSpanContextReading(t *testing.T) {
+ defer ResetTracingContext()
+ s, err := tracing.CreateEntrySpan("/entry", func(key string) (string, error) {
+ if key == Header {
+ return header, nil
+ }
+ return "", nil
+ })
+ assert.NoError(t, err)
+ s.End()
+ time.Sleep(time.Millisecond * 50)
+ spans := GetReportedSpans()
+ assert.Equal(t, 1, len(spans), "span count not correct")
+ tracingContext := spans[0].Context()
+ assert.Equal(t, traceID, tracingContext.GetTraceID(), "trace id not correct")
+ assert.Equal(t, 1, len(spans[0].Refs()), "refs not correct")
+ refCtx := spans[0].Refs()[0]
+ assert.Equal(t, traceID, refCtx.GetTraceID(), "ref trace id not correct")
+ assert.Equal(t, parentSegmentID, refCtx.GetParentSegmentID(), "ref segment id not correct")
+ assert.Equal(t, parentEndpoint, refCtx.GetParentEndpoint(), "ref endpoint not correct")
+ assert.Equal(t, int32(parentSpanID), refCtx.GetParentSpanID(), "ref span id not correct")
+ assert.Equal(t, parentService, refCtx.GetParentService(), "ref service not correct")
+ assert.Equal(t, parentServiceInstance, refCtx.GetParentServiceInstance(), "ref service instance not correct")
+ assert.Equal(t, parentEndpoint, refCtx.GetParentEndpoint(), "ref endpoint not correct")
+}
+
+func TestSpanOperation(t *testing.T) {
+ defer ResetTracingContext()
+ spanCreations := []func(op tracing.SpanOption) (tracing.Span, error){
+ func(op tracing.SpanOption) (tracing.Span, error) {
+ return tracing.CreateEntrySpan("test", func(headerKey string) (string, error) {
+ return "", nil
+ }, op)
+ },
+ func(op tracing.SpanOption) (tracing.Span, error) {
+ return tracing.CreateLocalSpan("test", op)
+ },
+ func(op tracing.SpanOption) (tracing.Span, error) {
+ return tracing.CreateExitSpan("test", "localhost:8080", func(headerKey, headerValue string) error {
+ return nil
+ }, op)
+ },
+ }
+
+ spanOptions := []struct {
+ op tracing.SpanOption
+ validate func(s *RootSegmentSpan) bool
+ }{
+ {tracing.WithLayer(tracing.SpanLayerHTTP), func(s *RootSegmentSpan) bool {
+ return s.DefaultSpan.Layer == agentv3.SpanLayer_Http
+ }},
+ {tracing.WithComponent(1), func(s *RootSegmentSpan) bool {
+ return s.DefaultSpan.ComponentID == 1
+ }},
+ {tracing.WithTag("test", "test1"), func(s *RootSegmentSpan) bool {
+ for _, k := range s.DefaultSpan.Tags {
+ if k.Key == "test" {
+ return k.Value == "test1"
+ }
+ }
+ return false
+ }},
+ }
+
+ for createInx, spanCreate := range spanCreations {
+ for _, op := range spanOptions {
+ create, err := spanCreate(op.op)
+ if err != nil {
+ assert.Nil(t, err, "create span error")
+ }
+ span := create.(*tracing.SpanWrapper).Span.(*RootSegmentSpan)
+ assert.Truef(t, op.validate(span), "span validation failed, create index: %d, option index: %d", createInx, op)
+ create.End()
+ }
+ }
+}
+
+func TestActiveSpan(t *testing.T) {
+ defer ResetTracingContext()
+ // active span in same goroutine
+ span, err := tracing.CreateEntrySpan("/entry", func(key string) (string, error) { return "", nil })
+ assert.NoError(t, err)
+ assert.Equal(t, span, tracing.ActiveSpan(), "active span not correct")
+ oldGLS := GetGLS()
+ // change goroutine
+ SetAsNewGoroutine()
+ assert.Nil(t, tracing.ActiveSpan(), "active span should be nil when cross goroutine")
+ // change back
+ SetGLS(oldGLS)
+ span.End()
+ assert.Nil(t, tracing.ActiveSpan(), "active span not correct")
+}
+
+func TestRuntimeContext(t *testing.T) {
+ defer ResetTracingContext()
+ assert.Nilf(t, tracing.GetRuntimeContextValue("test"), "runtime context data should be nil")
+ tracing.SetRuntimeContextValue("test", "test")
+ assert.Equal(t, "test", tracing.GetRuntimeContextValue("test"), "runtime context data should be \"test\"")
+ // switch to the new goroutine
+ oldGLS := GetGLS()
+ SetAsNewGoroutine()
+ assert.Equal(t, "test", tracing.GetRuntimeContextValue("test"), "runtime context data should be \"test\"")
+ assert.Nilf(t, tracing.GetRuntimeContextValue("test1"), "runtime context data should be nil")
+ tracing.SetRuntimeContextValue("test1", "test1")
+ assert.Equal(t, "test1", tracing.GetRuntimeContextValue("test1"), "runtime context data should be \"test1\"")
+ // switch back to the old goroutine
+ SetGLS(oldGLS)
+ assert.Nilf(t, tracing.GetRuntimeContextValue("test1"), "runtime context data should be nil")
+}
+
+func validateSpanOperation(t *testing.T, cases []spanOperationTestCase) {
+ for _, tt := range cases {
+ spans := make([]tracing.Span, 0)
+ for i, op := range tt.operations {
+ span, err := op(spans)
+ assert.Nilf(t, err, "create span error, operation index: %d", i)
+ time.Sleep(time.Millisecond * 50)
+ if span != nil {
+ spans = append(spans, span)
+ }
+ }
+
+ time.Sleep(time.Millisecond * 100)
+ assert.Equal(t, len(tt.exceptedSpans), len(GetReportedSpans()), "span count not equal")
+ for i, exceptedSpan := range tt.exceptedSpans {
+ var span DefaultSpan
+ if i == 0 {
+ tmp, ok := GetReportedSpans()[len(GetReportedSpans())-1-i].(*RootSegmentSpan)
+ assert.True(t, ok, "first span is not root segment span")
+ span = tmp.DefaultSpan
+ } else {
+ found := false
+ for _, s := range GetReportedSpans() {
+ if s.OperationName() != exceptedSpan.operationName {
+ continue
+ }
+ tmp, ok := s.(*SegmentSpanImpl)
+ assert.Truef(t, ok, "span is not segment span, span index: %d", i)
+ span = tmp.DefaultSpan
+ found = true
+ break
+ }
+ assert.Truef(t, found, "span not found, span index: %d, name: %s", i, exceptedSpan.operationName)
+ }
+ assert.Equalf(t, exceptedSpan.spanType, span.SpanType, "span type not equal, span index: %d", i)
+ assert.Equalf(t, exceptedSpan.operationName, span.OperationName, "operation name not equal, span index: %d", i)
+ if exceptedSpan.parentSpanOpName != "" {
+ assert.Equalf(t, exceptedSpan.parentSpanOpName, span.Parent.GetOperationName(), "parent operation name not equal, span index: %d", i)
+ } else {
+ assert.Nilf(t, span.Parent, "parent span not nil, span index: %d", i)
+ }
+ if exceptedSpan.peer != "" {
+ assert.Equalf(t, exceptedSpan.peer, span.Peer, "span peer not equal, span index: %d", i)
+ } else {
+ assert.Truef(t, span.Peer == "", "span peer not empty, span index: %d", i)
+ }
+
+ assert.Greaterf(t, Millisecond(span.StartTime), int64(0), "start time not greater than 0, span index: %d", i)
+ assert.Greaterf(t, Millisecond(span.EndTime), Millisecond(span.StartTime), "end time not greater than 0, span indeX: %d", i)
+ }
+
+ Tracing.Reporter = NewStoreReporter()
+ }
+}
diff --git a/plugins/ginv2/go.mod b/plugins/ginv2/go.mod
new file mode 100644
index 0000000..e5f7a8b
--- /dev/null
+++ b/plugins/ginv2/go.mod
@@ -0,0 +1,39 @@
+module github.com/apache/skywalking-go/plugins/ginv2
+
+go 1.18
+
+require (
+ github.com/apache/skywalking-go/plugins/core v0.0.0-20230414024435-7b292984eb80
+ github.com/gin-gonic/gin v1.9.0
+)
+
+require (
+ github.com/bytedance/sonic v1.8.0 // indirect
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+ github.com/dave/dst v0.27.2 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.11.2 // indirect
+ github.com/goccy/go-json v0.10.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.0.9 // indirect
+ github.com/leodido/go-urn v1.2.1 // indirect
+ github.com/mattn/go-isatty v0.0.17 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pelletier/go-toml/v2 v2.0.6 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.9 // indirect
+ golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
+ golang.org/x/crypto v0.5.0 // indirect
+ golang.org/x/net v0.8.0 // indirect
+ golang.org/x/sys v0.6.0 // indirect
+ golang.org/x/text v0.8.0 // indirect
+ google.golang.org/protobuf v1.29.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
+
+replace github.com/apache/skywalking-go/plugins/core => ../core
+
+replace github.com/apache/skywalking-go => ../../
diff --git a/plugins/ginv2/go.sum b/plugins/ginv2/go.sum
new file mode 100644
index 0000000..302cee1
--- /dev/null
+++ b/plugins/ginv2/go.sum
@@ -0,0 +1,91 @@
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
+github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+github.com/dave/dst v0.27.2 h1:4Y5VFTkhGLC1oddtNwuxxe36pnyLxMFXT51FOzH8Ekc=
+github.com/dave/dst v0.27.2/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
+github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
+github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
+github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
+github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
+github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
+github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
+github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
+golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
+google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0=
+google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+skywalking.apache.org/repo/goapi v0.0.0-20230314034821-0c5a44bb767a h1:m8DTnaSEOEnPXRWmA6g7isbdqw7WPZP6SnaEHz1Sx7s=
diff --git a/plugins/core/tracer.go b/plugins/ginv2/instrument.go
similarity index 51%
copy from plugins/core/tracer.go
copy to plugins/ginv2/instrument.go
index 9890dfc..f6b8076 100644
--- a/plugins/core/tracer.go
+++ b/plugins/ginv2/instrument.go
@@ -15,28 +15,49 @@
// specific language governing permissions and limitations
// under the License.
-package core
+package ginv2
import (
- "github.com/apache/skywalking-go/reporter"
+ "embed"
+ "strings"
+
+ "github.com/apache/skywalking-go/plugins/core/instrument"
)
-type CorrelationConfig struct {
- MaxKeyCount int
- MaxValueSize int
+//go:embed *
+var fs embed.FS
+
+//skywalking:nocopy
+type Instrument struct {
+}
+
+func NewInstrument() *Instrument {
+ return &Instrument{}
+}
+
+func (i *Instrument) Name() string {
+ return "ginv2"
+}
+
+func (i *Instrument) BasePackage() string {
+ return "github.com/gin-gonic/gin"
+}
+
+func (i *Instrument) VersionChecker(version string) bool {
+ return strings.HasPrefix(version, "v1.")
}
-type Tracer struct {
- Service string
- Instance string
- Reporter reporter.Reporter
- // 0 not init 1 init
- initFlag int32
- Sampler Sampler
- // correlation *CorrelationConfig // temporarily disable, because haven't been implemented yet
- cdsWatchers []reporter.AgentConfigChangeWatcher
+func (i *Instrument) Points() []*instrument.Point {
+ return []*instrument.Point{
+ {
+ PackagePath: "",
+ At: instrument.NewMethodEnhance("Engine", "handleHTTPRequest",
+ instrument.WithArgsCount(1), instrument.WithArgType(0, "*Context")),
+ Interceptor: "HTTPInterceptor",
+ },
+ }
}
-func (t *Tracer) InitSuccess() bool {
- return t.initFlag == 1
+func (i *Instrument) FS() *embed.FS {
+ return &fs
}
diff --git a/plugins/ginv2/intercepter.go b/plugins/ginv2/intercepter.go
new file mode 100644
index 0000000..49c5412
--- /dev/null
+++ b/plugins/ginv2/intercepter.go
@@ -0,0 +1,60 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package ginv2
+
+import (
+ "fmt"
+
+ "github.com/gin-gonic/gin"
+
+ "github.com/apache/skywalking-go/plugins/core/operator"
+ "github.com/apache/skywalking-go/plugins/core/tracing"
+)
+
+type HTTPInterceptor struct {
+}
+
+func (h *HTTPInterceptor) BeforeInvoke(invocation *operator.Invocation) error {
+ context := invocation.Args[0].(*gin.Context)
+ s, err := tracing.CreateEntrySpan(
+ fmt.Sprintf("%s:%s", context.Request.Method, context.Request.RequestURI), func(headerKey string) (string, error) {
+ return context.Request.Header.Get(headerKey), nil
+ },
+ tracing.WithLayer(tracing.SpanLayerHTTP),
+ tracing.WithTag(tracing.TagHTTPMethod, context.Request.Method),
+ tracing.WithTag(tracing.TagURL, context.Request.Host+context.Request.URL.Path))
+ if err != nil {
+ return err
+ }
+ invocation.Context = s
+ return nil
+}
+
+func (h *HTTPInterceptor) AfterInvoke(invocation *operator.Invocation, result ...interface{}) error {
+ if invocation.Context == nil {
+ return nil
+ }
+ context := invocation.Args[0].(*gin.Context)
+ span := invocation.Context.(tracing.Span)
+ span.Tag(tracing.TagStatusCode, fmt.Sprintf("%d", context.Writer.Status()))
+ if len(context.Errors) > 0 {
+ span.Error(context.Errors.String())
+ }
+ span.End()
+ return nil
+}
diff --git a/tools/go-agent/cmd/helper.go b/tools/go-agent/cmd/helper.go
index d97e152..751d163 100644
--- a/tools/go-agent/cmd/helper.go
+++ b/tools/go-agent/cmd/helper.go
@@ -23,7 +23,8 @@ import (
)
type EnhancementToolFlags struct {
- Help bool `skyflag:"-h"`
+ Help bool `swflag:"-h"`
+ Debug string `swflag:"-debug"`
}
func PrintUsageWithExit() {
diff --git a/tools/go-agent/cmd/main.go b/tools/go-agent/cmd/main.go
index 77b3ac1..85b1148 100644
--- a/tools/go-agent/cmd/main.go
+++ b/tools/go-agent/cmd/main.go
@@ -18,46 +18,75 @@
package main
import (
+ "fmt"
"log"
"os"
"os/exec"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument/api"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/tools"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/api"
+ "github.com/apache/skywalking-go/tools/go-agent/tools"
)
var toolFlags = &EnhancementToolFlags{}
func main() {
+ writeArgs()
args := os.Args[1:]
+ var err error
+ var firstNonOptionIndex int
// Print usage
- if err := tools.ParseFlags(toolFlags, args); err != nil || toolFlags.Help {
+ if firstNonOptionIndex, err = tools.ParseFlags(toolFlags, args); err != nil || toolFlags.Help {
PrintUsageWithExit()
}
+ if toolFlags.Debug != "" {
+ stat, err1 := os.Stat(toolFlags.Debug)
+ if err1 != nil {
+ fmt.Printf("debug path not existing: %s", toolFlags.Debug)
+ return
+ }
+ if !stat.IsDir() {
+ fmt.Printf("debug path must be a directory: %s", toolFlags.Debug)
+ return
+ }
+ }
+
// only enhance the "compile" phase
- cmdName := tools.ParseProxyCommandName(args)
+ cmdName := tools.ParseProxyCommandName(args, firstNonOptionIndex)
if cmdName != "compile" {
- executeDelegateCommand(args)
+ executeDelegateCommand(args[firstNonOptionIndex:])
return
}
// parse the args
compileOptions := &api.CompileOptions{}
- if err := tools.ParseFlags(compileOptions, args); err != nil {
- executeDelegateCommand(args)
+ if _, err = tools.ParseFlags(compileOptions, args); err != nil {
+ executeDelegateCommand(args[firstNonOptionIndex:])
return
}
// execute the enhancement
- args, err := instrument.Execute(compileOptions, args)
+ args, err = instrument.Execute(compileOptions, args)
if err != nil {
log.Fatal(err)
}
// execute the delegate command with updated args
- executeDelegateCommand(args)
+ executeDelegateCommand(args[firstNonOptionIndex:])
+}
+
+func writeArgs() {
+ homeDir, err := os.UserHomeDir()
+ if err != nil {
+ panic(err)
+ }
+ file, err := os.OpenFile(homeDir+"/Desktop/skywalking-go.txt", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o666)
+ if err != nil {
+ os.Exit(1)
+ }
+ defer file.Close()
+ _, _ = fmt.Fprintf(file, "%v\n", os.Args[1:])
}
func executeDelegateCommand(args []string) {
diff --git a/tools/go-agent/go.mod b/tools/go-agent/go.mod
index 7a43be9..4413213 100644
--- a/tools/go-agent/go.mod
+++ b/tools/go-agent/go.mod
@@ -1,29 +1,52 @@
-module github.com/apache/skywalking-go/tools/go-agent-enhance
+module github.com/apache/skywalking-go/tools/go-agent
go 1.18
require (
- github.com/apache/skywalking-go/plugins/core v0.0.0-20230412041451-ba963278b31e
+ github.com/apache/skywalking-go/plugins/core v0.0.0-20230414024435-7b292984eb80
+ github.com/apache/skywalking-go/plugins/ginv2 v0.0.0-20230412041451-ba963278b31e
github.com/dave/dst v0.27.2
github.com/sirupsen/logrus v1.9.0
+ golang.org/x/text v0.8.0
)
require (
github.com/apache/skywalking-go v0.0.0-20230412041451-ba963278b31e // indirect
+ github.com/bytedance/sonic v1.8.0 // indirect
+ github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/gin-gonic/gin v1.9.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.11.2 // indirect
+ github.com/goccy/go-json v0.10.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.0.9 // indirect
+ github.com/leodido/go-urn v1.2.1 // indirect
+ github.com/mattn/go-isatty v0.0.17 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.2.9 // indirect
+ golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
+ golang.org/x/crypto v0.5.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
- golang.org/x/text v0.8.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.29.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
skywalking.apache.org/repo/goapi v0.0.0-20230314034821-0c5a44bb767a // indirect
)
replace github.com/apache/skywalking-go => ../../
replace github.com/apache/skywalking-go/plugins/core => ../../plugins/core
+
+replace github.com/apache/skywalking-go/plugins/ginv2 => ../../plugins/ginv2
diff --git a/tools/go-agent/go.sum b/tools/go-agent/go.sum
index 451cfeb..27c2af3 100644
--- a/tools/go-agent/go.sum
+++ b/tools/go-agent/go.sum
@@ -3,8 +3,14 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
+github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -23,6 +29,18 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
+github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
+github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
+github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
+github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -47,10 +65,25 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
+github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
+github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -62,16 +95,31 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
+github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
+golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -116,6 +164,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -181,7 +230,9 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
skywalking.apache.org/repo/goapi v0.0.0-20230314034821-0c5a44bb767a h1:m8DTnaSEOEnPXRWmA6g7isbdqw7WPZP6SnaEHz1Sx7s=
skywalking.apache.org/repo/goapi v0.0.0-20230314034821-0c5a44bb767a/go.mod h1:LcZMcxDjdJPn5yetydFnxe0l7rmiv8lvHEnzRbsey14=
diff --git a/tools/go-agent/instrument/agentcore/instrument.go b/tools/go-agent/instrument/agentcore/instrument.go
index 5331e0d..7c589db 100644
--- a/tools/go-agent/instrument/agentcore/instrument.go
+++ b/tools/go-agent/instrument/agentcore/instrument.go
@@ -24,11 +24,11 @@ import (
"strings"
"github.com/apache/skywalking-go/plugins/core"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/api"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/runtime"
+ "github.com/apache/skywalking-go/tools/go-agent/tools"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument/api"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument/runtime"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/tools"
-
+ "github.com/dave/dst"
"github.com/dave/dst/decorator"
"github.com/dave/dst/dstutil"
)
@@ -38,12 +38,13 @@ var (
EnhanceFromBasePackage = "github.com/apache/skywalking-go/plugins/core"
CopiedBasePackage = "skywalking-go/agent/core"
- CopiedSubPackages = []string{"", "tracing"}
+ CopiedSubPackages = []string{"", "tracing", "operator"}
)
type Instrument struct {
hasCopyPath bool
needsCopyDir string
+ compileOpts *api.CompileOptions
}
func NewInstrument() *Instrument {
@@ -51,10 +52,11 @@ func NewInstrument() *Instrument {
}
func (i *Instrument) CouldHandle(opts *api.CompileOptions) bool {
+ i.compileOpts = opts
return strings.HasPrefix(opts.Package, EnhanceBasePackage)
}
-func (i *Instrument) FilterAndEdit(path string, cursor *dstutil.Cursor) bool {
+func (i *Instrument) FilterAndEdit(path string, cursor *dstutil.Cursor, allFiles []*dst.File) bool {
if i.hasCopyPath {
return false
}
@@ -69,7 +71,7 @@ func (i *Instrument) FilterAndEdit(path string, cursor *dstutil.Cursor) bool {
return false
}
-func (i *Instrument) AfterEnhanceFile(path string) error {
+func (i *Instrument) AfterEnhanceFile(fromPath, newPath string) error {
return nil
}
@@ -81,22 +83,6 @@ func (i *Instrument) WriteExtraFiles(dir string) ([]string, error) {
results := make([]string, 0)
if sub == "" {
sub = "."
- // append the context adapter if is root package
- tmp, err := tools.WriteMultipleFile(dir, map[string]string{
- "adapter_context.go": tools.ExecuteTemplate(`package core
-
-import (
- _ "unsafe"
-)
-
-//go:linkname {{.}} {{.}}
-var {{.}} = TaskTracingContextSnapshot
-`, runtime.TLSTakeSnapshotMethodName),
- })
- if err != nil {
- return nil, err
- }
- results = append(results, tmp...)
}
files, err := core.FS.ReadDir(sub)
if err != nil {
@@ -129,14 +115,78 @@ var {{.}} = TaskTracingContextSnapshot
if err != nil {
return nil, err
}
+ debugInfo, err := i.buildDSTDebugInfo(f)
+ if err != nil {
+ return nil, err
+ }
tools.ChangePackageImportPath(parse, pkgUpdates)
copiedFilePath := filepath.Join(dir, f.Name())
- if err := tools.WriteDSTFile(copiedFilePath, "", parse); err != nil {
+ if err := tools.WriteDSTFile(copiedFilePath, parse, debugInfo); err != nil {
return nil, err
}
results = append(results, copiedFilePath)
}
+ // write extra file to link the operator and TLS methods
+ if sub == "." {
+ file, err := i.writeLinkerFile(dir)
+ if err != nil {
+ return nil, err
+ }
+ results = append(results, file)
+ }
+
return results, nil
}
+
+func (i *Instrument) buildDSTDebugInfo(entry fs.DirEntry) (*tools.DebugInfo, error) {
+ if i.compileOpts.DebugDir == "" {
+ return nil, nil
+ }
+ debugPath := filepath.Join(i.compileOpts.DebugDir, "plugins", "core", entry.Name())
+ return tools.BuildDSTDebugInfo(debugPath, nil)
+}
+
+func (i *Instrument) writeLinkerFile(dir string) (string, error) {
+ return tools.WriteFile(dir, "runtime_linker.go", tools.ExecuteTemplate(`package core
+
+import (
+ _ "unsafe"
+)
+
+//go:linkname {{.TLSGetLinkMethod}} {{.TLSGetLinkMethod}}
+var {{.TLSGetLinkMethod}} func() interface{}
+
+//go:linkname {{.TLSSetLinkMethod}} {{.TLSSetLinkMethod}}
+var {{.TLSSetLinkMethod}} func(interface{})
+
+//go:linkname {{.SetGlobalOperatorLinkMethod}} {{.SetGlobalOperatorLinkMethod}}
+var {{.SetGlobalOperatorLinkMethod}} func(interface{})
+
+//go:linkname {{.GetGlobalOperatorLinkMethod}} {{.GetGlobalOperatorLinkMethod}}
+var {{.GetGlobalOperatorLinkMethod}} func() interface{}
+
+func init() {
+ if {{.TLSGetLinkMethod}} != nil && {{.TLSSetLinkMethod}} != nil {
+ GetGLS = {{.TLSGetLinkMethod}}
+ SetGLS = {{.TLSSetLinkMethod}}
+ }
+ if {{.SetGlobalOperatorLinkMethod}} != nil && {{.GetGlobalOperatorLinkMethod}} != nil {
+ SetGlobalOperator = {{.SetGlobalOperatorLinkMethod}}
+ GetGlobalOperator = {{.GetGlobalOperatorLinkMethod}}
+ SetGlobalOperator(&Tracer{initFlag: 1, Sampler: NewConstSampler(true)})
+ }
+}
+`, struct {
+ TLSGetLinkMethod string
+ TLSSetLinkMethod string
+ SetGlobalOperatorLinkMethod string
+ GetGlobalOperatorLinkMethod string
+ }{
+ TLSGetLinkMethod: runtime.TLSGetMethodName,
+ TLSSetLinkMethod: runtime.TLSSetMethodName,
+ SetGlobalOperatorLinkMethod: runtime.GlobalTracerSetMethodName,
+ GetGlobalOperatorLinkMethod: runtime.GlobalTracerGetMethodName,
+ }))
+}
diff --git a/tools/go-agent/instrument/api/flags.go b/tools/go-agent/instrument/api/flags.go
index 0a39718..48e5fc5 100644
--- a/tools/go-agent/instrument/api/flags.go
+++ b/tools/go-agent/instrument/api/flags.go
@@ -20,8 +20,11 @@ package api
import "path/filepath"
type CompileOptions struct {
- Package string `skyflag:"-p"`
- Output string `skyflag:"-o"`
+ Package string `swflag:"-p"`
+ Output string `swflag:"-o"`
+ AllArgs []string `swflag:"all-args"`
+
+ DebugDir string `swflag:"-debug"` // from tools flag
}
func (c *CompileOptions) IsValid() bool {
diff --git a/tools/go-agent/instrument/api/instrument.go b/tools/go-agent/instrument/api/instrument.go
index 9376803..a309fc2 100644
--- a/tools/go-agent/instrument/api/instrument.go
+++ b/tools/go-agent/instrument/api/instrument.go
@@ -18,6 +18,7 @@
package api
import (
+ "github.com/dave/dst"
"github.com/dave/dst/dstutil"
)
@@ -25,9 +26,9 @@ type Instrument interface {
// CouldHandle verify current instrument can handle this request
CouldHandle(opts *CompileOptions) bool
// FilterAndEdit filter the matched data which decode by DST, and edit the data
- FilterAndEdit(path string, cursor *dstutil.Cursor) bool
+ FilterAndEdit(path string, cursor *dstutil.Cursor, allFiles []*dst.File) bool
// AfterEnhanceFile after the enhanced file been written, check the file is needs rewrite
- AfterEnhanceFile(path string) error
+ AfterEnhanceFile(fromPath, newPath string) error
// WriteExtraFiles customized the extra files when there have instrumented files
WriteExtraFiles(dir string) ([]string, error)
}
diff --git a/tools/go-agent/instrument/framework/enhance_instance.go b/tools/go-agent/instrument/framework/enhance_instance.go
new file mode 100644
index 0000000..990618b
--- /dev/null
+++ b/tools/go-agent/instrument/framework/enhance_instance.go
@@ -0,0 +1,100 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package framework
+
+import (
+ "fmt"
+
+ "github.com/apache/skywalking-go/tools/go-agent/tools"
+
+ "github.com/dave/dst"
+)
+
+var EnhanceInstanceField = "skywalkingDynamicField"
+
+type InstanceEnhance struct {
+ typeSpec *dst.TypeSpec
+}
+
+func NewInstanceEnhance(typeSpec *dst.TypeSpec) *InstanceEnhance {
+ return &InstanceEnhance{typeSpec: typeSpec}
+}
+
+func (i *InstanceEnhance) EnhanceField() {
+ structType := i.typeSpec.Type.(*dst.StructType)
+ structType.Fields.List = append(structType.Fields.List, &dst.Field{
+ Names: []*dst.Ident{dst.NewIdent(EnhanceInstanceField)},
+ Type: dst.NewIdent("interface{}"),
+ })
+}
+
+func (i *InstanceEnhance) BuildForAdapter() []dst.Decl {
+ return []dst.Decl{
+ &dst.FuncDecl{
+ Name: &dst.Ident{Name: "GetSkyWalkingDynamicField"},
+ Recv: &dst.FieldList{
+ List: []*dst.Field{
+ {
+ Names: []*dst.Ident{dst.NewIdent("receiver")},
+ Type: &dst.StarExpr{X: dst.NewIdent(i.typeSpec.Name.Name)},
+ },
+ },
+ },
+ Type: &dst.FuncType{
+ Params: &dst.FieldList{},
+ Results: &dst.FieldList{
+ List: []*dst.Field{
+ {Type: dst.NewIdent("interface{}")},
+ },
+ },
+ },
+ Body: &dst.BlockStmt{
+ List: tools.GoStringToStats(fmt.Sprintf("return receiver.%s", EnhanceInstanceField)),
+ },
+ },
+ &dst.FuncDecl{
+ Name: &dst.Ident{Name: "SetSkyWalkingDynamicField"},
+ Recv: &dst.FieldList{
+ List: []*dst.Field{
+ {
+ Names: []*dst.Ident{dst.NewIdent("receiver")},
+ Type: &dst.StarExpr{X: dst.NewIdent(i.typeSpec.Name.Name)},
+ },
+ },
+ },
+ Type: &dst.FuncType{
+ Params: &dst.FieldList{
+ List: []*dst.Field{
+ {
+ Names: []*dst.Ident{dst.NewIdent("param")},
+ Type: dst.NewIdent("interface{}"),
+ },
+ },
+ },
+ Results: &dst.FieldList{},
+ },
+ Body: &dst.BlockStmt{
+ List: tools.GoStringToStats(fmt.Sprintf("receiver.%s = param", EnhanceInstanceField)),
+ },
+ },
+ }
+}
+
+func (i *InstanceEnhance) ReplaceFileContent(path, content string) string {
+ return content
+}
diff --git a/tools/go-agent/instrument/framework/enhance_method.go b/tools/go-agent/instrument/framework/enhance_method.go
new file mode 100644
index 0000000..64674ce
--- /dev/null
+++ b/tools/go-agent/instrument/framework/enhance_method.go
@@ -0,0 +1,230 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package framework
+
+import (
+ "fmt"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "github.com/apache/skywalking-go/plugins/core/instrument"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/agentcore"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/framework/rewrite"
+ "github.com/apache/skywalking-go/tools/go-agent/tools"
+
+ "github.com/dave/dst"
+
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+)
+
+var GenerateMethodPrefix = "_skywalking_enhance_"
+var GenerateVarPrefix = "_skywalking_var_"
+
+var methodEnhanceAdapterFiles = make(map[string]bool)
+
+type MethodEnhance struct {
+ funcDecl *dst.FuncDecl
+ path string
+ packageName string
+ fullPackage string
+
+ InstrumentName string
+ InterceptorDefineName string
+ InterceptorGeneratedName string
+ InterceptorVarName string
+
+ Parameters []*tools.ParameterInfo
+ Recvs []*tools.ParameterInfo
+ Results []*tools.ParameterInfo
+
+ FuncID string
+ AdapterPreFuncName string
+ AdapterPostFuncName string
+
+ replacementKey string
+ replacementValue string
+}
+
+func NewMethodEnhance(inst instrument.Instrument, matcher *instrument.Point, f *dst.FuncDecl, path string) *MethodEnhance {
+ fullPackage := filepath.Join(inst.BasePackage(), matcher.PackagePath)
+ enhance := &MethodEnhance{
+ funcDecl: f,
+ path: path,
+ fullPackage: fullPackage,
+ packageName: filepath.Base(fullPackage),
+ InstrumentName: inst.Name(),
+ InterceptorDefineName: matcher.Interceptor,
+ Parameters: tools.EnhanceParameterNames(f.Type.Params, false),
+ Results: tools.EnhanceParameterNames(f.Type.Results, true),
+ }
+ if f.Recv != nil {
+ enhance.Recvs = tools.EnhanceParameterNames(f.Recv, false)
+ }
+
+ enhance.FuncID = buildFrameworkFuncID(filepath.Join(inst.BasePackage(), matcher.PackagePath), f)
+ enhance.AdapterPreFuncName = fmt.Sprintf("%s%s", rewrite.GenerateMethodPrefix, enhance.FuncID)
+ enhance.AdapterPostFuncName = fmt.Sprintf("%s%s_ret", rewrite.GenerateMethodPrefix, enhance.FuncID)
+
+ // the interceptor name needs to add the function id ensure there no conflict in the framework package
+ titleCase := cases.Title(language.English)
+ packageTitle := filepath.Base(titleCase.String(filepath.Join(inst.BasePackage(), matcher.PackagePath)))
+ enhance.InterceptorGeneratedName = fmt.Sprintf("%s%s%s", rewrite.TypePrefix, packageTitle, enhance.InterceptorDefineName)
+ enhance.InterceptorVarName = fmt.Sprintf("%sinterceptor_%s", rewrite.GenerateVarPrefix, enhance.FuncID)
+ return enhance
+}
+
+func (m *MethodEnhance) BuildForInvoker() {
+ insertsTmpl, err := templatesFS.ReadFile("templates/method_inserts.tmpl")
+ if err != nil {
+ panic(fmt.Errorf("reading method inserts: %w", err))
+ }
+ result := tools.ExecuteTemplate(string(insertsTmpl), m)
+ m.replacementKey = fmt.Sprintf("//goagent:enhance_%s\n", m.FuncID)
+ m.replacementValue = result
+
+ m.funcDecl.Body.Decs.Lbrace.Prepend("\n", m.replacementKey)
+}
+
+func (m *MethodEnhance) BuildForAdapter() []dst.Decl {
+ result := make([]dst.Decl, 0)
+ if !methodEnhanceAdapterFiles[m.path] {
+ // append the import for logger, one file only need import once
+ result = append(result, tools.GoStringToDecls(fmt.Sprintf(`import (
+ "%s/log"
+ "%s/operator"
+
+ %s "%s" // current enhancing package path, for rewrite phase in next step
+)`, agentcore.EnhanceFromBasePackage, agentcore.EnhanceFromBasePackage, m.packageName, m.fullPackage))...)
+ methodEnhanceAdapterFiles[m.path] = true
+ }
+
+ result = append(result, tools.GoStringToDecls(fmt.Sprintf(`var %s = &%s{}`, m.InterceptorVarName, m.InterceptorGeneratedName))...)
+ preFunc := &dst.FuncDecl{
+ Name: &dst.Ident{Name: m.AdapterPreFuncName},
+ Type: &dst.FuncType{
+ Params: &dst.FieldList{},
+ Results: &dst.FieldList{},
+ },
+ }
+ for i, recv := range m.Recvs {
+ preFunc.Type.Params.List = append(preFunc.Type.Params.List, &dst.Field{
+ Names: []*dst.Ident{dst.NewIdent(fmt.Sprintf("recv_%d", i))},
+ Type: &dst.StarExpr{X: m.addPackagePrefixForArgsAndClone(m.packageName, recv.Type)},
+ })
+ }
+ for i, parameter := range m.Parameters {
+ preFunc.Type.Params.List = append(preFunc.Type.Params.List, &dst.Field{
+ Names: []*dst.Ident{dst.NewIdent(fmt.Sprintf("param_%d", i))},
+ Type: &dst.StarExpr{X: m.addPackagePrefixForArgsAndClone(m.packageName, parameter.Type)},
+ })
+ }
+ for i, result := range m.Results {
+ preFunc.Type.Results.List = append(preFunc.Type.Results.List, &dst.Field{
+ Names: []*dst.Ident{dst.NewIdent(fmt.Sprintf("ret_%d", i))},
+ Type: &dst.StarExpr{X: m.addPackagePrefixForArgsAndClone(m.packageName, result.Type)},
+ })
+ }
+ preFunc.Type.Results.List = append(preFunc.Type.Results.List, &dst.Field{
+ Names: []*dst.Ident{dst.NewIdent("inv")},
+ Type: &dst.StarExpr{X: &dst.SelectorExpr{X: dst.NewIdent("operator"), Sel: dst.NewIdent("Invocation")}},
+ }, &dst.Field{
+ Names: []*dst.Ident{dst.NewIdent("keep")},
+ Type: dst.NewIdent("bool"),
+ })
+
+ beforeFile, err := templatesFS.ReadFile("templates/method_intercept_before.tmpl")
+ if err != nil {
+ panic(fmt.Errorf("reading method before intercept template failure: %w", err))
+ }
+ preFunc.Body = &dst.BlockStmt{
+ List: tools.GoStringToStats(tools.ExecuteTemplate(string(beforeFile), m)),
+ }
+ result = append(result, preFunc)
+
+ postFunc := &dst.FuncDecl{
+ Name: &dst.Ident{Name: m.AdapterPostFuncName},
+ Type: &dst.FuncType{
+ Params: &dst.FieldList{},
+ Results: &dst.FieldList{},
+ },
+ }
+ postFunc.Type.Params.List = append(postFunc.Type.Params.List, &dst.Field{
+ Names: []*dst.Ident{dst.NewIdent("invocation")},
+ Type: &dst.StarExpr{X: &dst.SelectorExpr{X: dst.NewIdent("operator"), Sel: dst.NewIdent("Invocation")}},
+ })
+ for inx, f := range m.Results {
+ postFunc.Type.Params.List = append(postFunc.Type.Params.List, &dst.Field{
+ Names: []*dst.Ident{dst.NewIdent(fmt.Sprintf("ret_%d", inx))},
+ Type: &dst.StarExpr{X: m.addPackagePrefixForArgsAndClone(m.packageName, f.Type)},
+ })
+ }
+ afterFile, err := templatesFS.ReadFile("templates/method_intercept_after.tmpl")
+ if err != nil {
+ panic(fmt.Errorf("reading method after intercept template failure: %w", err))
+ }
+ postFunc.Body = &dst.BlockStmt{
+ List: tools.GoStringToStats(tools.ExecuteTemplate(string(afterFile), m)),
+ }
+ result = append(result, postFunc)
+ return result
+}
+
+func (m *MethodEnhance) addPackagePrefixForArgsAndClone(pkg string, tp dst.Expr) dst.Expr {
+ switch t := tp.(type) {
+ case *dst.Ident:
+ if rewrite.IsBasicDataType(t.Name) {
+ return dst.Clone(tp).(dst.Expr)
+ }
+ // otherwise, add the package prefix
+ return &dst.SelectorExpr{
+ X: dst.NewIdent(pkg),
+ Sel: dst.NewIdent(t.Name),
+ }
+ case *dst.StarExpr:
+ t.X = m.addPackagePrefixForArgsAndClone(pkg, t.X)
+ return t
+ default:
+ return dst.Clone(tp).(dst.Expr)
+ }
+}
+
+func (m *MethodEnhance) ReplaceFileContent(path, content string) string {
+ if m.path == path {
+ return strings.Replace(content, m.replacementKey, m.replacementValue, 1)
+ }
+ return content
+}
+
+func buildFrameworkFuncID(pkgPath string, node *dst.FuncDecl) string {
+ var receiver string
+ if node.Recv != nil {
+ expr, ok := node.Recv.List[0].Type.(*dst.StarExpr)
+ if !ok {
+ return ""
+ }
+ ident, ok := expr.X.(*dst.Ident)
+ if !ok {
+ return ""
+ }
+ receiver = ident.Name
+ }
+ return fmt.Sprintf("%s_%s%s",
+ regexp.MustCompile(`[/.\-@]`).ReplaceAllString(pkgPath, "_"), receiver, node.Name)
+}
diff --git a/tools/go-agent/instrument/framework/instrument.go b/tools/go-agent/instrument/framework/instrument.go
new file mode 100644
index 0000000..55dedd5
--- /dev/null
+++ b/tools/go-agent/instrument/framework/instrument.go
@@ -0,0 +1,384 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package framework
+
+import (
+ "bytes"
+ "embed"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/apache/skywalking-go/plugins/core"
+ "github.com/apache/skywalking-go/plugins/core/instrument"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/api"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/framework/rewrite"
+ "github.com/apache/skywalking-go/tools/go-agent/tools"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+
+ "github.com/sirupsen/logrus"
+)
+
+//go:embed templates
+var templatesFS embed.FS
+
+type Instrument struct {
+ realInst instrument.Instrument
+ methodFilters []*instrument.Point
+ structFilters []*instrument.Point
+
+ compileOpts *api.CompileOptions
+
+ enhancements []Enhance
+ extraFilesWrote bool
+}
+
+func NewInstrument() *Instrument {
+ return &Instrument{}
+}
+
+type Enhance interface {
+ BuildForAdapter() []dst.Decl
+ ReplaceFileContent(path, content string) string
+}
+
+func (i *Instrument) CouldHandle(opts *api.CompileOptions) bool {
+ for _, ins := range instruments {
+ // must have the same base package prefix
+ if !strings.HasPrefix(opts.Package, ins.BasePackage()) {
+ continue
+ }
+ // check the version of the framework could handler
+ version, err := i.tryToFindThePluginVersion(opts, ins.BasePackage())
+ if err != nil {
+ logrus.Warnf("ignore the plugin %s, because: %s", ins.Name(), err)
+ continue
+ }
+
+ if ins.VersionChecker(version) {
+ i.realInst = ins
+ i.compileOpts = opts
+ for _, p := range ins.Points() {
+ switch p.At.Type {
+ case instrument.EnhanceTypeMethod:
+ i.methodFilters = append(i.methodFilters, p)
+ case instrument.EnhanceTypeStruct:
+ i.structFilters = append(i.structFilters, p)
+ }
+ }
+ return true
+ }
+ }
+ return false
+}
+
+func (i *Instrument) FilterAndEdit(path string, cursor *dstutil.Cursor, allFiles []*dst.File) bool {
+ switch n := cursor.Node().(type) {
+ case *dst.TypeSpec:
+ for _, filter := range i.structFilters {
+ if i.verifyPackageIsMatch(path, filter) && i.validateStructIsMatch(filter.At, n, allFiles) {
+ i.enhanceStruct(i.realInst, filter, n, path)
+ return true
+ }
+ }
+ case *dst.FuncDecl:
+ for _, filter := range i.methodFilters {
+ if i.verifyPackageIsMatch(path, filter) && i.validateMethodInsMatch(filter.At, n, allFiles) {
+ i.enhanceMethod(i.realInst, filter, n, path)
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func (i *Instrument) enhanceStruct(_ instrument.Instrument, _ *instrument.Point, typeSpec *dst.TypeSpec, _ string) {
+ enhance := NewInstanceEnhance(typeSpec)
+ enhance.EnhanceField()
+ i.enhancements = append(i.enhancements, enhance)
+}
+
+func (i *Instrument) enhanceMethod(inst instrument.Instrument, matcher *instrument.Point, funcDecl *dst.FuncDecl, path string) {
+ enhance := NewMethodEnhance(inst, matcher, funcDecl, path)
+ enhance.BuildForInvoker()
+ i.enhancements = append(i.enhancements, enhance)
+}
+
+func (i *Instrument) verifyPackageIsMatch(_ string, point *instrument.Point) bool {
+ pointPackagePath := filepath.Join(i.realInst.BasePackage(), point.PackagePath)
+ // check the package path
+ return i.compileOpts.Package == pointPackagePath
+}
+
+func (i *Instrument) AfterEnhanceFile(fromPath, newPath string) error {
+ contentBytes, err := os.ReadFile(newPath)
+ if err != nil {
+ return err
+ }
+
+ // update the file content if needed
+ content := string(contentBytes)
+ var oldContent = content
+ for _, enhance := range i.enhancements {
+ content = enhance.ReplaceFileContent(fromPath, content)
+ }
+ if oldContent == content {
+ return nil
+ }
+
+ return os.WriteFile(newPath, []byte(content), 0o600)
+}
+
+func (i *Instrument) WriteExtraFiles(basePath string) ([]string, error) {
+ // if no enhancements or already wrote extra files, then ignore
+ if len(i.enhancements) == 0 || i.extraFilesWrote {
+ return nil, nil
+ }
+ i.extraFilesWrote = true
+
+ packageName := filepath.Base(i.compileOpts.Package)
+ context := rewrite.NewContext(i.compileOpts.Package, packageName)
+
+ var results = make([]string, 0)
+ // write adapter file
+ var files []string
+ var err error
+ if files, err = i.writeAdapterFile(context, basePath); err != nil {
+ return nil, err
+ }
+ results = append(results, files...)
+
+ // copy basic support files(operators)
+ if files, err = i.copyOperatorsFS(context, basePath, packageName); err != nil {
+ return nil, err
+ }
+ results = append(results, files...)
+
+ // copy user defined files(interceptors)
+ if files, err = i.copyFrameworkFS(context, i.compileOpts.Package, basePath, packageName); err != nil {
+ return nil, err
+ }
+ results = append(results, files...)
+ return results, nil
+}
+
+func (i *Instrument) copyFrameworkFS(context *rewrite.Context, compilePkgFullPath, baseDir, packageName string) ([]string, error) {
+ subPkgPath := strings.TrimPrefix(compilePkgFullPath, i.realInst.BasePackage())
+ if subPkgPath == "" {
+ subPkgPath = "."
+ }
+
+ var debugBaseDir string
+ if i.compileOpts.DebugDir != "" {
+ debugBaseDir = filepath.Join(i.compileOpts.DebugDir, "plugins", i.realInst.Name(), subPkgPath)
+ }
+ pkgCopiedEntries, err := i.realInst.FS().ReadDir(subPkgPath)
+ if err != nil {
+ return nil, err
+ }
+ files := make([]*rewrite.FileInfo, 0)
+ for _, entry := range pkgCopiedEntries {
+ if entry.IsDir() {
+ continue
+ }
+ if entry.Name() == "go.mod" || entry.Name() == "go.sum" || strings.HasSuffix(entry.Name(), "_test.go") {
+ continue
+ }
+
+ readFile, err1 := fs.ReadFile(i.realInst.FS(), filepath.Join(subPkgPath, entry.Name()))
+ if err1 != nil {
+ return nil, err1
+ }
+ // ignore nocopy files
+ if bytes.Contains(readFile, []byte("//skywalking:nocopy")) {
+ continue
+ }
+
+ files = append(files, rewrite.NewFile(packageName, entry.Name(), string(readFile)))
+ }
+
+ rewrited, err := context.MultipleFilesWithWritten("skywalking_enhance_", baseDir, packageName, files, debugBaseDir)
+ if err != nil {
+ return nil, err
+ }
+ return rewrited, nil
+}
+
+func (i *Instrument) copyOperatorsFS(context *rewrite.Context, baseDir, packageName string) ([]string, error) {
+ result := make([]string, 0)
+ var debugBaseDir string
+ for _, dir := range rewrite.OperatorDirs {
+ entries, err := core.FS.ReadDir(dir)
+ if err != nil {
+ return nil, err
+ }
+ files := make([]*rewrite.FileInfo, 0)
+ for _, entry := range entries {
+ if strings.HasSuffix(entry.Name(), "_test.go") {
+ continue
+ }
+ file, err1 := fs.ReadFile(core.FS, filepath.Join(dir, entry.Name()))
+ if err1 != nil {
+ return nil, err1
+ }
+ files = append(files, rewrite.NewFile(dir, entry.Name(), string(file)))
+ }
+ if i.compileOpts.DebugDir != "" {
+ debugBaseDir = filepath.Join(i.compileOpts.DebugDir, "plugins", "core", dir)
+ }
+
+ rewrited, err := context.MultipleFilesWithWritten("skywalking_agent_core_", baseDir, filepath.Base(dir), files, debugBaseDir)
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, rewrited...)
+ }
+
+ // write extra file for link the operator function
+ tmpFiles, err := tools.WriteMultipleFile(baseDir, map[string]string{
+ "skywalking_agent_core_linker.go": tools.ExecuteTemplate(`package {{.PackageName}}
+
+import (
+ _ "unsafe"
+)
+
+//go:linkname {{.OperatorGetLinkMethod}} {{.OperatorGetLinkMethod}}
+var {{.OperatorGetLinkMethod}} func() interface{}
+
+func init() {
+ if {{.OperatorGetLinkMethod}} != nil {
+ {{.OperatorGetRealMethod}} = func() {{.OperatorTypeName}} {
+ tmpOp := {{.OperatorGetLinkMethod}}()
+ if tmpOp == nil {
+ return nil
+ }
+ if opVal, ok := tmpOp.({{.OperatorTypeName}}); ok {
+ return opVal
+ }
+ return nil
+ }
+ }
+}
+`, struct {
+ PackageName string
+ OperatorGetLinkMethod string
+ OperatorGetRealMethod string
+ OperatorTypeName string
+ }{
+ PackageName: packageName,
+ OperatorGetLinkMethod: rewrite.GlobalOperatorLinkGetMethodName,
+ OperatorGetRealMethod: rewrite.GlobalOperatorRealGetMethodName,
+ OperatorTypeName: rewrite.GlobalOperatorTypeName,
+ }),
+ })
+ if err != nil {
+ return nil, err
+ }
+ result = append(result, tmpFiles...)
+ return result, nil
+}
+
+func (i *Instrument) writeAdapterFile(ctx *rewrite.Context, basePath string) ([]string, error) {
+ file := &dst.File{
+ Name: dst.NewIdent("adapter"), // write to adapter temporary, it will be rewritten later
+ }
+
+ for _, enhance := range i.enhancements {
+ file.Decls = append(file.Decls, enhance.BuildForAdapter()...)
+ }
+
+ ctx.SingleFile(file)
+
+ adapterFile := filepath.Join(basePath, "skywalking_adapter.go")
+ if err := tools.WriteDSTFile(adapterFile, file, nil); err != nil {
+ return nil, err
+ }
+ return []string{adapterFile}, nil
+}
+
+func (i *Instrument) validateStructIsMatch(matcher *instrument.EnhanceMatcher, node *dst.TypeSpec, allFiles []*dst.File) bool {
+ if matcher.Name != node.Name.Name {
+ return false
+ }
+ if _, ok := node.Type.(*dst.StructType); !ok {
+ return false
+ }
+ for _, filter := range matcher.StructFilters {
+ if !filter(node, allFiles) {
+ return false
+ }
+ }
+ return true
+}
+
+func (i *Instrument) validateMethodInsMatch(matcher *instrument.EnhanceMatcher, node *dst.FuncDecl, allFiles []*dst.File) bool {
+ if matcher.Name != node.Name.Name {
+ return false
+ }
+ if matcher.Receiver != "" {
+ if node.Recv == nil || len(node.Recv.List) == 0 {
+ return false
+ }
+ var data dst.Expr
+ switch t := node.Recv.List[0].Type.(type) {
+ case *dst.StarExpr:
+ data = t.X
+ case *dst.TypeAssertExpr:
+ data = t.X
+ default:
+ return false
+ }
+
+ if id, ok := data.(*dst.Ident); !ok {
+ return false
+ } else if id.Name != matcher.Receiver {
+ return false
+ }
+ }
+ for _, filter := range matcher.MethodFilters {
+ if !filter(node, allFiles) {
+ return false
+ }
+ }
+ return true
+}
+
+func (i *Instrument) tryToFindThePluginVersion(opts *api.CompileOptions, pkg string) (string, error) {
+ for _, arg := range opts.AllArgs {
+ // find the go file
+ if !strings.HasSuffix(arg, ".go") {
+ continue
+ }
+
+ parts := strings.SplitN(arg, pkg, 2)
+ // example: github.com/gin-gonic/gin@1.1.1/gin.go
+ if len(parts) != 2 || !strings.HasPrefix(parts[1], "@") {
+ return "", fmt.Errorf("could not found the go version of the package %s, go file path: %s", pkg, arg)
+ }
+ firstDir := strings.Index(parts[1], "/")
+ if firstDir == -1 {
+ return "", fmt.Errorf("could not found the first directory index for package: %s, go file path: %s", pkg, arg)
+ }
+ return parts[1][1:firstDir], nil
+ }
+ return "", nil
+}
diff --git a/agent/core/tracing/compile.go b/tools/go-agent/instrument/framework/register.go
similarity index 69%
copy from agent/core/tracing/compile.go
copy to tools/go-agent/instrument/framework/register.go
index 5ccde8b..0c0bcf1 100644
--- a/agent/core/tracing/compile.go
+++ b/tools/go-agent/instrument/framework/register.go
@@ -15,21 +15,20 @@
// specific language governing permissions and limitations
// under the License.
-package tracing
+package framework
import (
- //go:nolint
- _ "fmt"
- _ "reflect"
+ "github.com/apache/skywalking-go/plugins/core/instrument"
+ "github.com/apache/skywalking-go/plugins/ginv2"
+)
- //go:nolint
- _ "github.com/pkg/errors"
+var instruments = make([]instrument.Instrument, 0)
- //go:nolint
- _ "github.com/apache/skywalking-go/agent/core"
- _ "github.com/apache/skywalking-go/reporter"
+func init() {
+ // register the framework instrument
+ registerFramework(ginv2.NewInstrument())
+}
- //go:nolint
- _ "skywalking.apache.org/repo/goapi/collect/common/v3"
- _ "skywalking.apache.org/repo/goapi/collect/language/agent/v3"
-)
+func registerFramework(ins instrument.Instrument) {
+ instruments = append(instruments, ins)
+}
diff --git a/tools/go-agent/instrument/framework/rewrite/context.go b/tools/go-agent/instrument/framework/rewrite/context.go
new file mode 100644
index 0000000..8665c08
--- /dev/null
+++ b/tools/go-agent/instrument/framework/rewrite/context.go
@@ -0,0 +1,298 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package rewrite
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/dave/dst"
+
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+)
+
+var GenerateMethodPrefix = "_skywalking_enhance_"
+var GenerateVarPrefix = "_skywalking_var_"
+var OperatorDirs = []string{"operator", "log", "tracing"}
+
+var OperatePrefix = "skywalkingOperator"
+var TypePrefix = OperatePrefix + "Type"
+var VarPrefix = OperatePrefix + "Var"
+var StaticMethodPrefix = OperatePrefix + "StaticMethod"
+
+type Context struct {
+ pkgFullPath string
+ titleCase cases.Caser
+ targetPackage string
+
+ currentPackageTitle string
+
+ packageImport map[string]*rewriteImportInfo
+ rewriteMapping *rewriteMapping
+}
+
+func NewContext(compilePkgFullPath, targetPackage string) *Context {
+ return &Context{
+ pkgFullPath: compilePkgFullPath,
+ titleCase: cases.Title(language.English),
+ targetPackage: targetPackage,
+ packageImport: make(map[string]*rewriteImportInfo),
+ rewriteMapping: newRewriteFuncMapping(make(map[string]string), make(map[string]string)),
+ }
+}
+
+type rewriteImportInfo struct {
+ pkgName string
+ isAgentCore bool
+ ctx *Context
+}
+
+func (c *Context) enhanceVarNameWhenRewrite(fieldType dst.Expr) (oldName, replacedName string) {
+ switch t := fieldType.(type) {
+ case *dst.Ident:
+ name := t.Name
+ if c.typeIsBasicTypeValueOrEnhanceName(name) {
+ return "", ""
+ }
+ if mappingName := c.rewriteMapping.findVarMappingName(name); mappingName != "" {
+ t.Name = mappingName
+ return "", ""
+ }
+ // keep the var name help debugging
+ c.rewriteMapping.addVarMapping(name, name)
+ return name, t.Name
+ case *dst.SelectorExpr:
+ return c.enhanceVarNameWhenRewrite(t.X)
+ case *dst.IndexExpr:
+ c.rewriteVarIfExistingMapping(t.Index)
+ return c.enhanceVarNameWhenRewrite(t.X)
+ }
+ return "", ""
+}
+
+// nolint
+func (c *Context) enhanceTypeNameWhenRewrite(fieldType dst.Expr, parent dst.Node, argIndex int) (string, string) {
+ switch t := fieldType.(type) {
+ case *dst.Ident:
+ name := t.Name
+ if c.typeIsBasicTypeValueOrEnhanceName(name) {
+ return "", ""
+ }
+ if c.callIsBasicNamesOrEnhanceName(name) {
+ return "", ""
+ }
+ if mappingName := c.rewriteMapping.findVarMappingName(name); mappingName != "" {
+ t.Name = mappingName
+ return "", ""
+ }
+ if mappingName := c.rewriteMapping.findTypeMappingName(name); mappingName != "" {
+ t.Name = mappingName
+ return "", ""
+ }
+ // if parent is function call, then the name should be method name
+ if _, ok := parent.(*dst.CallExpr); ok {
+ t.Name = fmt.Sprintf("%s%s%s", StaticMethodPrefix, c.currentPackageTitle, name)
+ } else {
+ t.Name = fmt.Sprintf("%s%s%s", TypePrefix, c.currentPackageTitle, name)
+ }
+ return name, t.Name
+ case *dst.SelectorExpr:
+ pkgRefName, ok := t.X.(*dst.Ident)
+ if !ok {
+ return c.enhanceTypeNameWhenRewrite(t.X, parent, -1)
+ }
+ // reference by package name
+ for refImportName, pkgInfo := range c.packageImport {
+ if pkgRefName.Name == refImportName {
+ switch p := parent.(type) {
+ case *dst.CallExpr:
+ if c.rewriteVarIfExistingMapping(t.Sel) {
+ if argIndex >= 0 {
+ p.Args[argIndex] = t.Sel
+ } else {
+ p.Fun = dst.NewIdent(t.Sel.Name)
+ }
+ } else {
+ p.Fun = pkgInfo.generateStaticMethod(t.Sel.Name)
+ }
+ case *dst.Field:
+ p.Type = pkgInfo.generateType(t.Sel.Name)
+ case *dst.Ellipsis:
+ p.Elt = pkgInfo.generateType(t.Sel.Name)
+ case *dst.StarExpr:
+ p.X = pkgInfo.generateType(t.Sel.Name)
+ case *dst.TypeAssertExpr:
+ p.Type = pkgInfo.generateType(t.Sel.Name)
+ case *dst.CompositeLit:
+ p.Type = pkgInfo.generateType(t.Sel.Name)
+ case *dst.ArrayType:
+ p.Elt = pkgInfo.generateType(t.Sel.Name)
+ }
+ }
+ }
+ // if the method call
+ if v := c.rewriteMapping.findVarMappingName(pkgRefName.Name); v != "" {
+ t.X = dst.NewIdent(v)
+ }
+ case *dst.StarExpr:
+ return c.enhanceTypeNameWhenRewrite(t.X, t, -1)
+ case *dst.ArrayType:
+ return c.enhanceTypeNameWhenRewrite(t.Elt, t, -1)
+ case *dst.Ellipsis:
+ return c.enhanceTypeNameWhenRewrite(t.Elt, t, -1)
+ case *dst.CompositeLit:
+ for _, elt := range t.Elts {
+ // for struct data, ex: "&xxx{k: v}"
+ if kv, ok := elt.(*dst.KeyValueExpr); ok {
+ c.rewriteVarIfExistingMapping(kv.Value)
+ }
+ }
+ return c.enhanceTypeNameWhenRewrite(t.Type, t, -1)
+ case *dst.UnaryExpr:
+ return c.enhanceTypeNameWhenRewrite(t.X, t, -1)
+ case *dst.CallExpr:
+ for inx, arg := range t.Args {
+ c.enhanceTypeNameWhenRewrite(arg, t, inx)
+ }
+
+ if id, ok := t.Fun.(*dst.Ident); ok {
+ if c.callIsBasicNamesOrEnhanceName(id.Name) {
+ return "", ""
+ }
+ return c.enhanceTypeNameWhenRewrite(t.Fun, t, -1)
+ }
+ c.enhanceTypeNameWhenRewrite(t.Fun, t, -1)
+ case *dst.FuncType:
+ c.enhanceFuncParameter(t.TypeParams)
+ c.enhanceFuncParameter(t.Params)
+ c.enhanceFuncParameter(t.Results)
+ case *dst.IndexExpr:
+ c.rewriteVarIfExistingMapping(t.Index)
+ return c.enhanceTypeNameWhenRewrite(t.X, t, -1)
+ case *dst.TypeAssertExpr:
+ c.enhanceTypeNameWhenRewrite(t.Type, t, -1)
+ return c.enhanceTypeNameWhenRewrite(t.X, t, -1)
+ case *dst.FuncLit:
+ c.enhanceTypeNameWhenRewrite(t.Type, t, -1)
+ for _, stmt := range t.Body.List {
+ c.enhanceFuncStmt(stmt)
+ }
+ case *dst.BinaryExpr:
+ c.enhanceTypeNameWhenRewrite(t.X, t, -1)
+ c.enhanceTypeNameWhenRewrite(t.Y, t, -1)
+ case *dst.ParenExpr:
+ c.rewriteVarIfExistingMapping(t.X)
+ case *dst.MapType:
+ c.enhanceTypeNameWhenRewrite(t.Key, t, -1)
+ c.enhanceTypeNameWhenRewrite(t.Value, t, -1)
+ }
+
+ return "", ""
+}
+
+func (c *Context) typeIsBasicTypeValueOrEnhanceName(name string) bool {
+ if strings.HasPrefix(name, OperatePrefix) || strings.HasPrefix(name, GenerateMethodPrefix) || IsBasicDataType(name) ||
+ name == "nil" || name == "true" || name == "false" {
+ return true
+ }
+ if _, valErr := strconv.ParseFloat(name, 64); valErr == nil {
+ return true
+ }
+ return false
+}
+
+func (c *Context) callIsBasicNamesOrEnhanceName(name string) bool {
+ return strings.HasPrefix(name, OperatePrefix) || strings.HasPrefix(name, GenerateMethodPrefix) ||
+ name == "make" || name == "recover" || name == "len"
+}
+
+// nolint
+func IsBasicDataType(name string) bool {
+ return name == "bool" || name == "int8" || name == "int16" || name == "int32" || name == "int64" || name == "uint8" ||
+ name == "uint16" || name == "uint32" || name == "uint64" || name == "int" || name == "uint" || name == "uintptr" ||
+ name == "float32" || name == "float64" || name == "complex64" || name == "complex128" || name == "string" || name == "error" ||
+ name == "interface{}" || name == "_"
+}
+
+func (r *rewriteImportInfo) generateStaticMethod(name string) *dst.Ident {
+ if r.ctx.typeIsBasicTypeValueOrEnhanceName(name) {
+ return dst.NewIdent(name)
+ }
+ if r.isAgentCore {
+ return dst.NewIdent(fmt.Sprintf("%s%s%s", StaticMethodPrefix, r.ctx.titleCase.String(r.pkgName), name))
+ }
+ return dst.NewIdent(name)
+}
+
+func (r *rewriteImportInfo) generateType(name string) *dst.Ident {
+ if r.isAgentCore {
+ return dst.NewIdent(fmt.Sprintf("%s%s%s", TypePrefix, r.ctx.titleCase.String(r.pkgName), name))
+ }
+ return dst.NewIdent(name)
+}
+
+type rewriteMapping struct {
+ // push or pop the names when the block statement is called
+ rewriteVarNames []map[string]string
+ rewriteTypeNames []map[string]string
+}
+
+func newRewriteFuncMapping(varNames, typeNames map[string]string) *rewriteMapping {
+ return &rewriteMapping{
+ rewriteVarNames: []map[string]string{varNames},
+ rewriteTypeNames: []map[string]string{typeNames},
+ }
+}
+
+func (m *rewriteMapping) addVarMapping(key, value string) {
+ m.rewriteVarNames[len(m.rewriteVarNames)-1][key] = value
+}
+
+func (m *rewriteMapping) addTypeMapping(key, value string) {
+ m.rewriteTypeNames[len(m.rewriteTypeNames)-1][key] = value
+}
+
+func (m *rewriteMapping) pushBlockStack() {
+ m.rewriteVarNames = append(m.rewriteVarNames, make(map[string]string))
+ m.rewriteTypeNames = append(m.rewriteTypeNames, make(map[string]string))
+}
+
+func (m *rewriteMapping) popBlockStack() {
+ m.rewriteVarNames = m.rewriteVarNames[:len(m.rewriteVarNames)-1]
+ m.rewriteTypeNames = m.rewriteTypeNames[:len(m.rewriteTypeNames)-1]
+}
+
+func (m *rewriteMapping) findVarMappingName(name string) string {
+ for i := len(m.rewriteVarNames) - 1; i >= 0; i-- {
+ if v, ok := m.rewriteVarNames[i][name]; ok {
+ return v
+ }
+ }
+ return ""
+}
+
+func (m *rewriteMapping) findTypeMappingName(name string) string {
+ for i := len(m.rewriteTypeNames) - 1; i >= 0; i-- {
+ if v, ok := m.rewriteTypeNames[i][name]; ok {
+ return v
+ }
+ }
+ return ""
+}
diff --git a/tools/go-agent/instrument/framework/rewrite/func.go b/tools/go-agent/instrument/framework/rewrite/func.go
new file mode 100644
index 0000000..5b48c85
--- /dev/null
+++ b/tools/go-agent/instrument/framework/rewrite/func.go
@@ -0,0 +1,205 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package rewrite
+
+import (
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+)
+
+var (
+ GlobalOperatorRealSetMethodName = VarPrefix + "OperatorSetOperator"
+ GlobalOperatorRealGetMethodName = VarPrefix + "OperatorGetOperator"
+ GlobalOperatorLinkSetMethodName = "_skywalking_set_global_operator"
+ GlobalOperatorLinkGetMethodName = "_skywalking_get_global_operator"
+
+ GlobalOperatorTypeName = TypePrefix + "OperatorOperator"
+)
+
+func (c *Context) Func(funcDecl *dst.FuncDecl, cursor *dstutil.Cursor) {
+ // only the static method needs rewrite
+ if funcDecl.Recv == nil {
+ // if the method name is generated, then ignore to enhance(for adapter)
+ if !strings.HasPrefix(funcDecl.Name.Name, GenerateMethodPrefix) {
+ funcDecl.Name = dst.NewIdent(fmt.Sprintf("%s%s%s", StaticMethodPrefix, c.currentPackageTitle, funcDecl.Name.Name))
+ }
+ } else if len(funcDecl.Recv.List) == 1 {
+ // if contains the receiver, then enhance the receiver type
+ field := funcDecl.Recv.List[0]
+ var typeName string
+ if len(field.Names) > 0 {
+ typeName = field.Names[0].Name
+ c.rewriteMapping.addVarMapping(typeName, typeName)
+ }
+ if k, v := c.enhanceTypeNameWhenRewrite(field.Type, field, -1); k != "" {
+ c.rewriteMapping.addTypeMapping(k, v)
+ }
+ }
+
+ // enhance method parameter and return value
+ c.enhanceFuncParameter(funcDecl.Type.Params)
+ c.enhanceFuncParameter(funcDecl.Type.Results)
+
+ // enhance the method body
+ for _, stmt := range funcDecl.Body.List {
+ c.enhanceFuncStmt(stmt)
+ }
+}
+
+// nolint
+func (c *Context) enhanceFuncStmt(stmt dst.Stmt) {
+ // for the variables created in the sub statement, ex: if, func(), the temporary variable count should be recorded
+ subCallTypes := []reflect.Type{
+ reflect.TypeOf(&dst.IfStmt{}),
+ reflect.TypeOf(&dst.BlockStmt{}),
+ }
+ dstutil.Apply(stmt, func(cursor *dstutil.Cursor) bool {
+ for _, t := range subCallTypes {
+ if reflect.TypeOf(cursor.Node()) == t {
+ c.rewriteMapping.pushBlockStack()
+ }
+ }
+ switch n := cursor.Node().(type) {
+ case *dst.BlockStmt:
+ for _, tmp := range n.List {
+ c.enhanceFuncStmt(tmp)
+ }
+ case *dst.AssignStmt:
+ for _, l := range n.Lhs {
+ if k, v := c.enhanceVarNameWhenRewrite(l); k != "" {
+ c.rewriteMapping.addVarMapping(k, v)
+ }
+ }
+ for i, r := range n.Rhs {
+ if k, v := c.enhanceTypeNameWhenRewrite(r, nil, i); k != "" {
+ c.rewriteMapping.addTypeMapping(k, v)
+ }
+ }
+ case *dst.BinaryExpr:
+ c.rewriteVarIfExistingMapping(n.X)
+ c.rewriteVarIfExistingMapping(n.Y)
+ case *dst.CallExpr:
+ c.enhanceTypeNameWhenRewrite(n.Fun, n, -1)
+ for inx, arg := range n.Args {
+ c.enhanceTypeNameWhenRewrite(arg, n, inx)
+ }
+ case *dst.ReturnStmt:
+ for inx, arg := range n.Results {
+ c.enhanceTypeNameWhenRewrite(arg, n, inx)
+ }
+ case *dst.FuncType:
+ c.enhanceFuncParameter(n.Params)
+ c.enhanceFuncParameter(n.Results)
+ case *dst.ExprStmt:
+ c.enhanceTypeNameWhenRewrite(n.X, n, -1)
+ case *dst.TypeAssertExpr:
+ c.enhanceTypeNameWhenRewrite(n.X, n, -1)
+ c.enhanceTypeNameWhenRewrite(n.Type, n, -1)
+ case *dst.IfStmt:
+ c.enhanceFuncStmt(n.Init)
+ c.enhanceTypeNameWhenRewrite(n.Cond, n, -1)
+ if n.Body != nil {
+ for _, stmt := range n.Body.List {
+ c.enhanceFuncStmt(stmt)
+ }
+ }
+ case *dst.RangeStmt:
+ c.enhanceTypeNameWhenRewrite(n.X, n, -1)
+ if k, v := c.enhanceVarNameWhenRewrite(n.Key); k != "" {
+ c.rewriteMapping.addVarMapping(k, v)
+ }
+ if k, v := c.enhanceVarNameWhenRewrite(n.Value); k != "" {
+ c.rewriteMapping.addVarMapping(k, v)
+ }
+ if n.Body != nil {
+ for _, stmt := range n.Body.List {
+ c.enhanceFuncStmt(stmt)
+ }
+ }
+ case *dst.ValueSpec:
+ c.Var(n)
+ default:
+ return true
+ }
+
+ return false
+ }, func(cursor *dstutil.Cursor) bool {
+ // all templates variables should be removed
+ for _, t := range subCallTypes {
+ if reflect.TypeOf(cursor.Node()) == t {
+ c.rewriteMapping.popBlockStack()
+ break
+ }
+ }
+ return true
+ })
+}
+
+func (c *Context) rewriteVarIfExistingMapping(exp dst.Expr) bool {
+ switch n := exp.(type) {
+ case *dst.Ident:
+ if v := c.rewriteMapping.findVarMappingName(n.Name); v != "" {
+ n.Name = v
+ return true
+ }
+ case *dst.SelectorExpr:
+ return c.rewriteVarIfExistingMapping(n.X)
+ case *dst.CompositeLit:
+ c.enhanceTypeNameWhenRewrite(n.Type, n, -1)
+ for _, elt := range n.Elts {
+ // for struct data, ex: "&xxx{k: v}"
+ if kv, ok := elt.(*dst.KeyValueExpr); ok {
+ c.rewriteVarIfExistingMapping(kv.Value)
+ }
+ }
+ case *dst.UnaryExpr:
+ c.enhanceTypeNameWhenRewrite(n.X, n, -1)
+ case *dst.IndexExpr:
+ c.rewriteVarIfExistingMapping(n.Index)
+ c.rewriteVarIfExistingMapping(n.X)
+ case *dst.CallExpr:
+ c.enhanceTypeNameWhenRewrite(n.Fun, n, -1)
+ for _, arg := range n.Args {
+ c.rewriteVarIfExistingMapping(arg)
+ }
+ }
+ return false
+}
+
+func (c *Context) enhanceFuncParameter(fields *dst.FieldList) {
+ if fields == nil {
+ return
+ }
+
+ for _, field := range fields.List {
+ if len(field.Names) > 0 {
+ for inx := range field.Names {
+ name := field.Names[inx].Name
+ // keep the var names for debugging
+ c.rewriteMapping.addVarMapping(name, name)
+ }
+ }
+ if k, v := c.enhanceTypeNameWhenRewrite(field.Type, field, -1); k != "" {
+ c.rewriteMapping.addTypeMapping(k, v)
+ }
+ }
+}
diff --git a/tools/go-agent/instrument/framework/rewrite/import.go b/tools/go-agent/instrument/framework/rewrite/import.go
new file mode 100644
index 0000000..5729a0d
--- /dev/null
+++ b/tools/go-agent/instrument/framework/rewrite/import.go
@@ -0,0 +1,57 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package rewrite
+
+import (
+ "fmt"
+ "path/filepath"
+
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/agentcore"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/dstutil"
+)
+
+func (c *Context) Import(imp *dst.ImportSpec, cursor *dstutil.Cursor) {
+ shouldRemove := false
+ operatorCode := false
+ // delete the original import("agent/core/xxx")
+ for _, subPackageName := range OperatorDirs {
+ if imp.Path.Value == fmt.Sprintf("%q", filepath.Join(agentcore.EnhanceFromBasePackage, subPackageName)) {
+ shouldRemove = true
+ operatorCode = true
+ break
+ }
+ }
+ // delete the same framework package import, such as the interceptor of http package("github.com/gin-gonic/gin")
+ if imp.Path.Value == fmt.Sprintf("%q", c.pkgFullPath) {
+ shouldRemove = true
+ }
+
+ if shouldRemove {
+ realPath := imp.Path.Value[1 : len(imp.Path.Value)-1]
+ subPath := filepath.Base(realPath)
+ info := &rewriteImportInfo{pkgName: subPath, isAgentCore: operatorCode, ctx: c}
+ if imp.Name == nil {
+ c.packageImport[subPath] = info
+ } else {
+ c.packageImport[imp.Name.Name] = info
+ }
+ cursor.Delete()
+ }
+}
diff --git a/tools/go-agent/instrument/framework/rewrite/rewrite.go b/tools/go-agent/instrument/framework/rewrite/rewrite.go
new file mode 100644
index 0000000..db8c7e3
--- /dev/null
+++ b/tools/go-agent/instrument/framework/rewrite/rewrite.go
@@ -0,0 +1,115 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package rewrite
+
+import (
+ "fmt"
+ "go/parser"
+ "go/token"
+ "path/filepath"
+
+ "github.com/apache/skywalking-go/tools/go-agent/tools"
+
+ "github.com/dave/dst"
+ "github.com/dave/dst/decorator"
+ "github.com/dave/dst/dstutil"
+)
+
+type FileInfo struct {
+ // the original file path, for debugging
+ OriginalFilePath string
+
+ PackageName string
+ FileName string
+ FileData string
+}
+
+type DebugInfo struct {
+ BaseDir string
+}
+
+func NewFile(packageName, fileName, data string) *FileInfo {
+ return &FileInfo{PackageName: packageName, FileName: fileName, FileData: data}
+}
+
+// MultipleFilesWithWritten for rewrite all operator/interceptor files
+func (c *Context) MultipleFilesWithWritten(writeFileNamePrefix, targetDir, fromPackage string,
+ originalFiles []*FileInfo, debugBaseDir string) ([]string, error) {
+ result := make([]string, 0)
+
+ for _, f := range originalFiles {
+ parseFile, err := decorator.ParseFile(nil, f.FileName, f.FileData, parser.ParseComments)
+ if err != nil {
+ return nil, err
+ }
+ var debugInfo *tools.DebugInfo
+ if debugBaseDir != "" {
+ debugInfo, err = tools.BuildDSTDebugInfo(filepath.Join(debugBaseDir, f.FileName), parseFile)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ c.processSingleFile(parseFile, fromPackage)
+ targetPath := filepath.Join(targetDir,
+ fmt.Sprintf("%s%s_%s", writeFileNamePrefix, f.PackageName, filepath.Base(f.FileName)))
+ if err := tools.WriteDSTFile(targetPath, parseFile, debugInfo); err != nil {
+ return nil, err
+ }
+ result = append(result, targetPath)
+ }
+ return result, nil
+}
+
+// SingleFile rewrite single file in memory, not write the file
+func (c *Context) SingleFile(file *dst.File) {
+ c.processSingleFile(file, c.targetPackage)
+}
+
+func (c *Context) processSingleFile(file *dst.File, fromPackage string) {
+ c.currentPackageTitle = c.titleCase.String(fromPackage)
+ file.Name.Name = c.targetPackage
+ dstutil.Apply(file, func(cursor *dstutil.Cursor) bool {
+ switch n := cursor.Node().(type) {
+ case *dst.FuncDecl:
+ c.Func(n, cursor)
+ case *dst.ImportSpec:
+ c.Import(n, cursor)
+ case *dst.TypeSpec:
+ c.Type(n)
+ case *dst.ValueSpec:
+ c.Var(n)
+ default:
+ return true
+ }
+ return false
+ }, func(cursor *dstutil.Cursor) bool {
+ return true
+ })
+
+ // remove the import decl if empty
+ dstutil.Apply(file, func(cursor *dstutil.Cursor) bool {
+ if decl, ok := cursor.Node().(*dst.GenDecl); ok && decl.Tok == token.IMPORT && len(decl.Specs) == 0 {
+ cursor.Delete()
+ return false
+ }
+ return true
+ }, func(cursor *dstutil.Cursor) bool {
+ return true
+ })
+}
diff --git a/tools/go-agent/instrument/framework/rewrite/type.go b/tools/go-agent/instrument/framework/rewrite/type.go
new file mode 100644
index 0000000..c1baa6b
--- /dev/null
+++ b/tools/go-agent/instrument/framework/rewrite/type.go
@@ -0,0 +1,56 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package rewrite
+
+import (
+ "fmt"
+
+ "github.com/dave/dst"
+)
+
+func (c *Context) Type(tp *dst.TypeSpec) {
+ oldName := tp.Name.Name
+ tp.Name = dst.NewIdent(fmt.Sprintf("%s%s%s", TypePrefix, c.currentPackageTitle, oldName))
+ c.rewriteMapping.addTypeMapping(oldName, tp.Name.Name)
+
+ // define interface type, ex: "type xxx interface {}"
+ if inter, ok := tp.Type.(*dst.InterfaceType); ok {
+ for _, method := range inter.Methods.List {
+ funcType, ok := method.Type.(*dst.FuncType)
+ if !ok {
+ continue
+ }
+
+ c.enhanceFuncParameter(funcType.Params)
+ c.enhanceFuncParameter(funcType.Results)
+ }
+ }
+
+ // define func type, ex: "type xxx func(x X) x"
+ if funcType, ok := tp.Type.(*dst.FuncType); ok {
+ c.enhanceFuncParameter(funcType.Params)
+ c.enhanceFuncParameter(funcType.Results)
+ }
+
+ // define struct type, ex: "type xx struct {}"
+ if structType, ok := tp.Type.(*dst.StructType); ok && structType.Fields != nil {
+ for _, field := range structType.Fields.List {
+ c.enhanceTypeNameWhenRewrite(field.Type, field, -1)
+ }
+ }
+}
diff --git a/tools/go-agent/cmd/helper.go b/tools/go-agent/instrument/framework/rewrite/var.go
similarity index 62%
copy from tools/go-agent/cmd/helper.go
copy to tools/go-agent/instrument/framework/rewrite/var.go
index d97e152..bcab56c 100644
--- a/tools/go-agent/cmd/helper.go
+++ b/tools/go-agent/instrument/framework/rewrite/var.go
@@ -15,26 +15,25 @@
// specific language governing permissions and limitations
// under the License.
-package main
+package rewrite
import (
"fmt"
- "os"
-)
-
-type EnhancementToolFlags struct {
- Help bool `skyflag:"-h"`
-}
+ "strings"
-func PrintUsageWithExit() {
- fmt.Printf(`Usage: go {build,install} -a [-work] -toolexec "%s" PACKAGE...
-
-The Go-agent-enhance tool is designed for automatic enhancement of Golang programs,
-providing the capability for Distribute Tracing.
+ "github.com/dave/dst"
+)
-Options:
- -h
- Print the usage message.
-`, os.Args[0])
- os.Exit(1)
+func (c *Context) Var(val *dst.ValueSpec) {
+ if len(val.Names) == 1 {
+ oldName := val.Names[0].Name
+ if !strings.HasPrefix(oldName, GenerateVarPrefix) {
+ val.Names[0] = dst.NewIdent(fmt.Sprintf("%s%s%s", VarPrefix, c.currentPackageTitle, oldName))
+ c.rewriteMapping.addVarMapping(oldName, val.Names[0].Name)
+ }
+ }
+ c.enhanceTypeNameWhenRewrite(val.Type, val, -1)
+ for _, subVal := range val.Values {
+ c.enhanceTypeNameWhenRewrite(subVal, val, -1)
+ }
}
diff --git a/tools/go-agent/instrument/framework/templates/method_inserts.tmpl b/tools/go-agent/instrument/framework/templates/method_inserts.tmpl
new file mode 100644
index 0000000..52454d5
--- /dev/null
+++ b/tools/go-agent/instrument/framework/templates/method_inserts.tmpl
@@ -0,0 +1,11 @@
+if {{ range $index, $value := .Results -}}
+ _sw_inv_res_{{$index -}},
+{{- end -}}_sw_invocation, _sw_keep := {{.AdapterPreFuncName}}({{ range $index, $value := .Recvs -}}
+ {{ if ne $index 0}},{{ end }}&{{$value.Name -}},
+{{- end -}}{{ range $index, $value := .Parameters -}}
+ {{ if ne $index 0}},{{ end }}&{{$value.Name -}}
+{{- end -}}); !_sw_keep { return {{- range $index, $value := .Results -}}
+ _sw_inv_res_{{$index -}},
+{{- end -}} } else { defer func() { {{.AdapterPostFuncName}}(_sw_invocation{{- range $index, $value := .Results -}}
+,{{- $value.Name -}}
+{{- end -}})}() };
\ No newline at end of file
diff --git a/tools/go-agent/instrument/framework/templates/method_intercept_after.tmpl b/tools/go-agent/instrument/framework/templates/method_intercept_after.tmpl
new file mode 100644
index 0000000..8b20ffa
--- /dev/null
+++ b/tools/go-agent/instrument/framework/templates/method_intercept_after.tmpl
@@ -0,0 +1,17 @@
+defer func() {
+ if r := recover(); r != nil {
+ // log error
+ // fmt.Printf("error: %v", r)
+ log.Errorf("execute interceptor after invoke error, instrument name: %s, interceptor name: %s, function ID: %s, error: %v",
+ "{{.InstrumentName}}", "{{.InterceptorDefineName}}", "{{.FuncID}}", r)
+ }
+}()
+
+// real invoke
+if err := {{.InterceptorVarName}}.AfterInvoke(invocation{{ range $index, $value := .Results -}}
+ , ret_$index
+ {{- end}}); err != nil {
+ // using go2sky log error
+ log.Warnf("execute interceptor after invoke error, instrument name: %s, interceptor name: %s, function ID: %s, error: %v",
+ "{{.InstrumentName}}", "{{.InterceptorDefineName}}", "{{.FuncID}}", err)
+}
\ No newline at end of file
diff --git a/tools/go-agent/instrument/framework/templates/method_intercept_before.tmpl b/tools/go-agent/instrument/framework/templates/method_intercept_before.tmpl
new file mode 100644
index 0000000..2d67031
--- /dev/null
+++ b/tools/go-agent/instrument/framework/templates/method_intercept_before.tmpl
@@ -0,0 +1,33 @@
+defer func() {
+ if err := recover(); err != nil {
+ // log error
+ log.Errorf("execute interceptor before invoke error, instrument name: %s, interceptor name: %s, function ID: %s, error: %v",
+ "{{.InstrumentName}}", "{{.InterceptorDefineName}}", "{{.FuncID}}", err)
+ }
+}()
+invocation := &operator.Invocation{}
+{{if .Recvs -}}
+invocation.CallerInstance = *recv_0 // for caller if exist
+{{- end}}
+invocation.Args = make([]interface{}, {{len .Parameters}})
+{{- range $index, $value := .Parameters}}
+invocation.Args[{{$index}}] = *param_{{$index}}
+{{- end}}
+
+// real invoke
+if err := {{.InterceptorVarName}}.BeforeInvoke(invocation); err != nil {
+ // using go2sky log error
+ log.Warnf("execute interceptor before invoke error, instrument name: %s, interceptor name: %s, function ID: %s, error: %v",
+ "{{.InstrumentName}}", "{{.InterceptorDefineName}}", "{{.FuncID}}", err)
+ return {{ range $index, $value := .Results -}}
+$value.DefaultValueAsString,
+{{- end}}invocation, true
+}
+if (invocation.Continue) {
+ return {{ range $index, $value := .Results -}}
+ invocation.Return[$index],
+{{- end}}invocation, false
+}
+return {{ range $index, $value := .Results -}}
+{{- if ne .index 0}}, {{end}}$value.DefaultValueAsString
+{{- end}}{{if .Results}}, {{- end}}invocation, true
\ No newline at end of file
diff --git a/tools/go-agent/instrument/instrument.go b/tools/go-agent/instrument/instrument.go
index 5c22226..4d2b501 100644
--- a/tools/go-agent/instrument/instrument.go
+++ b/tools/go-agent/instrument/instrument.go
@@ -29,15 +29,17 @@ import (
"github.com/sirupsen/logrus"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument/agentcore"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument/api"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument/runtime"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/tools"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/agentcore"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/api"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/framework"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/runtime"
+ "github.com/apache/skywalking-go/tools/go-agent/tools"
)
var instruments = []api.Instrument{
runtime.NewInstrument(),
agentcore.NewInstrument(),
+ framework.NewInstrument(),
}
func Execute(opts *api.CompileOptions, args []string) ([]string, error) {
@@ -95,12 +97,17 @@ func instrumentFiles(buildDir string, inst api.Instrument, args []string) error
return err
}
+ allFiles := make([]*dst.File, 0)
+ for _, f := range parsedFiles {
+ allFiles = append(allFiles, f.dstFile)
+ }
+
// filter and edit the files
instrumentedFiles := make([]string, 0)
for path, info := range parsedFiles {
hasInstruted := false
dstutil.Apply(info.dstFile, func(cursor *dstutil.Cursor) bool {
- if inst.FilterAndEdit(path, cursor) {
+ if inst.FilterAndEdit(path, cursor, allFiles) {
hasInstruted = true
}
return true
@@ -118,10 +125,14 @@ func instrumentFiles(buildDir string, inst api.Instrument, args []string) error
info := parsedFiles[updateFileSrc]
filename := filepath.Base(updateFileSrc)
dest := filepath.Join(buildDir, filename)
- if err := tools.WriteDSTFile(dest, updateFileSrc, info.dstFile); err != nil {
+ debugInfo, err := tools.BuildDSTDebugInfo(updateFileSrc, nil)
+ if err != nil {
+ return err
+ }
+ if err := tools.WriteDSTFile(dest, info.dstFile, debugInfo); err != nil {
return err
}
- if err := inst.AfterEnhanceFile(dest); err != nil {
+ if err := inst.AfterEnhanceFile(updateFileSrc, dest); err != nil {
return err
}
args[info.argsIndex] = dest
diff --git a/tools/go-agent/instrument/runtime/instrument.go b/tools/go-agent/instrument/runtime/instrument.go
index a916ad3..33718c8 100644
--- a/tools/go-agent/instrument/runtime/instrument.go
+++ b/tools/go-agent/instrument/runtime/instrument.go
@@ -21,15 +21,19 @@ import (
"github.com/dave/dst"
"github.com/dave/dst/dstutil"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/instrument/api"
- "github.com/apache/skywalking-go/tools/go-agent-enhance/tools"
+ "github.com/apache/skywalking-go/tools/go-agent/instrument/api"
+ "github.com/apache/skywalking-go/tools/go-agent/tools"
)
var (
- TLSFieldName = "skywalking_tls"
- TLSGetMethodName = "_skywalking_get_gls"
- TLSSetMethodName = "_skywalking_set_gls"
- TLSTakeSnapshotMethodName = "_skywalking_tls_take_snapshot"
+ TLSFieldName = "skywalking_tls"
+ TLSGetMethodName = "_skywalking_get_gls"
+ TLSSetMethodName = "_skywalking_set_gls"
+
+ GlobalTracerFieldName = "globalSkyWalkingOperator"
+ GlobalTracerSnapshotInterface = "skywalkingGoroutineSnapshotCreator"
+ GlobalTracerSetMethodName = "_skywalking_set_global_operator"
+ GlobalTracerGetMethodName = "_skywalking_get_global_operator"
)
type Instrument struct {
@@ -43,7 +47,7 @@ func (r *Instrument) CouldHandle(opts *api.CompileOptions) bool {
return opts.Package == "runtime"
}
-func (r *Instrument) FilterAndEdit(path string, cursor *dstutil.Cursor) bool {
+func (r *Instrument) FilterAndEdit(path string, cursor *dstutil.Cursor, allFiles []*dst.File) bool {
switch n := cursor.Node().(type) {
case *dst.TypeSpec:
if n.Name != nil && n.Name.Name != "g" {
@@ -56,8 +60,7 @@ func (r *Instrument) FilterAndEdit(path string, cursor *dstutil.Cursor) bool {
// append the tls field
st.Fields.List = append(st.Fields.List, &dst.Field{
Names: []*dst.Ident{dst.NewIdent(TLSFieldName)},
- Type: dst.NewIdent("interface{}"),
- })
+ Type: dst.NewIdent("interface{}")})
tools.LogWithStructEnhance("runtime", "g", TLSFieldName, "tls field")
return true
case *dst.FuncDecl:
@@ -74,21 +77,20 @@ func (r *Instrument) FilterAndEdit(path string, cursor *dstutil.Cursor) bool {
results := tools.EnhanceParameterNames(n.Type.Results, true)
tools.InsertStmtsBeforeBody(n.Body, `defer func() {
- if {{(index .Parameters 1).Name}} != nil && {{(index .Results 0).Name}} != nil &&
- {{.SnapshotMethod}} != nil && {{(index .Parameters 1).Name}}.{{.TLSField}} != nil {
- {{(index .Results 0).Name}}.{{.TLSField}} = {{.SnapshotMethod}}({{(index .Parameters 1).Name}}.{{.TLSField}})
- }
+ {{(index .Results 0).Name}}.{{.TLSField}} = goroutineChange({{(index .Parameters 1).Name}}.{{.TLSField}})
}()
`, struct {
- Parameters []*tools.ParameterInfo
- Results []*tools.ParameterInfo
- SnapshotMethod string
- TLSField string
+ Parameters []*tools.ParameterInfo
+ Results []*tools.ParameterInfo
+ TLSField string
+ OperatorField string
+ SnapshotInterface string
}{
- Parameters: parameters,
- Results: results,
- SnapshotMethod: TLSTakeSnapshotMethodName,
- TLSField: TLSFieldName,
+ Parameters: parameters,
+ Results: results,
+ TLSField: TLSFieldName,
+ OperatorField: GlobalTracerFieldName,
+ SnapshotInterface: GlobalTracerSnapshotInterface,
})
tools.LogWithMethodEnhance("runtime", "", "newproc1", "support cross goroutine context propagating")
return true
@@ -96,20 +98,19 @@ func (r *Instrument) FilterAndEdit(path string, cursor *dstutil.Cursor) bool {
return false
}
-func (r *Instrument) AfterEnhanceFile(path string) error {
+func (r *Instrument) AfterEnhanceFile(fromPath, newPath string) error {
return nil
}
func (r *Instrument) WriteExtraFiles(dir string) ([]string, error) {
return tools.WriteMultipleFile(dir, map[string]string{
- "tls.go": tools.ExecuteTemplate(`package runtime
+ "skywalking_tls_operator.go": tools.ExecuteTemplate(`package runtime
import (
_ "unsafe"
)
-//go:linkname {{.TLSTaskSnapshotMethod}} {{.TLSTaskSnapshotMethod}}
-var {{.TLSTaskSnapshotMethod}} func(interface{}) interface{}
+var {{.GlobalTracerFieldName}} interface{}
//go:linkname {{.TLSGetMethod}} {{.TLSGetMethod}}
var {{.TLSGetMethod}} = _skywalking_tls_get_impl
@@ -117,6 +118,12 @@ var {{.TLSGetMethod}} = _skywalking_tls_get_impl
//go:linkname {{.TLSSetMethod}} {{.TLSSetMethod}}
var {{.TLSSetMethod}} = _skywalking_tls_set_impl
+//go:linkname {{.GlobalOperatorSetMethodName}} {{.GlobalOperatorSetMethodName}}
+var {{.GlobalOperatorSetMethodName}} = _skywalking_global_operator_set_impl
+
+//go:linkname {{.GlobalOperatorGetMethodName}} {{.GlobalOperatorGetMethodName}}
+var {{.GlobalOperatorGetMethodName}} = _skywalking_global_operator_get_impl
+
//go:nosplit
func _skywalking_tls_get_impl() interface{} {
return getg().m.curg.{{.TLSFiledName}}
@@ -125,16 +132,47 @@ func _skywalking_tls_get_impl() interface{} {
//go:nosplit
func _skywalking_tls_set_impl(v interface{}) {
getg().m.curg.{{.TLSFiledName}} = v
-}`, struct {
- TLSFiledName string
- TLSGetMethod string
- TLSSetMethod string
- TLSTaskSnapshotMethod string
+}
+
+//go:nosplit
+func _skywalking_global_operator_set_impl(v interface{}) {
+ globalSkyWalkingOperator = v
+}
+
+//go:nosplit
+func _skywalking_global_operator_get_impl() interface{} {
+ return globalSkyWalkingOperator
+}
+
+type ContextSnapshoter interface {
+ TakeSnapShot(val interface{}) interface{}
+}
+
+func goroutineChange(tls interface{}) interface{} {
+ if tls == nil {
+ return nil
+ }
+ if taker, ok := tls.(ContextSnapshoter); ok {
+ return taker.TakeSnapShot(tls)
+ }
+ return tls
+}
+`, struct {
+ TLSFiledName string
+ TLSGetMethod string
+ TLSSetMethod string
+ GlobalTracerFieldName string
+ GlobalTracerSnapshotInterface string
+ GlobalOperatorSetMethodName string
+ GlobalOperatorGetMethodName string
}{
- TLSFiledName: TLSFieldName,
- TLSGetMethod: TLSGetMethodName,
- TLSSetMethod: TLSSetMethodName,
- TLSTaskSnapshotMethod: TLSTakeSnapshotMethodName,
+ TLSFiledName: TLSFieldName,
+ TLSGetMethod: TLSGetMethodName,
+ TLSSetMethod: TLSSetMethodName,
+ GlobalTracerFieldName: GlobalTracerFieldName,
+ GlobalTracerSnapshotInterface: GlobalTracerSnapshotInterface,
+ GlobalOperatorSetMethodName: GlobalTracerSetMethodName,
+ GlobalOperatorGetMethodName: GlobalTracerGetMethodName,
}),
})
}
diff --git a/tools/go-agent/tools/dst.go b/tools/go-agent/tools/dst.go
index 951596b..39b49d3 100644
--- a/tools/go-agent/tools/dst.go
+++ b/tools/go-agent/tools/dst.go
@@ -18,9 +18,14 @@
package tools
import (
+ "bytes"
"fmt"
+ "go/ast"
"go/printer"
+ "go/token"
+ "io"
"os"
+ "strings"
"github.com/dave/dst"
"github.com/dave/dst/decorator"
@@ -43,23 +48,148 @@ func ChangePackageImportPath(file dst.Node, pkgChanges map[string]string) {
})
}
-func WriteDSTFile(path, srcPath string, file *dst.File) error {
+type DebugInfo struct {
+ FilePath string
+ Line int
+ CheckOldLine bool
+}
+
+func BuildDSTDebugInfo(srcPath string, file *dst.File) (*DebugInfo, error) {
+ result := &DebugInfo{FilePath: srcPath}
+ if file != nil {
+ fset, f, err := decorator.RestoreFile(file)
+ if err != nil {
+ return nil, err
+ }
+ originalFile, err := os.ReadFile(srcPath)
+ if err != nil {
+ return nil, err
+ }
+ line, err := findFirstNoImportLocation(fset, f, bytes.NewBuffer(originalFile))
+ if err != nil {
+ return nil, err
+ }
+ result.Line = line
+ result.CheckOldLine = true
+ } else {
+ result.Line = 1
+ result.CheckOldLine = false
+ }
+
+ return result, nil
+}
+
+func WriteDSTFile(path string, file *dst.File, debug *DebugInfo) error {
output, err := os.Create(path)
if err != nil {
return err
}
defer output.Close()
- if srcPath != "" {
- if _, err = output.WriteString(fmt.Sprintf("//line %s:1\n", srcPath)); err != nil {
+
+ fset, af, err := decorator.RestoreFile(file)
+ if err != nil {
+ return err
+ }
+
+ if debug != nil {
+ return writeDSTFileWithDebug(fset, af, debug, output)
+ }
+ return printer.Fprint(output, fset, af)
+}
+
+func writeDSTFileWithDebug(fset *token.FileSet, file *ast.File, debug *DebugInfo, output *os.File) error {
+ var changeInfo *dstFilePathChangeInfo
+ if !debug.CheckOldLine {
+ changeInfo = &dstFilePathChangeInfo{
+ oldDebugPath: debug.FilePath,
+ oldDebugLine: 1,
+ newDebugLine: 1,
+ }
+ if _, err := output.WriteString(fmt.Sprintf("//line %s:%d\n", debug.FilePath, debug.Line)); err != nil {
+ return err
+ }
+ if err := printer.Fprint(output, fset, file); err != nil {
return err
}
+ return nil
}
- fset, af, err := decorator.RestoreFile(file)
- if err != nil {
+ var buffer bytes.Buffer
+ if err := printer.Fprint(&buffer, fset, file); err != nil {
return err
}
- if err := printer.Fprint(output, fset, af); err != nil {
+ newPosition, err := findFirstNoImportLocation(fset, file, bytes.NewBuffer(buffer.Bytes()))
+ if err != nil {
return err
}
+ changeInfo = &dstFilePathChangeInfo{
+ oldDebugPath: debug.FilePath,
+ oldDebugLine: debug.Line,
+ newDebugLine: newPosition,
+ }
+
+ lineCount := 1
+ alreadyChange := false
+ for {
+ line, err := buffer.ReadBytes('\n')
+ if err != nil {
+ if err == io.EOF && !alreadyChange {
+ return fmt.Errorf("rewrite file line number failure: %v", err)
+ }
+ break
+ }
+
+ if lineCount == changeInfo.newDebugLine {
+ line = []byte(fmt.Sprintf("//line %s:%d\n%s", debug.FilePath, changeInfo.oldDebugLine, line))
+ alreadyChange = true
+ }
+
+ if _, e := output.Write(line); e != nil {
+ return err
+ }
+
+ lineCount++
+ }
return nil
}
+
+type dstFilePathChangeInfo struct {
+ oldDebugPath string
+ oldDebugLine int
+ newDebugLine int
+}
+
+func findFirstNoImportLocation(fset *token.FileSet, file *ast.File, fileContent *bytes.Buffer) (int, error) {
+ var pos token.Pos
+ for _, decl := range file.Decls {
+ if genDecl, ok := decl.(*ast.GenDecl); ok {
+ if genDecl.Tok == token.IMPORT {
+ pos = genDecl.End()
+ continue
+ }
+ }
+ break
+ }
+ if pos == 0 {
+ if len(file.Decls) > 0 {
+ return fset.Position(file.Decls[0].Pos()).Line, nil
+ }
+ return 1, nil
+ }
+ importEndLine := fset.Position(pos).Line
+ lineNumber := 0
+ for {
+ line, err := fileContent.ReadBytes('\n')
+ if err != nil {
+ return 0, err
+ }
+ lineNumber++
+ if lineNumber < importEndLine {
+ continue
+ }
+ trimed := strings.TrimSpace(string(line))
+ if trimed == "" || trimed == ")" {
+ continue
+ }
+ return lineNumber, nil
+ }
+}
diff --git a/tools/go-agent/tools/enhancement.go b/tools/go-agent/tools/enhancement.go
index d362fba..dca0137 100644
--- a/tools/go-agent/tools/enhancement.go
+++ b/tools/go-agent/tools/enhancement.go
@@ -75,6 +75,17 @@ func main() {
return parsed.Decls[0].(*dst.FuncDecl).Body.List
}
+func GoStringToDecls(goString string) []dst.Decl {
+ parsed, err := decorator.Parse(fmt.Sprintf(`
+package main
+%s`, goString))
+ if err != nil {
+ panic(fmt.Sprintf("parsing go failure: %v\n%s", err, goString))
+ }
+
+ return parsed.Decls
+}
+
func InsertStmtsBeforeBody(body *dst.BlockStmt, tmpl string, data interface{}) {
body.List = append(GoStringToStats(ExecuteTemplate(tmpl, data)), body.List...)
}
diff --git a/tools/go-agent/tools/files.go b/tools/go-agent/tools/files.go
index 99ba059..cf84726 100644
--- a/tools/go-agent/tools/files.go
+++ b/tools/go-agent/tools/files.go
@@ -34,3 +34,11 @@ func WriteMultipleFile(baseDir string, nameWithData map[string]string) ([]string
return paths, nil
}
+
+func WriteFile(baseDir, fileName, data string) (string, error) {
+ res := filepath.Join(baseDir, fileName)
+ if err := os.WriteFile(res, []byte(data), 0o600); err != nil {
+ return "", err
+ }
+ return res, nil
+}
diff --git a/tools/go-agent/tools/flags.go b/tools/go-agent/tools/flags.go
index a23c2d4..5256c76 100644
--- a/tools/go-agent/tools/flags.go
+++ b/tools/go-agent/tools/flags.go
@@ -24,67 +24,77 @@ import (
"strings"
)
-const flagTagKey = "skyflag"
+const flagTagKey = "swflag"
-func ParseFlags(result interface{}, args []string) error {
+func ParseFlags(result interface{}, args []string) (noOpIndex int, err error) {
if len(args) == 0 {
- return fmt.Errorf("no args")
+ return 0, fmt.Errorf("no args")
}
flags := parseFlagsFromStruct(result)
if len(flags) == 0 {
- return nil
+ return 0, nil
}
i := 0
+ firstNonOptionIndex := -1
for i < len(args)-1 {
- i += parseFlag(flags, args[i], args[i+1])
+ shift, isOption := parseFlag(flags, args[i], args[i+1])
+ if !isOption && firstNonOptionIndex == -1 {
+ firstNonOptionIndex = i
+ }
+ i += shift
}
if i < len(args) {
parseFlag(flags, args[i], "")
}
- return nil
+
+ // process the all args flag
+ if v, exist := flags["all-args"]; exist {
+ v.Set(reflect.ValueOf(args))
+ }
+ return firstNonOptionIndex, nil
}
-func ParseProxyCommandName(args []string) string {
+func ParseProxyCommandName(args []string, firstNonOptionIndex int) string {
if len(args) == 0 {
return ""
}
- cmd := filepath.Base(args[0])
+ cmd := filepath.Base(args[firstNonOptionIndex])
if ext := filepath.Ext(cmd); ext != "" {
cmd = strings.TrimSuffix(cmd, ext)
}
return cmd
}
-func parseFlag(flags map[string]reflect.Value, curArg, nextArg string) int {
+func parseFlag(flags map[string]reflect.Value, curArg, nextArg string) (shift int, isOption bool) {
if curArg[0] != '-' {
- return 1
+ return 1, false
}
kv := strings.SplitN(curArg, "=", 2)
option := kv[0]
if v, exist := flags[option]; !exist {
if len(kv) == 2 {
- return 1
+ return 1, true
} else if nextArg == "" || (len(nextArg) > 1 && nextArg[0] != '-') {
- return 2
+ return 2, true
}
- return 1
+ return 1, true
} else if len(kv) == 2 {
v.SetString(kv[1])
- return 1
+ return 1, true
} else {
switch v.Kind() {
case reflect.String:
v.SetString(nextArg)
- return 2
+ return 2, true
case reflect.Bool:
v.SetBool(true)
- return 1
+ return 1, true
}
- return 1
+ return 1, true
}
}