errors

package module
v0.0.0-...-a867182 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 6, 2024 License: MIT Imports: 13 Imported by: 23

README

errors 包

原理

一句话:通过汇编,从调用栈中获取 pc 和 pc 列表。
性能提升的具体细节,这两篇技术文章中有详细说明:

  1. 关于 golang 错误处理的一些优化想法
  2. golang文件行号探索

此库下有两个功能模块:

  1. errors:功能和 pkg/errors 类似,性能比后者高一个数量级以上。
  2. errors/logrus 和 errors/zap:分别包装了 sirupsen/logrusgo.uber.org/zap ,目标是 100% 兼容后者。 利用了 errors 获取行号的接口,能减少 1300ns ~ 2500ns 的时间损耗,会持续优化。如有不兼容的地方,欢迎吐槽。

模拟 Go2 错误处理方式

参考 Go2 的 check与hanle关键字,实现了类是的错误处理逻辑:

func TestHandlerCheck(t *testing.T) {
	defer func() {
		fmt.Printf("1 -> ")
	}()

	handler, err := NewHandler() // 当 tag.Try(err) 时,跳转此处并返回 err1
	fmt.Printf("2 -> ")
	if err != nil {
		fmt.Printf("3 -> ")
		return
	}

	fmt.Printf("5 -> ")
	handler.Check(errors.New("err"))

	fmt.Printf("6 -> ")
	return
}

以上代码将输出:

2 -> 5 -> 2 -> 3 -> 1 ->

当然,如果使用 defer + panic 实现相关功能也可以。 不过如果忘了 defer recover 有可能会早成程序退出,而且很多公司都禁用这种方式。

性能基准测试

  1. errors 和 pkg/errors 比较

由测试结果可知,在性能上,这个 errors 库比 Go 语言官方 errors 库(不带调用栈)的性能还要好。 和 pkg/errors 已经拉开了一个数量级以上的差距。

BenchmarkNewAndFormatting

func BenchmarkNewAndFormatting(b *testing.B) {
    depths := []int{1, 10} //嵌套深度
    std, pkg, lxt := "std", "pkg", "lxt"

    stdText := func(err error) []byte {
        buf := bytes.NewBuffer(make([]byte, 0, 1024))
        for ; err != nil; err = errors.Unwrap(err) {
            buf.WriteString(err.Error())
        }
        return buf.Bytes()
    }

    runs := []struct {
        t    string          //函数名字
        name string          //函数名字
        f    func(depth int) //调用方法
    }{
        {std, "text", func(depth int) {
            err := errors.New(errMsg)
            for j := 0; j < depth; j++ {
                err = fmt.Errorf("%w; %s", err, errTrace)
            }
            stdText(err)
        }},
        {lxt, "text", func(depth int) {
            var err error = NewCode(0, errCode, errMsg)
            for j := 0; j < depth; j++ {
                err = Wrap(err, errTrace)
            }
            MarshalText(err)
        }},
        {lxt, "json", func(depth int) {
            var err error = NewCode(0, errCode, errMsg)
            for j := 0; j < depth; j++ {
                err = Wrap(err, errTrace)
            }
            MarshalJSON(err)
        }},
        {pkg, "text.%+v", func(depth int) {
            err := pkgerrs.New(errMsg)
            for j := 0; j < depth; j++ {
                err = pkgerrs.Wrap(err, errTrace)
            }
            _ = fmt.Sprintf("%+v", err)
        }},
        {pkg, "text.%v", func(depth int) {
            err := pkgerrs.New(errMsg)
            for j := 0; j < depth; j++ {
                err = pkgerrs.Wrap(err, errTrace)
            }
            _ = fmt.Sprintf("%v", err)
        }},
    }

    for _, run := range runs {
        for _, depth := range depths {
            name := fmt.Sprintf("%s.%s-%d", run.t, run.name, depth)
            b.Run(name, func(b *testing.B) {
                b.ReportAllocs()
                b.ResetTimer()
                for i := 0; i < b.N; i++ {
                    run.f(depth)
                }
                b.StopTimer()
            })
        }
    }
}


