-
Notifications
You must be signed in to change notification settings - Fork 245
feat(tracing): Add Store, P2P and Config tracing #2972
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
base: main
Are you sure you want to change the base?
Changes from all commits
78b54f2
ae87a59
fdd943b
74d4a5f
d796589
d52f187
b2b3218
41bce54
fd34073
fd7425a
f30a577
570509b
caa0684
c154f23
607f4a3
c5d7c41
80e2b17
07a45b6
423bb15
3e373ce
ed217d7
17eb5aa
931d2ac
ee2d158
8d7fc84
32db6c8
a3fa329
776a2ea
d3bdb1e
4d1d3ff
ed675a8
b1a827e
30edc0c
1ff13f3
9d726af
db319d2
2edad3e
b1aaa84
3ab312c
fecea22
d63f852
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 |
|---|---|---|
| @@ -0,0 +1,270 @@ | ||
| package server | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/hex" | ||
|
|
||
| "connectrpc.com/connect" | ||
| "go.opentelemetry.io/otel" | ||
| "go.opentelemetry.io/otel/attribute" | ||
| "go.opentelemetry.io/otel/codes" | ||
| "go.opentelemetry.io/otel/trace" | ||
| "google.golang.org/protobuf/types/known/emptypb" | ||
|
|
||
| pb "github.com/evstack/ev-node/types/pb/evnode/v1" | ||
| "github.com/evstack/ev-node/types/pb/evnode/v1/v1connect" | ||
| ) | ||
|
|
||
| // tracedStoreServer decorates a StoreServiceHandler with OpenTelemetry spans. | ||
| type tracedStoreServer struct { | ||
| inner v1connect.StoreServiceHandler | ||
| tracer trace.Tracer | ||
| } | ||
|
|
||
| // WithTracingStoreServer decorates the provided store service handler with tracing spans. | ||
| func WithTracingStoreServer(inner v1connect.StoreServiceHandler) v1connect.StoreServiceHandler { | ||
| return &tracedStoreServer{ | ||
| inner: inner, | ||
| tracer: otel.Tracer("ev-node/store-service"), | ||
| } | ||
| } | ||
|
|
||
| func (t *tracedStoreServer) GetBlock( | ||
| ctx context.Context, | ||
| req *connect.Request[pb.GetBlockRequest], | ||
| ) (*connect.Response[pb.GetBlockResponse], error) { | ||
| var attrs []attribute.KeyValue | ||
| switch identifier := req.Msg.Identifier.(type) { | ||
| case *pb.GetBlockRequest_Height: | ||
| attrs = append(attrs, attribute.Int64("height", int64(identifier.Height))) | ||
| case *pb.GetBlockRequest_Hash: | ||
| if identifier.Hash != nil { | ||
| attrs = append(attrs, attribute.String("hash", hex.EncodeToString(identifier.Hash))) | ||
| } | ||
| } | ||
|
|
||
| ctx, span := t.tracer.Start(ctx, "StoreService.GetBlock", trace.WithAttributes(attrs...)) | ||
| defer span.End() | ||
|
|
||
| res, err := t.inner.GetBlock(ctx, req) | ||
| if err != nil { | ||
| span.RecordError(err) | ||
| span.SetStatus(codes.Error, err.Error()) | ||
| return nil, err | ||
| } | ||
|
|
||
| span.SetAttributes( | ||
| attribute.Bool("found", res.Msg.Block != nil), | ||
| ) | ||
| if res.Msg.Block != nil && res.Msg.Block.Data != nil { | ||
| totalSize := 0 | ||
| for _, tx := range res.Msg.Block.Data.Txs { | ||
| totalSize += len(tx) | ||
| } | ||
| span.SetAttributes( | ||
| attribute.Int("block_size_bytes", totalSize), | ||
| attribute.Int("tx_count", len(res.Msg.Block.Data.Txs)), | ||
| ) | ||
| } | ||
| return res, nil | ||
| } | ||
|
|
||
| func (t *tracedStoreServer) GetState( | ||
| ctx context.Context, | ||
| req *connect.Request[emptypb.Empty], | ||
| ) (*connect.Response[pb.GetStateResponse], error) { | ||
| ctx, span := t.tracer.Start(ctx, "StoreService.GetState") | ||
| defer span.End() | ||
|
|
||
| res, err := t.inner.GetState(ctx, req) | ||
| if err != nil { | ||
| span.RecordError(err) | ||
| span.SetStatus(codes.Error, err.Error()) | ||
| return nil, err | ||
| } | ||
|
Comment on lines
+79
to
+84
Contributor
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. This block of code for handling errors from the inner service call and updating the span is repeated in almost every traced method in this file. To improve maintainability and reduce boilerplate, consider extracting this logic into a generic helper function. For example, you could create a function that takes the context, request, the inner function call, and returns the response and error, handling the span creation and error recording internally. Here's a conceptual example: func traceUnary[Req, Res any](
tracer trace.Tracer,
ctx context.Context,
spanName string,
req *connect.Request[Req],
call func(context.Context, *connect.Request[Req]) (*connect.Response[Res], error),
// ... other params for attributes
) (*connect.Response[Res], error) {
ctx, span := tracer.Start(ctx, spanName)
defer span.End()
res, err := call(ctx, req)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}
// ... set success attributes
return res, nil
}Applying this pattern would make the tracing decorators much more concise. |
||
|
|
||
| if res.Msg.State != nil { | ||
| span.SetAttributes( | ||
| attribute.Int64("height", int64(res.Msg.State.LastBlockHeight)), | ||
| attribute.String("app_hash", hex.EncodeToString(res.Msg.State.AppHash)), | ||
| attribute.Int64("da_height", int64(res.Msg.State.DaHeight)), | ||
| ) | ||
| } | ||
| return res, nil | ||
| } | ||
|
|
||
| func (t *tracedStoreServer) GetMetadata( | ||
| ctx context.Context, | ||
| req *connect.Request[pb.GetMetadataRequest], | ||
| ) (*connect.Response[pb.GetMetadataResponse], error) { | ||
| ctx, span := t.tracer.Start(ctx, "StoreService.GetMetadata", | ||
| trace.WithAttributes( | ||
| attribute.String("key", req.Msg.Key), | ||
| ), | ||
| ) | ||
| defer span.End() | ||
|
|
||
| res, err := t.inner.GetMetadata(ctx, req) | ||
| if err != nil { | ||
| span.RecordError(err) | ||
| span.SetStatus(codes.Error, err.Error()) | ||
| return nil, err | ||
| } | ||
|
|
||
| span.SetAttributes( | ||
| attribute.Int("value_size_bytes", len(res.Msg.Value)), | ||
| ) | ||
| return res, nil | ||
| } | ||
|
|
||
| func (t *tracedStoreServer) GetGenesisDaHeight( | ||
| ctx context.Context, | ||
| req *connect.Request[emptypb.Empty], | ||
| ) (*connect.Response[pb.GetGenesisDaHeightResponse], error) { | ||
| ctx, span := t.tracer.Start(ctx, "StoreService.GetGenesisDaHeight") | ||
| defer span.End() | ||
|
|
||
| res, err := t.inner.GetGenesisDaHeight(ctx, req) | ||
| if err != nil { | ||
| span.RecordError(err) | ||
| span.SetStatus(codes.Error, err.Error()) | ||
| return nil, err | ||
| } | ||
|
|
||
| span.SetAttributes( | ||
| attribute.Int64("genesis_da_height", int64(res.Msg.Height)), | ||
| ) | ||
| return res, nil | ||
| } | ||
|
|
||
| func (t *tracedStoreServer) GetP2PStoreInfo( | ||
| ctx context.Context, | ||
| req *connect.Request[emptypb.Empty], | ||
| ) (*connect.Response[pb.GetP2PStoreInfoResponse], error) { | ||
| ctx, span := t.tracer.Start(ctx, "StoreService.GetP2PStoreInfo") | ||
| defer span.End() | ||
|
|
||
| res, err := t.inner.GetP2PStoreInfo(ctx, req) | ||
| if err != nil { | ||
| span.RecordError(err) | ||
| span.SetStatus(codes.Error, err.Error()) | ||
| return nil, err | ||
| } | ||
|
|
||
| span.SetAttributes( | ||
| attribute.Int("store_count", len(res.Msg.Stores)), | ||
| ) | ||
| return res, nil | ||
| } | ||
|
|
||
| // tracedP2PServer decorates a P2PServiceHandler with OpenTelemetry spans. | ||
| type tracedP2PServer struct { | ||
| inner v1connect.P2PServiceHandler | ||
| tracer trace.Tracer | ||
| } | ||
|
|
||
| // WithTracingP2PServer decorates the provided P2P service handler with tracing spans. | ||
| func WithTracingP2PServer(inner v1connect.P2PServiceHandler) v1connect.P2PServiceHandler { | ||
| return &tracedP2PServer{ | ||
| inner: inner, | ||
| tracer: otel.Tracer("ev-node/p2p-service"), | ||
| } | ||
| } | ||
|
|
||
| func (t *tracedP2PServer) GetPeerInfo( | ||
| ctx context.Context, | ||
| req *connect.Request[emptypb.Empty], | ||
| ) (*connect.Response[pb.GetPeerInfoResponse], error) { | ||
| ctx, span := t.tracer.Start(ctx, "P2PService.GetPeerInfo") | ||
| defer span.End() | ||
|
|
||
| res, err := t.inner.GetPeerInfo(ctx, req) | ||
| if err != nil { | ||
| span.RecordError(err) | ||
| span.SetStatus(codes.Error, err.Error()) | ||
| return nil, err | ||
| } | ||
|
|
||
| span.SetAttributes( | ||
| attribute.Int("peer_count", len(res.Msg.Peers)), | ||
| ) | ||
| return res, nil | ||
| } | ||
|
|
||
| func (t *tracedP2PServer) GetNetInfo( | ||
| ctx context.Context, | ||
| req *connect.Request[emptypb.Empty], | ||
| ) (*connect.Response[pb.GetNetInfoResponse], error) { | ||
| ctx, span := t.tracer.Start(ctx, "P2PService.GetNetInfo") | ||
| defer span.End() | ||
|
|
||
| res, err := t.inner.GetNetInfo(ctx, req) | ||
| if err != nil { | ||
| span.RecordError(err) | ||
| span.SetStatus(codes.Error, err.Error()) | ||
| return nil, err | ||
| } | ||
|
|
||
| if res.Msg.NetInfo != nil { | ||
| span.SetAttributes( | ||
| attribute.String("node_id", res.Msg.NetInfo.Id), | ||
| attribute.Int("listen_address_count", len(res.Msg.NetInfo.ListenAddresses)), | ||
| ) | ||
| } | ||
| return res, nil | ||
| } | ||
|
|
||
| // tracedConfigServer decorates a ConfigServiceHandler with OpenTelemetry spans. | ||
| type tracedConfigServer struct { | ||
| inner v1connect.ConfigServiceHandler | ||
| tracer trace.Tracer | ||
| } | ||
|
|
||
| // WithTracingConfigServer decorates the provided config service handler with tracing spans. | ||
| func WithTracingConfigServer(inner v1connect.ConfigServiceHandler) v1connect.ConfigServiceHandler { | ||
| return &tracedConfigServer{ | ||
| inner: inner, | ||
| tracer: otel.Tracer("ev-node/config-service"), | ||
| } | ||
| } | ||
|
|
||
| func (t *tracedConfigServer) GetNamespace( | ||
| ctx context.Context, | ||
| req *connect.Request[emptypb.Empty], | ||
| ) (*connect.Response[pb.GetNamespaceResponse], error) { | ||
| ctx, span := t.tracer.Start(ctx, "ConfigService.GetNamespace") | ||
| defer span.End() | ||
|
|
||
| res, err := t.inner.GetNamespace(ctx, req) | ||
| if err != nil { | ||
| span.RecordError(err) | ||
| span.SetStatus(codes.Error, err.Error()) | ||
| return nil, err | ||
| } | ||
|
|
||
| span.SetAttributes( | ||
| attribute.String("header_namespace", res.Msg.HeaderNamespace), | ||
| attribute.String("data_namespace", res.Msg.DataNamespace), | ||
| ) | ||
| return res, nil | ||
| } | ||
|
|
||
| func (t *tracedConfigServer) GetSignerInfo( | ||
| ctx context.Context, | ||
| req *connect.Request[emptypb.Empty], | ||
| ) (*connect.Response[pb.GetSignerInfoResponse], error) { | ||
| ctx, span := t.tracer.Start(ctx, "ConfigService.GetSignerInfo") | ||
| defer span.End() | ||
|
|
||
| res, err := t.inner.GetSignerInfo(ctx, req) | ||
| if err != nil { | ||
| span.RecordError(err) | ||
| span.SetStatus(codes.Error, err.Error()) | ||
| return nil, err | ||
| } | ||
|
|
||
| span.SetAttributes( | ||
| attribute.String("signer_address", hex.EncodeToString(res.Msg.Address)), | ||
| ) | ||
| return res, nil | ||
| } | ||
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.
to delete