Skip to content

Commit f6f0da4

Browse files
authored
polish code in rules (#171)
* sync databasesql * sync other plugin
1 parent 378efdb commit f6f0da4

File tree

78 files changed

+1077
-413
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+1077
-413
lines changed

docs/how-to-write-tests-for-plugins.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ represents for the `get` redis operation. You should use the `verifier` to verif
4949
import "github.com/alibaba/opentelemetry-go-auto-instrumentation/test/verifier"
5050

5151
verifier.WaitAndAssertTraces(func (stubs []tracetest.SpanStubs) {
52-
verifier.VerifyDbAttributes(stubs[0][0], "set", "", "redis", "", "localhost", "set a b ex 5: ", "set")
53-
verifier.VerifyDbAttributes(stubs[1][0], "get", "", "redis", "", "localhost", "get a: ", "get")
52+
verifier.VerifyDbAttributes(stubs[0][0], "set", "", "redis", "", "localhost", "set a b ex 5 ", "set")
53+
verifier.VerifyDbAttributes(stubs[1][0], "get", "", "redis", "", "localhost", "get a ", "get")
5454
})
5555
```
5656

pkg/inst-api-semconv/instrumenter/db/db_client_extractor.go

+56-28
Original file line numberDiff line numberDiff line change
@@ -16,59 +16,87 @@ package db
1616

1717
import (
1818
"context"
19+
"fmt"
20+
"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/instrumenter"
1921
"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/utils"
2022
"go.opentelemetry.io/otel/attribute"
21-
semconv "go.opentelemetry.io/otel/semconv/v1.19.0"
23+
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
24+
"strconv"
2225
)
2326

27+
type DbExperimentalAttributesEnabler interface {
28+
Enable() bool
29+
}
30+
31+
type defaultDbExperimentalAttributesEnabler struct {
32+
}
33+
34+
func (d defaultDbExperimentalAttributesEnabler) Enable() bool {
35+
return false
36+
}
37+
38+
var experimentalAttributesEnabler = instrumenter.NewDefaultInstrumentEnabler()
39+
2440
type DbClientCommonAttrsExtractor[REQUEST any, RESPONSE any, GETTER DbClientCommonAttrsGetter[REQUEST]] struct {
25-
Getter GETTER
41+
Getter GETTER
42+
AttributesFilter func(attrs []attribute.KeyValue) []attribute.KeyValue
2643
}
2744

2845
func (d *DbClientCommonAttrsExtractor[REQUEST, RESPONSE, GETTER]) GetSpanKey() attribute.Key {
2946
return utils.DB_CLIENT_KEY
3047
}
3148

32-
func (d *DbClientCommonAttrsExtractor[REQUEST, RESPONSE, GETTER]) OnStart(attributes []attribute.KeyValue, parentContext context.Context, request REQUEST) []attribute.KeyValue {
33-
attributes = append(attributes, attribute.KeyValue{
34-
Key: semconv.DBNameKey,
35-
Value: attribute.StringValue(d.Getter.GetName(request)),
36-
}, attribute.KeyValue{
49+
func (d *DbClientCommonAttrsExtractor[REQUEST, RESPONSE, GETTER]) OnStart(attributes []attribute.KeyValue, parentContext context.Context, request REQUEST) ([]attribute.KeyValue, context.Context) {
50+
return attributes, parentContext
51+
}
52+
53+
func (d *DbClientCommonAttrsExtractor[REQUEST, RESPONSE, GETTER]) OnEnd(attrs []attribute.KeyValue, context context.Context, request REQUEST, response RESPONSE, err error) ([]attribute.KeyValue, context.Context) {
54+
attrs = append(attrs, attribute.KeyValue{
3755
Key: semconv.DBSystemKey,
3856
Value: attribute.StringValue(d.Getter.GetSystem(request)),
39-
}, attribute.KeyValue{
40-
Key: semconv.DBUserKey,
41-
Value: attribute.StringValue(d.Getter.GetUser(request)),
42-
}, attribute.KeyValue{
43-
Key: semconv.DBConnectionStringKey,
44-
Value: attribute.StringValue(d.Getter.GetConnectionString(request)),
4557
})
46-
return attributes
47-
}
48-
49-
func (d *DbClientCommonAttrsExtractor[REQUEST, RESPONSE, GETTER]) OnEnd(attrs []attribute.KeyValue, context context.Context, request REQUEST, response RESPONSE, err error) []attribute.KeyValue {
50-
return attrs
58+
if d.AttributesFilter != nil {
59+
attrs = d.AttributesFilter(attrs)
60+
}
61+
return attrs, context
5162
}
5263

5364
type DbClientAttrsExtractor[REQUEST any, RESPONSE any, GETTER DbClientAttrsGetter[REQUEST]] struct {
5465
Base DbClientCommonAttrsExtractor[REQUEST, RESPONSE, GETTER]
5566
}
5667

57-
func (d *DbClientAttrsExtractor[REQUEST, RESPONSE, GETTER]) OnStart(attrs []attribute.KeyValue, parentContext context.Context, request REQUEST) []attribute.KeyValue {
58-
attrs = d.Base.OnStart(attrs, parentContext, request)
68+
func (d *DbClientAttrsExtractor[REQUEST, RESPONSE, GETTER]) OnStart(attrs []attribute.KeyValue, parentContext context.Context, request REQUEST) ([]attribute.KeyValue, context.Context) {
69+
attrs, parentContext = d.Base.OnStart(attrs, parentContext, request)
70+
if d.Base.AttributesFilter != nil {
71+
attrs = d.Base.AttributesFilter(attrs)
72+
}
73+
return attrs, parentContext
74+
}
75+
76+
func (d *DbClientAttrsExtractor[REQUEST, RESPONSE, GETTER]) OnEnd(attrs []attribute.KeyValue, context context.Context, request REQUEST, response RESPONSE, err error) ([]attribute.KeyValue, context.Context) {
77+
attrs, context = d.Base.OnEnd(attrs, context, request, response, err)
5978
attrs = append(attrs, attribute.KeyValue{
60-
Key: semconv.DBStatementKey,
79+
Key: semconv.DBQueryTextKey,
6180
Value: attribute.StringValue(d.Base.Getter.GetStatement(request)),
6281
}, attribute.KeyValue{
63-
Key: semconv.DBOperationKey,
82+
Key: semconv.DBOperationNameKey,
6483
Value: attribute.StringValue(d.Base.Getter.GetOperation(request)),
84+
}, attribute.KeyValue{
85+
Key: semconv.ServerAddressKey,
86+
Value: attribute.StringValue(d.Base.Getter.GetServerAddress(request)),
6587
})
66-
return attrs
67-
}
68-
69-
func (d *DbClientAttrsExtractor[REQUEST, RESPONSE, GETTER]) OnEnd(attrs []attribute.KeyValue, context context.Context, request REQUEST, response RESPONSE, err error) []attribute.KeyValue {
70-
attrs = d.Base.OnEnd(attrs, context, request, response, err)
71-
return attrs
88+
if d.Base.AttributesFilter != nil {
89+
attrs = d.Base.AttributesFilter(attrs)
90+
}
91+
if experimentalAttributesEnabler.Enable() {
92+
params := d.Base.Getter.GetParameters(request)
93+
if params != nil && len(params) > 0 {
94+
for i, param := range params {
95+
attrs = append(attrs, attribute.String("db.query.parameter."+strconv.Itoa(i), fmt.Sprintf("%v", param)))
96+
}
97+
}
98+
}
99+
return attrs, context
72100
}
73101

74102
func (d *DbClientAttrsExtractor[REQUEST, RESPONSE, GETTER]) GetSpanKey() attribute.Key {

pkg/inst-api-semconv/instrumenter/db/db_client_extractor_test.go

+35-25
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
"context"
1919
"github.com/alibaba/opentelemetry-go-auto-instrumentation/pkg/inst-api/utils"
2020
"go.opentelemetry.io/otel/attribute"
21-
semconv "go.opentelemetry.io/otel/semconv/v1.19.0"
21+
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
2222
"log"
2323
"testing"
2424
)
@@ -35,21 +35,13 @@ type mongoAttrsGetter struct {
3535
}
3636

3737
func (m mongoAttrsGetter) GetSystem(request testRequest) string {
38-
return "test"
39-
}
40-
41-
func (m mongoAttrsGetter) GetUser(request testRequest) string {
42-
return "test"
43-
}
44-
45-
func (m mongoAttrsGetter) GetName(request testRequest) string {
4638
if request.Name != "" {
4739
return request.Name
4840
}
4941
return ""
5042
}
5143

52-
func (m mongoAttrsGetter) GetConnectionString(request testRequest) string {
44+
func (m mongoAttrsGetter) GetServerAddress(request testRequest) string {
5345
return "test"
5446
}
5547

@@ -64,6 +56,10 @@ func (m mongoAttrsGetter) GetOperation(request testRequest) string {
6456
return ""
6557
}
6658

59+
func (m mongoAttrsGetter) GetParameters(request testRequest) []any {
60+
return nil
61+
}
62+
6763
func TestGetSpanKey(t *testing.T) {
6864
dbExtractor := &DbClientAttrsExtractor[testRequest, any, mongoAttrsGetter]{}
6965
if dbExtractor.GetSpanKey() != utils.DB_CLIENT_KEY {
@@ -82,33 +78,47 @@ func TestDbClientExtractorStart(t *testing.T) {
8278
dbExtractor := DbClientAttrsExtractor[testRequest, testResponse, mongoAttrsGetter]{}
8379
attrs := make([]attribute.KeyValue, 0)
8480
parentContext := context.Background()
85-
attrs = dbExtractor.OnStart(attrs, parentContext, testRequest{Name: "test"})
86-
if attrs[0].Key != semconv.DBNameKey || attrs[0].Value.AsString() != "test" {
87-
t.Fatalf("db name should be test")
81+
attrs, _ = dbExtractor.OnStart(attrs, parentContext, testRequest{Name: "test"})
82+
if len(attrs) != 0 {
83+
log.Fatal("attrs should be empty")
8884
}
89-
if attrs[1].Key != semconv.DBSystemKey || attrs[1].Value.AsString() != "test" {
85+
}
86+
87+
func TestDbClientExtractorEnd(t *testing.T) {
88+
dbExtractor := DbClientAttrsExtractor[testRequest, testResponse, mongoAttrsGetter]{}
89+
attrs := make([]attribute.KeyValue, 0)
90+
parentContext := context.Background()
91+
attrs, _ = dbExtractor.OnEnd(attrs, parentContext, testRequest{Name: "test"}, testResponse{}, nil)
92+
if attrs[0].Key != semconv.DBSystemKey || attrs[0].Value.AsString() != "test" {
9093
t.Fatalf("db system should be test")
9194
}
92-
if attrs[2].Key != semconv.DBUserKey || attrs[2].Value.AsString() != "test" {
95+
if attrs[1].Key != semconv.DBQueryTextKey || attrs[1].Value.AsString() != "test" {
9396
t.Fatalf("db user should be test")
9497
}
95-
if attrs[3].Key != semconv.DBConnectionStringKey || attrs[3].Value.AsString() != "test" {
96-
t.Fatalf("db connection key should be test")
98+
if attrs[2].Key != semconv.DBOperationNameKey || attrs[2].Value.AsString() != "" {
99+
t.Fatalf("db connection key should be empty")
97100
}
98-
if attrs[4].Key != semconv.DBStatementKey || attrs[4].Value.AsString() != "test" {
101+
if attrs[3].Key != semconv.ServerAddressKey || attrs[3].Value.AsString() != "test" {
99102
t.Fatalf("db statement key should be test")
100103
}
101-
if attrs[5].Key != semconv.DBOperationKey || attrs[5].Value.AsString() != "" {
102-
t.Fatalf("db operation key should be empty")
103-
}
104104
}
105105

106-
func TestDbClientExtractorEnd(t *testing.T) {
106+
func TestDbClientExtractorWithFilter(t *testing.T) {
107107
dbExtractor := DbClientAttrsExtractor[testRequest, testResponse, mongoAttrsGetter]{}
108+
dbExtractor.Base.AttributesFilter = func(attrs []attribute.KeyValue) []attribute.KeyValue {
109+
return []attribute.KeyValue{{
110+
Key: "test",
111+
Value: attribute.StringValue("test"),
112+
}}
113+
}
108114
attrs := make([]attribute.KeyValue, 0)
109115
parentContext := context.Background()
110-
attrs = dbExtractor.OnEnd(attrs, parentContext, testRequest{Name: "test"}, testResponse{}, nil)
111-
if len(attrs) != 0 {
112-
log.Fatal("attrs should be empty")
116+
attrs, _ = dbExtractor.OnStart(attrs, parentContext, testRequest{Name: "test"})
117+
if attrs[0].Key != "test" || attrs[0].Value.AsString() != "test" {
118+
panic("attribute should be test")
119+
}
120+
attrs, _ = dbExtractor.OnEnd(attrs, parentContext, testRequest{Name: "test"}, testResponse{}, nil)
121+
if attrs[0].Key != "test" || attrs[0].Value.AsString() != "test" {
122+
panic("attribute should be test")
113123
}
114124
}

pkg/inst-api-semconv/instrumenter/db/db_client_getter.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@ package db
1616

1717
type DbClientCommonAttrsGetter[REQUEST any] interface {
1818
GetSystem(REQUEST) string
19-
GetUser(REQUEST) string
20-
GetName(REQUEST) string
21-
GetConnectionString(REQUEST) string
19+
GetServerAddress(REQUEST) string
2220
}
2321

2422
type DbClientAttrsGetter[REQUEST any] interface {
2523
DbClientCommonAttrsGetter[REQUEST]
2624
GetStatement(REQUEST) string
2725
GetOperation(REQUEST) string
26+
GetParameters(REQUEST) []any
2827
}
2928

3029
type SqlClientAttributesGetter[REQUEST any] interface {

pkg/inst-api-semconv/instrumenter/db/db_span_name_extractor.go

+1-6
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,9 @@ type DBSpanNameExtractor[REQUEST any] struct {
1919
}
2020

2121
func (d *DBSpanNameExtractor[REQUEST]) Extract(request REQUEST) string {
22-
dbName := d.Getter.GetName(request)
2322
operation := d.Getter.GetOperation(request)
2423
if operation == "" {
25-
if dbName == "" {
26-
return "DB Query"
27-
} else {
28-
return dbName
29-
}
24+
return "DB Query"
3025
}
3126
return operation
3227
}

pkg/inst-api-semconv/instrumenter/db/db_span_name_extractor_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ func TestDbNameExtractor(t *testing.T) {
2121
Getter: mongoAttrsGetter{},
2222
}
2323
if dbSpanNameExtractor.Extract(testRequest{}) != "DB Query" {
24-
t.Fatalf("Should have returned DB_QUERY")
24+
t.Fatalf("Should have returned DB Query")
2525
}
26-
if dbSpanNameExtractor.Extract(testRequest{Name: "test"}) != "test" {
26+
if dbSpanNameExtractor.Extract(testRequest{Name: "test", Operation: "test"}) != "test" {
2727
t.Fatalf("Should have returned test")
2828
}
2929
if dbSpanNameExtractor.Extract(testRequest{Operation: "op_test"}) != "op_test" {

pkg/inst-api-semconv/instrumenter/http/http_attrs_extractor.go

+36-23
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,20 @@ import (
2424
// TODO: http.route
2525

2626
type HttpCommonAttrsExtractor[REQUEST any, RESPONSE any, GETTER1 HttpCommonAttrsGetter[REQUEST, RESPONSE], GETTER2 net.NetworkAttrsGetter[REQUEST, RESPONSE]] struct {
27-
HttpGetter GETTER1
28-
NetGetter GETTER2
27+
HttpGetter GETTER1
28+
NetGetter GETTER2
29+
AttributesFilter func(attrs []attribute.KeyValue) []attribute.KeyValue
2930
}
3031

31-
func (h *HttpCommonAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2]) OnStart(attributes []attribute.KeyValue, parentContext context.Context, request REQUEST) []attribute.KeyValue {
32+
func (h *HttpCommonAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2]) OnStart(attributes []attribute.KeyValue, parentContext context.Context, request REQUEST) ([]attribute.KeyValue, context.Context) {
3233
attributes = append(attributes, attribute.KeyValue{
3334
Key: semconv.HTTPRequestMethodKey,
3435
Value: attribute.StringValue(h.HttpGetter.GetRequestMethod(request)),
3536
})
36-
return attributes
37+
return attributes, parentContext
3738
}
3839

39-
func (h *HttpCommonAttrsExtractor[REQUEST, RESPONSE, GETTER, GETTER2]) OnEnd(attributes []attribute.KeyValue, context context.Context, request REQUEST, response RESPONSE, err error) []attribute.KeyValue {
40+
func (h *HttpCommonAttrsExtractor[REQUEST, RESPONSE, GETTER, GETTER2]) OnEnd(attributes []attribute.KeyValue, context context.Context, request REQUEST, response RESPONSE, err error) ([]attribute.KeyValue, context.Context) {
4041
statusCode := h.HttpGetter.GetHttpResponseStatusCode(request, response, err)
4142
protocolName := h.NetGetter.GetNetworkProtocolName(request, response)
4243
protocolVersion := h.NetGetter.GetNetworkProtocolVersion(request, response)
@@ -50,17 +51,17 @@ func (h *HttpCommonAttrsExtractor[REQUEST, RESPONSE, GETTER, GETTER2]) OnEnd(att
5051
Key: semconv.NetworkProtocolVersionKey,
5152
Value: attribute.StringValue(protocolVersion),
5253
})
53-
return attributes
54+
return attributes, context
5455
}
5556

5657
type HttpClientAttrsExtractor[REQUEST any, RESPONSE any, GETTER1 HttpClientAttrsGetter[REQUEST, RESPONSE], GETTER2 net.NetworkAttrsGetter[REQUEST, RESPONSE]] struct {
5758
Base HttpCommonAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2]
5859
NetworkExtractor net.NetworkAttrsExtractor[REQUEST, RESPONSE, GETTER2]
5960
}
6061

61-
func (h *HttpClientAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2]) OnStart(attributes []attribute.KeyValue, parentContext context.Context, request REQUEST) []attribute.KeyValue {
62-
attributes = h.Base.OnStart(attributes, parentContext, request)
63-
attributes = h.NetworkExtractor.OnStart(attributes, parentContext, request)
62+
func (h *HttpClientAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2]) OnStart(attributes []attribute.KeyValue, parentContext context.Context, request REQUEST) ([]attribute.KeyValue, context.Context) {
63+
attributes, parentContext = h.Base.OnStart(attributes, parentContext, request)
64+
attributes, parentContext = h.NetworkExtractor.OnStart(attributes, parentContext, request)
6465
fullUrl := h.Base.HttpGetter.GetUrlFull(request)
6566
// TODO: add resend count
6667
attributes = append(attributes, attribute.KeyValue{
@@ -73,13 +74,19 @@ func (h *HttpClientAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2]) OnStart(
7374
Key: semconv.ServerPortKey,
7475
Value: attribute.IntValue(h.Base.HttpGetter.GetServerPort(request)),
7576
})
76-
return attributes
77+
if h.Base.AttributesFilter != nil {
78+
attributes = h.Base.AttributesFilter(attributes)
79+
}
80+
return attributes, parentContext
7781
}
7882

79-
func (h *HttpClientAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2]) OnEnd(attributes []attribute.KeyValue, context context.Context, request REQUEST, response RESPONSE, err error) []attribute.KeyValue {
80-
attributes = h.Base.OnEnd(attributes, context, request, response, err)
81-
attributes = h.NetworkExtractor.OnEnd(attributes, context, request, response, err)
82-
return attributes
83+
func (h *HttpClientAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2]) OnEnd(attributes []attribute.KeyValue, context context.Context, request REQUEST, response RESPONSE, err error) ([]attribute.KeyValue, context.Context) {
84+
attributes, context = h.Base.OnEnd(attributes, context, request, response, err)
85+
attributes, context = h.NetworkExtractor.OnEnd(attributes, context, request, response, err)
86+
if h.Base.AttributesFilter != nil {
87+
attributes = h.Base.AttributesFilter(attributes)
88+
}
89+
return attributes, context
8390
}
8491

8592
type HttpServerAttrsExtractor[REQUEST any, RESPONSE any, GETTER1 HttpServerAttrsGetter[REQUEST, RESPONSE], GETTER2 net.NetworkAttrsGetter[REQUEST, RESPONSE], GETTER3 net.UrlAttrsGetter[REQUEST]] struct {
@@ -88,9 +95,9 @@ type HttpServerAttrsExtractor[REQUEST any, RESPONSE any, GETTER1 HttpServerAttrs
8895
UrlExtractor net.UrlAttrsExtractor[REQUEST, RESPONSE, GETTER3]
8996
}
9097

91-
func (h *HttpServerAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2, GETTER3]) OnStart(attributes []attribute.KeyValue, parentContext context.Context, request REQUEST) []attribute.KeyValue {
92-
attributes = h.Base.OnStart(attributes, parentContext, request)
93-
attributes = h.UrlExtractor.OnStart(attributes, parentContext, request)
98+
func (h *HttpServerAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2, GETTER3]) OnStart(attributes []attribute.KeyValue, parentContext context.Context, request REQUEST) ([]attribute.KeyValue, context.Context) {
99+
attributes, parentContext = h.Base.OnStart(attributes, parentContext, request)
100+
attributes, parentContext = h.UrlExtractor.OnStart(attributes, parentContext, request)
94101
userAgent := h.Base.HttpGetter.GetHttpRequestHeader(request, "User-Agent")
95102
var firstUserAgent string
96103
if len(userAgent) > 0 {
@@ -104,12 +111,18 @@ func (h *HttpServerAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2, GETTER3])
104111
Key: semconv.UserAgentOriginalKey,
105112
Value: attribute.StringValue(firstUserAgent),
106113
})
107-
return attributes
114+
if h.Base.AttributesFilter != nil {
115+
attributes = h.Base.AttributesFilter(attributes)
116+
}
117+
return attributes, parentContext
108118
}
109119

110-
func (h *HttpServerAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2, GETTER3]) OnEnd(attributes []attribute.KeyValue, context context.Context, request REQUEST, response RESPONSE, err error) []attribute.KeyValue {
111-
attributes = h.Base.OnEnd(attributes, context, request, response, err)
112-
attributes = h.UrlExtractor.OnEnd(attributes, context, request, response, err)
113-
attributes = h.NetworkExtractor.OnEnd(attributes, context, request, response, err)
114-
return attributes
120+
func (h *HttpServerAttrsExtractor[REQUEST, RESPONSE, GETTER1, GETTER2, GETTER3]) OnEnd(attributes []attribute.KeyValue, context context.Context, request REQUEST, response RESPONSE, err error) ([]attribute.KeyValue, context.Context) {
121+
attributes, context = h.Base.OnEnd(attributes, context, request, response, err)
122+
attributes, context = h.UrlExtractor.OnEnd(attributes, context, request, response, err)
123+
attributes, context = h.NetworkExtractor.OnEnd(attributes, context, request, response, err)
124+
if h.Base.AttributesFilter != nil {
125+
attributes = h.Base.AttributesFilter(attributes)
126+
}
127+
return attributes, context
115128
}

0 commit comments

Comments
 (0)