测试结果

BenchmarkNewAndFormatting/std.text-1
BenchmarkNewAndFormatting/std.text-1-12      1963789   658.9 ns/op  1088 B/op   4 allocs/op
BenchmarkNewAndFormatting/std.text-10
BenchmarkNewAndFormatting/std.text-10-12     452484    2575 ns/op  1913 B/op  22 allocs/op
BenchmarkNewAndFormatting/lxt.text-1
BenchmarkNewAndFormatting/lxt.text-1-12       2825418   429.8 ns/op   421 B/op   4 allocs/op
BenchmarkNewAndFormatting/lxt.text-10
BenchmarkNewAndFormatting/lxt.text-10-12      831126    1529 ns/op  1814 B/op  13 allocs/op
BenchmarkNewAndFormatting/lxt.json-1
BenchmarkNewAndFormatting/lxt.json-1-12       2325892   650.5 ns/op   485 B/op   4 allocs/op
BenchmarkNewAndFormatting/lxt.json-10
BenchmarkNewAndFormatting/lxt.json-10-12       570873   1912 ns/op  2071 B/op  13 allocs/op
BenchmarkNewAndFormatting/pkg.text.%+v-1
BenchmarkNewAndFormatting/pkg.text.%+v-1-12    110577   9163 ns/op  1827 B/op  28 allocs/op
BenchmarkNewAndFormatting/pkg.text.%+v-10
BenchmarkNewAndFormatting/pkg.text.%+v-10-12   24076    52849 ns/op  9980 B/op   154 allocs/op
BenchmarkNewAndFormatting/pkg.text.%v-1
BenchmarkNewAndFormatting/pkg.text.%v-1-12     534991   2099 ns/op   672 B/op   9 allocs/op
BenchmarkNewAndFormatting/pkg.text.%v-10
BenchmarkNewAndFormatting/pkg.text.%v-10-12    95394    11317 ns/op  4315 B/op  54 allocs/op
  1. errors/logrus 和 errors/zap 性能提升

errors/logrus、 errors/zap 和 errors/zerolog 分别替换了 sirupsen/logrusgo.uber.org/zaprs/zerolog 的代码行号获取逻辑。

由结果可知,能减少 500ns ~ 2500ns 的损耗,而且是兼容性升级,非常值得尝试。

BenchmarkLog

