This is an automated email from the ASF dual-hosted git repository.
wu-sheng 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 b52ef57 fix(grpc): support Go 1.25 and 1.26 plugin scenarios (#252)
b52ef57 is described below
commit b52ef5762fab16d85704a7d35b8968165d351b85
Author: 何嘉伟 <[email protected]>
AuthorDate: Thu Jul 30 13:28:05 2026 +0800
fix(grpc): support Go 1.25 and 1.26 plugin scenarios (#252)
---
CHANGES.md | 4 +
.../advanced-features/manual-apis/toolkit-trace.md | 16 +++
docs/en/agent/support-plugins.md | 6 +-
go.mod | 2 +-
go.sum | 4 +-
go.work.sum | 7 +-
plugins/grpc/server_handleStream_interceptor.go | 17 ++-
.../grpc/server_handleStream_interceptor_test.go | 132 +++++++++++++++++++++
plugins/grpc/server_sendresponse_interceptor.go | 19 +--
plugins/toolkit-activation/instrument.go | 19 ++-
.../trace/span_ref_intercepter.go | 58 +++++++++
test/plugins/runner-helper/go.sum | 12 ++
test/plugins/runner-helper/main_test.go | 99 ++++++++++++++++
.../runner-helper/templates/docker-compose.tpl | 2 +-
.../plugins/runner-helper/templates/dockerfile.tpl | 6 +
test/plugins/runner-helper/templates/scenarios.tpl | 8 +-
.../runner-helper/templates/wsl-scenarios.tpl | 8 +-
test/plugins/scenarios/gin/plugin.yml | 8 +-
test/plugins/scenarios/grpc/plugin.yml | 6 +
.../scenarios/trace-activation/config/excepted.yml | 24 ++++
test/plugins/scenarios/trace-activation/main.go | 1 +
test/plugins/scenarios/trace-activation/plugin.yml | 2 +-
.../scenarios/trace-activation/test_service.go | 10 ++
toolkit/trace/span.go | 11 ++
24 files changed, 455 insertions(+), 26 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index 6cdbf33..51b834f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -13,13 +13,17 @@ Release Notes.
* Replace external `goapi` dependency with in-repo generated protocols.
* Support pprof profiling.
* Align the agent with the supported Go releases (retire EOL Go 1.19-1.23):
publish Go 1.24, 1.25, 1.26 base images, bump the module `go.mod` floor to Go
1.24, and run the CI build, plugin, and e2e jobs on Go 1.24-1.26.
+* Support managing toolkit spans across goroutines through `SpanRef`.
#### Plugins
+* Support gRPC v1.81.1 with Go 1.25 and Go 1.26.
+
#### Documentation
#### Bug Fixes
+* Fix gRPC server tracing with recent internal stream types.
* Fix plugin interceptors bypassed on Windows.
* Fix wrong tracing context switch when trace ignore plugin activated.
* Fix data race when sending trace data to reporter.
diff --git a/docs/en/advanced-features/manual-apis/toolkit-trace.md
b/docs/en/advanced-features/manual-apis/toolkit-trace.md
index 0b3da0c..5088448 100644
--- a/docs/en/advanced-features/manual-apis/toolkit-trace.md
+++ b/docs/en/advanced-features/manual-apis/toolkit-trace.md
@@ -73,6 +73,22 @@ Use `trace.StopSpan()` API to stop current span
trace.StopSpan()
```
+When the application retains the returned `SpanRef`, use `SpanRef.End()` to
+finish that exact span and `SpanRef.SetOperationName()` to rename it:
+
+```go
+spanRef, err := trace.CreateLocalSpan("originalName")
+if err == nil {
+ spanRef.SetOperationName("operationName")
+ spanRef.End()
+}
+```
+
+`SpanRef.End()` must be called from the goroutine that created the span and in
+LIFO order. For cross-goroutine work, use the asynchronous lifecycle described
+below. `trace.StopSpan()` remains available for applications that manage spans
+through the current tracing context.
+
### Add Span’s Tag and Log
Use `trace.AddLog()` to record log in span.
diff --git a/docs/en/agent/support-plugins.md b/docs/en/agent/support-plugins.md
index 095bb44..4a50226 100644
--- a/docs/en/agent/support-plugins.md
+++ b/docs/en/agent/support-plugins.md
@@ -3,7 +3,7 @@ The following plugins provide the distributed tracing
capability, and the OAP ba
metrics based on the tracing data.
* HTTP Server
- * `gin`: [Gin](https://github.com/gin-gonic/gin) tested v1.7.0 to v1.9.0.
+ * `gin`: [Gin](https://github.com/gin-gonic/gin) tested v1.7.0 to v1.9.0 and
v1.10.1.
* `http`: [Native HTTP](https://pkg.go.dev/net/http) tested go v1.24 to go
v1.26.
* `go-restfulv3`: [Go-Restful](https://github.com/emicklei/go-restful)
tested v3.7.1 to 3.10.2.
* `mux`: [Mux](https://github.com/gorilla/mux) tested v1.7.0 to v1.8.0.
@@ -19,7 +19,7 @@ metrics based on the tracing data.
* `dubbo`: [Dubbo](https://github.com/apache/dubbo-go) tested v3.0.1 to
v3.0.5.
* `kratosv2`: [Kratos](https://github.com/go-kratos/kratos) tested v2.3.1 to
v2.6.2.
* `microv4`: [Go-Micro](https://github.com/go-micro/go-micro) tested v4.6.0
to v4.10.2.
- * `grpc` : [gRPC](https://github.com/grpc/grpc-go) tested v1.55.0 to v1.64.0.
+ * `grpc` : [gRPC](https://github.com/grpc/grpc-go) tested v1.55.0 to v1.81.1.
* Database Client
* `gorm`: [GORM](https://github.com/go-gorm/gorm) tested v1.22.0 to v1.25.10.
* [MySQL Driver](https://github.com/go-gorm/mysql)
@@ -46,4 +46,4 @@ The meter plugin provides the advanced metrics collections.
The logging plugin provides the advanced logging collections.
* `logrus`: [Logrus](https://github.com/sirupsen/logrus) tested v1.8.2 to
v1.9.3.
-* `zap`: [Zap](http://go.uber.org/zap) tested v1.17.0 to v1.24.0.
\ No newline at end of file
+* `zap`: [Zap](http://go.uber.org/zap) tested v1.17.0 to v1.24.0.
diff --git a/go.mod b/go.mod
index ba1c9fe..5aad77e 100644
--- a/go.mod
+++ b/go.mod
@@ -18,5 +18,5 @@ require (
github.com/google/go-cmp v0.5.9 // indirect
github.com/klauspost/compress v1.15.9 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect
- google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 //
indirect
+ google.golang.org/genproto/googleapis/rpc
v0.0.0-20230526161137-0005af68ea54 // indirect
)
diff --git a/go.sum b/go.sum
index f02da08..6019143 100644
--- a/go.sum
+++ b/go.sum
@@ -65,8 +65,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod
h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod
h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
-google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod
h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230526161137-0005af68ea54
h1:wQvmPUaH4JVFCzNAL9ShNjezVoq3OhlinNMLYSAN9Vg=
+google.golang.org/genproto/googleapis/rpc
v0.0.0-20230526161137-0005af68ea54/go.mod
h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod
h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/protobuf v1.26.0-rc.1/go.mod
h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
diff --git a/go.work.sum b/go.work.sum
index c50e493..8341850 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -1840,10 +1840,13 @@ google.golang.org/genproto
v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f7
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod
h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod
h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod
h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
-google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54
h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M=
-google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod
h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk=
+google.golang.org/genproto v0.0.0-20230525234025-438c736192d0
h1:x1vNwUhVOcsYoKyEGCZBH694SBmmBjA2EfauFVEI2+M=
+google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod
h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY=
+google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a
h1:HiYVD+FGJkTo+9zj1gqz0anapsa1JxjiSrN+BJKyUmE=
+google.golang.org/genproto/googleapis/api
v0.0.0-20230525234020-1aefcd67740a/go.mod
h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9
h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ=
google.golang.org/genproto/googleapis/api
v0.0.0-20230525234035-dd9d682886f9/go.mod
h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
+google.golang.org/genproto/googleapis/rpc
v0.0.0-20230525234015-3fc162c6f38a/go.mod
h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19
h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM=
google.golang.org/genproto/googleapis/rpc
v0.0.0-20230525234030-28d5490b6b19/go.mod
h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod
h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
diff --git a/plugins/grpc/server_handleStream_interceptor.go
b/plugins/grpc/server_handleStream_interceptor.go
index d4aef66..c835ece 100644
--- a/plugins/grpc/server_handleStream_interceptor.go
+++ b/plugins/grpc/server_handleStream_interceptor.go
@@ -18,6 +18,9 @@
package grpc
import (
+ "context"
+ "fmt"
+
"google.golang.org/grpc/metadata"
"github.com/apache/skywalking-go/plugins/core/operator"
@@ -27,8 +30,20 @@ import (
type ServerHandleStreamInterceptor struct {
}
+type serverTransportStream interface {
+ Method() string
+ Context() context.Context
+}
+
func (h *ServerHandleStreamInterceptor) BeforeInvoke(invocation
operator.Invocation) error {
- stream := invocation.Args()[1].(*nativeStream)
+ args := invocation.Args()
+ if len(args) < 2 {
+ return fmt.Errorf("grpc handleStream expects at least 2
arguments, got %d", len(args))
+ }
+ stream, ok := args[1].(serverTransportStream)
+ if !ok {
+ return fmt.Errorf("unsupported grpc server transport stream
type %T", args[1])
+ }
method := stream.Method()
ctx := stream.Context()
md, _ := metadata.FromIncomingContext(ctx)
diff --git a/plugins/grpc/server_handleStream_interceptor_test.go
b/plugins/grpc/server_handleStream_interceptor_test.go
new file mode 100644
index 0000000..c950fb4
--- /dev/null
+++ b/plugins/grpc/server_handleStream_interceptor_test.go
@@ -0,0 +1,132 @@
+// 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 grpc
+
+import (
+ "context"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/apache/skywalking-go/plugins/core"
+ "github.com/apache/skywalking-go/plugins/core/operator"
+ "github.com/apache/skywalking-go/plugins/core/tracing"
+)
+
+type testServerTransportStream struct {
+ ctx context.Context
+ method string
+}
+
+func (s *testServerTransportStream) Context() context.Context {
+ return s.ctx
+}
+
+func (s *testServerTransportStream) Method() string {
+ return s.method
+}
+
+func TestServerHandleStreamAcceptsStreamInterface(t *testing.T) {
+ core.ResetTracingContext()
+ defer core.ResetTracingContext()
+
+ // gRPC v1.81 passes *transport.ServerStream where older releases passed
+ // *transport.Stream. Both expose this public method contract.
+ stream := &testServerTransportStream{
+ ctx: context.Background(),
+ method: "/api.Echo/ServerStreamingEcho",
+ }
+ invocation := operator.NewInvocation(nil, nil, stream)
+ interceptor := &ServerHandleStreamInterceptor{}
+
+ if err := interceptor.BeforeInvoke(invocation); err != nil {
+ t.Fatal(err)
+ }
+ if invocation.GetContext() == nil {
+ t.Fatal("entry span was not stored in invocation context")
+ }
+ if err := interceptor.AfterInvoke(invocation); err != nil {
+ t.Fatal(err)
+ }
+
+ deadline := time.Now().Add(2 * time.Second)
+ for len(core.GetReportedSpans()) == 0 && time.Now().Before(deadline) {
+ time.Sleep(20 * time.Millisecond)
+ }
+ spans := core.GetReportedSpans()
+ if len(spans) != 1 {
+ t.Fatalf("reported spans = %d, want 1", len(spans))
+ }
+ if got := spans[0].OperationName(); got !=
"api.Echo.ServerStreamingEcho" {
+ t.Fatalf("operation name = %q", got)
+ }
+}
+
+func TestServerHandleStreamRejectsUnsupportedArgument(t *testing.T) {
+ invocation := operator.NewInvocation(nil, nil, struct{}{})
+ err := (&ServerHandleStreamInterceptor{}).BeforeInvoke(invocation)
+ if err == nil || !strings.Contains(err.Error(), "unsupported grpc
server transport stream type") {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func TestServerSendResponseAcceptsStreamInterface(t *testing.T) {
+ core.ResetTracingContext()
+ defer core.ResetTracingContext()
+
+ root, err := tracing.CreateEntrySpan("api.Echo.UnaryEcho", func(string)
(string, error) {
+ return "", nil
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ stream := &testServerTransportStream{
+ ctx: context.Background(),
+ method: "/api.Echo/UnaryEcho",
+ }
+ invocation := operator.NewInvocation(nil, nil, stream)
+ interceptor := &ServerSendResponseInterceptor{}
+
+ if err := interceptor.BeforeInvoke(invocation); err != nil {
+ t.Fatal(err)
+ }
+ if invocation.GetContext() == nil {
+ t.Fatal("send response span was not stored in invocation
context")
+ }
+ if err := interceptor.AfterInvoke(invocation, error(nil)); err != nil {
+ t.Fatal(err)
+ }
+ root.End()
+
+ deadline := time.Now().Add(2 * time.Second)
+ for len(core.GetReportedSpans()) < 2 && time.Now().Before(deadline) {
+ time.Sleep(20 * time.Millisecond)
+ }
+ spans := core.GetReportedSpans()
+ if len(spans) != 2 {
+ t.Fatalf("reported spans = %d, want 2", len(spans))
+ }
+ found := false
+ for _, span := range spans {
+ if span.OperationName() ==
"api.Echo.UnaryEcho/Server/Response/SendResponse" {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Fatal("send response span was not reported")
+ }
+}
diff --git a/plugins/grpc/server_sendresponse_interceptor.go
b/plugins/grpc/server_sendresponse_interceptor.go
index 54cffa4..7cb39a3 100644
--- a/plugins/grpc/server_sendresponse_interceptor.go
+++ b/plugins/grpc/server_sendresponse_interceptor.go
@@ -29,16 +29,21 @@ func (h *ServerSendResponseInterceptor)
BeforeInvoke(invocation operator.Invocat
if tracing.ActiveSpan() == nil {
return nil
}
- var cs *nativeStream
- if arg1, ok := invocation.Args()[1].(*nativeStream); ok {
- cs = arg1
- } else if arg2, ok := invocation.Args()[2].(*nativeStream); ok {
- cs = arg2
- } else {
+ args := invocation.Args()
+ var stream serverTransportStream
+ for _, index := range []int{1, 2} {
+ if len(args) > index {
+ if candidate, ok :=
args[index].(serverTransportStream); ok {
+ stream = candidate
+ break
+ }
+ }
+ }
+ if stream == nil {
return nil
}
- method := cs.Method()
+ method := stream.Method()
s, err := tracing.CreateLocalSpan(formatOperationName(method,
"/Server/Response/SendResponse"),
tracing.WithLayer(tracing.SpanLayerRPCFramework),
tracing.WithTag(tracing.TagURL, method),
diff --git a/plugins/toolkit-activation/instrument.go
b/plugins/toolkit-activation/instrument.go
index 23a09da..62c1b24 100644
--- a/plugins/toolkit-activation/instrument.go
+++ b/plugins/toolkit-activation/instrument.go
@@ -115,7 +115,7 @@ func metricPoint() []*instrument.Point {
}
func tracePoint() []*instrument.Point {
- return []*instrument.Point{
+ points := []*instrument.Point{
{
PackagePath: "trace", At:
instrument.NewStructEnhance("SpanRef"),
},
@@ -167,6 +167,10 @@ func tracePoint() []*instrument.Point {
PackagePath: "trace", At:
instrument.NewMethodEnhance("*SpanRef", "AddEvent"),
Interceptor: "AsyncAddEventInterceptor",
},
+ }
+ points = append(points, spanRefLifecyclePoints()...)
+
+ return append(points, []*instrument.Point{
{
PackagePath: "trace", At:
instrument.NewStaticMethodEnhance("AddEvent"),
Interceptor: "AddEventInterceptor",
@@ -207,6 +211,19 @@ func tracePoint() []*instrument.Point {
PackagePath: "trace", At:
instrument.NewStaticMethodEnhance("Error"),
Interceptor: "ErrorIntercepter",
},
+ }...)
+}
+
+func spanRefLifecyclePoints() []*instrument.Point {
+ return []*instrument.Point{
+ {
+ PackagePath: "trace", At:
instrument.NewMethodEnhance("*SpanRef", "End"),
+ Interceptor: "SpanRefEndInterceptor",
+ },
+ {
+ PackagePath: "trace", At:
instrument.NewMethodEnhance("*SpanRef", "SetOperationName"),
+ Interceptor: "SpanRefSetOperationNameInterceptor",
+ },
}
}
diff --git a/plugins/toolkit-activation/trace/span_ref_intercepter.go
b/plugins/toolkit-activation/trace/span_ref_intercepter.go
new file mode 100644
index 0000000..9f6023a
--- /dev/null
+++ b/plugins/toolkit-activation/trace/span_ref_intercepter.go
@@ -0,0 +1,58 @@
+// 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. The 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 traceactivation
+
+import (
+ "github.com/apache/skywalking-go/plugins/core/operator"
+ "github.com/apache/skywalking-go/plugins/core/tracing"
+)
+
+type SpanRefEndInterceptor struct {
+}
+
+func (h *SpanRefEndInterceptor) BeforeInvoke(invocation operator.Invocation)
error {
+ return nil
+}
+
+func (h *SpanRefEndInterceptor) AfterInvoke(invocation operator.Invocation,
result ...interface{}) error {
+ if span := spanFromRef(invocation); span != nil {
+ span.End()
+ }
+ return nil
+}
+
+type SpanRefSetOperationNameInterceptor struct {
+}
+
+func (h *SpanRefSetOperationNameInterceptor) BeforeInvoke(invocation
operator.Invocation) error {
+ return nil
+}
+
+func (h *SpanRefSetOperationNameInterceptor) AfterInvoke(invocation
operator.Invocation, result ...interface{}) error {
+ if span := spanFromRef(invocation); span != nil {
+ span.SetOperationName(invocation.Args()[0].(string))
+ }
+ return nil
+}
+
+func spanFromRef(invocation operator.Invocation) tracing.Span {
+ enhanced, ok := invocation.CallerInstance().(operator.EnhancedInstance)
+ if !ok {
+ return nil
+ }
+ span, _ := enhanced.GetSkyWalkingDynamicField().(tracing.Span)
+ return span
+}
diff --git a/test/plugins/runner-helper/go.sum
b/test/plugins/runner-helper/go.sum
index 808621e..5859bab 100644
--- a/test/plugins/runner-helper/go.sum
+++ b/test/plugins/runner-helper/go.sum
@@ -1,7 +1,19 @@
+github.com/creack/pty v1.1.9/go.mod
h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/kr/pretty v0.1.0/go.mod
h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod
h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod
h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod
h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/rogpeppe/go-internal v1.6.1/go.mod
h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.9.0
h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod
h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod
h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/test/plugins/runner-helper/main_test.go
b/test/plugins/runner-helper/main_test.go
new file mode 100644
index 0000000..749aed0
--- /dev/null
+++ b/test/plugins/runner-helper/main_test.go
@@ -0,0 +1,99 @@
+// Licensed to the 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. The 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 main
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestRenderDockerFileRefreshesWorkspaceGoDirective(t *testing.T) {
+ tests := []struct {
+ goVersion string
+ want bool
+ }{
+ {goVersion: "1.17", want: false},
+ {goVersion: "1.18", want: true},
+ {goVersion: "1.25", want: true},
+ {goVersion: "1.26", want: true},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.goVersion, func(t *testing.T) {
+ projectDir := t.TempDir()
+ workspaceDir := filepath.Join(projectDir, "workspace")
+ if err := os.MkdirAll(workspaceDir, 0o755); err != nil {
+ t.Fatal(err)
+ }
+ context := &Context{
+ WorkSpaceDir: workspaceDir,
+ ProjectDir: projectDir,
+ GoVersion: tt.goVersion,
+ ScenarioName: "grpc",
+ CaseName: "compatibility",
+ GoAgentPath: filepath.Join(projectDir,
"agent"),
+ Config: &Config{StartScript:
"./bin/startup.sh"},
+ }
+
+ if err := RenderDockerFile(context); err != nil {
+ t.Fatal(err)
+ }
+ content, err := os.ReadFile(filepath.Join(workspaceDir,
"Dockerfile"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ got := strings.Contains(string(content), "RUN go mod
tidy\n# go mod tidy") &&
+ strings.Contains(string(content), "RUN go work
use .")
+ if got != tt.want {
+ t.Fatalf("workspace refresh present = %v, want
%v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestRenderScenariosAcceptsModernComposeMajorVersions(t *testing.T) {
+ workspaceDir := t.TempDir()
+ context := &Context{
+ WorkSpaceDir: workspaceDir,
+ ScenarioName: "http",
+ }
+
+ if err := RenderScenariosScript(context); err != nil {
+ t.Fatal(err)
+ }
+ if err := RenderWSLScenariosScript(context); err != nil {
+ t.Fatal(err)
+ }
+
+ for _, name := range []string{"scenarios.sh", "wsl-scenarios.sh"} {
+ content, err := os.ReadFile(filepath.Join(workspaceDir, name))
+ if err != nil {
+ t.Fatal(err)
+ }
+ script := string(content)
+ for _, want := range []string{
+ `compose_major=${compose_version#v}`,
+ `compose_major=${compose_major%%.*}`,
+ `(( compose_major >= 2 ))`,
+ } {
+ if !strings.Contains(script, want) {
+ t.Fatalf("generated %s does not contain %q",
name, want)
+ }
+ }
+ }
+}
diff --git a/test/plugins/runner-helper/templates/docker-compose.tpl
b/test/plugins/runner-helper/templates/docker-compose.tpl
index a7410af..d46c72d 100644
--- a/test/plugins/runner-helper/templates/docker-compose.tpl
+++ b/test/plugins/runner-helper/templates/docker-compose.tpl
@@ -122,4 +122,4 @@ services:
timeout: {{$service.HealthCheck.Timeout}}
retries: {{$service.HealthCheck.Retries}}
{{- end }}
- {{- end }}
\ No newline at end of file
+ {{- end }}
diff --git a/test/plugins/runner-helper/templates/dockerfile.tpl
b/test/plugins/runner-helper/templates/dockerfile.tpl
index f1b8fed..9ebf7e1 100644
--- a/test/plugins/runner-helper/templates/dockerfile.tpl
+++ b/test/plugins/runner-helper/templates/dockerfile.tpl
@@ -33,6 +33,12 @@ WORKDIR
/skywalking-go/test/plugins/workspace/{{.Context.ScenarioName}}/{{.Conte
RUN echo "replace github.com/apache/skywalking-go/toolkit =>
../../../../../toolkit" >> ./go.mod
{{ end }}
RUN go mod tidy
+{{ if .GreaterThanGo18 -}}
+# go mod tidy may raise the scenario module's Go directive to a patch release
+# (for example 1.25.0). Refresh the workspace directive so Go does not reject
+# the generated module as requiring a newer version than go.work.
+RUN go work use .
+{{ end }}
ENV GO_BUILD_OPTS=" -toolexec \"/skywalking-go{{.ToolExecPath}}\" -a -work "
diff --git a/test/plugins/runner-helper/templates/scenarios.tpl
b/test/plugins/runner-helper/templates/scenarios.tpl
index b91d5f3..437a79f 100644
--- a/test/plugins/runner-helper/templates/scenarios.tpl
+++ b/test/plugins/runner-helper/templates/scenarios.tpl
@@ -19,10 +19,12 @@
set -ex
compose_version=$(docker-compose version --short)
+compose_major=${compose_version#v}
+compose_major=${compose_major%%.*}
-if [[ $compose_version =~ ^(v)?1 ]]; then
+if [[ $compose_major == "1" ]]; then
separator="_"
-elif [[ $compose_version =~ ^(v)?2 ]]; then
+elif [[ $compose_major =~ ^[0-9]+$ ]] && (( compose_major >= 2 )); then
separator="-"
else
echo "Unsupported Docker Compose version: $compose_version"
@@ -54,4 +56,4 @@ else
docker-compose -p ${project_name} -f {{.DockerComposeFilePath}} rm -f
fi
-exit $status
\ No newline at end of file
+exit $status
diff --git a/test/plugins/runner-helper/templates/wsl-scenarios.tpl
b/test/plugins/runner-helper/templates/wsl-scenarios.tpl
index 7d5137c..57abda4 100644
--- a/test/plugins/runner-helper/templates/wsl-scenarios.tpl
+++ b/test/plugins/runner-helper/templates/wsl-scenarios.tpl
@@ -66,10 +66,12 @@ sed -i "s/HTTP_HOST=127\.0\.0\.1/HTTP_HOST=$WINDOWS_HOST/g"
validator.sh
compose_version=$(docker-compose version --short)
+compose_major=${compose_version#v}
+compose_major=${compose_major%%.*}
-if [[ $compose_version =~ ^(v)?1 ]]; then
+if [[ $compose_major == "1" ]]; then
separator="_"
-elif [[ $compose_version =~ ^(v)?2 ]]; then
+elif [[ $compose_major =~ ^[0-9]+$ ]] && (( compose_major >= 2 )); then
separator="-"
else
echo "Unsupported Docker Compose version: $compose_version"
@@ -119,4 +121,4 @@ else
docker-compose -p ${project_name} -f "docker-compose.yml" rm -f
fi
-exit $status
\ No newline at end of file
+exit $status
diff --git a/test/plugins/scenarios/gin/plugin.yml
b/test/plugins/scenarios/gin/plugin.yml
index 6de1dba..324fb9b 100644
--- a/test/plugins/scenarios/gin/plugin.yml
+++ b/test/plugins/scenarios/gin/plugin.yml
@@ -22,4 +22,10 @@ export-port: 8080
support-version:
- go: 1.24
framework:
- - v1.9.0
\ No newline at end of file
+ - v1.9.0
+ - go: 1.25
+ framework:
+ - v1.10.1
+ - go: 1.26
+ framework:
+ - v1.10.1
diff --git a/test/plugins/scenarios/grpc/plugin.yml
b/test/plugins/scenarios/grpc/plugin.yml
index 9acaeb5..a273ce6 100644
--- a/test/plugins/scenarios/grpc/plugin.yml
+++ b/test/plugins/scenarios/grpc/plugin.yml
@@ -30,3 +30,9 @@ support-version:
- v1.60.0
- v1.62.0
- v1.64.0
+ - go: 1.25
+ framework:
+ - v1.81.1
+ - go: 1.26
+ framework:
+ - v1.81.1
diff --git a/test/plugins/scenarios/trace-activation/config/excepted.yml
b/test/plugins/scenarios/trace-activation/config/excepted.yml
index 27ad4d9..31e2e68 100644
--- a/test/plugins/scenarios/trace-activation/config/excepted.yml
+++ b/test/plugins/scenarios/trace-activation/config/excepted.yml
@@ -217,6 +217,30 @@ segmentItems:
logs:
- logEvent:
- { key: debug, value: foo }
+ - operationName: testSpanRefLifecycle_success
+ parentSpanId: 8
+ spanId: 15
+ spanLayer: Unknown
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 0
+ isError: false
+ spanType: Local
+ peer: ''
+ skipAnalysis: false
+ tags:
+ - { key: spanRefLifecycle, value: success }
+ - operationName: testSpanRefLifecycleSibling
+ parentSpanId: 8
+ spanId: 16
+ spanLayer: Unknown
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 0
+ isError: false
+ spanType: Local
+ peer: ''
+ skipAnalysis: false
- operationName: GET:/consumer
parentSpanId: -1
spanId: 0
diff --git a/test/plugins/scenarios/trace-activation/main.go
b/test/plugins/scenarios/trace-activation/main.go
index 25de3eb..c55df01 100644
--- a/test/plugins/scenarios/trace-activation/main.go
+++ b/test/plugins/scenarios/trace-activation/main.go
@@ -43,6 +43,7 @@ func consumerHandler(w http.ResponseWriter, r *http.Request) {
testComponent()
testAsyncInCrossGoroutine()
testEvent()
+ testSpanRefLifecycle()
}
func main() {
diff --git a/test/plugins/scenarios/trace-activation/plugin.yml
b/test/plugins/scenarios/trace-activation/plugin.yml
index 5e23a6e..d0aacc1 100644
--- a/test/plugins/scenarios/trace-activation/plugin.yml
+++ b/test/plugins/scenarios/trace-activation/plugin.yml
@@ -23,4 +23,4 @@ support-version:
- go: 1.24
- go: 1.25
- go: 1.26
-toolkit: true
\ No newline at end of file
+toolkit: true
diff --git a/test/plugins/scenarios/trace-activation/test_service.go
b/test/plugins/scenarios/trace-activation/test_service.go
index ac4fb6e..95f09b0 100644
--- a/test/plugins/scenarios/trace-activation/test_service.go
+++ b/test/plugins/scenarios/trace-activation/test_service.go
@@ -124,3 +124,13 @@ func testAsyncInCrossGoroutine() {
}()
<-ch
}
+
+func testSpanRefLifecycle() {
+ span, _ := trace.CreateLocalSpan("testSpanRefLifecycle_failed")
+ span.SetOperationName("testSpanRefLifecycle_success")
+ span.SetTag("spanRefLifecycle", "success")
+ span.End()
+
+ sibling, _ := trace.CreateLocalSpan("testSpanRefLifecycleSibling")
+ sibling.End()
+}
diff --git a/toolkit/trace/span.go b/toolkit/trace/span.go
index 40df3bf..12630cf 100644
--- a/toolkit/trace/span.go
+++ b/toolkit/trace/span.go
@@ -40,6 +40,17 @@ func (*SpanRef) PrepareAsync() {
func (*SpanRef) AsyncFinish() {
}
+// End finishes the span referenced by SpanRef.
+//
+// End must be called from the goroutine that created the span and in LIFO
+// order. Use PrepareAsync, StopSpan, and AsyncFinish for cross-goroutine work.
+func (*SpanRef) End() {
+}
+
+// SetOperationName resets the operation name of the span referenced by
SpanRef.
+func (*SpanRef) SetOperationName(string) {
+}
+
// nolint
func (*SpanRef) SetTag(key string, value string) {
}