diff --git a/contrib/rpc/grpcx/internal/utils/utils.go b/contrib/rpc/grpcx/internal/utils/utils.go index aa42aea22..6f84d6d19 100644 --- a/contrib/rpc/grpcx/internal/utils/utils.go +++ b/contrib/rpc/grpcx/internal/utils/utils.go @@ -9,6 +9,7 @@ package utils import ( "fmt" + "unicode/utf8" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" @@ -40,5 +41,13 @@ func MarshalMessageToJsonStringForTracing(value interface{}, msgType string, max } else { messageContent = fmt.Sprintf("%v", value) } + + if !utf8.ValidString(messageContent) { + messageContent = fmt.Sprintf( + "[%s Message Is Invalid UTF-8 Content For Tracing]", + msgType, + ) + } + return messageContent } diff --git a/net/gclient/gclient_tracing.go b/net/gclient/gclient_tracing.go index 1dd0d043f..961775adf 100644 --- a/net/gclient/gclient_tracing.go +++ b/net/gclient/gclient_tracing.go @@ -24,7 +24,6 @@ import ( "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/net/gtrace" "github.com/gogf/gf/v2/os/gctx" - "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/gconv" ) @@ -92,13 +91,14 @@ func internalMiddlewareTracing(c *Client, r *http.Request) (response *Response, reqBodyContentBytes, _ := io.ReadAll(response.Body) response.Body = utils.NewReadCloser(reqBodyContentBytes, false) + resBodyContent, err := gtrace.SafeContentForHttp(reqBodyContentBytes, response.Header) + if err != nil { + span.SetStatus(codes.Error, fmt.Sprintf(`converting safe content failed: %s`, err.Error())) + } + span.AddEvent(tracingEventHttpResponse, trace.WithAttributes( attribute.String(tracingEventHttpResponseHeaders, gconv.String(httputil.HeaderToMap(response.Header))), - attribute.String(tracingEventHttpResponseBody, gstr.StrLimit( - string(reqBodyContentBytes), - gtrace.MaxContentLogSize(), - "...", - )), + attribute.String(tracingEventHttpResponseBody, resBodyContent), )) return } diff --git a/net/gclient/gclient_tracing_tracer.go b/net/gclient/gclient_tracing_tracer.go index 47829d21c..8e9d87a67 100644 --- a/net/gclient/gclient_tracing_tracer.go +++ b/net/gclient/gclient_tracing_tracer.go @@ -23,7 +23,6 @@ import ( "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/net/gtrace" - "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/gconv" ) @@ -153,14 +152,15 @@ func (ct *clientTracer) wroteRequest(info httptrace.WroteRequestInfo) { ct.span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, info.Err)) } + reqBodyContent, err := gtrace.SafeContentForHttp(ct.requestBody, ct.request.Header) + if err != nil { + ct.span.SetStatus(codes.Error, fmt.Sprintf(`converting safe content failed: %s`, err.Error())) + } + ct.span.AddEvent(tracingEventHttpRequest, trace.WithAttributes( attribute.String(tracingEventHttpRequestHeaders, gconv.String(ct.headers)), attribute.String(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ct.Context).String()), - attribute.String(tracingEventHttpRequestBody, gstr.StrLimit( - string(ct.requestBody), - gtrace.MaxContentLogSize(), - "...", - )), + attribute.String(tracingEventHttpRequestBody, reqBodyContent), )) } diff --git a/net/ghttp/ghttp_middleware_tracing.go b/net/ghttp/ghttp_middleware_tracing.go index 6a7c2ccb8..b1e6fbaca 100644 --- a/net/ghttp/ghttp_middleware_tracing.go +++ b/net/ghttp/ghttp_middleware_tracing.go @@ -7,12 +7,9 @@ package ghttp import ( - "compress/gzip" "context" "fmt" "io" - "net/http" - "strings" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -26,7 +23,6 @@ import ( "github.com/gogf/gf/v2/internal/utils" "github.com/gogf/gf/v2/net/gtrace" "github.com/gogf/gf/v2/os/gctx" - "github.com/gogf/gf/v2/text/gstr" "github.com/gogf/gf/v2/util/gconv" ) @@ -92,16 +88,16 @@ func internalMiddlewareServerTracing(r *Request) { return } r.Body = utils.NewReadCloser(reqBodyContentBytes, false) + reqBodyContent, err := gtrace.SafeContentForHttp(reqBodyContentBytes, r.Header) + if err != nil { + span.SetStatus(codes.Error, fmt.Sprintf(`converting safe content failed: %s`, err.Error())) + } span.AddEvent(tracingEventHttpRequest, trace.WithAttributes( attribute.String(tracingEventHttpRequestUrl, r.URL.String()), attribute.String(tracingEventHttpRequestHeaders, gconv.String(httputil.HeaderToMap(r.Header))), attribute.String(tracingEventHttpRequestBaggage, gtrace.GetBaggageMap(ctx).String()), - attribute.String(tracingEventHttpRequestBody, gstr.StrLimitRune( - string(reqBodyContentBytes), - gtrace.MaxContentLogSize(), - "...", - )), + attribute.String(tracingEventHttpRequestBody, reqBodyContent), )) // Continue executing. @@ -116,19 +112,11 @@ func internalMiddlewareServerTracing(r *Request) { if err = r.GetError(); err != nil { span.SetStatus(codes.Error, fmt.Sprintf(`%+v`, err)) } + // Response content logging. - var resBodyContent = gstr.StrLimitRune(r.Response.BufferString(), gtrace.MaxContentLogSize(), "...") - if gzipAccepted(r.Response.Header()) { - reader, err := gzip.NewReader(strings.NewReader(r.Response.BufferString())) - if err != nil { - span.SetStatus(codes.Error, fmt.Sprintf(`read gzip response err:%+v`, err)) - } - defer reader.Close() - uncompressed, err := io.ReadAll(reader) - if err != nil { - span.SetStatus(codes.Error, fmt.Sprintf(`get uncompress value err:%+v`, err)) - } - resBodyContent = gstr.StrLimitRune(string(uncompressed), gtrace.MaxContentLogSize(), "...") + resBodyContent, err := gtrace.SafeContentForHttp(r.Response.Buffer(), r.Response.Header()) + if err != nil { + span.SetStatus(codes.Error, fmt.Sprintf(`converting safe content failed: %s`, err.Error())) } span.AddEvent(tracingEventHttpResponse, trace.WithAttributes( @@ -136,16 +124,3 @@ func internalMiddlewareServerTracing(r *Request) { attribute.String(tracingEventHttpResponseBody, resBodyContent), )) } - -// gzipAccepted returns whether the client will accept gzip-encoded content. -func gzipAccepted(header http.Header) bool { - a := header.Get("Content-Encoding") - parts := strings.Split(a, ",") - for _, part := range parts { - part = strings.TrimSpace(part) - if part == "gzip" || strings.HasPrefix(part, "gzip;") { - return true - } - } - return false -} diff --git a/net/ghttp/ghttp_z_unit_feature_response_test.go b/net/ghttp/ghttp_z_unit_feature_response_test.go index b93fd7d11..a563ec8d0 100644 --- a/net/ghttp/ghttp_z_unit_feature_response_test.go +++ b/net/ghttp/ghttp_z_unit_feature_response_test.go @@ -8,14 +8,15 @@ package ghttp_test import ( "fmt" - "github.com/gogf/gf/v2/encoding/gxml" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/os/gview" "net/http" "strings" "testing" "time" + "github.com/gogf/gf/v2/encoding/gxml" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/test/gtest" diff --git a/net/gtrace/gtrace_content.go b/net/gtrace/gtrace_content.go new file mode 100644 index 000000000..ccb82601c --- /dev/null +++ b/net/gtrace/gtrace_content.go @@ -0,0 +1,54 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gtrace + +import ( + "net/http" + "strings" + + "github.com/gogf/gf/v2/encoding/gcompress" + + "github.com/gogf/gf/v2/text/gstr" +) + +// SafeContentForHttp cuts and returns given content by `MaxContentLogSize`. +// It appends string `...` to the tail of the result if the content size is greater than `MaxContentLogSize`. +func SafeContentForHttp(data []byte, header http.Header) (string, error) { + var err error + if gzipAccepted(header) { + if data, err = gcompress.UnGzip(data); err != nil { + return string(data), err + } + } + + return SafeContent(data), nil +} + +// SafeContent cuts and returns given content by `MaxContentLogSize`. +// It appends string `...` to the tail of the result if the content size is greater than `MaxContentLogSize`. +func SafeContent(data []byte) string { + content := string(data) + if gstr.LenRune(content) > MaxContentLogSize() { + content = gstr.StrLimitRune(content, MaxContentLogSize(), "...") + } + + return content +} + +// gzipAccepted returns whether the client will accept gzip-encoded content. +func gzipAccepted(header http.Header) bool { + a := header.Get("Content-Encoding") + parts := strings.Split(a, ",") + for _, part := range parts { + part = strings.TrimSpace(part) + if part == "gzip" || strings.HasPrefix(part, "gzip;") { + return true + } + } + + return false +} diff --git a/net/gtrace/gtrace_z_unit_test.go b/net/gtrace/gtrace_z_unit_test.go index 97a4d2ee6..700c587dc 100644 --- a/net/gtrace/gtrace_z_unit_test.go +++ b/net/gtrace/gtrace_z_unit_test.go @@ -8,8 +8,12 @@ package gtrace_test import ( "context" + "net/http" + "strings" "testing" + "github.com/gogf/gf/v2/encoding/gcompress" + "github.com/gogf/gf/v2/net/gtrace" "github.com/gogf/gf/v2/test/gtest" "github.com/gogf/gf/v2/text/gstr" @@ -53,3 +57,56 @@ func TestWithUUID(t *testing.T) { t.Assert(gtrace.GetTraceID(newCtx), gstr.Replace(uuid, "-", "")) }) } + +func TestSafeContent(t *testing.T) { + var ( + defText = "δΈ­" + shortData = strings.Repeat(defText, gtrace.MaxContentLogSize()-1) + standData = strings.Repeat(defText, gtrace.MaxContentLogSize()) + longData = strings.Repeat(defText, gtrace.MaxContentLogSize()+1) + header = http.Header{} + gzipHeader = http.Header{ + "Content-Encoding": []string{"gzip"}, + } + ) + + // safe content + gtest.C(t, func(t *gtest.T) { + + t1, err := gtrace.SafeContentForHttp([]byte(shortData), header) + t.AssertNil(err) + t.Assert(t1, shortData) + t.Assert(gtrace.SafeContent([]byte(shortData)), shortData) + + t2, err := gtrace.SafeContentForHttp([]byte(standData), header) + t.AssertNil(err) + t.Assert(t2, standData) + t.Assert(gtrace.SafeContent([]byte(standData)), standData) + + t3, err := gtrace.SafeContentForHttp([]byte(longData), header) + t.AssertNil(err) + t.Assert(t3, standData+"...") + t.Assert(gtrace.SafeContent([]byte(longData)), standData+"...") + }) + + // compress content + var ( + compressShortData, _ = gcompress.Gzip([]byte(shortData)) + compressStandData, _ = gcompress.Gzip([]byte(standData)) + compressLongData, _ = gcompress.Gzip([]byte(longData)) + ) + gtest.C(t, func(t *gtest.T) { + + t1, err := gtrace.SafeContentForHttp(compressShortData, gzipHeader) + t.AssertNil(err) + t.Assert(t1, shortData) + + t2, err := gtrace.SafeContentForHttp(compressStandData, gzipHeader) + t.AssertNil(err) + t.Assert(t2, standData) + + t3, err := gtrace.SafeContentForHttp(compressLongData, gzipHeader) + t.AssertNil(err) + t.Assert(t3, standData+"...") + }) +}