func BenchmarkLog(b *testing.B) {
	b.Run("zerolog", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		logger := zerolog.New(io.Discard)
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			logger.Info().
				Str("string", `some string format log information`).
				Int("int", 3).
				Msg("some log messages")
		}
	})
	b.Run("zerolog+lxt", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		logger := New(io.Discard)
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			logger.Info().
				Str("string", `some string format log information`).
				Int("int", 3).
				Msg("some log messages")
		}
	})
	b.Run("zerolog+caller", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		logger := zerolog.New(io.Discard)
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			logger.Info().
				Caller().
				Str("string", `some string format log information`).
				Int("int", 3).
				Msg("some log messages")
		}
	})
	b.Run("zerolog+lxt caller", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		logger := New(io.Discard)
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			logger.Info().
				Caller().
				Str("string", `some string format log information`).
				Int("int", 3).
				Msg("some log messages")
		}
	})

	b.Run("zerolog+context-caller", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		logger := zerolog.New(io.Discard)
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			log := logger.
				With().
				Caller().Logger()
			log.Info().
				Str("string", `some string format log information`).
				Int("int", 3).
				Msg("some log messages")
		}
	})
	b.Run("zerolog+lxt context-caller", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		logger := New(io.Discard)
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			log := logger.
				With().
				Caller().Logger()
			log.Info().
				Str("string", `some string format log information`).
				Int("int", 3).
				Msg("some log messages")
		}
	})

	b.Run("logrus", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		logger := logrus.New()
		logger.SetOutput(io.Discard)
		// logrus.SetReportCaller(true)
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			logger.WithFields(logrus.Fields{
				"string": "some string format log information",
				"int":    3,
			}).Info("some log messages")
		}
	})
	b.Run("logrus+caller", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		logger := logrus.New()
		logger.SetOutput(io.Discard)
		logger.SetReportCaller(true)
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			logger.WithFields(logrus.Fields{
				"string": "some string format log information",
				"int":    3,
			}).Info("some log messages")
		}
	})
	b.Run("logrus+lxt caller", func(b *testing.B) {
		// logrus.SetReportCaller(false)
		b.StopTimer()
		b.ReportAllocs()
		logger := lxtlog.New()
		logger.SetOutput(io.Discard)
		// logrus.SetReportCaller(true)
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			logger.WithFields(lxtlog.Fields{
				"string": "some string format log information",
				"int":    3,
			}).Info("some log messages")
		}
	})

	b.Run("zap", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		cfg := zap.NewProductionConfig()
		core := zapcore.NewCore(
			// zapcore.NewJSONEncoder(cfg.EncoderConfig),
			zapcore.NewConsoleEncoder(cfg.EncoderConfig),
			zapcore.AddSync(io.Discard),
			zapcore.InfoLevel,
		)
		logger := zap.New(core)
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			logger.Info("some log messages",
				zap.String("string", `some string format log information`),
				zap.Int("int", 3),
			)
		}
	})
	b.Run("zap+caller", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		cfg := zap.NewProductionConfig()
		core := zapcore.NewCore(
			// zapcore.NewJSONEncoder(cfg.EncoderConfig),
			zapcore.NewConsoleEncoder(cfg.EncoderConfig),
			zapcore.AddSync(io.Discard),
			zapcore.InfoLevel,
		)
		logger := zap.New(core, zap.WithCaller(true))
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			logger.Info("some log messages",
				zap.String("string", `some string format log information`),
				zap.Int("int", 3),
			)
		}
	})
	b.Run("zap+lxt caller", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		cfg := zap.NewProductionConfig()
		core := zapcore.NewCore(
			// zapcore.NewJSONEncoder(cfg.EncoderConfig),
			zapcore.NewConsoleEncoder(cfg.EncoderConfig),
			zapcore.AddSync(io.Discard),
			zapcore.InfoLevel,
		)
		logger := lxtzaplog.New(core, zap.WithCaller(false))
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			logger.Info("some log messages",
				zap.String("string", `some string format log information`),
				zap.Int("int", 3),
			)
		}
	})

	b.Run("zap-sugar", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		cfg := zap.NewProductionConfig()
		core := zapcore.NewCore(
			// zapcore.NewJSONEncoder(cfg.EncoderConfig),
			zapcore.NewConsoleEncoder(cfg.EncoderConfig),
			zapcore.AddSync(io.Discard),
			zapcore.InfoLevel,
		)
		logger := zap.New(core)
		sugar := logger.Sugar()
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			sugar.Info("some log messages",
				"string", `some string format log information`,
				"int", 3,
				"backoff", time.Second,
			)
		}
	})
	b.Run("zap-sugar+caller", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		cfg := zap.NewProductionConfig()
		core := zapcore.NewCore(
			// zapcore.NewJSONEncoder(cfg.EncoderConfig),
			zapcore.NewConsoleEncoder(cfg.EncoderConfig),
			zapcore.AddSync(io.Discard),
			zapcore.InfoLevel,
		)
		logger := zap.New(core, zap.WithCaller(true))
		sugar := logger.Sugar()
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			sugar.Info("some log messages",
				"string", `some string format log information`,
				"int", 3,
				"backoff", time.Second,
			)
		}
	})

	b.Run("zap-sugar+lxt caller", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		cfg := zap.NewProductionConfig()
		core := zapcore.NewCore(
			// zapcore.NewJSONEncoder(cfg.EncoderConfig),
			zapcore.NewConsoleEncoder(cfg.EncoderConfig),
			zapcore.AddSync(io.Discard),
			zapcore.InfoLevel,
		)
		logger := lxtzaplog.New(core, zap.WithCaller(false))
		sugar := logger.Sugar()
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			sugar.Info("some log messages",
				"string", `some string format log information`,
				"int", 3,
				"backoff", time.Second,
			)
		}
	})

	b.Run("lxt caller", func(b *testing.B) {
		b.StopTimer()
		b.ReportAllocs()
		b.StartTimer()
		for i := 0; i < b.N; i++ {
			c := CallerFrame(errors.GetPC())
			io.Discard.Write([]byte(zap.String("caller", c.File).String))
		}
	})
}

