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 c97a4ee  Support GORM framework (#43)
c97a4ee is described below

commit c97a4ee8cfa939792cf009c87e6066c11dd4e245
Author: mrproliu <[email protected]>
AuthorDate: Tue May 23 04:45:33 2023 +0000

    Support GORM framework (#43)
---
 .github/workflows/plugin-tests.yaml                |   1 +
 docs/en/agent/support-plugins.md                   |   5 +-
 .../development-guide.md                           |  27 +++
 .../write-plugin-testing.md                        |  10 ++
 go.work                                            |   2 +
 go.work.sum                                        |   1 +
 plugins/core/instrument/declare.go                 |   5 +
 plugins/core/instrument/enhance.go                 |   4 +
 plugins/core/instrument/struct.go                  |   4 +-
 plugins/gorm/entry/callback.go                     |  64 +++++++
 .../declare.go => gorm/entry/database.go}          |  22 +--
 plugins/gorm/entry/instrument.go                   |  69 ++++++++
 plugins/gorm/entry/interceptor.go                  |  82 +++++++++
 plugins/gorm/entry/interceptor_test.go             | 109 ++++++++++++
 plugins/gorm/go.mod                                |   9 +
 plugins/gorm/go.sum                                |   6 +
 plugins/gorm/mysql/instrument.go                   |  81 +++++++++
 plugins/gorm/mysql/interceptor.go                  |  68 ++++++++
 .../declare.go => gorm/mysql/interceptor_test.go}  |  29 ++--
 test/plugins/runner-helper/context.go              |  30 +++-
 .../runner-helper/templates/docker-compose.tpl     |  47 ++++++
 test/plugins/scenarios/gorm/bin/startup.sh         |  22 +++
 test/plugins/scenarios/gorm/config/excepted.yml    | 117 +++++++++++++
 test/plugins/scenarios/gorm/go.mod                 |  26 +++
 test/plugins/scenarios/gorm/go.sum                 | 183 +++++++++++++++++++++
 test/plugins/scenarios/gorm/main.go                | 130 +++++++++++++++
 test/plugins/scenarios/gorm/plugin.yml             |  48 ++++++
 .../go-agent/instrument/plugins/enhance_method.go  |  10 +-
 tools/go-agent/instrument/plugins/instrument.go    |   6 +-
 tools/go-agent/instrument/plugins/register.go      |   6 +
 tools/go-agent/tools/enhancement.go                |   2 +-
 31 files changed, 1183 insertions(+), 42 deletions(-)

diff --git a/.github/workflows/plugin-tests.yaml 
b/.github/workflows/plugin-tests.yaml
index 93d583e..0fad989 100644
--- a/.github/workflows/plugin-tests.yaml
+++ b/.github/workflows/plugin-tests.yaml
@@ -60,6 +60,7 @@ jobs:
           - http
           - dubbo
           - go-restfulv3
+          - gorm
           - plugin_exclusion
     steps:
       - uses: actions/checkout@v2
