-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SLT-331] feat(omnirpc): omnirpc request tracing #3289
Changes from all commits
305dde4
275dfaa
fc96c21
81a732e
9055ff6
54eacfa
151d6cd
ad40ff4
7eb7448
fc15e54
cf9763f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,8 @@ package http | |
import ( | ||
"context" | ||
"strings" | ||
|
||
"github.com/synapsecns/sanguine/core/metrics" | ||
) | ||
|
||
// Client contains a post client for interacting with json rpc servers. | ||
|
@@ -62,14 +64,14 @@ func init() { | |
|
||
// NewClient creates a client from the client type | ||
// defaults to fast http. | ||
func NewClient(clientType ClientType) Client { | ||
func NewClient(handler metrics.Handler, clientType ClientType) Client { | ||
switch clientType { | ||
case FastHTTP: | ||
return NewFastHTTPClient() | ||
return NewFastHTTPClient(handler) | ||
case Resty: | ||
return NewRestyClient() | ||
return NewRestyClient(handler) | ||
default: | ||
return NewRestyClient() | ||
return NewRestyClient(handler) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle unknown In the
Comment on lines
+67
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Action Required: Update Several instances of
Please ensure that each 🔗 Analysis chainEnsure all call sites of The Run the following script to find all usages of 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Find all call sites of NewClient that need to be updated.
# Search for usage of 'NewClient(' in Go files
rg --type go 'NewClient\('
Length of output: 5031 Script: #!/bin/bash
# Description: Find all call sites of NewClient that do not include the handler parameter.
# Search for 'NewClient(' in Go files with fewer than expected arguments
rg --type go 'NewClient\s*\(\s*(?!handler\s*,).*?\)'
Length of output: 312 Script: #!/bin/bash
# Description: Find all call sites of NewClient that include only one argument.
# Search for 'NewClient(' in Go files with only one argument
rg --type go 'NewClient\s*\(\s*[^,()]+\s*\)'
Length of output: 990 |
||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -3,12 +3,16 @@ package http | |||||||||||||||||||||||||||||||||||||||||||||||||
import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
"context" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"fmt" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"sync" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"time" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/ImVexed/fasturl" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/puzpuzpuz/xsync" | ||||||||||||||||||||||||||||||||||||||||||||||||||
http2 "github.com/synapsecns/fasthttp-http2" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/synapsecns/sanguine/core/metrics" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"github.com/valyala/fasthttp" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"sync" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"time" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"go.opentelemetry.io/otel/attribute" | ||||||||||||||||||||||||||||||||||||||||||||||||||
"go.opentelemetry.io/otel/trace" | ||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// dialer is an allocated fasthttp dialer for increasing dns cache time. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -26,6 +30,7 @@ type fastHTTPClient struct { | |||||||||||||||||||||||||||||||||||||||||||||||||
// no longer needed. This allows Request recycling, reduces GC pressure | ||||||||||||||||||||||||||||||||||||||||||||||||||
// and usually improves performance. | ||||||||||||||||||||||||||||||||||||||||||||||||||
reqPool sync.Pool | ||||||||||||||||||||||||||||||||||||||||||||||||||
handler metrics.Handler | ||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification 'handler' Field Not Properly Initialized in 'fastHTTPClient' The
🔗 Analysis chainEnsure 'handler' field is properly initialized in 'fastHTTPClient' The addition of the Run the following script to check where 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Find all calls to NewFastHTTPClient and check initialization of 'handler'
# Search for NewFastHTTPClient usage
rg --type go 'NewFastHTTPClient\((.*)\)' -A 2 -B 2
Length of output: 875 |
||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// FastClient is an interface for storing both fasthttp.Clients and fasthttp.HostClients. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -42,16 +47,20 @@ var _ FastClient = &fasthttp.HostClient{} | |||||||||||||||||||||||||||||||||||||||||||||||||
// while substantially faster than resty, this can be a bad choice in certain cases: | ||||||||||||||||||||||||||||||||||||||||||||||||||
// - Context Cancellation not respected: fasthttp does not support context cancellation, so we hardcode a timeout here | ||||||||||||||||||||||||||||||||||||||||||||||||||
// this is less than ideal and puts additional load on both the application and rpc servers since we pessimistically fetch | ||||||||||||||||||||||||||||||||||||||||||||||||||
func NewFastHTTPClient() Client { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return &fastHTTPClient{clients: xsync.NewMapOf[FastClient](), defaultClient: &fasthttp.Client{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
NoDefaultUserAgentHeader: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
Dial: dialer.Dial, | ||||||||||||||||||||||||||||||||||||||||||||||||||
DialDualStack: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||
ReadTimeout: time.Second * 30, | ||||||||||||||||||||||||||||||||||||||||||||||||||
WriteTimeout: time.Second * 30, | ||||||||||||||||||||||||||||||||||||||||||||||||||
DisableHeaderNamesNormalizing: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
DisablePathNormalizing: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||||||||||||||||||||||||||
func NewFastHTTPClient(handler metrics.Handler) Client { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return &fastHTTPClient{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
clients: xsync.NewMapOf[FastClient](), | ||||||||||||||||||||||||||||||||||||||||||||||||||
defaultClient: &fasthttp.Client{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
NoDefaultUserAgentHeader: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
Dial: dialer.Dial, | ||||||||||||||||||||||||||||||||||||||||||||||||||
DialDualStack: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||
ReadTimeout: time.Second * 30, | ||||||||||||||||||||||||||||||||||||||||||||||||||
WriteTimeout: time.Second * 30, | ||||||||||||||||||||||||||||||||||||||||||||||||||
DisableHeaderNamesNormalizing: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
DisablePathNormalizing: true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||
handler: handler, | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+50
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add nil check for The function signature update and initialization of the Apply this diff to add a nil check: func NewFastHTTPClient(handler metrics.Handler) Client {
+ if handler == nil {
+ handler = metrics.DefaultHandler() // Replace with appropriate default handler
+ }
return &fastHTTPClient{
clients: xsync.NewMapOf[FastClient](),
defaultClient: &fasthttp.Client{
NoDefaultUserAgentHeader: true,
Dial: dialer.Dial,
DialDualStack: false,
ReadTimeout: time.Second * 30,
WriteTimeout: time.Second * 30,
DisableHeaderNamesNormalizing: true,
DisablePathNormalizing: true,
},
handler: handler,
}
}
|
||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
type rawResponse struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -135,9 +144,10 @@ func (f *fastHTTPClient) AcquireRequest() *fastHTTPRequest { | |||||||||||||||||||||||||||||||||||||||||||||||||
v := f.reqPool.Get() | ||||||||||||||||||||||||||||||||||||||||||||||||||
if v == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return &fastHTTPRequest{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
&fasthttp.Request{}, | ||||||||||||||||||||||||||||||||||||||||||||||||||
f, | ||||||||||||||||||||||||||||||||||||||||||||||||||
nil, | ||||||||||||||||||||||||||||||||||||||||||||||||||
Request: &fasthttp.Request{}, | ||||||||||||||||||||||||||||||||||||||||||||||||||
client: f, | ||||||||||||||||||||||||||||||||||||||||||||||||||
context: nil, | ||||||||||||||||||||||||||||||||||||||||||||||||||
handler: f.handler, | ||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+147
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initialize The initialization of the Consider initializing Apply this diff to initialize return &fastHTTPRequest{
Request: &fasthttp.Request{},
client: f,
- context: nil,
+ context: context.Background(),
handler: f.handler,
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
//nolint: forcetypeassert | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -158,6 +168,7 @@ type fastHTTPRequest struct { | |||||||||||||||||||||||||||||||||||||||||||||||||
// we need to respect context cancellation even after response | ||||||||||||||||||||||||||||||||||||||||||||||||||
//nolint: containedctx | ||||||||||||||||||||||||||||||||||||||||||||||||||
context context.Context | ||||||||||||||||||||||||||||||||||||||||||||||||||
handler metrics.Handler | ||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential The Suggestion: Ensure that |
||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
// Reset clears request contents. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -193,6 +204,18 @@ func (f *fastHTTPRequest) SetRequestURI(uri string) Request { | |||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
func (f *fastHTTPRequest) Do() (Response, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
_, span := f.handler.Tracer().Start( | ||||||||||||||||||||||||||||||||||||||||||||||||||
f.context, | ||||||||||||||||||||||||||||||||||||||||||||||||||
"Do", | ||||||||||||||||||||||||||||||||||||||||||||||||||
trace.WithAttributes( | ||||||||||||||||||||||||||||||||||||||||||||||||||
attribute.String("uri", f.Request.URI().String()), | ||||||||||||||||||||||||||||||||||||||||||||||||||
attribute.String("headers", fmt.Sprintf("%v", f.Request.Header.String())), | ||||||||||||||||||||||||||||||||||||||||||||||||||
attribute.String("body", string(f.Request.Body())), | ||||||||||||||||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||
defer func() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
metrics.EndSpan(span) | ||||||||||||||||||||||||||||||||||||||||||||||||||
}() | ||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+207
to
+218
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Limit or sanitize body content in span attributes The addition of tracing spans enhances observability, which is good. However, including the entire request body in the span attributes might lead to performance issues for large payloads and potential exposure of sensitive data. Consider limiting the amount of body content included in the span attributes or implement a sanitization mechanism to prevent sensitive data exposure. Apply this diff to limit the body content: _, span := f.handler.Tracer().Start(
f.context,
"Do",
trace.WithAttributes(
attribute.String("uri", f.Request.URI().String()),
attribute.String("headers", fmt.Sprintf("%v", f.Request.Header.String())),
- attribute.String("body", string(f.Request.Body())),
+ attribute.String("body_length", fmt.Sprintf("%d", len(f.Request.Body()))),
),
)
defer func() {
metrics.EndSpan(span)
}() 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
defer f.Reset() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
uri := f.Request.URI() | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,29 +2,37 @@ | |
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/go-resty/resty/v2" | ||
"github.com/synapsecns/sanguine/core/metrics" | ||
"go.opentelemetry.io/otel/attribute" | ||
"go.opentelemetry.io/otel/trace" | ||
) | ||
|
||
// RestyClient is a resty client for making requests to the http client. | ||
type RestyClient struct { | ||
client *resty.Client | ||
client *resty.Client | ||
handler metrics.Handler | ||
} | ||
|
||
// NewRestyClient creates a resty client. | ||
// while much slower than fasthttp, this client requests context cancellation. | ||
func NewRestyClient() Client { | ||
return &RestyClient{client: resty.New()} | ||
func NewRestyClient(handler metrics.Handler) Client { | ||
return &RestyClient{client: resty.New(), handler: handler} | ||
} | ||
|
||
type restyRequest struct { | ||
*resty.Request | ||
endpoint string | ||
handler metrics.Handler | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initialize 'handler' to prevent nil pointer dereference At line 28, the Consider ensuring that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential nil pointer dereference of The To prevent this, ensure that Example fix by initializing func (r RestyClient) NewRequest() Request {
return &restyRequest{
Request: r.client.R(),
+ handler: metrics.NoOpHandler(),
}
} Alternatively, add a nil check in each method before starting the trace: func (r *restyRequest) SetHeaderBytes(key, value []byte) Request {
+ if r.handler == nil {
+ return r
+ }
_, span := r.handler.Tracer().Start(
r.Request.Context(),
"SetHeaderBytes",
trace.WithAttributes(
attribute.String("key", common.Bytes2Hex(key)),
attribute.String("value", common.Bytes2Hex(value)),
))
defer func() {
metrics.EndSpan(span)
}()
r.Request.SetHeader(string(key), string(value))
return r
} Also applies to: 40-49, 55-62, 68-75, 81-90, 96-103, 108-115 |
||
} | ||
|
||
// NewRequest create a new request. | ||
func (r RestyClient) NewRequest() Request { | ||
return &restyRequest{ | ||
Request: r.client.R(), | ||
handler: r.handler, | ||
} | ||
} | ||
|
||
|
@@ -54,9 +62,24 @@ | |
return r | ||
} | ||
|
||
func (r *restyRequest) Do() (Response, error) { | ||
//nolint: wrapcheck | ||
return r.Request.Post(r.endpoint) | ||
func (r *restyRequest) Do() (_ Response, err error) { | ||
_, span := r.handler.Tracer().Start( | ||
r.Request.Context(), | ||
"Do", | ||
trace.WithAttributes( | ||
attribute.String("uri", r.endpoint), | ||
attribute.String("headers", fmt.Sprintf("%v", r.Request.Header)), | ||
attribute.String("body", r.Request.Body.(string)), | ||
), | ||
) | ||
defer func() { | ||
metrics.EndSpanWithErr(span, err) | ||
}() | ||
resp, err := r.Request.Post(r.endpoint) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not get response from %s: %w", r.endpoint, err) | ||
} | ||
return resp, nil | ||
Comment on lines
+65
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Security Issues Detected in Do Method The Issues:
Recommendations:
🔗 Analysis chainLGTM with security concerns: Do method updated with tracing and improved error handling The
Consider the following improvements:
To verify the usage of sensitive information in tracing, run the following script: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check for potential logging of sensitive information in tracing
# Test: Search for trace.WithAttributes calls and check for potential sensitive data
rg --type go -e 'trace\.WithAttributes' -A 5 | grep -E '(headers|body|uri)'
Length of output: 1915 Script: #!/bin/bash
# Description: Identify instances of trace.WithAttributes that log headers, body, or uri in Go files.
# Search for 'trace.WithAttributes' in Go files and filter for 'headers', 'body', or 'uri'
rg --type go 'trace\.WithAttributes' | grep -E '(headers|body|uri)'
Length of output: 983 |
||
} | ||
|
||
var _ Client = &RestyClient{} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -1,17 +1,25 @@ | ||||||||||
package http_test | ||||||||||
|
||||||||||
import ( | ||||||||||
"testing" | ||||||||||
|
||||||||||
"github.com/brianvoe/gofakeit/v6" | ||||||||||
"github.com/stretchr/testify/suite" | ||||||||||
"github.com/synapsecns/sanguine/core" | ||||||||||
"github.com/synapsecns/sanguine/core/config" | ||||||||||
"github.com/synapsecns/sanguine/core/metrics" | ||||||||||
"github.com/synapsecns/sanguine/core/metrics/localmetrics" | ||||||||||
"github.com/synapsecns/sanguine/core/testsuite" | ||||||||||
"github.com/synapsecns/sanguine/services/omnirpc/http" | ||||||||||
"testing" | ||||||||||
) | ||||||||||
|
||||||||||
var buildInfo = config.NewBuildInfo(config.DefaultVersion, config.DefaultCommit, "omnirpc", config.DefaultDate) | ||||||||||
|
||||||||||
// clientSuite defines the basic test suite. | ||||||||||
type HTTPSuite struct { | ||||||||||
*testsuite.TestSuite | ||||||||||
clients []http.Client | ||||||||||
metrics metrics.Handler | ||||||||||
} | ||||||||||
|
||||||||||
// NewTestSuite creates a new test suite and performs some basic checks afterward. | ||||||||||
|
@@ -27,8 +35,24 @@ func (c *HTTPSuite) SetupTest() { | |||||||||
c.TestSuite.SetupTest() | ||||||||||
|
||||||||||
for _, clientType := range http.AllClientTypes { | ||||||||||
c.clients = append(c.clients, http.NewClient(clientType)) | ||||||||||
c.clients = append(c.clients, http.NewClient(metrics.NewNullHandler(), clientType)) | ||||||||||
} | ||||||||||
Comment on lines
+38
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the initialized metrics handler when creating clients In Apply this diff to use the metrics handler stored in 39- c.clients = append(c.clients, http.NewClient(metrics.NewNullHandler(), clientType))
39+ c.clients = append(c.clients, http.NewClient(c.metrics, clientType))
40 } 📝 Committable suggestion
Suggested change
|
||||||||||
} | ||||||||||
|
||||||||||
func (c *HTTPSuite) SetupSuite() { | ||||||||||
c.TestSuite.SetupSuite() | ||||||||||
// don't use metrics on ci for integration tests | ||||||||||
isCI := core.GetEnvBool("CI", false) | ||||||||||
useMetrics := !isCI | ||||||||||
metricsHandler := metrics.Null | ||||||||||
|
||||||||||
if useMetrics { | ||||||||||
localmetrics.SetupTestJaeger(c.GetSuiteContext(), c.T()) | ||||||||||
metricsHandler = metrics.Jaeger | ||||||||||
} | ||||||||||
var err error | ||||||||||
c.metrics, err = metrics.NewByType(c.GetSuiteContext(), buildInfo, metricsHandler) | ||||||||||
c.Require().NoError(err) | ||||||||||
} | ||||||||||
|
||||||||||
func TestCommonSuite(t *testing.T) { | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enforce the association of
metrics.Handler
withCapturedRequest
.To prevent issues with uninitialized
c.Handler
, consider requiring themetrics.Handler
to be set during the creation ofCapturedRequest
instances.Modify the
NewRequest
method to accept ametrics.Handler
parameter:This ensures that every
CapturedRequest
has a validHandler
andContext
from the beginning.Also applies to: 161-164