Skip to content

Commit 877b74f

Browse files
asmyasnikovkprokopenkoCopilot
authored
* Added sugar.PrintErrorWithoutStack and sugar.UnwrapError helpers (#1818)
* added helper for clear stacktrace errors * moved PrintErrorWithoutStack from root to sugar * sugar.UnwrapError * Update sugar/errors.go * Update sugar/errors.go * fix linter issue * fix * fix * added at keyword * Update sugar/errors_test.go * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Konstantin Prokopenko <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 3510af8 commit 877b74f

File tree

4 files changed

+292
-1
lines changed

4 files changed

+292
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
* Added `sugar.PrintErrorWithoutStack` helper for remove stack records from error string
2+
* Added `sugar.UnwrapError` helper for unwrap source error to root errors
3+
14
## v3.110.1
25
* Added the ability to send BulkRequest exceeding the GrpcMaxMessageSize
36

internal/xerrors/stacktrace.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
77
)
88

9+
const KeywordAt = "at"
10+
911
type withStackTraceOptions struct {
1012
skipDepth int
1113
}
@@ -51,7 +53,7 @@ type stackError struct {
5153
}
5254

5355
func (e *stackError) Error() string {
54-
return e.err.Error() + " at `" + e.stackRecord + "`"
56+
return e.err.Error() + " " + KeywordAt + " `" + e.stackRecord + "`"
5557
}
5658

