Skip to content

Commit

Permalink
init commit
Browse files Browse the repository at this point in the history
  • Loading branch information
viatoriche committed Sep 23, 2024
0 parents commit 30050ab
Show file tree
Hide file tree
Showing 8 changed files with 1,701 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Stack trace implementation for Go
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/acronis/go-stacktrace

go 1.22.6
Empty file added go.sum
Empty file.
123 changes: 123 additions & 0 deletions sprint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package stacktrace

import "fmt"

const (
DefaultMessageDelimiter = " * "
DefaultTraceDelimiter = "\n\n"
DefaultStackDelimiter = "\n |_ "
DefaultEnsureDuplicates = false
)

type SprintOpt interface {
Apply(o *SprintOptions)
}

type SprintOptions struct {
// MessageDelimiter is a delimiter between message and stack trace
MessageDelimiter string
// TraceDelimiter is a delimiter between stack traces
TraceDelimiter string
// StackDelimiter is a delimiter between stack trace elements
StackDelimiter string
// EnsureDuplicates ensures that duplicates are not printed
EnsureDuplicates bool
dups map[string]struct{}
}

func NewSprintOptions() *SprintOptions {
opts := &SprintOptions{
EnsureDuplicates: DefaultEnsureDuplicates,
dups: make(map[string]struct{}),
TraceDelimiter: DefaultTraceDelimiter,
MessageDelimiter: DefaultMessageDelimiter,
StackDelimiter: DefaultStackDelimiter,
}
return opts
}

type messageDelimiterOpt string

func (v messageDelimiterOpt) Apply(o *SprintOptions) {
o.MessageDelimiter = string(v)
}

type traceDelimiterOpt string

func (v traceDelimiterOpt) Apply(o *SprintOptions) {
o.TraceDelimiter = string(v)
}

type stackDelimiterOpt string

func (v stackDelimiterOpt) Apply(o *SprintOptions) {
o.StackDelimiter = string(v)
}

type ensureDuplicatesOpt struct{}

func (ensureDuplicatesOpt) Apply(o *SprintOptions) {
o.EnsureDuplicates = true
}

func WithMessageDelimiter(delimiter string) SprintOpt {
return messageDelimiterOpt(delimiter)
}

func WithTraceDelimiter(delimiter string) SprintOpt {
return traceDelimiterOpt(delimiter)
}

func WithStackDelimiter(delimiter string) SprintOpt {
return stackDelimiterOpt(delimiter)
}

func WithEnsureDuplicates() SprintOpt {
return &ensureDuplicatesOpt{}
}

func (st *StackTrace) sprint(opts *SprintOptions) string {
trace := st.Header()
trace = fmt.Sprintf("%s%s%s", trace, opts.MessageDelimiter, st.FullMessageWithInfo())

listTraces := ""
for _, elem := range st.List {
elemStr := elem.sprint(opts)
if elemStr == "" {
continue
}
if listTraces != "" {
listTraces = fmt.Sprintf("%s%s%s", listTraces, opts.TraceDelimiter, elemStr)
} else {
listTraces = elemStr
}
}

if _, ok := opts.dups[trace]; ok {
return listTraces
}

if st.Wrapped != nil {
wrappedStr := st.Wrapped.sprint(opts)
if wrappedStr == "" {
return listTraces
}
trace = fmt.Sprintf("%s%s%s", trace, opts.StackDelimiter, wrappedStr)
} else if opts.EnsureDuplicates {
opts.dups[trace] = struct{}{}
}

if listTraces != "" {
trace = fmt.Sprintf("%s%s%s", trace, opts.TraceDelimiter, listTraces)
}
return trace
}

func (st *StackTrace) Sprint(opts ...SprintOpt) string {
o := NewSprintOptions()
for _, opt := range opts {
opt.Apply(o)
}
res := st.sprint(o)
return res
}
70 changes: 70 additions & 0 deletions sprint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package stacktrace

import "testing"

func TestError_Sprintf(t *testing.T) {
type fields struct {
Severity Severity
Type Type
Location string
Position *Position
Wrapped *StackTrace
Err error
Message string
WrappingMessage string
Info StructInfo
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "Test simple",
fields: fields{
Severity: SeverityError,
Type: TypeValidating,
Location: "/tmp/location.raml",
Message: "error message",
},
want: "[error] validating: /tmp/location.raml:1\n\terror message",
},
{
name: "Test with wrapped",
fields: fields{
Severity: SeverityError,
Type: TypeValidating,
Location: "/tmp/location.raml",
Position: &Position{1, 2},
Message: "error message",
WrappingMessage: "wrapping message",
Wrapped: &StackTrace{
Severity: SeverityCritical,
Type: TypeParsing,
Location: "/tmp/location2.raml",
Position: &Position{3, 4},
Message: "error message 2",
},
},
want: "[error] validating: /tmp/location.raml:1:2\n\twrapping message: error message\n[critical] parsing: /tmp/location2.raml:3:4\n\terror message 2",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := &StackTrace{
Severity: tt.fields.Severity,
Type: tt.fields.Type,
Location: tt.fields.Location,
Position: tt.fields.Position,
Wrapped: tt.fields.Wrapped,
Err: tt.fields.Err,
Message: tt.fields.Message,
WrappingMessage: tt.fields.WrappingMessage,
Info: tt.fields.Info,
}
if got := e.Sprint(); got != tt.want {
t.Errorf("Sprint() = %v, want %v", got, tt.want)
}
})
}
}
Loading

0 comments on commit 30050ab

Please sign in to comment.