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 15f2a54  feature: Support http headers collection for Gin (#178)
15f2a54 is described below

commit 15f2a54ffa1f10e22728f609073592cc554c7a24
Author: IceSoda177 <dylanwu...@foxmail.com>
AuthorDate: Mon Apr 1 11:06:54 2024 +0800

    feature: Support http headers collection for Gin (#178)
---
 CHANGES.md                                         |  1 +
 docs/en/agent/plugin-configurations.md             |  2 +
 plugins/core/operator/tools.go                     |  1 +
 plugins/core/tools/strconv.go                      | 11 ++++++
 plugins/core/tracer_tools.go                       |  5 +++
 plugins/core/tracing/span.go                       |  1 +
 plugins/{core/operator/tools.go => gin/config.go}  | 14 +++----
 plugins/gin/intercepter.go                         | 33 +++++++++++++++-
 plugins/gin/intercepter_test.go                    | 44 +++++++++++++++++++++-
 test/plugins/scenarios/gin/bin/startup.sh          |  3 ++
 test/plugins/scenarios/gin/config/excepted.yml     |  1 +
 test/plugins/scenarios/gin/main.go                 | 14 ++++++-
 tools/go-agent/config/agent.default.yaml           |  5 +++
 .../go-agent/instrument/plugins/enhance_config.go  |  4 ++
 14 files changed, 126 insertions(+), 13 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index fe3da3b..11de5e2 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -10,6 +10,7 @@ Release Notes.
 #### Plugins
 * Support [Pulsar](https://github.com/apache/pulsar-client-go) MQ.
 * Support [Segmentio-Kafka](https://github.com/segmentio/kafka-go) MQ.
+* Support http headers collection for Gin
 
 0.4.0
 ------------------
diff --git a/docs/en/agent/plugin-configurations.md 
b/docs/en/agent/plugin-configurations.md
index b69a7b9..b4c7318 100644
--- a/docs/en/agent/plugin-configurations.md
+++ b/docs/en/agent/plugin-configurations.md
@@ -7,3 +7,5 @@
 | sql.collect_parameter          | 
SW_AGENT_PLUGIN_CONFIG_SQL_COLLECT_PARAMETER          | false         | Collect 
the parameter of the SQL request.                      |
 | redis.max_args_bytes           | SW_AGENT_PLUGIN_CONFIG_REDIS_MAX_ARGS_BYTES 
          | 1024          | Limit the bytes size of redis args request.         
           |
 | reporter.discard               | SW_AGENT_REPORTER_DISCARD                   
          | false         | Discard the reporter.                               
           |
+| gin.collect_request_headers    | 
SW_AGENT_PLUGIN_CONFIG_GIN_COLLECT_REQUEST_HEADERS    |               | Collect 
the http header of gin request.                        |
+| gin.header_length_threshold    | 
SW_AGENT_PLUGIN_CONFIG_GIN_HEADER_LENGTH_THRESHOLD    | 2048          | 
Controlling the length limitation of all header values.        |
\ No newline at end of file
diff --git a/plugins/core/operator/tools.go b/plugins/core/operator/tools.go
index f12885f..6a4a804 100644
--- a/plugins/core/operator/tools.go
+++ b/plugins/core/operator/tools.go
@@ -23,6 +23,7 @@ type ToolsOperator interface {
        ParseFloat(val string, bitSize int) (float64, error)
        ParseBool(val string) bool
        ParseInt(val string, base, bitSize int) (int64, error)
+       ParseStringArray(val string) ([]string, error)
        Atoi(val string) (int, error)
        NewSyncMap() interface{}
 }
diff --git a/plugins/core/tools/strconv.go b/plugins/core/tools/strconv.go
index 13210a7..02f030f 100644
--- a/plugins/core/tools/strconv.go
+++ b/plugins/core/tools/strconv.go
@@ -52,6 +52,17 @@ func ParseInt(val string, base, bitSize int) (int64, error) {
        return op.Tools().(operator.ToolsOperator).ParseInt(val, base, bitSize)
 }
 
+func ParseStringArray(val string) ([]string, error) {
+       if val == "" {
+               return []string{}, nil
+       }
+       op := operator.GetOperator()
+       if op == nil {
+               return []string{}, nil
+       }
+       return op.Tools().(operator.ToolsOperator).ParseStringArray(val)
+}
+
 func Atoi(s string) (int, error) {
        if s == "" {
                return 0, nil
diff --git a/plugins/core/tracer_tools.go b/plugins/core/tracer_tools.go
index 7d43781..aa61050 100644
--- a/plugins/core/tracer_tools.go
+++ b/plugins/core/tracer_tools.go
@@ -100,6 +100,11 @@ func (t *TracerTools) ParseInt(val string, base, bitSize 
int) (int64, error) {
        return strconv.ParseInt(val, base, bitSize)
 }
 
+func (t *TracerTools) ParseStringArray(val string) ([]string, error) {
+       newVal := strings.ReplaceAll(val, " ", "")
+       return strings.Split(newVal, ","), nil
+}
+
 func (t *TracerTools) Atoi(val string) (int, error) {
        return strconv.Atoi(val)
 }
diff --git a/plugins/core/tracing/span.go b/plugins/core/tracing/span.go
index edc8005..d21c968 100644
--- a/plugins/core/tracing/span.go
+++ b/plugins/core/tracing/span.go
@@ -53,6 +53,7 @@ const (
        TagStatusCode      = "status_code"
        TagHTTPMethod      = "http.method"
        TagHTTPParams      = "http.params"
+       TagHTTPHeaders     = "http.headers"
        TagDBType          = "db.type"
        TagDBInstance      = "db.instance"
        TagDBStatement     = "db.statement"
diff --git a/plugins/core/operator/tools.go b/plugins/gin/config.go
similarity index 70%
copy from plugins/core/operator/tools.go
copy to plugins/gin/config.go
index f12885f..711aad3 100644
--- a/plugins/core/operator/tools.go
+++ b/plugins/gin/config.go
@@ -15,14 +15,10 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package operator
+package gin
 
-type ToolsOperator interface {
-       ReflectGetValue(instance interface{}, filters []interface{}) interface{}
-       GetEnvValue(key string) string
-       ParseFloat(val string, bitSize int) (float64, error)
-       ParseBool(val string) bool
-       ParseInt(val string, base, bitSize int) (int64, error)
-       Atoi(val string) (int, error)
-       NewSyncMap() interface{}
+//skywalking:config gin
+var config struct {
+       CollectRequestHeaders []string `config:"collect_request_headers"`
+       HeaderLengthThreshold int      `config:"header_length_threshold"`
 }
diff --git a/plugins/gin/intercepter.go b/plugins/gin/intercepter.go
index af07621..c1a74a0 100644
--- a/plugins/gin/intercepter.go
+++ b/plugins/gin/intercepter.go
@@ -19,11 +19,13 @@ package gin
 
 import (
        "fmt"
-
-       "github.com/gin-gonic/gin"
+       "net/http"
+       "strings"
 
        "github.com/apache/skywalking-go/plugins/core/operator"
        "github.com/apache/skywalking-go/plugins/core/tracing"
+
+       "github.com/gin-gonic/gin"
 )
 
 type ContextInterceptor struct {
@@ -45,6 +47,11 @@ func (h *ContextInterceptor) BeforeInvoke(invocation 
operator.Invocation) error
        if err != nil {
                return err
        }
+
+       if len(config.CollectRequestHeaders) > 0 {
+               collectRequestHeaders(s, context.Request.Header)
+       }
+
        invocation.SetContext(s)
        return nil
 }
@@ -70,3 +77,25 @@ func isFirstHandle(c interface{}) bool {
        }
        return true
 }
+
+func collectRequestHeaders(span tracing.Span, requestHeaders http.Header) {
+       var headerTagValues []string
+       for _, header := range config.CollectRequestHeaders {
+               var headerValue = requestHeaders.Get(header)
+               if headerValue != "" {
+                       headerTagValues = append(headerTagValues, 
header+"="+headerValue)
+               }
+       }
+
+       if len(headerTagValues) == 0 {
+               return
+       }
+
+       tagValue := strings.Join(headerTagValues, "\n")
+       if len(tagValue) > config.HeaderLengthThreshold {
+               maxLen := config.HeaderLengthThreshold
+               tagValue = tagValue[:maxLen]
+       }
+
+       span.Tag(tracing.TagHTTPHeaders, tagValue)
+}
diff --git a/plugins/gin/intercepter_test.go b/plugins/gin/intercepter_test.go
index f50dd11..d5ba898 100644
--- a/plugins/gin/intercepter_test.go
+++ b/plugins/gin/intercepter_test.go
@@ -26,9 +26,9 @@ import (
 
        "github.com/apache/skywalking-go/plugins/core"
        "github.com/apache/skywalking-go/plugins/core/operator"
+       "github.com/apache/skywalking-go/plugins/core/tracing"
 
        "github.com/gin-gonic/gin"
-
        "github.com/stretchr/testify/assert"
 )
 
@@ -75,3 +75,45 @@ type testWriter struct {
 func (i *testWriter) Status() int {
        return 200
 }
+
+func TestCollectHeaders(t *testing.T) {
+       defer core.ResetTracingContext()
+
+       config.CollectRequestHeaders = []string{"h1", "h2"}
+       config.HeaderLengthThreshold = 17
+
+       interceptor := &ContextInterceptor{}
+       request, err := http.NewRequest("GET", 
"http://localhost/skywalking/trace";, http.NoBody)
+       assert.Nil(t, err, "new request error should be nil")
+       request.Header.Set("h1", "h1-value")
+       request.Header.Set("h2", "h2-value")
+
+       c := &gin.Context{
+               Request: request,
+               Writer:  &testWriter{},
+       }
+
+       invocation := operator.NewInvocation(c)
+       err = interceptor.BeforeInvoke(invocation)
+       assert.Nil(t, err, "before invoke error should be nil")
+       assert.NotNil(t, invocation.GetContext(), "context should not be nil")
+
+       time.Sleep(100 * time.Millisecond)
+
+       err = interceptor.AfterInvoke(invocation)
+       assert.Nil(t, err, "after invoke error should be nil")
+
+       time.Sleep(100 * time.Millisecond)
+       spans := core.GetReportedSpans()
+       assert.Equal(t, 1, len(spans), "spans length should be 1")
+       assert.Equal(t, 4, len(spans[0].Tags()), "tags length should be 4")
+
+       index := 0
+       for ; index < len(spans[0].Tags()); index++ {
+               if spans[0].Tags()[index].Key == tracing.TagHTTPHeaders {
+                       break
+               }
+       }
+       assert.Less(t, index, 4, "the index should be less than 4")
+       assert.Equal(t, "h1=h1-value\nh2=h2", spans[0].Tags()[index].Value, 
"the tag Value should be h1=h1-value\nh2=h2-value")
+}
diff --git a/test/plugins/scenarios/gin/bin/startup.sh 
b/test/plugins/scenarios/gin/bin/startup.sh
index 84a29aa..a622e42 100755
--- a/test/plugins/scenarios/gin/bin/startup.sh
+++ b/test/plugins/scenarios/gin/bin/startup.sh
@@ -19,4 +19,7 @@
 home="$(cd "$(dirname $0)"; pwd)"
 go build ${GO_BUILD_OPTS} -o gin
 
+export SW_AGENT_PLUGIN_CONFIG_GIN_COLLECT_REQUEST_HEADERS=h1,h2
+export SW_AGENT_PLUGIN_CONFIG_GIN_HEADER_LENGTH_THRESHOLD=17
+
 ./gin
\ No newline at end of file
diff --git a/test/plugins/scenarios/gin/config/excepted.yml 
b/test/plugins/scenarios/gin/config/excepted.yml
index e928323..d1a3c09 100644
--- a/test/plugins/scenarios/gin/config/excepted.yml
+++ b/test/plugins/scenarios/gin/config/excepted.yml
@@ -34,6 +34,7 @@ segmentItems:
             tags:
               - {key: http.method, value: GET}
               - {key: url, value: 'localhost:8080/provider'}
+              - {key: http.headers, value: "h1=h1-value\nh2=h2"}
               - {key: status_code, value: '200'}
             refs:
               - {parentEndpoint: 'GET:/consumer', networkAddress: 
'localhost:8080', refType: CrossProcess,
diff --git a/test/plugins/scenarios/gin/main.go 
b/test/plugins/scenarios/gin/main.go
index b44133e..8298f99 100644
--- a/test/plugins/scenarios/gin/main.go
+++ b/test/plugins/scenarios/gin/main.go
@@ -30,13 +30,25 @@ import (
 func main() {
        engine := gin.New()
        engine.Handle("GET", "/consumer", func(context *gin.Context) {
-               resp, err := http.Get("http://localhost:8080/provider";)
+               request, err := http.NewRequest("GET", 
"http://localhost:8080/provider";, nil)
+               if err != nil {
+                       log.Print(err)
+                       context.Status(http.StatusInternalServerError)
+                       return
+               }
+
+               request.Header.Set("h1", "h1-value")
+               request.Header.Set("h2", "h2-value")
+
+               client := &http.Client{}
+               resp, err := client.Do(request)
                if err != nil {
                        log.Print(err)
                        context.Status(http.StatusInternalServerError)
                        return
                }
                defer resp.Body.Close()
+
                body, err := io.ReadAll(resp.Body)
                if err != nil {
                        log.Print(err)
diff --git a/tools/go-agent/config/agent.default.yaml 
b/tools/go-agent/config/agent.default.yaml
index ee1e5cd..38c1cdf 100644
--- a/tools/go-agent/config/agent.default.yaml
+++ b/tools/go-agent/config/agent.default.yaml
@@ -103,3 +103,8 @@ plugin:
     redis:
       # Limit the bytes size of redis args request
       max_args_bytes: ${SW_AGENT_PLUGIN_CONFIG_REDIS_MAX_ARGS_BYTES:1024}
+    gin: 
+      # Collect the http header of gin request
+      collect_request_headers: 
${SW_AGENT_PLUGIN_CONFIG_GIN_COLLECT_REQUEST_HEADERS:}
+      # Controlling the length limitation of all header values
+      header_length_threshold: 
${SW_AGENT_PLUGIN_CONFIG_GIN_HEADER_LENGTH_THRESHOLD:2048}
\ No newline at end of file
diff --git a/tools/go-agent/instrument/plugins/enhance_config.go 
b/tools/go-agent/instrument/plugins/enhance_config.go
index 798fb09..6db9401 100644
--- a/tools/go-agent/instrument/plugins/enhance_config.go
+++ b/tools/go-agent/instrument/plugins/enhance_config.go
@@ -149,6 +149,8 @@ func NewConfigField(f *dst.Field) (*ConfigField, error) {
        switch t := f.Type.(type) {
        case *dst.Ident:
                conf.Type = t.Name
+       case *dst.ArrayType:
+               conf.Type = tools.GenerateTypeNameByExp(t)
        case *dst.StructType:
                fs, err := NewConfigFields(t)
                if err != nil {
@@ -235,6 +237,8 @@ func (f *ConfigField) GenerateAssignFieldValue(varName 
string, field, path []str
                parseResStr = "if v, err := tools.ParseFloat(result, 64); err 
!= nil { panic(" + parseErrorMessage + ") } else { return v }"
        case "float":
                parseResStr = "if v, err := tools.ParseFloat(result, 64); err 
!= nil { panic(" + parseErrorMessage + ") } else { return v }"
+       case "[]string":
+               parseResStr = "if v, err := tools.ParseStringArray(result); err 
!= nil { panic(" + parseErrorMessage + ") } else { return v }"
        default:
                panic("unsupported config type " + f.Type)
        }

Reply via email to