diff --git a/docs/en/agent/support-plugins.md b/docs/en/agent/support-plugins.md
index 7cd4b40..51b1997 100644
--- a/docs/en/agent/support-plugins.md
+++ b/docs/en/agent/support-plugins.md
@@ -7,4 +7,7 @@
 * HTTP Client
   * `http`: [Native HTTP](https://pkg.go.dev/net/http)
 * RPC Frameworks
-  * `dubbo`: [Dubbo](https://github.com/apache/dubbo-go)
\ No newline at end of file
+  * `dubbo`: [Dubbo](https://github.com/apache/dubbo-go)
+* Database Client
+  * `gorm`: [GORM](https://github.com/go-gorm/gorm)
+    * [MySQL Driver](https://github.com/go-gorm/mysql)
\ No newline at end of file
diff --git a/docs/en/development-and-contribution/development-guide.md 
b/docs/en/development-and-contribution/development-guide.md
index 4b6600c..96c97dd 100644
--- a/docs/en/development-and-contribution/development-guide.md
+++ b/docs/en/development-and-contribution/development-guide.md
@@ -35,6 +35,33 @@ The basic information includes the following methods, 
corresponding to the [Inst
 Note: Please declare `//skywalking:nocopy` at any position in this file to 
indicate that the file would not be copied. This file is only used for guidance 
during hybrid compilation. 
 Also, this file involves the use of the `embed` package, and if the target 
framework does not import the package `embed`, a compilation error may occur.
 
+### Manage Instrument and Interceptor codes in hierarchy structure
+
+Instrument and interceptor codes are placed in root by default. 
+In complex instrumentation scenarios, there could be dozens of interceptors, 
we provide `PluginSourceCodePath` to build a hierarchy folder structure to 
manage those codes.
+
+Notice: The instrumentation still works without proper setting of this, but 
the debug tool would lose the location of the source codes.
+
+#### Example
+
+For example, the framework needs to enhance two packages, as shown in the 
following directory structure:
+
+```
+- plugins
+  - test
+    - go.mod
+    - package1
+      - instrument.go
+      - interceptor.go
+    - package2
+      - instrument.go
+      - interceptor.go
+    ...
+```
+
+In the above directory structure, the **test** framework needs to provide 
multiple different enhancement objects. 
+In this case, a `PluginSourceCodePath` Source Code Path** method needs to be 
added for each enhancement object, the values of this method should be 
`package1` and `package2`.
+
 ### Instrument Point
 
 Instrument points are used to declare that which methods and structs in the 
current package should be instrumented. They mainly include the following 
information:
diff --git a/docs/en/development-and-contribution/write-plugin-testing.md 
b/docs/en/development-and-contribution/write-plugin-testing.md
index 496eb12..11f3bbe 100644
--- a/docs/en/development-and-contribution/write-plugin-testing.md
+++ b/docs/en/development-and-contribution/write-plugin-testing.md
@@ -24,6 +24,16 @@ It includes the following information:
 6. **support-version**: The version information supported by the current 
plugin.
    1. **go**: The supported Golang language version for the current plugin.
    2. **framework**: A list of plugin version information. It would be used to 
switch between multiple framework versions.
+7. **dependencies**: If your program relies on certain containers, please 
declare them here. The syntax is largely similar to the services in 
`docker-compose`.
+   1. **image**: The image name of service.
+   2. **hostname**: The hostname of the container which deployed. 
+   3. **port**: The port list of the container which deployed.
+   4. **expose**: The export port list of the container which deployed.
+   5. **environment**: The environment variables of the container which 
deployed.
+   6. **command**: The start command of the container.
+   7. **healthcheck**: The health check command of the container. If the 
service defines a healthcheck, 
+   then the service being tested would depend on the current service's 
`service_healthy` status. 
+   Otherwise, it depends on the `service_started` status.
 
 ### URL Access
 
diff --git a/go.work b/go.work
index d1a5a28..1811bbd 100644
--- a/go.work
+++ b/go.work
@@ -9,6 +9,7 @@ use (
        ./plugins/gin
        ./plugins/http
        ./plugins/go-restfulv3
+       ./plugins/gorm
 
        ./test/benchmark-codebase/consumer
        ./test/benchmark-codebase/provider
@@ -20,6 +21,7 @@ use (
        ./test/plugins/scenarios/gin
        ./test/plugins/scenarios/http
        ./test/plugins/scenarios/go-restfulv3
+       ./test/plugins/scenarios/gorm
 
        ./test/plugins/scenarios/plugin_exclusion
 
diff --git a/go.work.sum b/go.work.sum
index 458c290..7e63d3c 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -738,6 +738,7 @@ github.com/spaolacci/murmur3 
v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1
 github.com/spf13/afero v1.3.3/go.mod 
h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
 github.com/spf13/afero v1.9.0 h1:sFSLUHgxdnN32Qy38hK3QkYBFXZj9DKjVjCUCtD7juY=
 github.com/spf13/afero v1.9.0/go.mod 
h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
+github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
 github.com/spf13/afero v1.9.2/go.mod 
h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
 github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
 github.com/stephens2424/writerset v1.0.2 
h1:znRLgU6g8RS5euYRcy004XeE4W+Tu44kALzy7ghPif8=
diff --git a/plugins/core/instrument/declare.go 
b/plugins/core/instrument/declare.go
index 9aaa65b..e73b378 100644
--- a/plugins/core/instrument/declare.go
+++ b/plugins/core/instrument/declare.go
@@ -27,6 +27,11 @@ type Instrument interface {
        FS() *embed.FS
 }
 
+type SourceCodeDetector interface {
+       // PluginSourceCodePath the relative path to the base plugin path
+       PluginSourceCodePath() string
+}
+
 type Point struct {
        PackagePath string
        At          *EnhanceMatcher
diff --git a/plugins/core/instrument/enhance.go 
b/plugins/core/instrument/enhance.go
index 66c58e4..117e392 100644
--- a/plugins/core/instrument/enhance.go
+++ b/plugins/core/instrument/enhance.go
@@ -70,6 +70,10 @@ func generateTypeNameByExp(exp dst.Expr) string {
                data = n.Name
        case *dst.SelectorExpr:
                data = generateTypeNameByExp(n.X) + "." + 
generateTypeNameByExp(n.Sel)
+       case *dst.Ellipsis:
+               data = "..." + generateTypeNameByExp(n.Elt)
+       case *dst.ArrayType:
+               data = "[]" + generateTypeNameByExp(n.Elt)
        default:
                return ""
        }
diff --git a/plugins/core/instrument/struct.go 
b/plugins/core/instrument/struct.go
index b315f6f..4a205ee 100644
--- a/plugins/core/instrument/struct.go
+++ b/plugins/core/instrument/struct.go
@@ -37,7 +37,9 @@ 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 {
+                       if filedName == "" && (len(field.Names) == 0 || 
field.Names[0].Name == filedName) {
+                               return verifyTypeName(field.Type, filedType)
+                       } else if field.Names[0].Name == filedName {
                                return verifyTypeName(field.Type, filedType)
                        }
                }
diff --git a/plugins/gorm/entry/callback.go b/plugins/gorm/entry/callback.go
new file mode 100644
index 0000000..5aa2b2b
--- /dev/null
+++ b/plugins/gorm/entry/callback.go
@@ -0,0 +1,64 @@
+// 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 entry
+
+import (
+       "fmt"
+
+       "gorm.io/gorm"
+
+       "github.com/apache/skywalking-go/plugins/core/tracing"
+)
+
+var spanKey = "skywalking-span"
+
+func beforeCallback(dbInfo DatabaseInfo, op string) func(db *gorm.DB) {
+       return func(db *gorm.DB) {
+               tableName := db.Statement.Table
+               operation := fmt.Sprintf("%s/%s", tableName, op)
+               s, err := tracing.CreateExitSpan(operation, dbInfo.Peer(), 
func(k, v string) error {
+                       return nil
+               }, tracing.WithComponent(dbInfo.ComponentID()),
+                       tracing.WithLayer(tracing.SpanLayerDatabase),
+                       tracing.WithTag(tracing.TagDBType, dbInfo.Type()))
+
+               if err != nil {
+                       db.Logger.Error(db.Statement.Context, "gorm:skyWalking 
failed to create exit span, got error: %v", err)
+                       return
+               }
+
+               db.Set(spanKey, s)
+       }
+}
+
+func afterCallback(dbInfo DatabaseInfo) func(db *gorm.DB) {
+       return func(db *gorm.DB) {
+               // get span from db instance's context
+               spanInterface, _ := db.Get(spanKey)
+               span, ok := spanInterface.(tracing.Span)
+               if !ok {
+                       return
+               }
+
+               defer span.End()
+
+               if db.Statement.Error != nil {
+                       span.Error(db.Statement.Error.Error())
+               }
+       }
+}
diff --git a/plugins/core/instrument/declare.go b/plugins/gorm/entry/database.go
similarity index 69%
copy from plugins/core/instrument/declare.go
copy to plugins/gorm/entry/database.go
index 9aaa65b..e08abd6 100644
--- a/plugins/core/instrument/declare.go
+++ b/plugins/gorm/entry/database.go
@@ -15,22 +15,10 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package instrument
+package entry
 
-import "embed"
-
-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
-
-       PackageName string // optional: for package path dir name is not same 
with package name
+type DatabaseInfo interface {
+       Type() string
+       ComponentID() int32
+       Peer() string
 }
diff --git a/plugins/gorm/entry/instrument.go b/plugins/gorm/entry/instrument.go
new file mode 100644
index 0000000..4880579
--- /dev/null
+++ b/plugins/gorm/entry/instrument.go
@@ -0,0 +1,69 @@
+// 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 entry
+
+import (
+       "embed"
+
+       "github.com/apache/skywalking-go/plugins/core/instrument"
+)
+
+//go:embed *
+var fs embed.FS
+
+//skywalking:nocopy
+type Instrument struct {
+}
+
+func NewInstrument() *Instrument {
+       return &Instrument{}
+}
+
+func (i *Instrument) Name() string {
+       return "gorm"
+}
+
+func (i *Instrument) BasePackage() string {
+       return "gorm.io/gorm"
+}
+
+func (i *Instrument) VersionChecker(version string) bool {
+       return true
+}
+
+func (i *Instrument) Points() []*instrument.Point {
+       return []*instrument.Point{
+               {
+                       PackagePath: "",
+                       At: instrument.NewStaticMethodEnhance("Open",
+                               instrument.WithArgsCount(2),
+                               instrument.WithArgType(0, "Dialector"), 
instrument.WithArgType(1, "...Option"),
+                               instrument.WithResultCount(2),
+                               instrument.WithResultType(0, "*DB"), 
instrument.WithResultType(1, "error")),
+                       Interceptor: "OpenInterceptor",
+               },
+       }
+}
+
+func (i *Instrument) PluginSourceCodePath() string {
+       return "entry"
+}
+
+func (i *Instrument) FS() *embed.FS {
+       return &fs
+}
diff --git a/plugins/gorm/entry/interceptor.go 
b/plugins/gorm/entry/interceptor.go
new file mode 100644
index 0000000..99c7b2a
--- /dev/null
+++ b/plugins/gorm/entry/interceptor.go
@@ -0,0 +1,82 @@
+// 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 entry
+
+import (
+       "gorm.io/gorm"
+
+       "github.com/apache/skywalking-go/plugins/core/operator"
+)
+
+type OpenInterceptor struct {
+}
+
+func (i *OpenInterceptor) BeforeInvoke(invocation operator.Invocation) error {
+       return nil
+}
+
+// AfterInvoke would be called after the target method invocation.
+func (i *OpenInterceptor) AfterInvoke(invocation operator.Invocation, result 
...interface{}) error {
+       if e, ok := result[1].(error); ok && e != nil {
+               return nil
+       }
+       db, ok := result[0].(*gorm.DB)
+       if !ok || db == nil {
+               return nil
+       }
+
+       // setup database info
+       info := i.setupDatabaseInfo(db)
+       if info == nil {
+               return nil
+       }
+
+       // add the callback
+       _ = 
db.Callback().Create().Before("gorm:create").Register("sky_create_create_span", 
beforeCallback(info, "create"))
+       _ = 
db.Callback().Query().Before("gorm:query").Register("sky_create_query_span", 
beforeCallback(info, "query"))
+       _ = 
db.Callback().Update().Before("gorm:update").Register("sky_create_update_span", 
beforeCallback(info, "update"))
+       _ = 
db.Callback().Delete().Before("gorm:delete").Register("sky_create_delete_span", 
beforeCallback(info, "delete"))
+       _ = 
db.Callback().Row().Before("gorm:row").Register("sky_create_row_span", 
beforeCallback(info, "row"))
+       _ = 
db.Callback().Raw().Before("gorm:raw").Register("sky_create_raw_span", 
beforeCallback(info, "raw"))
+
+       // after database operation
+       _ = 
db.Callback().Create().After("gorm:create").Register("sky_end_create_span", 
afterCallback(info))
+       _ = 
db.Callback().Query().After("gorm:query").Register("sky_end_query_span", 
afterCallback(info))
+       _ = 
db.Callback().Update().After("gorm:update").Register("sky_end_update_span", 
afterCallback(info))
+       _ = 
db.Callback().Delete().After("gorm:delete").Register("sky_end_delete_span", 
afterCallback(info))
+       _ = db.Callback().Row().After("gorm:row").Register("sky_end_row_span", 
afterCallback(info))
+       _ = db.Callback().Raw().After("gorm:raw").Register("sky_end_raw_span", 
afterCallback(info))
+
+       return nil
+}
+
+func (i *OpenInterceptor) setupDatabaseInfo(db *gorm.DB) DatabaseInfo {
+       if db.Config == nil || db.Config.Dialector == nil {
+               return nil
+       }
+       ins, ok := db.Config.Dialector.(operator.EnhancedInstance)
+       if !ok {
+               return nil
+       }
+       dbInfo, ok := ins.GetSkyWalkingDynamicField().(DatabaseInfo)
+       if !ok || dbInfo == nil {
+               return nil
+       }
+
+       return dbInfo
+}
diff --git a/plugins/gorm/entry/interceptor_test.go 
b/plugins/gorm/entry/interceptor_test.go
new file mode 100644
index 0000000..9321b5f
--- /dev/null
+++ b/plugins/gorm/entry/interceptor_test.go
@@ -0,0 +1,109 @@
+// 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 entry
+
+import (
+       "context"
+       "database/sql"
+       "fmt"
+       "testing"
+       "time"
+
+       "github.com/apache/skywalking-go/plugins/core"
+       "github.com/apache/skywalking-go/plugins/gorm/mysql"
+
+       "github.com/stretchr/testify/assert"
+
+       "gorm.io/gorm"
+       "gorm.io/gorm/utils/tests"
+)
+
+func init() {
+       core.ResetTracingContext()
+}
+
+var errConnectionExecute = fmt.Errorf("test error")
+
+const peerAddress = "localhost:3306"
+
+func TestInterceptor(t *testing.T) {
+       defer core.ResetTracingContext()
+
+       interceptor := &OpenInterceptor{}
+       db, err := gorm.Open(NewTestDialector(&mysql.DatabaseInfo{PeerAddress: 
peerAddress}))
+       assert.Nil(t, err, "failed to open database")
+       assert.NotNil(t, db, "failed to open database")
+       err = interceptor.AfterInvoke(nil, db, err)
+       assert.Nil(t, err, "failed to invoke AfterInvoke")
+
+       res := db.Exec("select * from test")
+
+       assert.Equal(t, errConnectionExecute, res.Error, "failed to invoke 
Rows")
+       time.Sleep(100 * time.Millisecond)
+       spans := core.GetReportedSpans()
+       assert.NotNil(t, spans, "spans should not be nil")
+       assert.Equal(t, 1, len(spans), "spans length should be 1")
+       assert.Equal(t, peerAddress, spans[0].Peer(), "peer should be 
localhost:3306")
+       assert.Equal(t, int32(5012), spans[0].ComponentID(), "component id 
should be 5012")
+       assert.Equal(t, "/raw", spans[0].OperationName(), "operation name 
should be /raw")
+       assert.Nil(t, spans[0].Refs(), "refs should be nil")
+       assert.Greater(t, spans[0].StartTime(), int64(0), "end time should be 
greater than zero")
+       assert.Greater(t, spans[0].EndTime(), int64(0), "end time should be 
greater than zero")
+}
+
+type TestDialector struct {
+       gorm.Dialector
+       field interface{}
+}
+
+func NewTestDialector(v interface{}) *TestDialector {
+       return &TestDialector{Dialector: &tests.DummyDialector{}, field: v}
+}
+
+func (i *TestDialector) Initialize(db *gorm.DB) error {
+       _ = i.Dialector.Initialize(db)
+       db.ConnPool = &TestConnPool{}
+       return nil
+}
+
+func (i *TestDialector) GetSkyWalkingDynamicField() interface{} {
+       return i.field
+}
+
+func (i *TestDialector) SetSkyWalkingDynamicField(v interface{}) {
+       i.field = v
+}
+
+type TestConnPool struct {
+}
+
+func (i *TestConnPool) PrepareContext(ctx context.Context, query string) 
(*sql.Stmt, error) {
+       return nil, errConnectionExecute
+}
+
+func (i *TestConnPool) ExecContext(ctx context.Context, query string, args 
...interface{}) (sql.Result, error) {
+       return nil, errConnectionExecute
+}
+
+func (i *TestConnPool) QueryContext(ctx context.Context, query string, args 
...interface{}) (*sql.Rows, error) {
+       return nil, errConnectionExecute
+}
+
+func (i *TestConnPool) QueryRowContext(ctx context.Context, query string, args 
...interface{}) *sql.Row {
+       return nil
+}
diff --git a/plugins/gorm/go.mod b/plugins/gorm/go.mod
new file mode 100644
index 0000000..4737ad9
--- /dev/null
+++ b/plugins/gorm/go.mod
@@ -0,0 +1,9 @@
+module github.com/apache/skywalking-go/plugins/gorm
+
+go 1.18
+
+require (
+       github.com/jinzhu/inflection v1.0.0 // indirect
+       github.com/jinzhu/now v1.1.5 // indirect
+       gorm.io/gorm v1.25.1 // indirect
+)
diff --git a/plugins/gorm/go.sum b/plugins/gorm/go.sum
new file mode 100644
index 0000000..b8adc45
--- /dev/null
+++ b/plugins/gorm/go.sum
@@ -0,0 +1,6 @@
+github.com/jinzhu/inflection v1.0.0 
h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod 
h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod 
h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64=
+gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
diff --git a/plugins/gorm/mysql/instrument.go b/plugins/gorm/mysql/instrument.go
new file mode 100644
index 0000000..ad7aa99
--- /dev/null
+++ b/plugins/gorm/mysql/instrument.go
@@ -0,0 +1,81 @@
+// 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 mysql
+
+import (
+       "embed"
+
+       "github.com/apache/skywalking-go/plugins/core/instrument"
+)
+
+//go:embed *
+var fs embed.FS
+
+const mysqlName = "mysql"
+
+//skywalking:nocopy
+type Instrument struct {
+}
+
+func NewInstrument() *Instrument {
+       return &Instrument{}
+}
+
+func (i *Instrument) Name() string {
+       return "gorm"
+}
+
+func (i *Instrument) BasePackage() string {
+       return "gorm.io/driver/mysql"
+}
+
+func (i *Instrument) VersionChecker(version string) bool {
+       return true
+}
+
+func (i *Instrument) Points() []*instrument.Point {
+       return []*instrument.Point{
+               {
+                       PackagePath: "",
+                       At: instrument.NewStaticMethodEnhance("Open",
+                               instrument.WithArgsCount(1), 
instrument.WithArgType(0, "string"),
+                               instrument.WithResultCount(1), 
instrument.WithResultType(0, "gorm.Dialector")),
+                       Interceptor: "InstanceInterceptor",
+               },
+               {
+                       PackagePath: "",
+                       At: instrument.NewStaticMethodEnhance("New",
+                               instrument.WithArgsCount(1), 
instrument.WithArgType(0, "Config"),
+                               instrument.WithResultCount(1), 
instrument.WithResultType(0, "gorm.Dialector")),
+                       Interceptor: "InstanceInterceptor",
+               },
+               {
+                       PackagePath: "",
+                       At: instrument.NewStructEnhance("Dialector",
+                               instrument.WithFiledType("", "*Config")),
+               },
+       }
+}
+
+func (i *Instrument) PluginSourceCodePath() string {
+       return mysqlName
+}
+
+func (i *Instrument) FS() *embed.FS {
+       return &fs
+}
diff --git a/plugins/gorm/mysql/interceptor.go 
b/plugins/gorm/mysql/interceptor.go
new file mode 100644
index 0000000..d7c62f6
--- /dev/null
+++ b/plugins/gorm/mysql/interceptor.go
@@ -0,0 +1,68 @@
+// 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 mysql
+
+import (
+       "gorm.io/driver/mysql"
+
+       driver "github.com/go-sql-driver/mysql"
+
+       "github.com/apache/skywalking-go/plugins/core/operator"
+)
+
+type InstanceInterceptor struct {
+}
+
+func (i *InstanceInterceptor) BeforeInvoke(invocation operator.Invocation) 
error {
+       return nil
+}
+
+func (i *InstanceInterceptor) AfterInvoke(invocation operator.Invocation, 
result ...interface{}) error {
+       if res, ok := result[0].(*mysql.Dialector); ok && res != nil && 
res.Config != nil && res.Config.DSN != "" {
+               dbInfo := i.buildDBInfo(res)
+               if caller, ok := result[0].(operator.EnhancedInstance); ok && 
dbInfo != nil {
+                       caller.SetSkyWalkingDynamicField(dbInfo)
+               }
+       }
+       return nil
+}
+
+func (i *InstanceInterceptor) buildDBInfo(dial *mysql.Dialector) *DatabaseInfo 
{
+       cfg, err := driver.ParseDSN(dial.Config.DSN)
+       if err != nil {
+               // ignore the db info if parse dsn failed
+               return nil
+       }
+       return &DatabaseInfo{PeerAddress: cfg.Addr}
+}
+
+type DatabaseInfo struct {
+       PeerAddress string
+}
+
+func (d *DatabaseInfo) Type() string {
+       return "mysql"
+}
+
+func (d *DatabaseInfo) ComponentID() int32 {
+       return 5012
+}
+
+func (d *DatabaseInfo) Peer() string {
+       return d.PeerAddress
+}
diff --git a/plugins/core/instrument/declare.go 
b/plugins/gorm/mysql/interceptor_test.go
similarity index 59%
copy from plugins/core/instrument/declare.go
copy to plugins/gorm/mysql/interceptor_test.go
index 9aaa65b..a9de4e3 100644
--- a/plugins/core/instrument/declare.go
+++ b/plugins/gorm/mysql/interceptor_test.go
@@ -15,22 +15,23 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package instrument
+package mysql
 
-import "embed"
+import (
+       "testing"
 
-type Instrument interface {
-       Name() string
-       BasePackage() string
-       VersionChecker(version string) bool
-       Points() []*Point
-       FS() *embed.FS
-}
+       "github.com/stretchr/testify/assert"
+
+       "gorm.io/driver/mysql"
+)
 
-type Point struct {
-       PackagePath string
-       At          *EnhanceMatcher
-       Interceptor string
+func TestMysqlDatabaseInfo(t *testing.T) {
+       interceptor := &InstanceInterceptor{}
+       open := mysql.Open("root:root@tcp(mysql-server:3306)/test")
 
-       PackageName string // optional: for package path dir name is not same 
with package name
+       info := interceptor.buildDBInfo(open.(*mysql.Dialector))
+       assert.NotNil(t, info, "info is not nil")
+       assert.Equal(t, "mysql", info.Type(), "type is mysql")
+       assert.Equal(t, "mysql-server:3306", info.Peer(), "peer is 
mysql-server:3306")
+       assert.Equal(t, int32(5012), info.ComponentID(), "component id is 5012")
 }
diff --git a/test/plugins/runner-helper/context.go 
b/test/plugins/runner-helper/context.go
index d2201bf..7c74408 100644
--- a/test/plugins/runner-helper/context.go
+++ b/test/plugins/runner-helper/context.go
@@ -76,12 +76,30 @@ type Context struct {
 }
 
 type Config struct {
-       EntryService   string           `yaml:"entry-service"`
-       HealthChecker  string           `yaml:"health-checker"`
-       StartScript    string           `yaml:"start-script"`
-       FrameworkName  string           `yaml:"framework"`
-       ExportPort     int              `yaml:"export-port"`
-       SupportVersion []SupportVersion `yaml:"support-version"`
+       EntryService   string                              
`yaml:"entry-service"`
+       HealthChecker  string                              
`yaml:"health-checker"`
+       StartScript    string                              `yaml:"start-script"`
+       FrameworkName  string                              `yaml:"framework"`
+       ExportPort     int                                 `yaml:"export-port"`
+       SupportVersion []SupportVersion                    
`yaml:"support-version"`
+       Dependencies   map[string]*DockerDependencyService `yaml:"dependencies"`
+}
+
+type DockerDependencyService struct {
+       Image       string              `yaml:"image"`
+       Hostname    string              `yaml:"hostname"`
+       Ports       []string            `yaml:"ports"`
+       Exports     []string            `yaml:"expose"`
+       Env         map[string]string   `yaml:"environment"`
+       Command     []string            `yaml:"command"`
+       HealthCheck *ServiceHealthCheck `yaml:"healthcheck"`
+}
+
+type ServiceHealthCheck struct {
+       Test     []string `yaml:"test"`
+       Interval string   `yaml:"interval"`
+       Timeout  string   `yaml:"timeout"`
+       Retries  int      `yaml:"retries"`
 }
 
 type SupportVersion struct {
diff --git a/test/plugins/runner-helper/templates/docker-compose.tpl 
b/test/plugins/runner-helper/templates/docker-compose.tpl
index adeb889..24b2ae9 100644
--- a/test/plugins/runner-helper/templates/docker-compose.tpl
+++ b/test/plugins/runner-helper/templates/docker-compose.tpl
@@ -39,6 +39,12 @@ services:
     depends_on:
       oap:
         condition: service_healthy
+      {{- if .Context.Config.Dependencies }}
+      {{- range $name, $service := .Context.Config.Dependencies }}
+      {{$name}}:
+        condition: {{ if $service.HealthCheck -}} service_healthy {{- else -}} 
service_started {{- end}}
+      {{- end }}
+      {{- end }}
     ports:
       - {{.Context.Config.ExportPort}}
     {{ if .Context.DebugMode -}}
@@ -64,3 +70,44 @@ services:
     volumes:
       - {{.Context.WorkSpaceDir}}:/workspace
     command: ["/bin/bash", "/workspace/validator.sh"]
+  {{- range $name, $service := .Context.Config.Dependencies }}
+  {{$name}}:
+    image: {{$service.Image}}
+    {{- if $service.Hostname }}
+    hostname: {{$service.Hostname}}
+    {{- end }}
+    {{- if $service.Ports }}
+    ports:
+      {{- range $service.Ports }}
+      - "{{.}}"
+      {{- end }}
+    {{- end }}
+    {{- if $service.Exports }}
+    expose:
+      {{- range $service.Exports }}
+      - "{{.}}"
+      {{- end }}
+    {{- end }}
+    {{- if $service.Env }}
+    environment:
+      {{- range $key, $value := $service.Env }}
+      {{$key}}: {{$value}}
+      {{- end }}
+    {{- end }}
+    {{- if $service.Command }}
+    command:
+      {{- range $service.Command }}
+      - "{{.}}"
+      {{- end }}
+    {{- end }}
+    {{- if $service.HealthCheck }}
+    healthcheck:
+      test:
+        {{- range $service.HealthCheck.Test }}
+        - "{{.}}"
+        {{- end }}
+      interval: {{$service.HealthCheck.Interval}}
+      timeout: {{$service.HealthCheck.Timeout}}
+      retries: {{$service.HealthCheck.Retries}}
+    {{- end }}
+  {{- end }}
\ No newline at end of file
diff --git a/test/plugins/scenarios/gorm/bin/startup.sh 
b/test/plugins/scenarios/gorm/bin/startup.sh
new file mode 100755
index 0000000..fffd7b4
--- /dev/null
+++ b/test/plugins/scenarios/gorm/bin/startup.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# 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.
+
+home="$(cd "$(dirname $0)"; pwd)"
+go build ${GO_BUILD_OPTS} -o gorm
+
+./gorm
\ No newline at end of file
diff --git a/test/plugins/scenarios/gorm/config/excepted.yml 
b/test/plugins/scenarios/gorm/config/excepted.yml
new file mode 100644
index 0000000..7c044c7
--- /dev/null
+++ b/test/plugins/scenarios/gorm/config/excepted.yml
@@ -0,0 +1,117 @@
+# 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.
+
+segmentItems:
+  - serviceName: gorm
+    segmentSize: ge 1
+    segments:
+      - segmentId: not null
+        spans:
+          - operationName: /raw
+            parentSpanId: 0
+            spanId: 1
+            spanLayer: Database
+            startTime: nq 0
+            endTime: nq 0
+            componentId: 5012
+            isError: false
+            spanType: Exit
+            peer: mysql-server:3306
+            skipAnalysis: false
+            tags:
+              - {key: db.type, value: mysql}
+          - operationName: users/create
+            parentSpanId: 0
+            spanId: 2
+            spanLayer: Database
+            startTime: nq 0
+            endTime: nq 0
+            componentId: 5012
+            isError: false
+            spanType: Exit
+            peer: mysql-server:3306
+            skipAnalysis: false
+            tags:
+              - {key: db.type, value: mysql}
+          - operationName: users/query
+            parentSpanId: 0
+            spanId: 3
+            spanLayer: Database
+            startTime: nq 0
+            endTime: nq 0
+            componentId: 5012
+            isError: false
+            spanType: Exit
+            peer: mysql-server:3306
+            skipAnalysis: false
+            tags:
+              - {key: db.type, value: mysql}
+          - operationName: users/row
+            parentSpanId: 0
+            spanId: 4
+            spanLayer: Database
+            startTime: nq 0
+            endTime: nq 0
+            componentId: 5012
+            isError: false
+            spanType: Exit
+            peer: mysql-server:3306
+            skipAnalysis: false
+            tags:
+              - {key: db.type, value: mysql}
+          - operationName: users/update
+            parentSpanId: 0
+            spanId: 5
+            spanLayer: Database
+            startTime: nq 0
+            endTime: nq 0
+            componentId: 5012
+            isError: false
+            spanType: Exit
+            peer: mysql-server:3306
+            skipAnalysis: false
+            tags:
+              - {key: db.type, value: mysql}
+          - operationName: users/delete
+            parentSpanId: 0
+            spanId: 6
+            spanLayer: Database
+            startTime: nq 0
+            endTime: nq 0
+            componentId: 5012
+            isError: false
+            spanType: Exit
+            peer: mysql-server:3306
+            skipAnalysis: false
+            tags:
+              - {key: db.type, value: mysql}
+          - operationName: GET:/execute
+            parentSpanId: -1
+            spanId: 0
+            spanLayer: Http
+            startTime: nq 0
+            endTime: nq 0
+            componentId: 5004
+            isError: false
+            spanType: Entry
+            peer: ''
+            skipAnalysis: false
+            tags:
+              - {key: http.method, value: GET}
+              - {key: url, value: 'service:8080/execute'}
+              - {key: status_code, value: '200'}
+meterItems: []
+logItems: []
diff --git a/test/plugins/scenarios/gorm/go.mod 
b/test/plugins/scenarios/gorm/go.mod
new file mode 100644
index 0000000..4b9d8d2
--- /dev/null
+++ b/test/plugins/scenarios/gorm/go.mod
@@ -0,0 +1,26 @@
+module test/plugins/scenarios/gorm
+
+go 1.18
+
+require (
+       github.com/apache/skywalking-go v0.0.0-20230519025440-37fd080ca733
+       gorm.io/driver/mysql v1.5.1
+       gorm.io/gorm v1.25.1
+)
+
+require (
+       github.com/go-sql-driver/mysql v1.7.0 // indirect
+       github.com/golang/protobuf v1.5.3 // indirect
+       github.com/google/go-cmp v0.5.9 // indirect
+       github.com/google/uuid v1.3.0 // indirect
+       github.com/jinzhu/inflection v1.0.0 // indirect
+       github.com/jinzhu/now v1.1.5 // indirect
+       github.com/pkg/errors v0.9.1 // indirect
+       golang.org/x/net v0.10.0 // indirect
+       golang.org/x/sys v0.8.0 // indirect
+       golang.org/x/text v0.9.0 // indirect
+       google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // 
indirect
+       google.golang.org/grpc v1.55.0 // indirect
+       google.golang.org/protobuf v1.30.0 // indirect
+       skywalking.apache.org/repo/goapi v0.0.0-20230314034821-0c5a44bb767a // 
indirect
+)
diff --git a/test/plugins/scenarios/gorm/go.sum 
b/test/plugins/scenarios/gorm/go.sum
new file mode 100644
index 0000000..64aa0a9
--- /dev/null
+++ b/test/plugins/scenarios/gorm/go.sum
@@ -0,0 +1,183 @@
+cloud.google.com/go v0.26.0/go.mod 
h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod 
h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+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/apache/skywalking-go v0.0.0-20230519025440-37fd080ca733 
h1:e4ElWRK4J7CumoRlLFaDnH3kyzql1RV+t9Gtv6whDgA=
+github.com/apache/skywalking-go v0.0.0-20230519025440-37fd080ca733/go.mod 
h1:r2FrabK/0XftMyosqre7WFZvZ1mEOVssJROo8xgcEW8=
+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/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=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod 
h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod 
h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane 
v0.9.1-0.20191026205805-5f8ba28d4473/go.mod 
h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod 
h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane 
v0.9.9-0.20201210154907-fd9021fe5dad/go.mod 
h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane 
v0.9.9-0.20210217033140-668b12f5399d/go.mod 
h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+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/go-sql-driver/mysql v1.7.0 
h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
+github.com/go-sql-driver/mysql v1.7.0/go.mod 
h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+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=
+github.com/golang/protobuf v1.3.2/go.mod 
h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod 
h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod 
h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod 
h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod 
h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod 
h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod 
h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod 
h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod 
h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod 
h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod 
h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod 
h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 
h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod 
h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.2.0/go.mod 
h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod 
h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod 
h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod 
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod 
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+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/go-cmp v0.5.9/go.mod 
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+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/jinzhu/inflection v1.0.0 
h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod 
h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod 
h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+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/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/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/testify v1.5.1/go.mod 
h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+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/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/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=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod 
h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod 
h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+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/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=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod 
h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod 
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod 
h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod 
h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod 
h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod 
h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod 
h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod 
h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod 
h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod 
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod 
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+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/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=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod 
h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod 
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod 
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod 
h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod 
h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod 
h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod 
h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod 
h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+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/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=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod 
h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod 
h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod 
h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod 
h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod 
h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod 
h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod 
h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod 
h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+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/grpc v1.19.0/go.mod 
h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod 
h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod 
h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod 
h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod 
h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod 
h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.38.0/go.mod 
h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.40.0/go.mod 
h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+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 v0.0.0-20200109180630-ec00e32a8dfd/go.mod 
h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod 
h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod 
h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod 
h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod 
h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod 
h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod 
h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod 
h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod 
h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod 
h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod 
h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.29.0/go.mod 
h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.30.0 
h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod 
h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+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=
+gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw=
+gorm.io/driver/mysql v1.5.1/go.mod 
h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o=
+gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64=
+gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
+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=
+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/test/plugins/scenarios/gorm/main.go 
b/test/plugins/scenarios/gorm/main.go
new file mode 100644
index 0000000..b25fd28
--- /dev/null
+++ b/test/plugins/scenarios/gorm/main.go
@@ -0,0 +1,130 @@
+// 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 main
+
+import (
+       "fmt"
+       "log"
+       "net/http"
+
+       "gorm.io/driver/mysql"
+       "gorm.io/gorm"
+
+       _ "github.com/apache/skywalking-go"
+)
+
+var db *gorm.DB
+
+type testFunc func(*gorm.DB) error
+
+type User struct {
+       ID   uint
+       Name string
+       Age  uint8
+}
+
+func executeHandler(w http.ResponseWriter, r *http.Request) {
+       tests := []struct {
+               name string
+               fn   testFunc
+       }{
+               {"raw", TestRaw},
+               {"create", TestCreate},
+               {"query", TestQuery},
+               {"row", TestRow},
+               {"update", TestUpdate},
+               {"delete", TestDelete},
+       }
+
+       dbWithCtx := db.WithContext(r.Context())
+       for _, test := range tests {
+               log.Printf("excute test case %s", test.name)
+               if err := test.fn(dbWithCtx); err != nil {
+                       log.Fatalf("test case %s failed: %v", test.name, err)
+               }
+       }
+       _, _ = w.Write([]byte("execute sql success"))
+}
+
+func TestRaw(db *gorm.DB) error {
+       if err := db.Exec(`CREATE TABLE IF NOT EXISTS users (id char(255), name 
VARCHAR(255), age INTEGER)`).Error; err != nil {
+               return fmt.Errorf("create error: %s", err.Error())
+       }
+
+       return nil
+}
+
+func TestCreate(db *gorm.DB) error {
+       user := User{Name: "Jinzhu", Age: 18}
+       if err := db.Create(&user).Error; err != nil {
+               return fmt.Errorf("create error: %w", err)
+       }
+
+       return nil
+}
+
+func TestQuery(db *gorm.DB) error {
+       var user User
+       if err := db.First(&user).Error; err != nil {
+               return fmt.Errorf("query error: %w", err)
+       }
+
+       return nil
+}
+
+func TestRow(db *gorm.DB) error {
+       var name string
+       var age uint8
+       row := db.Table("users").Where("name = ?", "jinzhu").Select("name", 
"age").Row()
+       row.Scan(&name, &age)
+
+       return nil
+}
+
+func TestUpdate(db *gorm.DB) error {
+       tx := db.Model(&User{}).Where("name = ?", "jinzhu").Update("name", 
"hello")
+       if err := tx.Error; err != nil {
+               return fmt.Errorf("update error: %w", err)
+       }
+
+       return nil
+}
+
+func TestDelete(db *gorm.DB) error {
+       if err := db.Delete(&User{}, 1).Error; err != nil {
+               return fmt.Errorf("delete error: %w", err)
+       }
+
+       return nil
+}
+
+func main() {
+       tmpDB, err := 
gorm.Open(mysql.Open("root:root@tcp(mysql-server:3306)/test"), &gorm.Config{})
+       if err != nil {
+               log.Fatalf("open db error: %v \n", err)
+       }
+       db = tmpDB
+
+       http.HandleFunc("/execute", executeHandler)
+
+       http.HandleFunc("/health", func(writer http.ResponseWriter, request 
*http.Request) {
+               writer.WriteHeader(http.StatusOK)
+       })
+
+       _ = http.ListenAndServe(":8080", nil)
+}
diff --git a/test/plugins/scenarios/gorm/plugin.yml 
b/test/plugins/scenarios/gorm/plugin.yml
new file mode 100644
index 0000000..0e01494
--- /dev/null
+++ b/test/plugins/scenarios/gorm/plugin.yml
@@ -0,0 +1,48 @@
+# 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.
+
+entry-service: http://${HTTP_HOST}:${HTTP_PORT}/execute
+health-checker: http://${HTTP_HOST}:${HTTP_PORT}/health
+start-script: ./bin/startup.sh
+framework: gorm.io/gorm
+export-port: 8080
+support-version:
+  - go: 1.17
+    framework:
+      - v1.22.0
+      - v1.23.0
+      - v1.24.0
+      - v1.24.1
+      - v1.24.2
+      - v1.24.3
+      - v1.24.4
+      - v1.24.5
+      - v1.25.0
+      - v1.25.1
+dependencies:
+  mysql-server:
+    image: mysql:5.7
+    hostname: mysql-server
+    expose:
+      - "3306"
+    environment:
+      MYSQL_ROOT_PASSWORD: root
+      MYSQL_DATABASE: test
+    healthcheck:
+      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
+      interval: 5s
+      timeout: 60s
+      retries: 120
\ No newline at end of file
diff --git a/tools/go-agent/instrument/plugins/enhance_method.go 
b/tools/go-agent/instrument/plugins/enhance_method.go
index e75e232..d0ff955 100644
--- a/tools/go-agent/instrument/plugins/enhance_method.go
+++ b/tools/go-agent/instrument/plugins/enhance_method.go
@@ -160,7 +160,7 @@ func (m *MethodEnhance) BuildForDelegator() []dst.Decl {
        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: parameter.PackagedType()},
+                       Type:  &dst.StarExpr{X: 
m.changeTypeIfNeeds(parameter.PackagedType())},
                })
        }
        for i, result := range m.Results {
@@ -214,6 +214,14 @@ func (m *MethodEnhance) BuildForDelegator() []dst.Decl {
        return result
 }
 
+func (m *MethodEnhance) changeTypeIfNeeds(tp dst.Expr) dst.Expr {
+       // change "...XXX" to "[]XXX" for reference type
+       if el, ok := tp.(*dst.Ellipsis); ok {
+               return &dst.ArrayType{Elt: el.Elt}
+       }
+       return tp
+}
+
 func (m *MethodEnhance) addPackagePrefixForArgsAndClone(pkg string, tp 
dst.Expr) dst.Expr {
        switch t := tp.(type) {
        case *dst.Ident:
diff --git a/tools/go-agent/instrument/plugins/instrument.go 
b/tools/go-agent/instrument/plugins/instrument.go
index 77309f2..18fa6f1 100644
--- a/tools/go-agent/instrument/plugins/instrument.go
+++ b/tools/go-agent/instrument/plugins/instrument.go
@@ -219,7 +219,11 @@ func (i *Instrument) copyFrameworkFS(context 
*rewrite.Context, compilePkgFullPat
 
        var debugBaseDir string
        if i.compileOpts.DebugDir != "" {
-               debugBaseDir = filepath.Join(i.compileOpts.DebugDir, "plugins", 
i.realInst.Name(), subPkgPath)
+               pathBuilder := filepath.Join(i.compileOpts.DebugDir, "plugins", 
i.realInst.Name())
+               if subIns, ok := i.realInst.(instrument.SourceCodeDetector); ok 
{
+                       pathBuilder = filepath.Join(pathBuilder, 
subIns.PluginSourceCodePath())
+               }
+               debugBaseDir = filepath.Join(pathBuilder, subPkgPath)
        }
        pkgCopiedEntries, err := i.realInst.FS().ReadDir(subPkgPath)
        if err != nil {
diff --git a/tools/go-agent/instrument/plugins/register.go 
b/tools/go-agent/instrument/plugins/register.go
index 273859a..f6ee641 100644
--- a/tools/go-agent/instrument/plugins/register.go
+++ b/tools/go-agent/instrument/plugins/register.go
@@ -22,6 +22,8 @@ import (
        "github.com/apache/skywalking-go/plugins/dubbo"
        "github.com/apache/skywalking-go/plugins/gin"
        "github.com/apache/skywalking-go/plugins/go-restfulv3"
+       gorm_entry "github.com/apache/skywalking-go/plugins/gorm/entry"
+       gorm_mysql "github.com/apache/skywalking-go/plugins/gorm/mysql"
        "github.com/apache/skywalking-go/plugins/http"
 )
 
@@ -33,6 +35,10 @@ func init() {
        registerFramework(http.NewInstrument())
        registerFramework(dubbo.NewInstrument())
        registerFramework(restfulv3.NewInstrument())
+
+       // gorm related instruments
+       registerFramework(gorm_entry.NewInstrument())
+       registerFramework(gorm_mysql.NewInstrument())
 }
 
 func registerFramework(ins instrument.Instrument) {
diff --git a/tools/go-agent/tools/enhancement.go 
b/tools/go-agent/tools/enhancement.go
index c0e7f63..f696d46 100644
--- a/tools/go-agent/tools/enhancement.go
+++ b/tools/go-agent/tools/enhancement.go
@@ -152,7 +152,7 @@ func GenerateTypeNameByExp(exp dst.Expr) string {
        case *dst.SelectorExpr:
                data = GenerateTypeNameByExp(n.X) + "." + 
GenerateTypeNameByExp(n.Sel)
        case *dst.Ellipsis:
-               data = "..." + GenerateTypeNameByExp(n.Elt)
+               data = "[]" + GenerateTypeNameByExp(n.Elt)
        case *dst.ArrayType:
                data = "[]" + GenerateTypeNameByExp(n.Elt)
        default:

Reply via email to