diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..adcb878 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,34 @@ +name: Test & Lint +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] +jobs: + test: + strategy: + matrix: + go-version: [1.13.x, 1.14.x, 1.15.x, 1.16.x] + os: [ubuntu-latest, macos-latest, windows-latest] + name: tests + runs-on: ${{ matrix.os }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: Run tests + run: go test ./... + golangci: + strategy: + matrix: + go-version: [1.13.x, 1.14.x, 1.15.x, 1.16.x] + os: [ubuntu-latest] + name: lint + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..516e2f8 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,22 @@ +--- +run: + concurency: 4 + deadline: 2m + issues-exit-code: 1 + tests: false + +output: + format: colored-line-number + print-issued-lines: true + print-linter-name: true + +linters: + enable-all: true + fast: false + +issues: + exclude-use-default: false + max-issues-per-linter: 100 + max-same-issues: 4 + new: false + diff --git a/README.md b/README.md index 95eb3c9..964a30c 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,6 @@ func main() { fmt.Printf("%v", err) } -// output +// output. // bar(1, aerrors_wrap): foo(1): errors in foo ``` \ No newline at end of file diff --git a/aerror.go b/aerror.go index 2612ec1..52a85da 100644 --- a/aerror.go +++ b/aerror.go @@ -1,26 +1,22 @@ -// This package adds async errors handling support in GO. See the README for -// more details. +// Package aerrors adds async errors handling support in GO. See the README for more details. package aerrors import ( "fmt" ) -var defaultErrorChanLen = 10 - // AsyncError queues your added errors in chan and handle them by provided handler method. // It may be started and stopped and run your func in a panic-safe goroutine. type AsyncError struct { - logger Logger - add chan error - stop chan struct{} - baseError error - handler ErrorHandler - errorChanLen int - errorChan chan error + logger Logger + add chan error + stop chan struct{} + baseError error + handler ErrorHandler + errorChan chan error } -// ErrorHandler is an interface for defining error handler +// ErrorHandler is an interface for defining error handler. type ErrorHandler interface { HandleError(err error) } @@ -47,34 +43,44 @@ type ErrorHandler interface { // // See "aerrors.With*" to modify the default behavior. func New(opts ...Option) *AsyncError { + const defaultErrorChanLen = 10 + a := &AsyncError{ add: make(chan error), stop: make(chan struct{}), - logger: DefaultLogger, errorChan: make(chan error, defaultErrorChanLen), + logger: nil, + handler: nil, + baseError: nil, } + for _, opt := range opts { opt(a) } + + if a.logger == nil { + a.logger = createDefaultLogger() + } + return a } -// Add puts your error in queue to handle. It blocks if we reached chan length +// Add puts your error in queue to handle. It blocks if we reached chan length. func (e *AsyncError) Add(err error) { e.errorChan <- err } -// AddAsync puts your error in queue in goroutine. It not blocks when we reached chan length +// AddAsync puts your error in queue in goroutine. It not blocks when we reached chan length. func (e *AsyncError) AddAsync(err error) { go e.Add(err) } -// Stop handle errors +// Stop handle errors. func (e *AsyncError) Stop() { e.stop <- struct{}{} } -// StartHandle starts handling errors +// StartHandle starts handling errors. func (e *AsyncError) StartHandle() { go e.start() } @@ -87,29 +93,30 @@ func (e *AsyncError) start() { case <-e.stop: e.logger.Info("stop") + return } } } -// Wrap your error -func Wrap(errp *error, format string, args ...interface{}) { +// Wrap your error. +func Wrap(errp *error, format string, args ...interface{}) { // nolint: goprintffuncname if errp != nil && *errp != nil { s := fmt.Sprintf(format, args...) *errp = fmt.Errorf("%s: %w", s, *errp) } } -// PanicToError recovers panic and creates an error from it +// PanicToError recovers panic and creates an error from it. func (e *AsyncError) PanicToError() { if p := recover(); p != nil { - err := fmt.Errorf("%v", p) + err := fmt.Errorf("%v", p) //nolint: goerr113 Wrap(&err, "recoverToError()") e.errorChan <- err } } -// Go runs your function in panic-safe goroutine +// Go runs your function in panic-safe goroutine. func (e *AsyncError) Go(f func()) { go func() { defer e.PanicToError() diff --git a/logger.go b/logger.go index 06a8efb..fb479d6 100644 --- a/logger.go +++ b/logger.go @@ -6,9 +6,6 @@ import ( "strings" ) -// DefaultLogger is used if none is specified. -var DefaultLogger Logger = PrintfLogger(log.New(os.Stdout, "aerrors: ", log.LstdFlags)) - // Logger is the interface used in this package for logging, so that any backend // can be plugged in. It is a subset of the github.com/go-logr/logr interface. type Logger interface { @@ -18,6 +15,10 @@ type Logger interface { Error(err error, msg string, keysAndValues ...interface{}) } +// DefaultLogger is used if none is specified. +func createDefaultLogger() Logger { + return PrintfLogger(log.New(os.Stdout, "aerrors: ", log.LstdFlags)) +} // PrintfLogger wraps a Printf-based logger (such as the standard library "log") // into an implementation of the Logger interface which logs errors only. @@ -39,8 +40,10 @@ func (pl printfLogger) Info(msg string, keysAndValues ...interface{}) { } func (pl printfLogger) Error(err error, msg string, keysAndValues ...interface{}) { + const BaseLength = 2 + pl.logger.Printf( - formatString(len(keysAndValues)+2), + formatString(len(keysAndValues)+BaseLength), append([]interface{}{msg, "error", err}, keysAndValues...)...) } @@ -48,15 +51,20 @@ func (pl printfLogger) Error(err error, msg string, keysAndValues ...interface{} // key/values. func formatString(numKeysAndValues int) string { var sb strings.Builder + sb.WriteString("%s") + if numKeysAndValues > 0 { sb.WriteString(", ") } + for i := 0; i < numKeysAndValues/2; i++ { if i > 0 { sb.WriteString(", ") } + sb.WriteString("%v=%v") } + return sb.String() -} \ No newline at end of file +}