From 58a405bfa8f8c8b3cd16068139987a436a562840 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 29 Jun 2019 18:17:33 +0800 Subject: [PATCH] add package gerror; improve internal/debug,glog package --- .gitignore | 1 - g/database/gdb/gdb.go | 9 ++- g/errors/gerror/gerror.go | 135 +++++++++++++++++++++++++++++++++ g/errors/gerror/gerror_test.go | 39 ++++++++++ g/internal/debug/stack.go | 117 ++++++++++++++++++++++------ g/os/glog/glog_logger.go | 33 +------- g/os/gtime/gtime_time.go | 10 +++ g/util/gutil/gutil_debug.go | 8 +- geg/os/glog/glog_error.go | 11 +-- geg/os/glog/glog_line2.go | 2 +- geg/other/test.go | 60 ++++++++++++++- geg/other/test2.go | 34 +-------- go.mod | 2 + go.sum | 2 + 14 files changed, 356 insertions(+), 107 deletions(-) create mode 100644 g/errors/gerror/gerror.go create mode 100644 g/errors/gerror/gerror_test.go create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 8470fac8f..8ea5a3d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,4 @@ bin/ cbuild **/.DS_Store .vscode/ -go.sum diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index d4c14ce8c..4ea743d86 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -14,13 +14,14 @@ import ( "database/sql" "errors" "fmt" + "time" + "github.com/gogf/gf/g/container/gmap" "github.com/gogf/gf/g/container/gring" "github.com/gogf/gf/g/container/gtype" "github.com/gogf/gf/g/container/gvar" "github.com/gogf/gf/g/os/gcache" "github.com/gogf/gf/g/util/grand" - "time" ) // 数据库操作接口 @@ -86,9 +87,9 @@ type DB interface { GetQueriedSqls() []*Sql GetLastSql() *Sql PrintQueriedSqls() - SetMaxIdleConns(n int) - SetMaxOpenConns(n int) - SetConnMaxLifetime(n int) + SetMaxIdleConnCount(n int) + SetMaxOpenConnCount(n int) + SetMaxConnLifetime(n int) // 内部方法接口 getCache() *gcache.Cache diff --git a/g/errors/gerror/gerror.go b/g/errors/gerror/gerror.go new file mode 100644 index 000000000..048d1e2fa --- /dev/null +++ b/g/errors/gerror/gerror.go @@ -0,0 +1,135 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// Package errors provides simple functions to manipulate errors. +package gerror + +import ( + "bytes" + "fmt" + "runtime" + "strings" + + "github.com/gogf/gf/g/util/gconv" + "github.com/pkg/errors" +) + +type stacker interface { + StackTrace() errors.StackTrace +} + +type causer interface { + Cause() error +} + +const ( + gFILTER_KEY = "/g/errors/gerror/gerror.go" +) + +var ( + // goRootForFilter is used for stack filtering purpose. + goRootForFilter = runtime.GOROOT() +) + +func init() { + if goRootForFilter != "" { + goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1) + } +} + +// New returns an error that formats as the given value. +func New(value interface{}) error { + if value == nil { + return nil + } + return NewText(gconv.String(value)) +} + +// NewText returns an error that formats as the given text. +func NewText(text string) error { + if text == "" { + return nil + } + return errors.New(text) +} + +// Wrap wraps error with text. +func Wrap(err error, text string) error { + if err == nil { + return nil + } + return errors.Wrap(err, text) +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is called, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + return errors.Wrapf(err, format, args...) +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + return errors.Cause(err) +} + +// Stack returns the stack callers as string. +func Stack(err error) string { + if err == nil { + return "" + } + if _, ok := err.(causer); !ok { + return "" + } + index := 1 + buffer := bytes.NewBuffer(nil) + for err != nil { + cause, ok := err.(causer) + if !ok { + if err, ok := err.(stacker); ok { + buffer.WriteString(fmt.Sprintf("%d.\t%s\n", index, err)) + index++ + formatSubStack(err, buffer) + } + break + } + if err, ok := err.(stacker); ok { + buffer.WriteString(fmt.Sprintf("%d.\t%s\n", index, err)) + index++ + formatSubStack(err, buffer) + } + err = cause.Cause() + } + return buffer.String() +} + +// formatSubStack formats the stack for error. +func formatSubStack(err stacker, buffer *bytes.Buffer) { + index := 1 + for _, f := range err.StackTrace() { + if fn := runtime.FuncForPC(uintptr(f) - 1); fn != nil { + file, line := fn.FileLine(uintptr(f) - 1) + if strings.Contains(file, gFILTER_KEY) { + continue + } + if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter { + continue + } + buffer.WriteString(fmt.Sprintf("\t%d).\t%s\n\t\t%s:%d\n", index, fn.Name(), file, line)) + index++ + } + } +} diff --git a/g/errors/gerror/gerror_test.go b/g/errors/gerror/gerror_test.go new file mode 100644 index 000000000..737744d58 --- /dev/null +++ b/g/errors/gerror/gerror_test.go @@ -0,0 +1,39 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gerror_test + +import ( + "testing" + + "github.com/gogf/gf/g/internal/errors" + "github.com/gogf/gf/g/test/gtest" +) + +func interfaceNil() interface{} { + return nil +} + +func nilError() error { + return nil +} + +func Test_Nil(t *testing.T) { + gtest.Case(t, func() { + gtest.Assert(errors.New(interfaceNil()), nil) + gtest.Assert(errors.Wrap(nilError(), "test"), nil) + }) +} + +func Test_Wrap(t *testing.T) { + gtest.Case(t, func() { + err := errors.New("1") + err = errors.Wrap(err, "func2 error") + err = errors.Wrap(err, "func3 error") + gtest.AssertNE(err, nil) + gtest.Assert(err.Error(), "func3 error: func2 error: 1") + }) +} diff --git a/g/internal/debug/stack.go b/g/internal/debug/stack.go index 2bb9600c5..77567c82b 100644 --- a/g/internal/debug/stack.go +++ b/g/internal/debug/stack.go @@ -1,6 +1,8 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. // Package debug contains facilities for programs to debug themselves while // they are running. @@ -10,41 +12,106 @@ import ( "bytes" "fmt" "runtime" - "strconv" + "strings" ) +const ( + gMAX_DEPTH = 1000 + gFILTER_KEY = "/g/internal/debug/stack.go" +) + +var ( + // goRootForFilter is used for stack filtering purpose. + goRootForFilter = runtime.GOROOT() +) + +func init() { + if goRootForFilter != "" { + goRootForFilter = strings.Replace(goRootForFilter, "\\", "/", -1) + } +} + // PrintStack prints to standard error the stack trace returned by runtime.Stack. func PrintStack(skip ...int) { - fmt.Print(string(Stack(skip...))) + fmt.Print(Stack(skip...)) } // Stack returns a formatted stack trace of the goroutine that calls it. // It calls runtime.Stack with a large enough buffer to capture the entire trace. -func Stack(skip ...int) []byte { - buffer := make([]byte, 512) +func Stack(skip ...int) string { + return StackWithFilter("", skip...) +} + +// StackWithFilter returns a formatted stack trace of the goroutine that calls it. +// It calls runtime.Stack with a large enough buffer to capture the entire trace. +// +// The parameter is used to filter the path of the caller. +func StackWithFilter(filter string, skip ...int) string { number := 0 if len(skip) > 0 { number = skip[0] } - for { - n := runtime.Stack(buffer, false) - if n < len(buffer) { - lines := bytes.Split(buffer[:n], []byte{'\n'}) - index := 1 - stacks := bytes.NewBuffer(nil) - for i, line := range lines { - if i < 5+number*2 || len(line) == 0 { - continue - } - if i%2 != 0 { - stacks.WriteString(strconv.Itoa(index) + ".\t") - index++ - } - stacks.Write(line) - stacks.WriteByte('\n') + name := "" + index := 1 + buffer := bytes.NewBuffer(nil) + for i := callerFromIndex() + number; i < gMAX_DEPTH; i++ { + if pc, file, line, ok := runtime.Caller(i); ok { + if filter != "" && strings.Contains(file, filter) { + continue } - return stacks.Bytes() + if goRootForFilter != "" && len(file) >= len(goRootForFilter) && file[0:len(goRootForFilter)] == goRootForFilter { + continue + } + if fn := runtime.FuncForPC(pc); fn == nil { + name = "unknown" + } else { + name = fn.Name() + } + buffer.WriteString(fmt.Sprintf("%d.\t%s\n\t%s:%d\n", index, name, file, line)) + index++ + } else { + break } - buffer = make([]byte, 2*len(buffer)) } + return buffer.String() +} + +// CallerPath returns the absolute file path along with its line number of the caller. +func Caller(skip ...int) string { + return CallerWithFilter("", skip...) +} + +// CallerPathWithFilter returns the absolute file path along with its line number of the caller. +// +// The parameter is used to filter the path of the caller. +func CallerWithFilter(filter string, skip ...int) string { + number := 0 + if len(skip) > 0 { + number = skip[0] + } + for i := callerFromIndex() + number; i < gMAX_DEPTH; i++ { + if _, file, line, ok := runtime.Caller(i); ok { + if filter != "" && strings.Contains(file, filter) { + continue + } + return fmt.Sprintf(`%s:%d`, file, line) + } else { + break + } + } + return "" +} + +// callerFromIndex returns the caller position exclusive of the debug package. +func callerFromIndex() int { + for i := 0; i < gMAX_DEPTH; i++ { + if _, file, _, ok := runtime.Caller(i); ok { + if strings.Contains(file, gFILTER_KEY) { + continue + } + // exclude the depth from the function of current package. + return i - 1 + } + } + return 0 } diff --git a/g/os/glog/glog_logger.go b/g/os/glog/glog_logger.go index 788451af7..84cca92a4 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -44,6 +44,7 @@ const ( gDEFAULT_FILE_POOL_FLAGS = os.O_CREATE | os.O_WRONLY | os.O_APPEND gDEFAULT_FPOOL_PERM = os.FileMode(0666) gDEFAULT_FPOOL_EXPIRE = 60000 + gPATH_FILTER_KEY = "/g/os/glog/glog" ) const ( @@ -256,10 +257,10 @@ func (l *Logger) print(std io.Writer, lead string, value ...interface{}) { // Caller path. callerPath := "" if l.flags&F_FILE_LONG > 0 { - callerPath = l.getLongFile() + ": " + callerPath = debug.CallerWithFilter(gPATH_FILTER_KEY) + ": " } if l.flags&F_FILE_SHORT > 0 { - callerPath = gfile.Basename(l.getLongFile()) + ": " + callerPath = gfile.Basename(debug.CallerWithFilter(gPATH_FILTER_KEY)) + ": " } if len(callerPath) > 0 { buffer.WriteString(callerPath) @@ -345,31 +346,5 @@ func (l *Logger) GetStack(skip ...int) string { if len(skip) > 0 { number = skip[0] + 1 } - return string(debug.Stack(number)) -} - -// getLongFile returns the absolute file path along with its line number of the caller. -func (l *Logger) getLongFile() string { - from := 0 - // Find the caller position exclusive of the glog file. - for i := 0; i < 1000; i++ { - if _, file, _, ok := runtime.Caller(i); ok { - if !gregex.IsMatchString("/os/glog/glog.+$", file) { - from = i - break - } - } - } - // Find the true caller file path using custom skip. - goRoot := runtime.GOROOT() - for i := from + l.stSkip; i < 1000; i++ { - if _, file, line, ok := runtime.Caller(i); ok && len(file) > 2 { - if (goRoot == "" || !gregex.IsMatchString("^"+goRoot, file)) && !gregex.IsMatchString(``, file) { - return fmt.Sprintf(`%s:%d`, file, line) - } - } else { - break - } - } - return "" + return debug.StackWithFilter(gPATH_FILTER_KEY, number) } diff --git a/g/os/gtime/gtime_time.go b/g/os/gtime/gtime_time.go index 368d515a6..675d3d88a 100644 --- a/g/os/gtime/gtime_time.go +++ b/g/os/gtime/gtime_time.go @@ -115,6 +115,16 @@ func (t *Time) Add(d time.Duration) *Time { return t } +// 当前时间加上指定时间段(使用字符串格式) +func (t *Time) AddStr(duration string) error { + if d, err := time.ParseDuration(duration); err != nil { + return err + } else { + t.Time = t.Time.Add(d) + } + return nil +} + // 时区转换为指定的时区(通过time.Location) func (t *Time) ToLocation(location *time.Location) *Time { t.Time = t.Time.In(location) diff --git a/g/util/gutil/gutil_debug.go b/g/util/gutil/gutil_debug.go index 97cf4c864..a9ba7c9f5 100644 --- a/g/util/gutil/gutil_debug.go +++ b/g/util/gutil/gutil_debug.go @@ -10,12 +10,6 @@ import ( "github.com/gogf/gf/g/internal/debug" ) -// PrintStack is alias for PrintStack. -// Deprecated. -func PrintStack() { - PrintStack() -} - // PrintStack prints to standard error the stack trace returned by runtime.Stack. func PrintStack(skip ...int) { number := 1 @@ -27,7 +21,7 @@ func PrintStack(skip ...int) { // Stack returns a formatted stack trace of the goroutine that calls it. // It calls runtime.Stack with a large enough buffer to capture the entire trace. -func Stack(skip ...int) []byte { +func Stack(skip ...int) string { number := 1 if len(skip) > 0 { number = skip[0] + 1 diff --git a/geg/os/glog/glog_error.go b/geg/os/glog/glog_error.go index 18738726c..b4eb1cfb5 100644 --- a/geg/os/glog/glog_error.go +++ b/geg/os/glog/glog_error.go @@ -1,11 +1,12 @@ package main -import ( - "github.com/gogf/gf/g/os/glog" -) +import "github.com/gogf/gf/g/os/glog" -func main() { - //glog.SetPath("/tmp/") +func Test() { glog.Error("This is error!") glog.Errorf("This is error, %d!", 2) } + +func main() { + Test() +} diff --git a/geg/os/glog/glog_line2.go b/geg/os/glog/glog_line2.go index 2e3730114..ade091755 100644 --- a/geg/os/glog/glog_line2.go +++ b/geg/os/glog/glog_line2.go @@ -6,7 +6,7 @@ import ( func PrintLog(content string) { glog.Skip(1).Line().Println("line number with skip:", content) - glog.Line().Println("line number without skip:", content) + glog.Line(true).Println("line number without skip:", content) } func main() { diff --git a/geg/other/test.go b/geg/other/test.go index 602c93c2a..535dbae55 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,13 +1,65 @@ package main import ( - "github.com/gogf/gf/g/util/gutil" + "fmt" + + "github.com/pkg/errors" + + "github.com/gogf/gf/g/errors/gerror" ) -func Test(s interface{}) { - gutil.PrintStack() +func Test1() error { + return gerror.New("test") +} + +func Test2() error { + return gerror.Wrap(Test1(), "error test1") +} + +type stackTracer interface { + StackTrace() errors.StackTrace } func main() { - Test(nil) + err := Test2() + + //fmt.Printf("%+v", err) + //fmt.Println("==============") + fmt.Println(gerror.Stack(err)) + return + + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + fmt.Println("ERROR:", err) + if err, ok := err.(stackTracer); ok { + for _, f := range err.StackTrace() { + fmt.Printf("%+s:%d\n", f, f) + } + } + break + } + fmt.Println("ERROR:", err) + if err, ok := err.(stackTracer); ok { + for _, f := range err.StackTrace() { + fmt.Printf("%+s:%d\n", f, f) + } + } + fmt.Println() + err = cause.Cause() + } + + //err, ok := Test2().(stackTracer) + //if !ok { + // panic("oops, err does not implement stackTracer") + //} + // + //st := err.StackTrace() + //fmt.Printf("%+v\n", st) + //fmt.Println() + //fmt.Printf("%+v\n", Test2()) } diff --git a/geg/other/test2.go b/geg/other/test2.go index 8623d32ca..76168a175 100644 --- a/geg/other/test2.go +++ b/geg/other/test2.go @@ -1,39 +1,11 @@ package main -import ( - "github.com/gogf/gf/g" - "github.com/gogf/gf/g/net/ghttp" -) +import "github.com/gogf/gf/g/os/glog" -type Schedule struct{} +func Test() { -type Task struct{} - -func (c *Schedule) ListDir(r *ghttp.Request) { - r.Response.Writeln("ListDir") -} - -func (c *Task) Add(r *ghttp.Request) { - r.Response.Writeln("Add") -} - -func (c *Task) Task(r *ghttp.Request) { - r.Response.Writeln("Task") -} - -// 实现权限校验 -// 通过事件回调,类似于中间件机制,但是可控制的粒度更细,可以精准注册到路由规则 -func AuthHookHandler(r *ghttp.Request) { - // 如果权限校验失败,调用 r.ExitAll() 退出执行流程 } func main() { - s := g.Server() - s.Group("/schedule").Bind([]ghttp.GroupItem{ - {"ALL", "*", AuthHookHandler, ghttp.HOOK_BEFORE_SERVE}, - {"POST", "/schedule", new(Schedule)}, - {"POST", "/task", new(Task)}, - }) - s.SetPort(8199) - s.Run() + glog.Line().Println("123") } diff --git a/go.mod b/go.mod index ef37cb8d6..a283f6e2b 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,3 @@ module github.com/gogf/gf + +require github.com/pkg/errors v0.8.1 diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..f29ab350a --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=