测试结果如下:

pkg: github.com/lxt1045/errors/zerolog
cpu: AMD Ryzen 9 7940H w/ Radeon 780M Graphics
BenchmarkLog
BenchmarkLog/zerolog
BenchmarkLog/zerolog-16
16198856	        71.87 ns/op	       0 B/op	       0 allocs/op
BenchmarkLog/zerolog+lxt
BenchmarkLog/zerolog+lxt-16
15661706	        71.98 ns/op	       0 B/op	       0 allocs/op
BenchmarkLog/zerolog+caller
BenchmarkLog/zerolog+caller-16
 1704080	       708.4 ns/op	     320 B/op	       4 allocs/op
BenchmarkLog/zerolog+lxt_caller
BenchmarkLog/zerolog+lxt_caller-16
 7110272	       162.0 ns/op	      83 B/op	       2 allocs/op
BenchmarkLog/zerolog+context-caller
BenchmarkLog/zerolog+context-caller-16
  876045	      1393 ns/op	     856 B/op	       7 allocs/op
BenchmarkLog/zerolog+lxt_context-caller
BenchmarkLog/zerolog+lxt_context-caller-16
 2933074	       406.9 ns/op	     608 B/op	       5 allocs/op
BenchmarkLog/logrus
BenchmarkLog/logrus-16
  560250	      2042 ns/op	    1362 B/op	      23 allocs/op
BenchmarkLog/logrus+caller
BenchmarkLog/logrus+caller-16
  238459	      5203 ns/op	    2388 B/op	      34 allocs/op
BenchmarkLog/logrus+lxt_caller
BenchmarkLog/logrus+lxt_caller-16
  374670	      3571 ns/op	    2084 B/op	      31 allocs/op
BenchmarkLog/zap
BenchmarkLog/zap-16
 2589802	       486.4 ns/op	     152 B/op	       3 allocs/op
BenchmarkLog/zap+caller
BenchmarkLog/zap+caller-16
  769772	      1631 ns/op	     425 B/op	       6 allocs/op
BenchmarkLog/zap+lxt_caller
BenchmarkLog/zap+lxt_caller-16
 1563028	       791.4 ns/op	     409 B/op	       4 allocs/op
BenchmarkLog/zap-sugar
BenchmarkLog/zap-sugar-16
 2392736	       520.7 ns/op	     112 B/op	       4 allocs/op
BenchmarkLog/zap-sugar+caller
BenchmarkLog/zap-sugar+caller-16
  590670	      1939 ns/op	     385 B/op	       7 allocs/op
BenchmarkLog/zap-sugar+lxt_caller
BenchmarkLog/zap-sugar+lxt_caller-16
 1537038	       806.3 ns/op	     176 B/op	       5 allocs/op
BenchmarkLog/lxt_caller
BenchmarkLog/lxt_caller-16
39783445	        29.44 ns/op	      32 B/op	       1 allocs/op

设计思路

交流学习

扫码加微信好友

Documentation

Index

Constants

View Source
const (
	DefaultCode = -1
	DefaultMsg  = ""
)
View Source
const (
	DefaultDepth = 32 // 默认构建的调用栈深度
)

Variables

View Source
var Getg = goid.Get

Functions

func CallerFrame