5759
func (e *stackError) Unwrap() error {

sugar/errors.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package sugar
2+
3+
import (
4+
"regexp"
5+
6+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
7+
)
8+
9+
var re = regexp.MustCompile("\\s+" + xerrors.KeywordAt + "\\s+`[^`]+`")
10+
11+
func removeStackRecords(s string) string {
12+
// Some error constructors (such as fmt.Errorf, grpcStatus.Error) are serializing the error string at the
13+
// construction time. Thats why the "true way" with casting the wrapped error into *xerrors.stackError
14+
// has no effect
15+
return re.ReplaceAllString(s, "")
16+
}
17+
18+
// PrintErrorWithoutStack removes stacktrace records from error string
19+
func PrintErrorWithoutStack(err error) string {
20+
return removeStackRecords(err.Error())
21+
}
22+
23+
// UnwrapError unwraps the source error into its root errors
24+
func UnwrapError(err error) (errs []error) {
25+
if err == nil {
26+
return nil
27+
}
28+
if x, has := err.(interface {
29+
Unwrap() error
30+
}); has {
31+
return UnwrapError(x.Unwrap())
32+
} else if x, has := err.(interface {
33+
Unwrap() []error
34+
}); has {
35+
for _, xx := range x.Unwrap() {
36+
errs = append(errs, UnwrapError(xx)...)
37+
}
38+
39+
return errs
40+
} else if len(errs) == 0 {
41+
return []error{err}
42+
}
43+
44+
return errs
45+
}

sugar/errors_test.go

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package sugar
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
"github.com/ydb-platform/ydb-go-genproto/protos/Ydb"
10+
"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Issue"
11+
grpcCodes "google.golang.org/grpc/codes"
12+
grpcStatus "google.golang.org/grpc/status"
13+
14+
"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
15+
)
16+
17+
func TestPrintWithoutStack(t *testing.T) {
18+
for _, tt := range []struct {
19+
in error
20+
out string
21+
}{
22+
{
23+
in: errors.New("test"),
24+
out: "test",
25+
},
26+
{
27+
in: xerrors.WithStackTrace(errors.New("test")),
28+
out: "test",
29+
},
30+
{
31+
in: xerrors.WithStackTrace(grpcStatus.Error(grpcCodes.Aborted, "test")),
32+
out: "rpc error: code = Aborted desc = test",
33+
},
34+
{
35+
in: fmt.Errorf("test1: %w",
36+
xerrors.WithStackTrace(grpcStatus.Error(grpcCodes.Aborted, "test2")),
37+
),
38+
out: "test1: rpc error: code = Aborted desc = test2",
39+
},
40+
{
41+
in: fmt.Errorf("test1: %w",
42+
xerrors.WithStackTrace(errors.New("test2")),
43+
),
44+
out: "test1: test2",
45+
},
46+
{
47+
in: fmt.Errorf("test1: %w",
48+
xerrors.WithStackTrace(
49+
fmt.Errorf("test2: %w",
50+
xerrors.WithStackTrace(
51+
errors.New("test3"),
52+
),
53+
),
54+
),
55+
),
56+
out: "test1: test2: test3",
57+
},
58+
} {
59+
t.Run(tt.in.Error(), func(t *testing.T) {
60+
require.Equal(t, tt.out, PrintErrorWithoutStack(tt.in))
61+
})
62+
}
63+
}
64+
65+
func TestRemoveStackRecords(t *testing.T) {
66+
require.Equal(t,
67+
"pool.With failed with 33 attempts: [\"attempt No.33: context deadline exceeded\",\"query: operation/UNAVAILABLE (code = 400050, address = localhost:2135, issues = [{'Failed to resolve tablet: 72075186234645162 after several retries'}])\",\"query: operation/UNAVAILABLE (code = 400050, address = localhost:2135, issues = [{'Failed to resolve tablet: 72075186234644725 after several retries'}])\"]", //nolint:lll
68+
removeStackRecords("pool.With failed with 33 attempts: [\"attempt No.33: context deadline exceeded\",\"query: operation/UNAVAILABLE (code = 400050, address = localhost:2135, issues = [{'Failed to resolve tablet: 72075186234645162 after several retries'}]) at `github.com/ydb-platform/ydb-go-sdk/v3/internal/conn.(*grpcClientStream).RecvMsg(grpc_client_stream.go:180)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.nextPart(result.go:221)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*streamResult).nextPart(result.go:203)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.newResult(result.go:166)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.execute(execute_query.go:138)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Session).execute(session.go:149)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Session).Query(session.go:197)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.do.func1(client.go:214)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).try(pool.go:465)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).With.func2(pool.go:493)` at `github.com/ydb-platform/ydb-go-sdk/v3/retry.Retry.func1(retry.go:264)` at `github.com/ydb-platform/ydb-go-sdk/v3/retry.opWithRecover(retry.go:418)`\",\"query: operation/UNAVAILABLE (code = 400050, address = localhost:2135, issues = [{'Failed to resolve tablet: 72075186234644725 after several retries'}]) at `github.com/ydb-platform/ydb-go-sdk/v3/internal/conn.(*grpcClientStream).RecvMsg(grpc_client_stream.go:180)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.nextPart(result.go:221)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*streamResult).nextPart(result.go:203)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.newResult(result.go:166)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.execute(execute_query.go:138)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Session).execute(session.go:149)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.(*Session).Query(session.go:197)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.do.func1(client.go:214)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).try(pool.go:465)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).With.func2(pool.go:493)` at `github.com/ydb-platform/ydb-go-sdk/v3/retry.Retry.func1(retry.go:264)` at `github.com/ydb-platform/ydb-go-sdk/v3/retry.opWithRecover(retry.go:418)`\"] at `github.com/ydb-platform/ydb-go-sdk/v3/retry.RetryWithResult(retry.go:374)` at `github.com/ydb-platform/ydb-go-sdk/v3/retry.Retry(retry.go:270)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).With(pool.go:499)` at `github.com/ydb-platform/ydb-go-sdk/v3/internal/query.do(client.go:222)`"), //nolint:lll
69+
)
70+
}
71+
72+
func TestUnwrapError(t *testing.T) {
73+
for _, tt := range []struct {
74+
err error
75+
errs []error
76+
}{
77+
{
78+
err: errors.New("test1"),
79+
errs: []error{errors.New("test1")},
80+
},
81+
{
82+
err: fmt.Errorf("%w", errors.New("test2")),
83+
errs: []error{errors.New("test2")},
84+
},
85+
{
86+
err: xerrors.Join(
87+
errors.New("test3_1"),
88+
errors.New("test3_2"),
89+
),
90+
errs: []error{
91+
errors.New("test3_1"),
92+
errors.New("test3_2"),
93+
},
94+
},
95+
{
96+
err: xerrors.Join(
97+
fmt.Errorf("%w", errors.New("test4_1")),
98+
fmt.Errorf("%w", errors.New("test4_2")),
99+
),
100+
errs: []error{
101+
errors.New("test4_1"),
102+
errors.New("test4_2"),
103+
},
104+
},
105+
{
106+
err: xerrors.WithStackTrace(fmt.Errorf("%w", errors.New("test5"))),
107+
errs: []error{errors.New("test5")},
108+
},
109+
{
110+
err: xerrors.Join(
111+
xerrors.WithStackTrace(fmt.Errorf("%w", errors.New("test6_1"))),
112+
xerrors.WithStackTrace(fmt.Errorf("%w", errors.New("test6_2"))),
113+
),
114+
errs: []error{
115+
errors.New("test6_1"),
116+
errors.New("test6_2"),
117+
},
118+
},
119+
{
120+
err: xerrors.WithStackTrace(
121+
xerrors.Join(
122+
xerrors.WithStackTrace(fmt.Errorf("%w", errors.New("test7_1"))),
123+
xerrors.WithStackTrace(fmt.Errorf("%w", errors.New("test7_2"))),
124+
),
125+
),
126+
errs: []error{
127+
errors.New("test7_1"),
128+
errors.New("test7_2"),
129+
},
130+
},
131+
{
132+
err: xerrors.WithStackTrace(
133+
xerrors.WithStackTrace(
134+
xerrors.Join(
135+
xerrors.WithStackTrace(fmt.Errorf("%w", errors.New("test8_1"))),
136+
xerrors.WithStackTrace(fmt.Errorf("%w", errors.New("test8_2"))),
137+
),
138+
),
139+
),
140+
errs: []error{
141+
errors.New("test8_1"),
142+
errors.New("test8_2"),
143+
},
144+
},
145+
{
146+
err: xerrors.WithStackTrace(
147+
xerrors.WithStackTrace(
148+
xerrors.Join(
149+
xerrors.WithStackTrace(fmt.Errorf("%w",
150+
xerrors.WithStackTrace(
151+
errors.New("test9_1"),
152+
),
153+
)),
154+
xerrors.WithStackTrace(fmt.Errorf("%w", errors.New("test9_2"))),
155+
),
156+
),
157+
),
158+
errs: []error{
159+
errors.New("test9_1"),
160+
errors.New("test9_2"),
161+
},
162+
},
163+
{
164+
err: xerrors.WithStackTrace(
165+
xerrors.WithStackTrace(
166+
xerrors.Join(
167+
xerrors.WithStackTrace(fmt.Errorf("%w",
168+
xerrors.WithStackTrace(
169+
grpcStatus.Error(grpcCodes.Aborted, "test10_1"),
170+
),
171+
)),
172+
xerrors.WithStackTrace(fmt.Errorf("%w", errors.New("test10_2"))),
173+
),
174+
),
175+
),
176+
errs: []error{
177+
grpcStatus.Error(grpcCodes.Aborted, "test10_1"),
178+
errors.New("test10_2"),
179+
},
180+
},
181+
{
182+
err: xerrors.WithStackTrace(
183+
xerrors.WithStackTrace(
184+
xerrors.Join(
185+
xerrors.WithStackTrace(fmt.Errorf("%w",
186+
xerrors.WithStackTrace(
187+
xerrors.Transport(
188+
grpcStatus.Error(grpcCodes.Aborted, "test11_1"),
189+
),
190+
),
191+
)),
192+
xerrors.WithStackTrace(fmt.Errorf("%w", errors.New("test11_2"))),
193+
),
194+
),
195+
),
196+
errs: []error{
197+
grpcStatus.Error(grpcCodes.Aborted, "test11_1"),
198+
errors.New("test11_2"),
199+
},
200+
},
201+
{
202+
err: xerrors.WithStackTrace(
203+
xerrors.WithStackTrace(
204+
xerrors.Join(
205+
xerrors.WithStackTrace(fmt.Errorf("%w",
206+
xerrors.WithStackTrace(
207+
xerrors.Operation(
208+
xerrors.WithStatusCode(Ydb.StatusIds_BAD_REQUEST),
209+
xerrors.WithIssues([]*Ydb_Issue.IssueMessage{
210+
{
211+
Message: "test12_1",
212+
},
213+
}),
214+
),
215+
),
216+
)),
217+
xerrors.WithStackTrace(fmt.Errorf("%w", errors.New("test12_2"))),
218+
),
219+
),
220+
),
221+
errs: []error{
222+
xerrors.Operation(
223+
xerrors.WithStatusCode(Ydb.StatusIds_BAD_REQUEST),
224+
xerrors.WithIssues([]*Ydb_Issue.IssueMessage{
225+
{
226+
Message: "test12_1",
227+
},
228+
}),
229+
),
230+
errors.New("test12_2"),
231+
},
232+
},
233+
} {
234+
t.Run(tt.err.Error(), func(t *testing.T) {
235+
require.Equal(t,
236+
fmt.Sprintf("%+v", tt.errs),
237+
fmt.Sprintf("%+v", UnwrapError(tt.err)),
238+
)
239+
})
240+
}
241+
}

0 commit comments

Comments
 (0)