Skip to content

Commit

Permalink
enhance: cut tracing content as unicode for safety (#3342)
Browse files Browse the repository at this point in the history
  • Loading branch information
seth-shi committed Mar 13, 2024
1 parent 199737c commit a8713da
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 49 deletions.
9 changes: 9 additions & 0 deletions contrib/rpc/grpcx/internal/utils/utils.go
Expand Up @@ -9,6 +9,7 @@ package utils

import (
"fmt"
"unicode/utf8"

"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
Expand Down Expand Up @@ -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
}
12 changes: 6 additions & 6 deletions net/gclient/gclient_tracing.go
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
}
12 changes: 6 additions & 6 deletions net/gclient/gclient_tracing_tracer.go
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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),
))
}

Expand Down
43 changes: 9 additions & 34 deletions net/ghttp/ghttp_middleware_tracing.go
Expand Up @@ -7,12 +7,9 @@
package ghttp

import (
"compress/gzip"
"context"
"fmt"
"io"
"net/http"
"strings"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
Expand All @@ -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"
)

Expand Down Expand Up @@ -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.
Expand All @@ -116,36 +112,15 @@ 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(
attribute.String(tracingEventHttpResponseHeaders, gconv.String(httputil.HeaderToMap(r.Response.Header()))),
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
}
7 changes: 4 additions & 3 deletions net/ghttp/ghttp_z_unit_feature_response_test.go
Expand Up @@ -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"
Expand Down
54 changes: 54 additions & 0 deletions 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
}
57 changes: 57 additions & 0 deletions net/gtrace/gtrace_z_unit_test.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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+"...")
})
}

0 comments on commit a8713da

Please sign in to comment.