func CallerFrame(l uintptr) (c *caller)

CallerFrame 使用 Read-copy update(RCU) 缓存提高性能

func CallersSkip

func CallersSkip(skip int) (cs []caller)

func Catch

func Catch(g guard, f func(err interface{}) bool)

func Check

func Check(err *Code)

func Clone

func Clone(err error, skips ...int) error

Clone 利用 code 和 msg 生成一个包含当前stack的新Error,

func Errorf

func Errorf(format string, a ...interface{}) error

Errorf 替换 fmt.Errorf

func GetPC

func GetPC() uintptr

func Is

func Is(err1, target error) bool

Is 检查code是不是一样的

func MarshalJSON

func MarshalJSON(err error) (bs []byte)

MarshalJSON 将err序列化为json格式

func MarshalJSON2

func MarshalJSON2(err error) (bs []byte)

func MarshalText

func MarshalText(err error) (bs []byte)

func New

func New(format string, a ...interface{}) error

New 替换 errors.New

func NewErr

func NewErr(code int, format string, a ...interface{}) error

func NewGuard

func NewGuard() guard

func NewHandler

func NewHandler() (handler, error)

func NewLine

func NewLine(format string, ifaces ...interface{}) error

func NewLineSlow

func NewLineSlow(format string, ifaces ...interface{}) error

func NewStackCache

func NewStackCache[V any](fNew func(s *[DefaultDepth]uintptr, l int) V) *stackCache1[V]

func NewWriteBuffer

func NewWriteBuffer(n int) (buf *writeBuffer)

func Setjmp

func Setjmp() (jump, error)

func Wrap

func Wrap(err error, format string, ifaces ...interface{}) error

Wrap 需要禁止内联,因为内联后,将无法通过 BP 获取正确的PC。

func WrapSlow

func WrapSlow(err error, format string, ifaces ...interface{}) error

func ZerologStack

func ZerologStack(skip int) zeroStack

Types

type Code

type Code struct {
	// contains filtered or unexported fields
}

func NewCode

func NewCode(skip, code int, format string, a ...interface{}) (c *Code)

func NewCodeSlow

func NewCodeSlow(skip, code int, format string, a ...interface{}) (c *Code)

func (*Code) Clone

func (e *Code) Clone(msg ...string) *Code

func (*Code) Clonef

func (e *Code) Clonef(format string, a ...interface{}) *Code

func (*Code) Code

func (e *Code) Code() int

func (*Code) Error

func (e *Code) Error() string

Error error interface, 序列化为string, 包含调用栈

func (*Code) Is

func (e *Code) Is(err error) bool

func (*Code) MarshalJSON

func (e *Code) MarshalJSON() (bs []byte, err error)

MarshalJSON json.Marshaler的方法, json.Marshal 里调用

func (*Code) MarshalZerologArray

func (e *Code) MarshalZerologArray(a *zerolog.Array)

func (*Code) MarshalZerologObject

func (e *Code) MarshalZerologObject(evt *zerolog.Event)

MarshalZerologObject for zerolog

func (*Code) Msg

func (e *Code) Msg() string

type RCUCache

type RCUCache[K comparable, V any] struct {
	New func(K) V
	// contains filtered or unexported fields
}

func (*RCUCache[K, V]) Get

func (c *RCUCache[K, V]) Get(key K) (v V)

func (*RCUCache[K, V]) JustGet

func (c *RCUCache[K, V]) JustGet(key K) (v V, ok bool)

func (*RCUCache[K, V]) Set

func (c *RCUCache[K, V]) Set(key K, value V)

type StackCache

type StackCache[V any] struct {
	RCUCache[string, V]
}

func (*StackCache[V]) Get

func (c *StackCache[V]) Get(s *[DefaultDepth]uintptr, l int) (v V)

func (*StackCache[V]) Set

func (c *StackCache[V]) Set(s *[DefaultDepth]uintptr, l int, v V)

Directories

Path Synopsis
log

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL