From 345c40442b83d8b7a59eebdddc9dbd47b0fbfff1 Mon Sep 17 00:00:00 2001 From: at15 Date: Mon, 10 Dec 2018 22:27:57 -0800 Subject: [PATCH 01/34] [doc][errors] Init plan for error categorization - this is the target for v0.0.9 - current errors is just wrap and multi error I want to have better error generation so error handling can be more specific and generate more useful message that shows the solution - also error categorization can help debugging on a larger scale, otherwise you need extra code to parse string and put them into different groups, at the spot of error occurs, most times the developer should already know what kind of error it is --- errors/doc/README.md | 2 + .../design/2018-12-10-error-categorization.md | 110 ++++++++++++++++++ log/doc/design/2018-09-05-clean-up.md | 6 +- 3 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 errors/doc/design/2018-12-10-error-categorization.md diff --git a/errors/doc/README.md b/errors/doc/README.md index 2eebc1b..384128c 100644 --- a/errors/doc/README.md +++ b/errors/doc/README.md @@ -1,5 +1,7 @@ # Doc +- [Design](design) + ## Survey wrap diff --git a/errors/doc/design/2018-12-10-error-categorization.md b/errors/doc/design/2018-12-10-error-categorization.md new file mode 100644 index 0000000..8a5fc40 --- /dev/null +++ b/errors/doc/design/2018-12-10-error-categorization.md @@ -0,0 +1,110 @@ +# 2018-12-10 Error categorization + +## Issues + +- [#62](https://github.com/dyweb/gommon/issues/62) Support fmt.Formatter interface +- [#73](https://github.com/dyweb/gommon/issues/73) Human readable suggestions for possible solutions +- [#76](https://github.com/dyweb/gommon/issues/76) A more complex error interface + +## Background + +Originally I was using [pkg/errors](https://github.com/pkg/errors), it works fine. +I wrap all my errors and many library I depend on also use it, there is not import issues. +However, my handling logic remain the same, have a `log.Fatal(err)` if it's a cli +or `w.Write([]byte(err.Error()))` if it's an http server. +I don't generate helpful suggestion like `you want config.yaml but only config.yml is found in current folder`. +for my library and app users (which sadly just myself most of the time). + +The are two reasons for that, First I don't know what errors will the standard library return, +there are detailed godoc, but I want them in one page so I don't need to jump around the doc. +Second I am just wrapping for the sake of wrapping, add things like `error read file` helps +human a bit when it got logged at last, but it's not a context that can be used by code, +in the end all the read file error are treated the same regardless why I read the file. + +However, the direct reason for me to stop using pkg/errors are there are pending issues and +I want to have multi error. pkg/errors is not going to add more complexity, +i.e. [#75](https://github.com/pkg/errors/issues/75) if you wrap a wrapped error, +it is going to have duplicated stack with less trace, the more you wrap, the less you have after first Cause (UnWrap). +The PR [#122](https://github.com/pkg/errors/pull/122) to fix that has been there for almost 2 years. +As for multi error, [hashicorp](https://github.com/hashicorp/go-multierror) and [uber-go/multierr](https://github.com/uber-go/multierr) +are good example, they also have pending issues like return bool for Append [#21](https://github.com/uber-go/multierr/issues/21) + +Thus I reinvent the wheel and gommon/errors is a mix of all of them, it contains error wrapping and multi error, +and implemented some of their pending issues like duplicated wrap and return append result in multi error. + + + +### Current implementation + +The implementation is pretty simple, the most complex part, print call stack, is not even implemented [#62](https://github.com/dyweb/gommon/issues/62), +it does not support `%+v` formatter. + +Wrap error is just an interface with one implementation which store a message as context and stack trace + +````go +type WrappedError struct { + msg string + cause error + stack *Stack +} +```` + +Multi error has two implementation, one is thread safe by using a mutex, one is just a slice + +````go +// multiErr is NOT thread (goroutine) safe +type multiErr struct { + errs []error +} + +// multiErrSafe is thread safe +type multiErrSafe struct { + mu sync.Mutex + errs []error +} +```` + +## Goals + +There are many things I planned to do, some are too heavy that I think a `errorx` library is needed, +will have a `logx` package as well + +- categorized errors + - standard library errors + - dependency errors (not my application/library) + - network, permanent and temp + - client/server error, client should know it's a server error or client itself figured out something wrong before/after calling server + - encoding/decoding + - invalid input i.e. wrong file name + - implementation, assert/sanity check failed at runtime and you don't want to panic +- allow traverse error chain in code +- try to align w/ [go2 proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) +- error collection and aggregation for real-time and future analysis + +## Plan + +First is to read [go2draft](https://go.googlesource.com/proposal/+/master/design/go2draft.md) again, +I have already forgot most of them, luckily I did take [notes](https://github.com/at15/papers-i-read/commit/a44b13fd8e1bcc51135ebe128f391ebc674904c4) +The four ways of error handling and the problem user want to ask when there is an RPC error +is really what I always wanted to say but didn't figure out how to (sigh). + +Second go over all the refers I put in [#76](https://github.com/dyweb/gommon/issues/76) and [go.ice#12](https://github.com/dyweb/go.ice/issues/12) + +- TiDB https://github.com/pingcap/tidb/tree/master/terror +- gRPC https://github.com/grpc-ecosystem/grpc-opentracing/blob/master/go/otgrpc/errors.go has error class +- docker https://github.com/moby/moby/blob/master/errdefs/doc.go `Errors that cross the package boundary should implement one (and only one) of these interfaces.` +- teleport (ssh servers) https://github.com/gravitational/trace/blob/master/errors.go concrete structs (different from docker's errdefs) +- dgraph https://github.com/dgraph-io/dgraph/blob/master/x/error.go the `func Check2(_ interface{}, err error)` is epic, I want `func Check3(_ interface{}, _ interface{}, err error)` +- [ ] error collection like sentry, it's good to ship all your errors like logs into a central place for cross application/node analysis +(if you can afford it) + +Third lines up the following + +- list all the common errors from standard library, i.e. file, http, encoding + - common error message (amazing error messages and where to find them) +- what categories will be included in `errors` +- ~~three~~ some real world use cases + - ayi, a command line productivity tool that read config, make some API call, run other processes + - libtsdb (well the project is dead but ...) a library that do encoding and calling servers + - benchhub, a web service that talks with many other error services +- what's in `errors` what's in `errorx` \ No newline at end of file diff --git a/log/doc/design/2018-09-05-clean-up.md b/log/doc/design/2018-09-05-clean-up.md index 13c1e99..9b66697 100644 --- a/log/doc/design/2018-09-05-clean-up.md +++ b/log/doc/design/2018-09-05-clean-up.md @@ -26,13 +26,13 @@ Performance ## Background -**History** +### History In the early design and implementation, gommon/log is basically following other libraries, v1 followed logrus (using structured logging, have entry, formatter and writer). v2 followed zap (using typed fields) and apex/log (using handler to replace formatter and writer). -**Goals** +### Goals Performance was a goal, though I always say it's not a major goal, but that can't explain those ugly magic number and manually inline in handlers. However, without out measurement, those things could lead to poor performance. @@ -47,7 +47,7 @@ it will be filled with useless information when debug level is turned on for eve however if you can control level of single package (including those from dependency), you can just ask user to run it with specific log flags like `--log-debug-packages=gommon,go.ice` -**Current** +### Current Current implementation (before this refactor) is already usable (for a long time). However, it's far from production ready, I need a 'new' gommon for a side project at work, From f1ac92e08dbae63bd07892172cf7c9d3d8dfcad4 Mon Sep 17 00:00:00 2001 From: at15 Date: Tue, 11 Dec 2018 21:00:32 -0800 Subject: [PATCH 02/34] [build] Add go-dev Dockerfile #98 - install c/c++ compile environment (thus pretty big) - use go1.11.2 - install dep --- build/go-dev/Dockerfile | 48 +++++++++++++++++++++++++++++++++++++++++ build/go-dev/Makefile | 3 +++ build/go-dev/README.md | 5 +++++ 3 files changed, 56 insertions(+) create mode 100644 build/go-dev/Dockerfile create mode 100644 build/go-dev/Makefile create mode 100644 build/go-dev/README.md diff --git a/build/go-dev/Dockerfile b/build/go-dev/Dockerfile new file mode 100644 index 0000000..228fb74 --- /dev/null +++ b/build/go-dev/Dockerfile @@ -0,0 +1,48 @@ +ARG BASE_IMAGE=ubuntu:18.04 +FROM $BASE_IMAGE + +LABEL maintainer="contact@dongyue.io" + +# https://github.com/dyweb/gommon/issues/98 +RUN \ + export DEBIAN_FRONTEND=noninteractive \ + && apt-get update \ + && apt-get upgrade -y --no-install-recommends \ + && apt-get install -y --no-install-recommends \ + bash \ + build-essential \ + ca-certificates \ + curl \ + wget \ + git-core \ + ssh-client \ + man \ + vim \ + zip \ + unzip \ + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +ENV GOPATH=/go +ENV PATH=$GOPATH/bin:/usr/local/go/bin:$PATH +# TODO: do we need chmod -R 777 like https://github.com/docker-library/golang/blob/master/Dockerfile-debian.template +RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" + +ARG BUILD_GO_VERSION=1.11.2 + +# glide no longer have release, just hard code it to latest version +ENV GO_VERSION=$BUILD_GO_VERSION \ + GLIDE_VERSION=v0.13.1 + +# TODO: might put glide under GOPATH/bin +RUN \ + curl -L https://dl.google.com/go/go$GO_VERSION.linux-amd64.tar.gz | tar -C /usr/local -xz \ + && curl -sSL https://github.com/Masterminds/glide/releases/download/$GLIDE_VERSION/glide-$GLIDE_VERSION-linux-amd64.tar.gz \ + | tar -vxz -C /usr/local/bin --strip=1 \ + && rm /usr/local/bin/README.md /usr/local/bin/LICENSE + +# dep releases are way behind master, so we install from source +RUN go get -u -v github.com/golang/dep/cmd/dep + +WORKDIR $GOPATH \ No newline at end of file diff --git a/build/go-dev/Makefile b/build/go-dev/Makefile new file mode 100644 index 0000000..36886b7 --- /dev/null +++ b/build/go-dev/Makefile @@ -0,0 +1,3 @@ +# TODO: add tag, publish to dockerhub etc. +build: + docker build . \ No newline at end of file diff --git a/build/go-dev/README.md b/build/go-dev/README.md new file mode 100644 index 0000000..75f3c78 --- /dev/null +++ b/build/go-dev/README.md @@ -0,0 +1,5 @@ +# go-dev + +go-dev is a base image for building go code + +See [#98](https://github.com/dyweb/gommon/issues/98) \ No newline at end of file From ef77a0f70b587b6c9bec10246660ba9b35dc5823 Mon Sep 17 00:00:00 2001 From: at15 Date: Fri, 14 Dec 2018 20:04:54 -0800 Subject: [PATCH 03/34] Remove example folder which only has readme - moved the content to go.ice https://github.com/dyweb/go.ice/issues/32 --- doc/style.md | 3 +++ example/README.md | 9 --------- 2 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 example/README.md diff --git a/doc/style.md b/doc/style.md index 5b21074..d6bac61 100644 --- a/doc/style.md +++ b/doc/style.md @@ -2,3 +2,6 @@ General coding style +## Ref + +- https://blog.golang.org/package-names \ No newline at end of file diff --git a/example/README.md b/example/README.md deleted file mode 100644 index 1b7a8ed..0000000 --- a/example/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Gommon example application - -## Examples - -- [ ] busy box, tree, curl, cat, make using yaml file etc. (command line app) -- [ ] gitboard, a github dashboard using github API w/o using [google/go-github](https://github.com/google/go-github) (web application w/ assets) - -## Style - From 80aab4d3117a474075e6e3e5e36d7e8fb15df638 Mon Sep 17 00:00:00 2001 From: at15 Date: Sat, 15 Dec 2018 12:57:28 -0800 Subject: [PATCH 04/34] [doc][errors] Add reference for upspin & go2 - go2 error inspection proposal is really close to upspin, upspin only have one error struct so it don't even need an interface for error wrapping, it's just e.Err, also match is much easiter, just make sure all the fields match because they are of same type - just realized hashicrop/errwrap allow to get error by type in the error chain, which is similar to the go 2 `As` syntax, the workaround without go 2 contract is using reflect I suppose (the proposal didn't give the answer ...) --- errors/doc/design/2018-12-14-reference.md | 129 ++++++++++++++++++++++ errors/doc/hashicorp-errwrap.md | 26 +++++ 2 files changed, 155 insertions(+) create mode 100644 errors/doc/design/2018-12-14-reference.md diff --git a/errors/doc/design/2018-12-14-reference.md b/errors/doc/design/2018-12-14-reference.md new file mode 100644 index 0000000..faca51e --- /dev/null +++ b/errors/doc/design/2018-12-14-reference.md @@ -0,0 +1,129 @@ +# 2018-12-14 Reference + +This doc go over the references mentioned in [error-categorization](2018-12-10-error-categorization.md) + +## Go 2 + +- notes: https://github.com/at15/papers-i-read/commit/a44b13fd8e1bcc51135ebe128f391ebc674904c4 +- source: https://go.googlesource.com/proposal/+/master/design/go2draft-error-values-overview.md + +TODO + +- [ ] it mentioned https://github.com/spacemonkeygo/errors which has class etc. + - it allows attach key value pairs to error + - defines common set of error https://github.com/spacemonkeygo/errors/blob/master/errors.go#L563-L600 +- [ ] https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html by rob pike + - defines error kinds https://godoc.org/upspin.io/errors#Kind + - https://godoc.org/upspin.io/errors#MarshalError to transfer error across the wire + +https://go.googlesource.com/proposal/+/master/design/go2draft-error-printing.md + +- error printing is only for read by human +- it print trace of error (w/ or w/o stack?) + - [ ] now in gommon/errors we only do wrapping in first error, however the call stack of init error should be different from the wrapping stack +- mentioned list of error (which is multi error) + +https://go.googlesource.com/proposal/+/master/design/go2draft-error-inspection.md + +- `Is` is just same as upspin, and I think it only works for sentinel errors like `io.EOF` + +````go +func Is(err, target error) bool { + for { + if err == target { + return true + } + wrapper, ok := err.(Wrapper) + if !ok { + return false + } + err = wrapper.Unwrap() + if err == nil { + return false + } + } +} +```` + +- `As` requires contracts ... (pass type as a parameter) + - without polymorphism + - [ ] I didn't quite get this part .. + +````go +func As(type E)(err error) (e E, ok bool) { + for { + if e, ok := err.(E); ok { + return e, true + } + wrapper, ok := err.(Wrapper) + if !ok { + return e, false + } + err = wrapper.Unwrap() + if err == nil { + return e, false + } + } +} +```` + +````go +// instead of pe, ok := err.(*os.PathError) +var pe *os.PathError +if errors.AsValue(&pe, err) { ... pe.Path ... } +```` + +## Upspin + +https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html + +- didn't use stack trace for error, show operational trace + +> There is a tension between making errors helpful and concise for the end user versus making them expansive and analytic for the implementer. +Too often the implementer wins and the errors are overly verbose, to the point of including stack traces or other overwhelming detail + +> Upspin's errors are an attempt to serve both the users and the implementers. +The reported errors are reasonably concise, concentrating on information the user should find helpful. +But they also contain internal details such as method names an implementer might find diagnostic but not in a way that overwhelms the user. +In practice we find that the tradeoff has worked well + +> In contrast, a stack trace-like error is worse in both respects. +The user does not have the context to understand the stack trace, +and an implementer shown a stack trace is denied the information that could be presented +if the server-side error was passed to the client. This is why Upspin error nesting behaves as an operational trace, +showing the path through the elements of the system, rather than as an execution trace, showing the path through the code. +The distinction is vital + +- `func Is(kind Kind, err error) bool` I think is almost same as the go 2 proposal `Is`, compare through the cause chain +- `func Match(template, err error) bool` is almost same as go 2 proposal `func As(type E)(err error) (e E, ok bool) {}` + +````go +// Is reports whether err is an *Error of the given Kind. +// If err is nil then Is returns false. +func Is(kind Kind, err error) bool { + e, ok := err.(*Error) + if !ok { + return false + } + if e.Kind != Other { + return e.Kind == kind + } + if e.Err != nil { + return Is(kind, e.Err) + } + return false +} + +// Match only operates on upspin's Error type, so it does not need the polymorphism like go 2 +func Match(err1, err2 error) bool { + e1, ok := err1.(*Error) + if !ok { + return false + } + e2, ok := err2.(*Error) + if !ok { + return false + } + // un wrap and compare etc. +} +```` \ No newline at end of file diff --git a/errors/doc/hashicorp-errwrap.md b/errors/doc/hashicorp-errwrap.md index 6d11060..557129c 100644 --- a/errors/doc/hashicorp-errwrap.md +++ b/errors/doc/hashicorp-errwrap.md @@ -42,4 +42,30 @@ func GetAll(err error, msg string) []error { return result } +```` + +````go +// GetAllType gets all the errors that are the same type as v. +// +// The order of the return value is the same as described in GetAll. +func GetAllType(err error, v interface{}) []error { + var result []error + + var search string + if v != nil { + search = reflect.TypeOf(v).String() + } + Walk(err, func(err error) { + var needle string + if err != nil { + needle = reflect.TypeOf(err).String() + } + + if needle == search { + result = append(result, err) + } + }) + + return result +} ```` \ No newline at end of file From 3cc31d6a30c46ac2bfc2d81dbf159fe3be5cf6a7 Mon Sep 17 00:00:00 2001 From: at15 Date: Sat, 15 Dec 2018 13:01:13 -0800 Subject: [PATCH 05/34] [errors][play] Add AsValue in go 2 error inspection - the proposal mentioned if polymorphism is not implemented in go 2, then a workaround is to use `AsValue` which is similar to `json.Unmarshal` - the implementation use `reflect.Typeof` to do type compare and use `Value.Set` to set the value (though it might be a bit hard to use because you need to declare the right error struct first and pass its pointer in ... which is troublesome ... let user do check and then do type assertion might be a better idea ... --- errors/playground/go2/as_test.go | 86 ++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 errors/playground/go2/as_test.go diff --git a/errors/playground/go2/as_test.go b/errors/playground/go2/as_test.go new file mode 100644 index 0000000..a8352de --- /dev/null +++ b/errors/playground/go2/as_test.go @@ -0,0 +1,86 @@ +package go2 + +import ( + "encoding/json" + "log" + "reflect" + "testing" +) + +// test go2 As implementation in go1 (without polymorphism) +// +// From https://go.googlesource.com/proposal/+/master/design/go2draft-error-inspection.md +// +// If Go 2 does not choose to adopt polymorphism or if we need a function to use in the interim, we could write a temporary helper: +// // instead of pe, ok := err.(*os.PathError) +// var pe *os.PathError +// if errors.AsValue(&pe, err) { ... pe.Path ... } + +func AsValue(v interface{}, err error) { + // TODO: I think I need to use reflect + + // TODO: it does not compare package import path? ... + // type of v **go2.PathError err *go2.PathError + log.Printf("type of v %s err %s", reflect.TypeOf(v), reflect.TypeOf(err)) + + // NOTE: val must be a pointer so we can unmarshal to it + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + // do nothing, panic so the test is easier + panic("v must be a pointer") + return + } + iv := reflect.Indirect(rv) + log.Printf("iv is nil? %t", iv.IsNil()) + log.Printf("type of iv %s", reflect.TypeOf(iv)) + ivi := iv.Interface() + log.Printf("type of ivi %s", reflect.TypeOf(ivi)) + // TODO: https://github.com/hashicorp/errwrap/blob/master/errwrap.go#L108-L130 + //rt := reflect.TypeOf(v) + //rv.Set(reflect.ValueOf(err)) + iv.Set(reflect.ValueOf(err)) +} + +// copied from os.PathError +// PathError records an error and the operation and file path that caused it. +type PathError struct { + Op string + Path string + Err error +} + +func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() } + +// Timeout reports whether this error represents a timeout. +//func (e *PathError) Timeout() bool { +// t, ok := e.Err.(timeout) +// return ok && t.Timeout() +//} + +func TestAsValue(t *testing.T) { + e1 := &PathError{Op: "open", Path: "/dev/null"} + e2 := &PathError{} + t.Logf("e2 Path %s", e2.Path) + AsValue(&e2, e1) + t.Logf("e2 Path %s", e2.Path) +} + +// TODO: can I use reflect type as first parameter to just do the match + +func TestJson(t *testing.T) { + t.Run("struct", func(t *testing.T) { + b := []byte(`{"a":1}`) + type s struct { + A int + } + var s1 s + err := json.Unmarshal(b, &s1) + t.Logf("s.A %d err %v", s1.A, err) + }) + // you can decode single number as json + t.Run("single number", func(t *testing.T) { + var a int + err := json.Unmarshal([]byte("1"), &a) + t.Logf("a %d err %v", a, err) + }) +} From c58ee4e9a4382f11f3b62d9cf9299f265ffdd39a Mon Sep 17 00:00:00 2001 From: at15 Date: Sat, 15 Dec 2018 14:22:19 -0800 Subject: [PATCH 06/34] [build] Add link for dyweb/go-dev #99 --- build/go-dev/Dockerfile | 3 ++- build/go-dev/Makefile | 19 ++++++++++++++++--- build/go-dev/README.md | 19 ++++++++++++++++++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/build/go-dev/Dockerfile b/build/go-dev/Dockerfile index 228fb74..9521b67 100644 --- a/build/go-dev/Dockerfile +++ b/build/go-dev/Dockerfile @@ -43,6 +43,7 @@ RUN \ && rm /usr/local/bin/README.md /usr/local/bin/LICENSE # dep releases are way behind master, so we install from source -RUN go get -u -v github.com/golang/dep/cmd/dep +RUN go get -u -v github.com/golang/dep/cmd/dep \ + && go get -u -v golang.org/x/tools/cmd/goimports WORKDIR $GOPATH \ No newline at end of file diff --git a/build/go-dev/Makefile b/build/go-dev/Makefile index 36886b7..bdfba7f 100644 --- a/build/go-dev/Makefile +++ b/build/go-dev/Makefile @@ -1,3 +1,16 @@ -# TODO: add tag, publish to dockerhub etc. -build: - docker build . \ No newline at end of file +DOCKER_REPO = dyweb/go-dev +GO_VERSIONS = 1.10.7 1.11.4 +BUILDS = $(addprefix build-, $(GO_VERSIONS)) +PUSHS = $(addprefix push-, $(GO_VERSIONS)) + +.PHONY: build push + +$(BUILDS): + docker build -t $(DOCKER_REPO):$(subst build-,,$@) --build-arg BUILD_GO_VERSION=$(subst build-,,$@) . + +$(PUSHS): + docker push $(DOCKER_REPO):$(subst push-,,$@) + +build: $(BUILDS) + +push: $(PUSHS) \ No newline at end of file diff --git a/build/go-dev/README.md b/build/go-dev/README.md index 75f3c78..cdbfa64 100644 --- a/build/go-dev/README.md +++ b/build/go-dev/README.md @@ -2,4 +2,21 @@ go-dev is a base image for building go code -See [#98](https://github.com/dyweb/gommon/issues/98) \ No newline at end of file +See [#98](https://github.com/dyweb/gommon/issues/98) + +- Docker Hub https://hub.docker.com/r/dyweb/go-dev + +## Use + +Use it as base in you multi stage build, the image is big, you don't want to use it directly unless +you need a full go environment to run your code (looking at ginkgo) + +TODO: example using multi stage build using dep and go mods + +## Build + +````bash +make build +# need to login to dockerhub and be member +make push +```` \ No newline at end of file From f32f02e40702ab643c805a84a1011b068cfdb1b2 Mon Sep 17 00:00:00 2001 From: at15 Date: Sun, 16 Dec 2018 19:28:46 -0800 Subject: [PATCH 07/34] [doc][erros] Add ref errorswrap, docker, sentry - errorswrap is simple but the walk etc. are good - docker defines interface of common error types and has helper function that wrap error with type and without extra context - sentry is using `map[string]interface{}` to add extra metadata for error, which is useful when reporting error to a central location --- .../design/2018-12-10-error-categorization.md | 3 +- errors/doc/design/2018-12-14-reference.md | 620 +++++++++++++++++- errors/playground/go2/as_test.go | 1 - 3 files changed, 621 insertions(+), 3 deletions(-) diff --git a/errors/doc/design/2018-12-10-error-categorization.md b/errors/doc/design/2018-12-10-error-categorization.md index 8a5fc40..f8822b9 100644 --- a/errors/doc/design/2018-12-10-error-categorization.md +++ b/errors/doc/design/2018-12-10-error-categorization.md @@ -95,8 +95,9 @@ Second go over all the refers I put in [#76](https://github.com/dyweb/gommon/iss - docker https://github.com/moby/moby/blob/master/errdefs/doc.go `Errors that cross the package boundary should implement one (and only one) of these interfaces.` - teleport (ssh servers) https://github.com/gravitational/trace/blob/master/errors.go concrete structs (different from docker's errdefs) - dgraph https://github.com/dgraph-io/dgraph/blob/master/x/error.go the `func Check2(_ interface{}, err error)` is epic, I want `func Check3(_ interface{}, _ interface{}, err error)` -- [ ] error collection like sentry, it's good to ship all your errors like logs into a central place for cross application/node analysis +- [x] error collection like sentry, it's good to ship all your errors like logs into a central place for cross application/node analysis (if you can afford it) + - https://github.com/getsentry/raven-go Third lines up the following diff --git a/errors/doc/design/2018-12-14-reference.md b/errors/doc/design/2018-12-14-reference.md index faca51e..5bb4dca 100644 --- a/errors/doc/design/2018-12-14-reference.md +++ b/errors/doc/design/2018-12-14-reference.md @@ -12,7 +12,7 @@ TODO - [ ] it mentioned https://github.com/spacemonkeygo/errors which has class etc. - it allows attach key value pairs to error - defines common set of error https://github.com/spacemonkeygo/errors/blob/master/errors.go#L563-L600 -- [ ] https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html by rob pike +- [x] https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html by rob pike - defines error kinds https://godoc.org/upspin.io/errors#Kind - https://godoc.org/upspin.io/errors#MarshalError to transfer error across the wire @@ -73,6 +73,8 @@ var pe *os.PathError if errors.AsValue(&pe, err) { ... pe.Path ... } ```` +- can use reflect to implement this, to compare type, just use `reflect.TypeOf(err).String` + ## Upspin https://commandcenter.blogspot.com/2017/12/error-handling-in-upspin.html @@ -126,4 +128,620 @@ func Match(err1, err2 error) bool { } // un wrap and compare etc. } +```` + +> Errors are for users, not just for programmers + +## errorwrap + +- https://github.com/hashicorp/errwrap/blob/master/errwrap.go +- do unwrapping +- allow match using string, exact match on error string, not strings.Contains +- allow get a slice of errors from error chain after matching `func GetAll(err error, msg string) []error ` +- match type use reflect `func GetAllType(err error, v interface{}) []error` + +````go +// Walk walks all the wrapped errors in err and calls the callback. If +// err isn't a wrapped error, this will be called once for err. If err +// is a wrapped error, the callback will be called for both the wrapper +// that implements error as well as the wrapped error itself. +func Walk(err error, cb WalkFunc) { + if err == nil { + return + } + + switch e := err.(type) { + case *wrappedError: + cb(e.Outer) + Walk(e.Inner, cb) + case Wrapper: + cb(err) + + for _, err := range e.WrappedErrors() { + Walk(err, cb) + } + default: + cb(err) + } +} + +// wrappedError is an implementation of error that has both the +// outer and inner errors. +type wrappedError struct { + Outer error + Inner error +} + +func (w *wrappedError) Error() string { + return w.Outer.Error() +} + +func (w *wrappedError) WrappedErrors() []error { + return []error{w.Outer, w.Inner} +} +```` + +## TiDB + +- https://github.com/pingcap/tidb/blob/master/terror/terror.go +- https://github.com/pingcap/errors fork of pkg/errors with juju adapter +- `ErroClass` is pretty application specific (common source of database errors) +- can convert to MySQL error code + +````go +// Error implements error interface and adds integer Class and Code, so +// errors with different message can be compared. +type Error struct { + class ErrClass + code ErrCode + message string + args []interface{} + file string + line int +} + +// Class returns ErrClass +func (e *Error) Class() ErrClass { + return e.class +} + +// Code returns ErrCode +func (e *Error) Code() ErrCode { + return e.code +} + +// ErrCode represents a specific error type in a error class. +// Same error code can be used in different error classes. +type ErrCode int + +// ErrClass represents a class of errors. +type ErrClass int + +// Error classes. +const ( + ClassAutoid ErrClass = iota + 1 + ClassDDL + ClassDomain + ClassEvaluator + ClassExecutor + ClassExpression + ClassAdmin + ClassKV + ClassMeta + ClassOptimizer + ClassParser + ClassPerfSchema + ClassPrivilege + ClassSchema + ClassServer + ClassStructure + ClassVariable + ClassXEval + ClassTable + ClassTypes + ClassGlobal + ClassMockTikv + ClassJSON + ClassTiKV + ClassSession + // Add more as needed. +) +```` + +## gRPC + +- https://github.com/grpc/grpc-go/blob/master/codes/codes.go +- https://github.com/grpc/grpc-go/blob/master/status/status.go + - `Code` return code defined in codes + - status can contains detail, which are message encoded into string +- https://github.com/grpc-ecosystem/grpc-opentracing/blob/master/go/otgrpc/errors.go + +````go +// A Class is a set of types of outcomes (including errors) that will often +// be handled in the same way. +type Class string + +const ( + Unknown Class = "0xx" + // Success represents outcomes that achieved the desired results. + Success Class = "2xx" + // ClientError represents errors that were the client's fault. + ClientError Class = "4xx" + // ServerError represents errors that were the server's fault. + ServerError Class = "5xx" +) + +// A Code is an unsigned 32-bit error code as defined in the gRPC spec. +type Code uint32 + +const ( + // OK is returned on success. + OK Code = 0 + + // Canceled indicates the operation was canceled (typically by the caller). + Canceled Code = 1 + + // Unknown error. An example of where this error may be returned is + // if a Status value received from another address space belongs to + // an error-space that is not known in this address space. Also + // errors raised by APIs that do not return enough error information + // may be converted to this error. + Unknown Code = 2 + + // InvalidArgument indicates client specified an invalid argument. + // Note that this differs from FailedPrecondition. It indicates arguments + // that are problematic regardless of the state of the system + // (e.g., a malformed file name). + InvalidArgument Code = 3 + + // DeadlineExceeded means operation expired before completion. + // For operations that change the state of the system, this error may be + // returned even if the operation has completed successfully. For + // example, a successful response from a server could have been delayed + // long enough for the deadline to expire. + DeadlineExceeded Code = 4 + + // NotFound means some requested entity (e.g., file or directory) was + // not found. + NotFound Code = 5 + + // AlreadyExists means an attempt to create an entity failed because one + // already exists. + AlreadyExists Code = 6 + + // PermissionDenied indicates the caller does not have permission to + // execute the specified operation. It must not be used for rejections + // caused by exhausting some resource (use ResourceExhausted + // instead for those errors). It must not be + // used if the caller cannot be identified (use Unauthenticated + // instead for those errors). + PermissionDenied Code = 7 + + // ResourceExhausted indicates some resource has been exhausted, perhaps + // a per-user quota, or perhaps the entire file system is out of space. + ResourceExhausted Code = 8 + + // FailedPrecondition indicates operation was rejected because the + // system is not in a state required for the operation's execution. + // For example, directory to be deleted may be non-empty, an rmdir + // operation is applied to a non-directory, etc. + // + // A litmus test that may help a service implementor in deciding + // between FailedPrecondition, Aborted, and Unavailable: + // (a) Use Unavailable if the client can retry just the failing call. + // (b) Use Aborted if the client should retry at a higher-level + // (e.g., restarting a read-modify-write sequence). + // (c) Use FailedPrecondition if the client should not retry until + // the system state has been explicitly fixed. E.g., if an "rmdir" + // fails because the directory is non-empty, FailedPrecondition + // should be returned since the client should not retry unless + // they have first fixed up the directory by deleting files from it. + // (d) Use FailedPrecondition if the client performs conditional + // REST Get/Update/Delete on a resource and the resource on the + // server does not match the condition. E.g., conflicting + // read-modify-write on the same resource. + FailedPrecondition Code = 9 + + // Aborted indicates the operation was aborted, typically due to a + // concurrency issue like sequencer check failures, transaction aborts, + // etc. + // + // See litmus test above for deciding between FailedPrecondition, + // Aborted, and Unavailable. + Aborted Code = 10 + + // OutOfRange means operation was attempted past the valid range. + // E.g., seeking or reading past end of file. + // + // Unlike InvalidArgument, this error indicates a problem that may + // be fixed if the system state changes. For example, a 32-bit file + // system will generate InvalidArgument if asked to read at an + // offset that is not in the range [0,2^32-1], but it will generate + // OutOfRange if asked to read from an offset past the current + // file size. + // + // There is a fair bit of overlap between FailedPrecondition and + // OutOfRange. We recommend using OutOfRange (the more specific + // error) when it applies so that callers who are iterating through + // a space can easily look for an OutOfRange error to detect when + // they are done. + OutOfRange Code = 11 + + // Unimplemented indicates operation is not implemented or not + // supported/enabled in this service. + Unimplemented Code = 12 + + // Internal errors. Means some invariants expected by underlying + // system has been broken. If you see one of these errors, + // something is very broken. + Internal Code = 13 + + // Unavailable indicates the service is currently unavailable. + // This is a most likely a transient condition and may be corrected + // by retrying with a backoff. + // + // See litmus test above for deciding between FailedPrecondition, + // Aborted, and Unavailable. + Unavailable Code = 14 + + // DataLoss indicates unrecoverable data loss or corruption. + DataLoss Code = 15 + + // Unauthenticated indicates the request does not have valid + // authentication credentials for the operation. + Unauthenticated Code = 16 + + _maxCode = 17 +) +```` + +## docker + +https://github.com/moby/moby/blob/master/errdefs/doc.go + +`Errors that cross the package boundary should implement one (and only one) of these interfaces.` + +- `defs.go` defines interfaces like `ErrNotFound` +- `helpers.go` have util method to create a wrapper, with wrap the error without any extra message, just to indicate type + - this is actually very useful, sometimes I just want to say what type of error it is without adding extra message + +````go +// helpers.go +type errNotFound struct{ error } + +func (errNotFound) NotFound() {} + +func (e errNotFound) Cause() error { + return e.error +} + +// NotFound is a helper to create an error of the class with the same name from any error type +func NotFound(err error) error { + if err == nil { + return nil + } + return errNotFound{err} +} + +// is.go +type causer interface { + Cause() error +} + +func getImplementer(err error) error { + switch e := err.(type) { + case + ErrNotFound, + ErrInvalidParameter, + ErrConflict, + ErrUnauthorized, + ErrUnavailable, + ErrForbidden, + ErrSystem, + ErrNotModified, + ErrAlreadyExists, + ErrNotImplemented, + ErrCancelled, + ErrDeadline, + ErrDataLoss, + ErrUnknown: + return err + case causer: + return getImplementer(e.Cause()) + default: + return err + } +} + +// IsNotFound returns if the passed in error is an ErrNotFound +func IsNotFound(err error) bool { + _, ok := getImplementer(err).(ErrNotFound) + return ok +} + +// defs.go + +// ErrNotFound signals that the requested object doesn't exist +type ErrNotFound interface { + NotFound() +} + +// ErrInvalidParameter signals that the user input is invalid +type ErrInvalidParameter interface { + InvalidParameter() +} + +// ErrConflict signals that some internal state conflicts with the requested action and can't be performed. +// A change in state should be able to clear this error. +type ErrConflict interface { + Conflict() +} + +// ErrUnauthorized is used to signify that the user is not authorized to perform a specific action +type ErrUnauthorized interface { + Unauthorized() +} + +// ErrUnavailable signals that the requested action/subsystem is not available. +type ErrUnavailable interface { + Unavailable() +} + +// ErrForbidden signals that the requested action cannot be performed under any circumstances. +// When a ErrForbidden is returned, the caller should never retry the action. +type ErrForbidden interface { + Forbidden() +} + +// ErrSystem signals that some internal error occurred. +// An example of this would be a failed mount request. +type ErrSystem interface { + System() +} + +// ErrNotModified signals that an action can't be performed because it's already in the desired state +type ErrNotModified interface { + NotModified() +} + +// ErrAlreadyExists is a special case of ErrConflict which signals that the desired object already exists +type ErrAlreadyExists interface { + AlreadyExists() +} + +// ErrNotImplemented signals that the requested action/feature is not implemented on the system as configured. +type ErrNotImplemented interface { + NotImplemented() +} + +// ErrUnknown signals that the kind of error that occurred is not known. +type ErrUnknown interface { + Unknown() +} + +// ErrCancelled signals that the action was cancelled. +type ErrCancelled interface { + Cancelled() +} + +// ErrDeadline signals that the deadline was reached before the action completed. +type ErrDeadline interface { + DeadlineExceeded() +} + +// ErrDataLoss indicates that data was lost or there is data corruption. +type ErrDataLoss interface { + DataLoss() +} +```` + +## Teleport + +https://github.com/gravitational/trace/blob/master/errors.go + +- interface is defined in function (so user can't use it) ... +- has a convert system error + +````go +// ConvertSystemError converts system error to appropriate trace error +// if it is possible, otherwise, returns original error +func ConvertSystemError(err error) error { + innerError := Unwrap(err) + + if os.IsExist(innerError) { + return WrapWithMessage(&AlreadyExistsError{Message: innerError.Error()}, innerError.Error()) + } + if os.IsNotExist(innerError) { + return WrapWithMessage(&NotFoundError{Message: innerError.Error()}, innerError.Error()) + } + if os.IsPermission(innerError) { + return WrapWithMessage(&AccessDeniedError{Message: innerError.Error()}, innerError.Error()) + } + switch realErr := innerError.(type) { + case *net.OpError: + return WrapWithMessage(&ConnectionProblemError{ + Message: realErr.Error(), + Err: realErr}, realErr.Error()) + case *os.PathError: + message := fmt.Sprintf("failed to execute command %v error: %v", realErr.Path, realErr.Err) + return WrapWithMessage(&AccessDeniedError{ + Message: message, + }, message) + case x509.SystemRootsError, x509.UnknownAuthorityError: + return wrapWithDepth(&TrustError{Err: innerError}, 2) + } + if _, ok := innerError.(net.Error); ok { + return WrapWithMessage(&ConnectionProblemError{ + Message: innerError.Error(), + Err: innerError}, innerError.Error()) + } + return err +} +```` + +````go +// NotImplemented returns a new instance of NotImplementedError +func NotImplemented(message string, args ...interface{}) error { + return WrapWithMessage(&NotImplementedError{ + Message: fmt.Sprintf(message, args...), + }, message, args...) +} + +// NotImplementedError defines an error condition to describe the result +// of a call to an unimplemented API +type NotImplementedError struct { + Message string `json:"message"` +} + +// Error returns log friendly description of an error +func (e *NotImplementedError) Error() string { + return e.Message +} + +// OrigError returns original error +func (e *NotImplementedError) OrigError() error { + return e +} + +// IsNotImplementedError indicates that this error is of NotImplementedError type +func (e *NotImplementedError) IsNotImplementedError() bool { + return true +} + +// IsNotImplemented returns whether this error is of NotImplementedError type +func IsNotImplemented(e error) bool { + type ni interface { + IsNotImplementedError() bool + } + err, ok := Unwrap(e).(ni) + return ok && err.IsNotImplementedError() +} +```` + +## Dgraph + +https://github.com/dgraph-io/dgraph/blob/master/x/error.go + +- they say there are moving to x.Trace, which is using opencensus + +````go +// Check logs fatal if err != nil. +func Check(err error) { + if err != nil { + log.Fatalf("%+v", Wrap(err)) + } +} + +// Check2 acts as convenience wrapper around Check, using the 2nd argument as error. +func Check2(_ interface{}, err error) { + Check(err) +} + +// Ignore function is used to ignore errors deliberately, while keeping the +// linter happy. +func Ignore(_ error) { + // Do nothing. +} +```` + +## Sentry + +- https://github.com/getsentry/raven-go +- https://docs.sentry.io/clients/go/ +- `raven.CaptureErrorAndWait(err, nil)` block call, send error and then exit +- `raven.CaptureError(err, map[string]string{"browser": "Firefox"})` +- use `map[string]interface{}` as meta data +- serialize to json and send to server + +````go +type causer interface { + Cause() error +} + +type errWrappedWithExtra struct { + err error + extraInfo map[string]interface{} +} +```` + +- https://github.com/getsentry/raven-go/blob/master/stacktrace.go extract package etc. + +````go +// https://docs.getsentry.com/hosted/clientdev/interfaces/#failure-interfaces +type Stacktrace struct { + // Required + Frames []*StacktraceFrame `json:"frames"` +} + +type StacktraceFrame struct { + // At least one required + Filename string `json:"filename,omitempty"` + Function string `json:"function,omitempty"` + Module string `json:"module,omitempty"` + + // Optional + Lineno int `json:"lineno,omitempty"` + Colno int `json:"colno,omitempty"` + AbsolutePath string `json:"abs_path,omitempty"` + ContextLine string `json:"context_line,omitempty"` + PreContext []string `json:"pre_context,omitempty"` + PostContext []string `json:"post_context,omitempty"` + InApp bool `json:"in_app"` +} + +```` + +- https://github.com/getsentry/raven-go/blob/master/client.go + +````go +// https://docs.getsentry.com/hosted/clientdev/#building-the-json-packet +type Packet struct { + // Required + Message string `json:"message"` + + // Required, set automatically by Client.Send/Report via Packet.Init if blank + EventID string `json:"event_id"` + Project string `json:"project"` + Timestamp Timestamp `json:"timestamp"` + Level Severity `json:"level"` + Logger string `json:"logger"` + + // Optional + Platform string `json:"platform,omitempty"` + Culprit string `json:"culprit,omitempty"` + ServerName string `json:"server_name,omitempty"` + Release string `json:"release,omitempty"` + Environment string `json:"environment,omitempty"` + Tags Tags `json:"tags,omitempty"` + Modules map[string]string `json:"modules,omitempty"` + Fingerprint []string `json:"fingerprint,omitempty"` + Extra Extra `json:"extra,omitempty"` + + Interfaces []Interface `json:"-"` +} + +// CaptureErrors formats and delivers an error to the Sentry server. +// Adds a stacktrace to the packet, excluding the call to this method. +func (client *Client) CaptureError(err error, tags map[string]string, interfaces ...Interface) string { + if client == nil { + return "" + } + + if err == nil { + return "" + } + + if client.shouldExcludeErr(err.Error()) { + return "" + } + + extra := extractExtra(err) + cause := pkgErrors.Cause(err) + + packet := NewPacketWithExtra(err.Error(), extra, append(append(interfaces, client.context.interfaces()...), NewException(cause, GetOrNewStacktrace(cause, 1, 3, client.includePaths)))...) + eventID, _ := client.Capture(packet, tags) + + return eventID +} + ```` \ No newline at end of file diff --git a/errors/playground/go2/as_test.go b/errors/playground/go2/as_test.go index a8352de..2f075f2 100644 --- a/errors/playground/go2/as_test.go +++ b/errors/playground/go2/as_test.go @@ -28,7 +28,6 @@ func AsValue(v interface{}, err error) { if rv.Kind() != reflect.Ptr || rv.IsNil() { // do nothing, panic so the test is easier panic("v must be a pointer") - return } iv := reflect.Indirect(rv) log.Printf("iv is nil? %t", iv.IsNil()) From 280a43edbb6e1d3a8b2353c2f1e39323089d249d Mon Sep 17 00:00:00 2001 From: at15 Date: Mon, 17 Dec 2018 01:09:41 -0800 Subject: [PATCH 08/34] [doc][errors] Add error inspection - use `Is` for value match, i.e. io.EOF - use `IsType` for type match using a target error as type template, using reflect since we don't have polymorphism - use `As`, similar to `IsType` but assign the value, this is the `AsValue` in go 2 proprosal, it's not very useful compared with `IsType` which you can use `IsType` and `GetType` with type assertion - use `GetType` to extract type of, the logic is same as `IsType` - provide `Walk` so user don't need to care abou unwrap logic - unwrap both error chain, and error in same level (MultiError or Error List ...) --- errors/doc/design/2018-12-14-reference.md | 24 +++++ errors/doc/design/2018-12-16-design.md | 109 ++++++++++++++++++++++ errors/playground/go2/is_test.go | 104 +++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 errors/doc/design/2018-12-16-design.md create mode 100644 errors/playground/go2/is_test.go diff --git a/errors/doc/design/2018-12-14-reference.md b/errors/doc/design/2018-12-14-reference.md index 5bb4dca..889f27c 100644 --- a/errors/doc/design/2018-12-14-reference.md +++ b/errors/doc/design/2018-12-14-reference.md @@ -141,6 +141,30 @@ func Match(err1, err2 error) bool { - match type use reflect `func GetAllType(err error, v interface{}) []error` ````go +// GetAllType gets all the errors that are the same type as v. +// +// The order of the return value is the same as described in GetAll. +func GetAllType(err error, v interface{}) []error { + var result []error + + var search string + if v != nil { + search = reflect.TypeOf(v).String() + } + Walk(err, func(err error) { + var needle string + if err != nil { + needle = reflect.TypeOf(err).String() + } + + if needle == search { + result = append(result, err) + } + }) + + return result +} + // Walk walks all the wrapped errors in err and calls the callback. If // err isn't a wrapped error, this will be called once for err. If err // is a wrapped error, the callback will be called for both the wrapper diff --git a/errors/doc/design/2018-12-16-design.md b/errors/doc/design/2018-12-16-design.md new file mode 100644 index 0000000..c40ef06 --- /dev/null +++ b/errors/doc/design/2018-12-16-design.md @@ -0,0 +1,109 @@ +# 2018-12-16 Design + +This doc is the design based on the [motivation for error categorization](2018-12-10-error-categorization.md) +and [reference](2018-12-14-reference.md). It defines what will be covered in gommon v0.0.9 for gommon/errors. + +Follow the go 2 design, we have following three problems + +- error handling, too many `if err != nil` +- [error formatting](https://go.googlesource.com/proposal/+/master/design/go2draft-error-printing.md) +- [error inspection](https://go.googlesource.com/proposal/+/master/design/go2draft-error-inspection.md) + +The solutions are + +- can't solve the error handling, only we have is multi error, which is useful for validation +- since we already have stack trace, we should print it properly, use format properly + - we can also store the wrapping call site, though this should be optional, NOTE: the wrapping call site is not same as stack trace +- `Is` with unwrap, `As` need more thought, include `Walk` and other util function from [hashicorp/errorswrap](https://github.com/hashicorp/errwrap/blob/master/errwrap.go) + - contains wrapper/inspector for common error from standard library, see `ConvertSystemError` in https://github.com/gravitational/trace/blob/master/errors.go + - give high level category for errors + +Use cases + +- a docker client that retry when the error is network error +- a api server that returns validation error after calling some other API +- a cli that tells user the config file looking is not found and there is a similar file + +What should be in `errors`, what should be in `errorx` + +- `errors` is for generating, wrapping, unwrapping, inspecting error, basic formatting +- `errorx` is for advanced formatting, provide solution for common error (i.e. if it's error not found, what's the dir, what is being looking for) + +## Error inspection + +- `Is` in go 2 does follow the chain and do exact match +- `As` do type match and return the error + +For unwrapping we need to handle two things, both wrapping and multi error + +- support both `UnWrap` and `Causer` but deprecate `Causer` interface and make it not exported +- support `MutliError` when unwrap, flatten multi error unless the target error type is MultiError + +I think for gommon, consider upspin, hashicorp/errorswrap we can have the following new methods + +````go +// Is is sames as go 2 proposal, it is manly used for checking sentinel error +// It does unwrapping under the hood +func Is(err, target error) bool { + for { + if err == target { + return true + } + // TODO: check both Causer and Wrapper interface + // TODO: also consider multi error, might use a walk function + wrapper, ok := err.(Wrapper) + if !ok { + return false + } + err = wrapper.Unwrap() + if err == nil { + return false + } + } +} + +// IsType checks if err's type is same as targets using reflect +func IsType(err, target error) bool { + ttype := reflect.TypeOf(target).String() + for { + if reflect.TypeOf(err).String() == ttype { + return true + } + } +} + + +// TODO: GetType or GetTypeOf ... maybe ~~MatchType~~ (IsType already does this) +func GetTypeOf(err, target error) (error, bool) { + +} + +// TODO: this is really hard to use without ploymorphism when you want to use switch case ... +// I think IsType is a better choice for now +func As(ptrToError interface{}, err error) (ok bool) { + +} + +type WalkFunc func(error) bool // TODO: a stop sign or continue sign, this can reduce some looping + +func Walk(err error, cb WalkFunc) { + switch e := err.(type) { + case *WrappedError: // TODO: make our implementation not exported, user shouldn't use that + if cb(e) { + Walk(e.cause, cb) + } + case Wrapper: + if cb(e) { + cb(e.Unwrap()) + } + case MultiError: + errs := e.Errors() + for i := 0; i < len(errs); i++ { + // TODO: have return value from WalkFunc makes Walk also need to return a bool + } + default: + cb(e) + } +} +```` + diff --git a/errors/playground/go2/is_test.go b/errors/playground/go2/is_test.go new file mode 100644 index 0000000..3c9782d --- /dev/null +++ b/errors/playground/go2/is_test.go @@ -0,0 +1,104 @@ +package go2 + +import ( + "errors" + "fmt" + "io" + "os" + "reflect" + "testing" +) + +// test go2 Is without un wrapping logic +// +// From https://go.googlesource.com/proposal/+/master/design/go2draft-error-inspection.md + +// check if same error +func Is(err, target error) bool { + if err == target { + return true + } + return false +} + +// check if same error type, all string based error are same type (created from errors.New) +func IsType(err, target error) bool { + // found from hashicorp/errwrap + if reflect.TypeOf(err).String() == reflect.TypeOf(target).String() { + return true + } + return false +} + +// return error if it is of same type, this is quite useless because we don't have unwrap logic +func GetType(err, target error) (error, bool) { + if reflect.TypeOf(err).String() == reflect.TypeOf(target).String() { + return err, true + } + return nil, false +} + +func giveMeEOF() error { + return io.EOF +} + +func TestIs(t *testing.T) { + t.Logf("%t", Is(fmt.Errorf("EOF"), io.EOF)) // false + t.Logf("%t", Is(errors.New("EOF"), io.EOF)) // false + t.Logf("%t", Is(giveMeEOF(), io.EOF)) // true +} + +var ErrTypeString = errors.New("I am just a string") +var ErrTypePathError = &os.PathError{} + +var ErrTypeFoo = errFoo{msg: "I am just a template"} + +type errFoo struct { + msg string +} + +func (e errFoo) Error() string { + return e.msg +} + +func TestIsType(t *testing.T) { + t.Run("string", func(t *testing.T) { + t.Logf("%t", IsType(errors.New("miao"), errors.New("wang"))) // true + t.Logf("%t", IsType(fmt.Errorf("miao"), errors.New("wang"))) // true + t.Logf("%t", IsType(fmt.Errorf("miao"), ErrTypeString)) // true + t.Logf("%t", IsType(os.ErrNotExist, ErrTypeString)) // true + }) + + t.Run("struct pointer", func(t *testing.T) { + _, err := os.Open("404") //os.PathError{} + t.Logf("%t", IsType(err, ErrTypeString)) // false + t.Logf("%t", IsType(err, &os.PathError{})) // true + t.Logf("%t", IsType(err, ErrTypePathError)) // true + }) + + t.Run("struct", func(t *testing.T) { + t.Logf("%t", IsType(errFoo{"miao"}, ErrTypeFoo)) // true + }) + + t.Run("example", func(t *testing.T) { + var err error + err = errFoo{msg: "a"} + // NOTE: it can be converted to a switch case as well + if IsType(err, ErrTypeFoo) { + fooErr := err.(errFoo) + t.Logf("%s", fooErr.msg) + } + }) +} + +func TestGetType(t *testing.T) { + e, ok := GetType(errors.New("miao"), ErrTypeString) + t.Logf("%v %t", e, ok) // true + e, ok = GetType(errors.New("miao"), ErrTypePathError) + t.Logf("%v %t", e, ok) // false + _, err := os.Open("404") //os.PathError{} + e, ok = GetType(err, ErrTypeString) + t.Logf("%v %t", e, ok) // false + e, ok = GetType(err, ErrTypePathError) + t.Logf("%v %t", e, ok) // true +} From b79596de2e45e493bdb4444633ee2c6e535659d8 Mon Sep 17 00:00:00 2001 From: at15 Date: Tue, 18 Dec 2018 21:57:12 -0800 Subject: [PATCH 09/34] [errors] Only keep Unwrap in Wrapper interface - deprecate `Causer` interface --- README.md | 10 +- errors/cause.go | 52 ++++++ errors/errors.go | 183 --------------------- errors/pkg.go | 14 +- errors/wrapper.go | 160 ++++++++++++++++++ errors/{errors_test.go => wrapper_test.go} | 38 ++--- 6 files changed, 244 insertions(+), 213 deletions(-) create mode 100644 errors/cause.go delete mode 100644 errors/errors.go create mode 100644 errors/wrapper.go rename errors/{errors_test.go => wrapper_test.go} (73%) diff --git a/README.md b/README.md index 9eabb02..45661df 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ Gommon is a collection of common util libraries written in Go. It has the following components: -- [errors](errors) Typed error with context, multi error -- [generator](generator) Render go template, generate methods for logger interface based on `gommon.yml` -- [log](log) A Javaish logger for Go, application can set level for their dependencies based on package, struct -- [noodle](noodle) Embed static assets for web application with `.noodleignore` support -- [structure](structure) Bring data structure like Set etc. to Golang +- [errors](errors) error wrapping, inspection, multi error (error list), common error types +- [log](log) fine grained level control and reasonable performance +- [noodle](noodle) embed static assets for web application with `.noodleignore` support +- [generator](generator) render go template, generate methods for logger interface based on `gommon.yml` +- [structure](structure) data structure like Set etc. to go - [util](util) small utils over standard libraries utils Deprecating diff --git a/errors/cause.go b/errors/cause.go new file mode 100644 index 0000000..65d0f22 --- /dev/null +++ b/errors/cause.go @@ -0,0 +1,52 @@ +package errors + +// cause.go contains interface and unwrap func for the deprecated causer interface we adopted from pkg/errors + +// causer returns the underlying error, a error without cause should return itself. +// It is based on the private `causer` interface in pkg/errors, so errors wrapped using pkg/errors can also be handled +// Deprecated: use Wrapper interface instead, +type causer interface { + Cause() error +} + +// Cause returns root cause of the error (if any), it stops at the last error that does not implement causer interface. +// If you want get direct cause, use DirectCause. +// If error is nil, it will return nil. If error is not wrapped it will return the error itself. +// error wrapped using https://github.com/pkg/errors also satisfies this interface and can be unwrapped as well. +func Cause(err error) error { + if err == nil { + return nil + } + for err != nil { + switch err.(type) { + case causer: + err = err.(causer).Cause() + case Wrapper: + err = err.(Wrapper).Unwrap() + default: + return err + } + } + return err +} + +// RootCause is alias for Cause +func RootCause(err error) error { + return Cause(err) +} + +// DirectCause returns the direct cause of the error (if any). It does NOT follow the cause chain, +// if you want to get root cause, use Cause +func DirectCause(err error) error { + if err == nil { + return nil + } + switch err.(type) { + case causer: + return err.(causer).Cause() + case Wrapper: + return err.(Wrapper).Unwrap() + default: + return err + } +} diff --git a/errors/errors.go b/errors/errors.go deleted file mode 100644 index ed1f03d..0000000 --- a/errors/errors.go +++ /dev/null @@ -1,183 +0,0 @@ -package errors - -import "fmt" - -// Causer returns the underlying error, a error without cause should return itself. -// It is based on the private `causer` interface in pkg/errors, so errors wrapped using pkg/errors can also be handled -type Causer interface { - Cause() error -} - -// Wrapper has cause and its own error message. It is based on the private `wrapper` interface in juju/errors -type Wrapper interface { - Causer - // Message return the top level error message without concat message from its cause - // i.e. when Error() returns `invalid config: file a.json does not exist` Message() returns `invalid config` - Message() string -} - -// TracedError is error with stack trace -type TracedError interface { - fmt.Formatter - ErrorStack() *Stack -} - -// Wrap creates a WrappedError with stack and set its cause to err. -// -// If the error being wrapped is already a TracedError, Wrap will reuse its stack trace instead of creating a new one. -// The error being wrapped has deeper stack than where the Wrap function is called and is closer to the root of error. -// This is based on https://github.com/pkg/errors/pull/122 to avoid having extra interface like WithMessage and WithStack -// like https://github.com/pkg/errors does. -// -// Wrap returns nil if the error you are trying to wrap is nil, thus if it is the last error checking, you can return -// the wrap function directly in one line instead of using typical three line error check and wrap. i.e. -// -// return errors.Wrap(f.Close(), "failed to close file") -// -// if err := f.Close(); err != nil { -// return errors.Wrap(err, "failed to close file") -// } -// return nil -// -func Wrap(err error, msg string) error { - if err == nil { - return nil - } - var stack *Stack - // reuse existing stack - if t, ok := err.(TracedError); ok { - stack = t.ErrorStack() - } else { - stack = callers() - } - return &WrappedError{ - msg: msg, - cause: err, - stack: stack, - } -} - -// Wrapf is Wrap with fmt.Sprintf -func Wrapf(err error, format string, args ...interface{}) error { - // NOTE: copied from wrap instead of call Wrap due to caller - // -- copy & paste start - if err == nil { - return nil - } - var stack *Stack - // reuse existing stack - if t, ok := err.(TracedError); ok { - stack = t.ErrorStack() - } else { - stack = callers() - } - // --- copy & paste end - return &WrappedError{ - msg: fmt.Sprintf(format, args...), - cause: err, - stack: stack, - } -} - -// Cause returns root cause of the error (if any), it stops at the last error that does not implement Causer interface. -// If you want get direct cause, use DirectCause. -// If error is nil, it will return nil. If error is not wrapped it will return the error itself. -// error wrapped using https://github.com/pkg/errors also satisfies this interface and can be unwrapped as well. -func Cause(err error) error { - if err == nil { - return nil - } - for err != nil { - causer, ok := err.(Causer) - if !ok { - break - } - err = causer.Cause() - } - return err -} - -// DirectCause returns the direct cause of the error (if any). It does NOT follow the cause chain, if you want to get -// root cause, use Cause -func DirectCause(err error) error { - if err == nil { - return nil - } - causer, ok := err.(Causer) - if !ok { - return nil - } - return causer.Cause() -} - -var ( - _ error = (*FreshError)(nil) - _ TracedError = (*FreshError)(nil) -) - -type FreshError struct { - msg string - stack *Stack -} - -func (fresh *FreshError) Error() string { - return fresh.msg -} - -func (fresh *FreshError) ErrorStack() *Stack { - return fresh.stack -} - -func (fresh *FreshError) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - // TODO: print stack if s.Flag('+') - fallthrough - case 's': - s.Write([]byte(fresh.msg)) - case 'q': - // %q a double-quoted string safely escaped with Go syntax - fmt.Fprintf(s, "%q", fresh.msg) - } -} - -var ( - _ error = (*WrappedError)(nil) - _ TracedError = (*WrappedError)(nil) - _ Causer = (*WrappedError)(nil) - _ Wrapper = (*WrappedError)(nil) -) - -type WrappedError struct { - msg string - cause error - stack *Stack -} - -func (wrapped *WrappedError) Error() string { - return wrapped.msg + ErrCauseSep + wrapped.cause.Error() -} - -func (wrapped *WrappedError) ErrorStack() *Stack { - return wrapped.stack -} - -func (wrapped *WrappedError) Cause() error { - return wrapped.cause -} - -func (wrapped *WrappedError) Message() string { - return wrapped.msg -} - -func (wrapped *WrappedError) Format(s fmt.State, verb rune) { - switch verb { - case 'v': - // TODO: print stack if s.Flag('+') - fallthrough - case 's': - s.Write([]byte(wrapped.Error())) - case 'q': - fmt.Fprintf(s, "%q", wrapped.Error()) - } -} diff --git a/errors/pkg.go b/errors/pkg.go index 7a551e1..1abe517 100644 --- a/errors/pkg.go +++ b/errors/pkg.go @@ -1,7 +1,9 @@ -// Package errors provides multi error, error wrapping. It defines error category code for machine post processing -package errors // import "github.com/dyweb/gommon/errors" +// Package errors provides error wrapping, multi error and error inspection. +package errors -import "fmt" +import ( + "fmt" +) const ( // MultiErrSep is the separator used when returning a slice of errors as single line message @@ -10,9 +12,9 @@ const ( ErrCauseSep = ": " ) -// New creates a FreshError with stack +// New creates a freshError with stack trace func New(msg string) error { - return &FreshError{ + return &freshError{ msg: msg, stack: callers(), } @@ -20,7 +22,7 @@ func New(msg string) error { // Errorf is New with fmt.Sprintf func Errorf(format string, args ...interface{}) error { - return &FreshError{ + return &freshError{ msg: fmt.Sprintf(format, args...), stack: callers(), } diff --git a/errors/wrapper.go b/errors/wrapper.go new file mode 100644 index 0000000..3d7620f --- /dev/null +++ b/errors/wrapper.go @@ -0,0 +1,160 @@ +package errors + +import ( + "fmt" +) + +// wrapper.go defines interface and util method for error wrapping + +// Wrapper is based go 2 proposal, it only has an Unwrap method to returns the underlying error +type Wrapper interface { + Unwrap() error +} + +// Message return the top level error message without traverse the error chain +// i.e. when Error() returns `invalid config: file a.json does not exist` Message() returns `invalid config` +type Messenger interface { + Message() string +} + +// Tracer is error with stack trace +type Tracer interface { + Stack() *Stack +} + +// Wrap creates a wrappedError with stack and set its cause to err. +// +// If the error being wrapped is already a Tracer, Wrap will reuse its stack trace instead of creating a new one. +// The error being wrapped has deeper stack than where the Wrap function is called and is closer to the root of error. +// This is based on https://github.com/pkg/errors/pull/122 to avoid having extra interface like WithMessage and WithStack +// like https://github.com/pkg/errors does. +// +// Wrap returns nil if the error you are trying to wrap is nil, thus if it is the last error checking in a func, +// you can return the wrap function directly in one line instead of using typical three line error check and wrap. +// +// return errors.Wrap(f.Close(), "failed to close file") +// +// if err := f.Close(); err != nil { +// return errors.Wrap(err, "failed to close file") +// } +// return nil。。 +func Wrap(err error, msg string) error { + if err == nil { + return nil + } + var stack *Stack + // reuse existing stack + if t, ok := err.(Tracer); ok { + stack = t.Stack() + } else { + stack = callers() + } + return &wrappedError{ + msg: msg, + cause: err, + stack: stack, + } +} + +// Wrapf is Wrap with fmt.Sprintf +func Wrapf(err error, format string, args ...interface{}) error { + // NOTE: copied from wrap instead of call Wrap due to caller + // -- copy & paste start + if err == nil { + return nil + } + var stack *Stack + // reuse existing stack + if t, ok := err.(Tracer); ok { + stack = t.Stack() + } else { + stack = callers() + } + // --- copy & paste end + return &wrappedError{ + msg: fmt.Sprintf(format, args...), + cause: err, + stack: stack, + } +} + +var ( + _ error = (*freshError)(nil) + _ Tracer = (*freshError)(nil) + _ fmt.Formatter = (*freshError)(nil) +) + +// freshError is a root error with stack trace +type freshError struct { + msg string + stack *Stack +} + +func (fresh *freshError) Error() string { + return fresh.msg +} + +func (fresh *freshError) Stack() *Stack { + return fresh.stack +} + +func (fresh *freshError) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + // TODO: print stack if s.Flag('+') + fallthrough + case 's': + s.Write([]byte(fresh.msg)) + case 'q': + // %q a double-quoted string safely escaped with Go syntax + fmt.Fprintf(s, "%q", fresh.msg) + } +} + +var ( + _ error = (*wrappedError)(nil) + _ Tracer = (*wrappedError)(nil) + _ causer = (*wrappedError)(nil) + _ Wrapper = (*wrappedError)(nil) + _ fmt.Formatter = (*wrappedError)(nil) +) + +// wrappedError implements the Wrapper +type wrappedError struct { + msg string + cause error + stack *Stack +} + +func (wrapped *wrappedError) Error() string { + return wrapped.msg + ErrCauseSep + wrapped.cause.Error() +} + +func (wrapped *wrappedError) Stack() *Stack { + return wrapped.stack +} + +// Deprecated: use Unwrap +func (wrapped *wrappedError) Cause() error { + return wrapped.cause +} + +func (wrapped *wrappedError) Unwrap() error { + return wrapped.cause +} + +func (wrapped *wrappedError) Message() string { + return wrapped.msg +} + +func (wrapped *wrappedError) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + // TODO: print stack if s.Flag('+') + fallthrough + case 's': + s.Write([]byte(wrapped.Error())) + case 'q': + fmt.Fprintf(s, "%q", wrapped.Error()) + } +} diff --git a/errors/errors_test.go b/errors/wrapper_test.go similarity index 73% rename from errors/errors_test.go rename to errors/wrapper_test.go index 1168b72..302bfc3 100644 --- a/errors/errors_test.go +++ b/errors/wrapper_test.go @@ -16,10 +16,10 @@ func TestNew(t *testing.T) { err := errors.New("don't let me go") assert.NotNil(err) assert.Equal("don't let me go", fmt.Sprintf("%v", err)) - terr, ok := err.(errors.TracedError) + terr, ok := err.(errors.Tracer) assert.True(ok) - errors.PrintFrames(terr.ErrorStack().Frames()) - assert.Equal(3, len(terr.ErrorStack().Frames())) + errors.PrintFrames(terr.Stack().Frames()) + assert.Equal(3, len(terr.Stack().Frames())) } func TestWrap(t *testing.T) { @@ -30,28 +30,28 @@ func TestWrap(t *testing.T) { errw := errors.Wrap(os.ErrClosed, "can't open closed file") assert.Equal("can't open closed file: file already closed", fmt.Sprintf("%v", errw)) - terr, ok := errw.(errors.TracedError) + terr, ok := errw.(errors.Tracer) assert.True(ok) if testutil.Dump().B() { - errors.PrintFrames(terr.ErrorStack().Frames()) + errors.PrintFrames(terr.Stack().Frames()) } - assert.Equal(3, len(terr.ErrorStack().Frames())) + assert.Equal(3, len(terr.Stack().Frames())) errw = errors.Wrap(freshErr(), "wrap again") - terr, ok = errw.(errors.TracedError) + terr, ok = errw.(errors.Tracer) assert.True(ok) if testutil.Dump().B() { - errors.PrintFrames(terr.ErrorStack().Frames()) + errors.PrintFrames(terr.Stack().Frames()) } - assert.Equal(4, len(terr.ErrorStack().Frames())) + assert.Equal(4, len(terr.Stack().Frames())) errw = errors.Wrap(wrappedStdErr(), "wrap again") - terr, ok = errw.(errors.TracedError) + terr, ok = errw.(errors.Tracer) assert.True(ok) if testutil.Dump().B() { - errors.PrintFrames(terr.ErrorStack().Frames()) + errors.PrintFrames(terr.Stack().Frames()) } - assert.Equal(4, len(terr.ErrorStack().Frames())) + assert.Equal(4, len(terr.Stack().Frames())) } func TestWrapf(t *testing.T) { @@ -65,21 +65,21 @@ func TestWrapf(t *testing.T) { // wrap standard error attach stack errw := errors.Wrapf(os.ErrClosed, "can't open closed file %s", "gommon.yml") assert.Equal("can't open closed file gommon.yml: file already closed", fmt.Sprintf("%v", errw)) - terr, ok := errw.(errors.TracedError) + terr, ok := errw.(errors.Tracer) assert.True(ok) if testutil.Dump().B() { - errors.PrintFrames(terr.ErrorStack().Frames()) + errors.PrintFrames(terr.Stack().Frames()) } - assert.Equal(3, len(terr.ErrorStack().Frames())) + assert.Equal(3, len(terr.Stack().Frames())) // wrap fresh error reuse stack ferr := freshErr() errw = errors.Wrapf(ferr, "wrap again %d", 2) - assert.Equal(ferr.(errors.TracedError).ErrorStack(), errw.(errors.TracedError).ErrorStack()) + assert.Equal(ferr.(errors.Tracer).Stack(), errw.(errors.Tracer).Stack()) // wrap wrapped error reuse stack errww := errors.Wrapf(errw, "wrap again %d", 3) - assert.Equal(errw.(errors.TracedError).ErrorStack(), errww.(errors.TracedError).ErrorStack()) + assert.Equal(errw.(errors.Tracer).Stack(), errww.(errors.Tracer).Stack()) } func TestCause(t *testing.T) { @@ -102,7 +102,7 @@ func TestDirectCause(t *testing.T) { assert.Equal(os.ErrClosed, errors.Cause(errww)) assert.Equal(os.ErrClosed, errors.Cause(errw)) assert.NotEqual(os.ErrClosed, errors.DirectCause(errww)) - assert.Equal("can't open closed file", errors.DirectCause(errww).(errors.Wrapper).Message()) + assert.Equal("can't open closed file", errors.DirectCause(errww).(errors.Messenger).Message()) } func TestWrappedError_Message(t *testing.T) { @@ -110,7 +110,7 @@ func TestWrappedError_Message(t *testing.T) { msg := "mewo" errw := errors.Wrap(os.ErrClosed, msg) - assert.Equal(msg, errw.(errors.Wrapper).Message()) + assert.Equal(msg, errw.(errors.Messenger).Message()) } func ExampleWrap() { From 9fa7a4b2f24b5792ca05b1fc236cce4946c72579 Mon Sep 17 00:00:00 2001 From: at15 Date: Tue, 18 Dec 2018 22:11:30 -0800 Subject: [PATCH 10/34] [errors] Add Ignore and Ignore2 - learned from dgraph x/error.go --- errors/pkg.go | 16 +++++++++++++++- errors/wrapper.go | 10 +++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/errors/pkg.go b/errors/pkg.go index 1abe517..305c80d 100644 --- a/errors/pkg.go +++ b/errors/pkg.go @@ -1,4 +1,4 @@ -// Package errors provides error wrapping, multi error and error inspection. +// Package errors provides multi error, wrapping and inspection. package errors import ( @@ -27,3 +27,17 @@ func Errorf(format string, args ...interface{}) error { stack: callers(), } } + +// Ignore swallow the error, you should NOT use it unless you know what you are doing (make the lint tool happy) +// It is inspired by dgraph x/error.go +func Ignore(_ error) { + // do nothing +} + +// Ignore2 allow you to ignore return value and error, it is useful for io.Writer like functions +// It is also inspired by dgraph x/error.go +func Ignore2(_ interface{}, _ error) { + // do nothing +} + +// yeah, there is no Ignore3 diff --git a/errors/wrapper.go b/errors/wrapper.go index 3d7620f..d67980d 100644 --- a/errors/wrapper.go +++ b/errors/wrapper.go @@ -6,7 +6,7 @@ import ( // wrapper.go defines interface and util method for error wrapping -// Wrapper is based go 2 proposal, it only has an Unwrap method to returns the underlying error +// Wrapper is based on go 2 proposal, it only has an Unwrap method to returns the underlying error type Wrapper interface { Unwrap() error } @@ -104,10 +104,10 @@ func (fresh *freshError) Format(s fmt.State, verb rune) { // TODO: print stack if s.Flag('+') fallthrough case 's': - s.Write([]byte(fresh.msg)) + Ignore2(s.Write([]byte(fresh.msg))) case 'q': // %q a double-quoted string safely escaped with Go syntax - fmt.Fprintf(s, "%q", fresh.msg) + Ignore2(fmt.Fprintf(s, "%q", fresh.msg)) } } @@ -153,8 +153,8 @@ func (wrapped *wrappedError) Format(s fmt.State, verb rune) { // TODO: print stack if s.Flag('+') fallthrough case 's': - s.Write([]byte(wrapped.Error())) + Ignore2(s.Write([]byte(wrapped.Error()))) case 'q': - fmt.Fprintf(s, "%q", wrapped.Error()) + Ignore2(fmt.Fprintf(s, "%q", wrapped.Error())) } } From fa52397a320f0a3ea379b465b2e74262bd2ea207 Mon Sep 17 00:00:00 2001 From: at15 Date: Wed, 19 Dec 2018 00:33:20 -0800 Subject: [PATCH 11/34] [errors] Change Stack to interface return frames - previously Stack is a struct that has no exported fields, since the only thing it does return []runtime.Frame, this should be the interface --- errors/cause.go | 2 +- errors/multi.go | 2 ++ errors/stack.go | 22 +++++++++++---- errors/stack_test.go | 7 ++--- errors/wrapper.go | 25 +++++++++++------ errors/wrapper_test.go | 64 +++++++++++++++++++----------------------- 6 files changed, 68 insertions(+), 54 deletions(-) diff --git a/errors/cause.go b/errors/cause.go index 65d0f22..1c4b4dd 100644 --- a/errors/cause.go +++ b/errors/cause.go @@ -4,7 +4,7 @@ package errors // causer returns the underlying error, a error without cause should return itself. // It is based on the private `causer` interface in pkg/errors, so errors wrapped using pkg/errors can also be handled -// Deprecated: use Wrapper interface instead, +// Deprecated: Use Wrapper interface instead, type causer interface { Cause() error } diff --git a/errors/multi.go b/errors/multi.go index e3b03f4..d7389e0 100644 --- a/errors/multi.go +++ b/errors/multi.go @@ -6,6 +6,8 @@ import ( "sync" ) +// TODO: add interface for error list so we can do flatten when unwrap, Errors.Errors is not good, or maybe just an not exported interface + // MultiErr is a slice of Error. It has two implementation, NewMultiErr return a non thread safe version, // NewMultiErrSafe return a thread safe version using mutex type MultiErr interface { diff --git a/errors/stack.go b/errors/stack.go index b941dd9..b0b4350 100644 --- a/errors/stack.go +++ b/errors/stack.go @@ -9,19 +9,31 @@ import ( // TODO: what if there is recursive function .... I guess any depth is not enough then? what is the limit on go runtime's side? const depth = 20 -type Stack struct { +type Stack interface { + Frames() []runtime.Frame +} + +// lazyStack does not loop the frames until its Frames method is called, +// It will the existing slice of frames if presented, so you can use a Stack interface to create a lazyStack +// s := lazyStack{frames: e.(Stack).Frames()} +type lazyStack struct { p *runtime.Frames depth int frames []runtime.Frame } -func (s *Stack) Frames() []runtime.Frame { - if s == nil || s.p == nil { +func (s *lazyStack) Frames() []runtime.Frame { + if s == nil { return nil } + // we can init lazyStack using exist frames if len(s.frames) != 0 { return s.frames } + // no existing stack and no pointer to frames, can't move on + if s.p == nil { + return nil + } frames := make([]runtime.Frame, 0, s.depth) for { frame, more := s.p.Next() @@ -36,7 +48,7 @@ func (s *Stack) Frames() []runtime.Frame { // TODO: handling print stack -func callers() *Stack { +func callers() *lazyStack { pcs := make([]uintptr, depth) // 3 skips runtime.Callers itself, callers function, and the function that creates error, i.e New, Errorf //more true | runtime.Callers /home/at15/app/go/src/runtime/extern.go:212 @@ -44,7 +56,7 @@ func callers() *Stack { //more true | github.com/dyweb/gommon/errors.New /home/at15/workspace/src/github.com/dyweb/gommon/errors/pkg.go:16 n := runtime.Callers(3, pcs) frames := runtime.CallersFrames(pcs[:n]) - return &Stack{ + return &lazyStack{ p: frames, depth: n, } diff --git a/errors/stack_test.go b/errors/stack_test.go index 8918f92..b93835a 100644 --- a/errors/stack_test.go +++ b/errors/stack_test.go @@ -3,14 +3,13 @@ package errors import ( "testing" - asst "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestStack_Frames(t *testing.T) { - assert := asst.New(t) s := callers() // when Frames() is not called, it is empty - assert.Equal(0, len(s.frames)) + assert.Equal(t, 0, len(s.frames)) PrintFrames(s.Frames()) - assert.Equal(s.depth, len(s.Frames())) + assert.Equal(t, s.depth, len(s.Frames())) } diff --git a/errors/wrapper.go b/errors/wrapper.go index d67980d..a78b2f0 100644 --- a/errors/wrapper.go +++ b/errors/wrapper.go @@ -19,7 +19,7 @@ type Messenger interface { // Tracer is error with stack trace type Tracer interface { - Stack() *Stack + Stack() Stack } // Wrap creates a wrappedError with stack and set its cause to err. @@ -42,10 +42,15 @@ func Wrap(err error, msg string) error { if err == nil { return nil } - var stack *Stack + if err == nil { + return nil + } + var stack *lazyStack // reuse existing stack if t, ok := err.(Tracer); ok { - stack = t.Stack() + stack = &lazyStack{ + frames: t.Stack().Frames(), + } } else { stack = callers() } @@ -63,10 +68,12 @@ func Wrapf(err error, format string, args ...interface{}) error { if err == nil { return nil } - var stack *Stack + var stack *lazyStack // reuse existing stack if t, ok := err.(Tracer); ok { - stack = t.Stack() + stack = &lazyStack{ + frames: t.Stack().Frames(), + } } else { stack = callers() } @@ -87,14 +94,14 @@ var ( // freshError is a root error with stack trace type freshError struct { msg string - stack *Stack + stack *lazyStack } func (fresh *freshError) Error() string { return fresh.msg } -func (fresh *freshError) Stack() *Stack { +func (fresh *freshError) Stack() Stack { return fresh.stack } @@ -123,14 +130,14 @@ var ( type wrappedError struct { msg string cause error - stack *Stack + stack *lazyStack } func (wrapped *wrappedError) Error() string { return wrapped.msg + ErrCauseSep + wrapped.cause.Error() } -func (wrapped *wrappedError) Stack() *Stack { +func (wrapped *wrappedError) Stack() Stack { return wrapped.stack } diff --git a/errors/wrapper_test.go b/errors/wrapper_test.go index 302bfc3..f76e1b9 100644 --- a/errors/wrapper_test.go +++ b/errors/wrapper_test.go @@ -5,112 +5,106 @@ import ( "os" "testing" - asst "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" "github.com/dyweb/gommon/errors" "github.com/dyweb/gommon/util/testutil" ) func TestNew(t *testing.T) { - assert := asst.New(t) err := errors.New("don't let me go") - assert.NotNil(err) - assert.Equal("don't let me go", fmt.Sprintf("%v", err)) + assert.NotNil(t, err) + assert.Equal(t, "don't let me go", err.Error()) + assert.Equal(t, "don't let me go", fmt.Sprintf("%v", err)) terr, ok := err.(errors.Tracer) - assert.True(ok) + assert.True(t, ok) errors.PrintFrames(terr.Stack().Frames()) - assert.Equal(3, len(terr.Stack().Frames())) + assert.Equal(t, 3, len(terr.Stack().Frames())) } func TestWrap(t *testing.T) { - assert := asst.New(t) n := errors.Wrap(nil, "nothing") - assert.Nil(n) + assert.Nil(t, n) errw := errors.Wrap(os.ErrClosed, "can't open closed file") - assert.Equal("can't open closed file: file already closed", fmt.Sprintf("%v", errw)) + assert.Equal(t, "can't open closed file: file already closed", fmt.Sprintf("%v", errw)) terr, ok := errw.(errors.Tracer) - assert.True(ok) + assert.True(t, ok) if testutil.Dump().B() { errors.PrintFrames(terr.Stack().Frames()) } - assert.Equal(3, len(terr.Stack().Frames())) + assert.Equal(t, 3, len(terr.Stack().Frames())) errw = errors.Wrap(freshErr(), "wrap again") terr, ok = errw.(errors.Tracer) - assert.True(ok) + assert.True(t, ok) if testutil.Dump().B() { errors.PrintFrames(terr.Stack().Frames()) } - assert.Equal(4, len(terr.Stack().Frames())) + assert.Equal(t, 4, len(terr.Stack().Frames())) errw = errors.Wrap(wrappedStdErr(), "wrap again") terr, ok = errw.(errors.Tracer) - assert.True(ok) + assert.True(t, ok) if testutil.Dump().B() { errors.PrintFrames(terr.Stack().Frames()) } - assert.Equal(4, len(terr.Stack().Frames())) + assert.Equal(t, 4, len(terr.Stack().Frames())) } func TestWrapf(t *testing.T) { // TODO: need to ensure Wrap and Wrapf are same ... - assert := asst.New(t) // wrap nil return nil n := errors.Wrapf(nil, "nothing %d", 2) - assert.Nil(n) + assert.Nil(t, n) // wrap standard error attach stack errw := errors.Wrapf(os.ErrClosed, "can't open closed file %s", "gommon.yml") - assert.Equal("can't open closed file gommon.yml: file already closed", fmt.Sprintf("%v", errw)) + assert.Equal(t, "can't open closed file gommon.yml: file already closed", fmt.Sprintf("%v", errw)) terr, ok := errw.(errors.Tracer) - assert.True(ok) + assert.True(t, ok) if testutil.Dump().B() { errors.PrintFrames(terr.Stack().Frames()) } - assert.Equal(3, len(terr.Stack().Frames())) + assert.Equal(t, 3, len(terr.Stack().Frames())) // wrap fresh error reuse stack ferr := freshErr() errw = errors.Wrapf(ferr, "wrap again %d", 2) - assert.Equal(ferr.(errors.Tracer).Stack(), errw.(errors.Tracer).Stack()) + // NOTE: since we changed Stack to interface, we can no longer compare the underlying pointer to struct directly... + assert.Equal(t, len(ferr.(errors.Tracer).Stack().Frames()), len(errw.(errors.Tracer).Stack().Frames())) // wrap wrapped error reuse stack errww := errors.Wrapf(errw, "wrap again %d", 3) - assert.Equal(errw.(errors.Tracer).Stack(), errww.(errors.Tracer).Stack()) + assert.Equal(t, len(errw.(errors.Tracer).Stack().Frames()), len(errww.(errors.Tracer).Stack().Frames())) } func TestCause(t *testing.T) { - assert := asst.New(t) n := errors.Wrap(nil, "nothing") - assert.Nil(errors.Cause(n)) + assert.Nil(t, errors.Cause(n)) errw := errors.Wrap(os.ErrClosed, "can't open closed file") - assert.Equal(os.ErrClosed, errors.Cause(errw)) + assert.Equal(t, os.ErrClosed, errors.Cause(errw)) errww := errors.Wrap(errw, "wrap again") - assert.Equal(os.ErrClosed, errors.Cause(errww)) + assert.Equal(t, os.ErrClosed, errors.Cause(errww)) } func TestDirectCause(t *testing.T) { - assert := asst.New(t) - errw := errors.Wrap(os.ErrClosed, "can't open closed file") errww := errors.Wrap(errw, "wrap again") - assert.Equal(os.ErrClosed, errors.Cause(errww)) - assert.Equal(os.ErrClosed, errors.Cause(errw)) - assert.NotEqual(os.ErrClosed, errors.DirectCause(errww)) - assert.Equal("can't open closed file", errors.DirectCause(errww).(errors.Messenger).Message()) + assert.Equal(t, os.ErrClosed, errors.Cause(errww)) + assert.Equal(t, os.ErrClosed, errors.Cause(errw)) + assert.NotEqual(t, os.ErrClosed, errors.DirectCause(errww)) + assert.Equal(t, "can't open closed file", errors.DirectCause(errww).(errors.Messenger).Message()) } func TestWrappedError_Message(t *testing.T) { - assert := asst.New(t) - msg := "mewo" errw := errors.Wrap(os.ErrClosed, msg) - assert.Equal(msg, errw.(errors.Messenger).Message()) + assert.Equal(t, msg, errw.(errors.Messenger).Message()) } func ExampleWrap() { From 7342edd48ee06de448a46fee96863a9187693332 Mon Sep 17 00:00:00 2001 From: at15 Date: Wed, 19 Dec 2018 22:54:35 -0800 Subject: [PATCH 12/34] [errors] Add Is, IsType, GetType - `Is` do exact value compare and unwrap error chain - `IsType` use reflection to check error type - `GetType` returns the matched result --- errors/cause.go | 18 +++---- errors/cause_test.go | 20 ++++++++ errors/multi.go | 10 +++- errors/types.go | 2 + errors/wrapper.go | 108 +++++++++++++++++++++++++++++++++++++++++ errors/wrapper_test.go | 105 ++++++++++++++++++++++++++++++++++----- 6 files changed, 239 insertions(+), 24 deletions(-) create mode 100644 errors/cause_test.go diff --git a/errors/cause.go b/errors/cause.go index 1c4b4dd..21bf913 100644 --- a/errors/cause.go +++ b/errors/cause.go @@ -19,10 +19,10 @@ func Cause(err error) error { } for err != nil { switch err.(type) { - case causer: - err = err.(causer).Cause() case Wrapper: err = err.(Wrapper).Unwrap() + case causer: + err = err.(causer).Cause() default: return err } @@ -30,22 +30,18 @@ func Cause(err error) error { return err } -// RootCause is alias for Cause -func RootCause(err error) error { - return Cause(err) -} - -// DirectCause returns the direct cause of the error (if any). It does NOT follow the cause chain, -// if you want to get root cause, use Cause +// DirectCause returns the direct cause of the error (if any). +// It does NOT follow the cause chain all the way down, just the first one (if any), +// If you want to get root cause, use Cause func DirectCause(err error) error { if err == nil { return nil } switch err.(type) { - case causer: - return err.(causer).Cause() case Wrapper: return err.(Wrapper).Unwrap() + case causer: + return err.(causer).Cause() default: return err } diff --git a/errors/cause_test.go b/errors/cause_test.go new file mode 100644 index 0000000..cc5b6b5 --- /dev/null +++ b/errors/cause_test.go @@ -0,0 +1,20 @@ +package errors_test + +import ( + "os" + "testing" + + "github.com/dyweb/gommon/errors" + "github.com/stretchr/testify/assert" +) + +func TestCause(t *testing.T) { + n := errors.Wrap(nil, "nothing") + assert.Nil(t, errors.Cause(n)) + + errw := errors.Wrap(os.ErrClosed, "can't open closed file") + assert.Equal(t, os.ErrClosed, errors.Cause(errw)) + + errww := errors.Wrap(errw, "wrap again") + assert.Equal(t, os.ErrClosed, errors.Cause(errww)) +} diff --git a/errors/multi.go b/errors/multi.go index d7389e0..be133d6 100644 --- a/errors/multi.go +++ b/errors/multi.go @@ -6,7 +6,15 @@ import ( "sync" ) -// TODO: add interface for error list so we can do flatten when unwrap, Errors.Errors is not good, or maybe just an not exported interface +// ErrorList is a list of errors that do not fit into the Wrapper model +// because they are at same level and don't have direct causal relationships. +// For example, a user request that lacks both username and password can have two parallel errors. +// +// The interface is mainly used for error unwrapping, for create error list, use MultiErr +// TODO: a better name than ErrorList +type ErrorList interface { + Errors() []error +} // MultiErr is a slice of Error. It has two implementation, NewMultiErr return a non thread safe version, // NewMultiErrSafe return a thread safe version using mutex diff --git a/errors/types.go b/errors/types.go index b121a37..aefee35 100644 --- a/errors/types.go +++ b/errors/types.go @@ -4,6 +4,8 @@ import "runtime" // NOTE: this is not the file that defines a sort of structs .... // TODO: error types, user, dev, std, lib, grpc etc. see https://github.com/dyweb/gommon/issues/66 +// TODO: Is function should do the unwrapping as well +// TODO: should consider put it into another package like errort func IsRuntimeError(err error) bool { _, ok := err.(runtime.Error) diff --git a/errors/wrapper.go b/errors/wrapper.go index a78b2f0..5fa5495 100644 --- a/errors/wrapper.go +++ b/errors/wrapper.go @@ -2,6 +2,7 @@ package errors import ( "fmt" + "reflect" ) // wrapper.go defines interface and util method for error wrapping @@ -42,6 +43,7 @@ func Wrap(err error, msg string) error { if err == nil { return nil } + // NOTE: keep the following in sync with Wrapf if err == nil { return nil } @@ -85,6 +87,112 @@ func Wrapf(err error, format string, args ...interface{}) error { } } +// Is walks the error chain and do direct compare. +// It should be used for checking sentinel errors like io.EOF +// It returns true on the first match. +// It returns false when there is no match. +// +// It unwraps both wrapped error and multi error +func Is(err, target error) bool { + for { + // TODO: put this before nil check allow Is(nil, nil) returns true, not sure if this is desired behaviour + if err == target { + return true + } + if err == nil { + return false + } + // support both Causer from pkg/errors and Wrapper from go 2 proposal + switch err.(type) { + case Wrapper: + err = err.(Wrapper).Unwrap() + case causer: + err = err.(causer).Cause() + case ErrorList: + // support multi error + errs := err.(ErrorList).Errors() + for i := 0; i < len(errs); i++ { + if Is(errs[i], target) { + return true + } + } + return false // NOTE: without the return we will be looping forever because err is not updated + default: + return false + } + } +} + +// IsType walks the error chain and match by type using reflect. +// It only returns match result, if you need the matched error, use GetType +// +// It should be used for checking errors that defined their own types, +// errors created using errors.New, errors.Errof should NOT be checked using this method +// because they have same type, string if you are using standard library, freshError if you are using gommon/errors +// +// It calls IsTypeOf to reduce the overhead of calling reflect on target error +func IsType(err, target error) bool { + _, ok := GetType(err, target) + return ok +} + +// IsTypeOf requires user to call reflect.TypeOf(exampleErr).String() as the type string +func IsTypeOf(err error, tpe string) bool { + _, ok := GetTypeOf(err, tpe) + return ok +} + +// GetType walks the error chain and match by type using reflect, +// It returns the matched error and match result. +// You still need to do a type conversion on the returned error. +// +// It calls GetTypeOf to reduce the overhead of calling reflect on target error +func GetType(err, target error) (matched error, ok bool) { + if err == nil || target == nil { + return nil, false + } + return GetTypeOf(err, reflect.TypeOf(target).String()) +} + +// GetTypeOf requires user to call reflect.TypeOf(exampleErr).String() as the type string +func GetTypeOf(err error, tpe string) (error, bool) { + if err == nil { + return nil, false + } + for { + if err == nil { + return nil, false + } + if reflect.TypeOf(err).String() == tpe { + return err, true + } + switch err.(type) { + case Wrapper: + err = err.(Wrapper).Unwrap() + case causer: + err = err.(causer).Cause() + case ErrorList: + errs := err.(ErrorList).Errors() + for i := 0; i < len(errs); i++ { + m, ok := GetTypeOf(errs[i], tpe) + if ok { + return m, true + } + } + return nil, false + default: + return nil, false + } + } +} + +// AsValue is in go 2 proposal as workaround if go 2 does not have polymorphism, +// however, it's pretty hard to use and user can have error easily, +// we decided to use GetType +//func AsValue(val interface{}, err error) bool { +// +//} + var ( _ error = (*freshError)(nil) _ Tracer = (*freshError)(nil) diff --git a/errors/wrapper_test.go b/errors/wrapper_test.go index f76e1b9..a0f11a8 100644 --- a/errors/wrapper_test.go +++ b/errors/wrapper_test.go @@ -23,7 +23,6 @@ func TestNew(t *testing.T) { } func TestWrap(t *testing.T) { - n := errors.Wrap(nil, "nothing") assert.Nil(t, n) @@ -81,17 +80,6 @@ func TestWrapf(t *testing.T) { assert.Equal(t, len(errw.(errors.Tracer).Stack().Frames()), len(errww.(errors.Tracer).Stack().Frames())) } -func TestCause(t *testing.T) { - n := errors.Wrap(nil, "nothing") - assert.Nil(t, errors.Cause(n)) - - errw := errors.Wrap(os.ErrClosed, "can't open closed file") - assert.Equal(t, os.ErrClosed, errors.Cause(errw)) - - errww := errors.Wrap(errw, "wrap again") - assert.Equal(t, os.ErrClosed, errors.Cause(errww)) -} - func TestDirectCause(t *testing.T) { errw := errors.Wrap(os.ErrClosed, "can't open closed file") errww := errors.Wrap(errw, "wrap again") @@ -101,6 +89,99 @@ func TestDirectCause(t *testing.T) { assert.Equal(t, "can't open closed file", errors.DirectCause(errww).(errors.Messenger).Message()) } +func TestIs(t *testing.T) { + t.Run("flat", func(t *testing.T) { + assert.False(t, errors.Is(nil, os.ErrClosed), "nil does not match sentinel error") + assert.True(t, errors.Is(os.ErrClosed, os.ErrClosed), "sentinel errors match themselves") + assert.False(t, errors.Is(os.ErrNotExist, os.ErrClosed), "different error value should not match") + }) + + t.Run("unwrap wrapper", func(t *testing.T) { + errw := errors.Wrap(os.ErrClosed, "can't read config") + assert.True(t, errors.Is(errw, os.ErrClosed), "unwrap wrapped error") + + errww := errors.Wrap(errw, "just wrap again") + assert.True(t, errors.Is(errww, os.ErrClosed), "unwrap wrapped error") + }) + + t.Run("unwrap multi error", func(t *testing.T) { + merr := errors.NewMultiErr() + merr.Append(os.ErrNotExist) + merr.Append(os.ErrClosed) + assert.True(t, errors.Is(merr, os.ErrClosed)) + assert.True(t, errors.Is(merr, os.ErrNotExist)) + assert.False(t, errors.Is(merr, os.ErrNoDeadline)) + }) + + t.Run("unwrap wrapper inside multi", func(t *testing.T) { + merr := errors.NewMultiErr() + merr.Append(errors.Wrap(os.ErrNotExist, "can't read config")) + merr.Append(os.ErrClosed) + assert.True(t, errors.Is(merr, os.ErrClosed)) + assert.True(t, errors.Is(merr, os.ErrNotExist)) + }) + + t.Run("unwrap multi inside wrapper", func(t *testing.T) { + merr := errors.NewMultiErr() + merr.Append(errors.Wrap(os.ErrNotExist, "can't read config")) + merr.Append(os.ErrClosed) + errw := errors.Wrap(merr, "just wrap it") + assert.True(t, errors.Is(errw, os.ErrClosed)) + assert.True(t, errors.Is(errw, os.ErrNotExist)) + }) +} + +func TestIsType(t *testing.T) { + t.Run("flat", func(t *testing.T) { + assert.True(t, errors.IsType(&os.PathError{}, &os.PathError{})) + assert.False(t, errors.IsType(errors.New("foo"), &os.PathError{})) + }) + + t.Run("unwrap wrapper", func(t *testing.T) { + errw := errors.Wrap(&os.PathError{Op: "open"}, "can't read config") + assert.True(t, errors.IsType(errw, &os.PathError{})) + assert.False(t, errors.IsType(errw, errors.New("foo"))) + }) + + t.Run("unwrap error inside multi", func(t *testing.T) { + merr := errors.NewMultiErr() + merr.Append(&os.PathError{Op: "open"}) + merr.Append(os.ErrNotExist) + assert.True(t, errors.IsType(merr, &os.PathError{})) + }) + + t.Run("unwrap wrapper inside multi", func(t *testing.T) { + errw := errors.Wrap(&os.PathError{Op: "open"}, "can't read config") + merr := errors.NewMultiErr() + merr.Append(errw) + assert.True(t, errors.IsType(errw, &os.PathError{})) + }) +} + +func TestGetType(t *testing.T) { + t.Run("flat", func(t *testing.T) { + e := &os.PathError{Op: "open"} + err, ok := errors.GetType(e, &os.PathError{}) + assert.True(t, ok) + assert.Equal(t, "open", err.(*os.PathError).Op) + }) + + t.Run("unwrap wrapper", func(t *testing.T) { + errw := errors.Wrap(&os.PathError{Op: "open"}, "can't read config") + err, ok := errors.GetType(errw, &os.PathError{}) + assert.True(t, ok) + assert.Equal(t, "open", err.(*os.PathError).Op) + }) + + t.Run("unwrap error inside multi", func(t *testing.T) { + merr := errors.NewMultiErr() + merr.Append(&os.PathError{Op: "open"}) + err, ok := errors.GetType(merr, &os.PathError{}) + assert.True(t, ok) + assert.Equal(t, "open", err.(*os.PathError).Op) + }) +} + func TestWrappedError_Message(t *testing.T) { msg := "mewo" errw := errors.Wrap(os.ErrClosed, msg) From 7367987f475c7272389ceb812ff1467fc0923aa9 Mon Sep 17 00:00:00 2001 From: at15 Date: Fri, 21 Dec 2018 01:16:46 -0800 Subject: [PATCH 13/34] [errors] Move `Is` `GetType` to inspect.go - they were in wrapper.go because they do unwrap, however when inspecting, we not only unwrap wrapped error, but also iterate error list (MuliError) --- errors/cause_test.go | 9 ++++ errors/inspect.go | 111 +++++++++++++++++++++++++++++++++++++++++ errors/inspect_test.go | 102 +++++++++++++++++++++++++++++++++++++ errors/wrapper.go | 110 +--------------------------------------- errors/wrapper_test.go | 102 ------------------------------------- 5 files changed, 224 insertions(+), 210 deletions(-) create mode 100644 errors/inspect.go create mode 100644 errors/inspect_test.go diff --git a/errors/cause_test.go b/errors/cause_test.go index cc5b6b5..f46fa1b 100644 --- a/errors/cause_test.go +++ b/errors/cause_test.go @@ -18,3 +18,12 @@ func TestCause(t *testing.T) { errww := errors.Wrap(errw, "wrap again") assert.Equal(t, os.ErrClosed, errors.Cause(errww)) } + +func TestDirectCause(t *testing.T) { + errw := errors.Wrap(os.ErrClosed, "can't open closed file") + errww := errors.Wrap(errw, "wrap again") + assert.Equal(t, os.ErrClosed, errors.Cause(errww)) + assert.Equal(t, os.ErrClosed, errors.Cause(errw)) + assert.NotEqual(t, os.ErrClosed, errors.DirectCause(errww)) + assert.Equal(t, "can't open closed file", errors.DirectCause(errww).(errors.Messenger).Message()) +} diff --git a/errors/inspect.go b/errors/inspect.go new file mode 100644 index 0000000..14144b3 --- /dev/null +++ b/errors/inspect.go @@ -0,0 +1,111 @@ +package errors + +import "reflect" + +// inspect.go defines functions for inspecting wrapped error or error list + +// Is walks the error chain and do direct compare. +// It should be used for checking sentinel errors like io.EOF +// It returns true on the first match. +// It returns false when there is no match. +// +// It unwraps both wrapped error and multi error +func Is(err, target error) bool { + for { + // TODO: put this before nil check allow Is(nil, nil) returns true, not sure if this is desired behaviour + if err == target { + return true + } + if err == nil { + return false + } + // support both Causer from pkg/errors and Wrapper from go 2 proposal + switch err.(type) { + case Wrapper: + err = err.(Wrapper).Unwrap() + case causer: + err = err.(causer).Cause() + case ErrorList: + // support multi error + errs := err.(ErrorList).Errors() + for i := 0; i < len(errs); i++ { + if Is(errs[i], target) { + return true + } + } + return false // NOTE: without the return we will be looping forever because err is not updated + default: + return false + } + } +} + +// IsType walks the error chain and match by type using reflect. +// It only returns match result, if you need the matched error, use GetType +// +// It should be used for checking errors that defined their own types, +// errors created using errors.New, errors.Errof should NOT be checked using this method +// because they have same type, string if you are using standard library, freshError if you are using gommon/errors +// +// It calls IsTypeOf to reduce the overhead of calling reflect on target error +func IsType(err, target error) bool { + _, ok := GetType(err, target) + return ok +} + +// IsTypeOf requires user to call reflect.TypeOf(exampleErr).String() as the type string +func IsTypeOf(err error, tpe string) bool { + _, ok := GetTypeOf(err, tpe) + return ok +} + +// GetType walks the error chain and match by type using reflect, +// It returns the matched error and match result. +// You still need to do a type conversion on the returned error. +// +// It calls GetTypeOf to reduce the overhead of calling reflect on target error +func GetType(err, target error) (matched error, ok bool) { + if err == nil || target == nil { + return nil, false + } + return GetTypeOf(err, reflect.TypeOf(target).String()) +} + +// GetTypeOf requires user to call reflect.TypeOf(exampleErr).String() as the type string +func GetTypeOf(err error, tpe string) (error, bool) { + if err == nil { + return nil, false + } + for { + if err == nil { + return nil, false + } + if reflect.TypeOf(err).String() == tpe { + return err, true + } + switch err.(type) { + case Wrapper: + err = err.(Wrapper).Unwrap() + case causer: + err = err.(causer).Cause() + case ErrorList: + errs := err.(ErrorList).Errors() + for i := 0; i < len(errs); i++ { + m, ok := GetTypeOf(errs[i], tpe) + if ok { + return m, true + } + } + return nil, false + default: + return nil, false + } + } +} + +// AsValue is in go 2 proposal as workaround if go 2 does not have polymorphism, +// however, it's pretty hard to use and user can have error easily, +// we decided to use GetType +//func AsValue(val interface{}, err error) bool { +// +//} diff --git a/errors/inspect_test.go b/errors/inspect_test.go new file mode 100644 index 0000000..0d2c8d0 --- /dev/null +++ b/errors/inspect_test.go @@ -0,0 +1,102 @@ +package errors_test + +import ( + "os" + "testing" + + "github.com/dyweb/gommon/errors" + "github.com/stretchr/testify/assert" +) + +func TestIs(t *testing.T) { + t.Run("flat", func(t *testing.T) { + assert.False(t, errors.Is(nil, os.ErrClosed), "nil does not match sentinel error") + assert.True(t, errors.Is(os.ErrClosed, os.ErrClosed), "sentinel errors match themselves") + assert.False(t, errors.Is(os.ErrNotExist, os.ErrClosed), "different error value should not match") + }) + + t.Run("unwrap wrapper", func(t *testing.T) { + errw := errors.Wrap(os.ErrClosed, "can't read config") + assert.True(t, errors.Is(errw, os.ErrClosed), "unwrap wrapped error") + + errww := errors.Wrap(errw, "just wrap again") + assert.True(t, errors.Is(errww, os.ErrClosed), "unwrap wrapped error") + }) + + t.Run("unwrap multi error", func(t *testing.T) { + merr := errors.NewMultiErr() + merr.Append(os.ErrNotExist) + merr.Append(os.ErrClosed) + assert.True(t, errors.Is(merr, os.ErrClosed)) + assert.True(t, errors.Is(merr, os.ErrNotExist)) + assert.False(t, errors.Is(merr, os.ErrNoDeadline)) + }) + + t.Run("unwrap wrapper inside multi", func(t *testing.T) { + merr := errors.NewMultiErr() + merr.Append(errors.Wrap(os.ErrNotExist, "can't read config")) + merr.Append(os.ErrClosed) + assert.True(t, errors.Is(merr, os.ErrClosed)) + assert.True(t, errors.Is(merr, os.ErrNotExist)) + }) + + t.Run("unwrap multi inside wrapper", func(t *testing.T) { + merr := errors.NewMultiErr() + merr.Append(errors.Wrap(os.ErrNotExist, "can't read config")) + merr.Append(os.ErrClosed) + errw := errors.Wrap(merr, "just wrap it") + assert.True(t, errors.Is(errw, os.ErrClosed)) + assert.True(t, errors.Is(errw, os.ErrNotExist)) + }) +} + +func TestIsType(t *testing.T) { + t.Run("flat", func(t *testing.T) { + assert.True(t, errors.IsType(&os.PathError{}, &os.PathError{})) + assert.False(t, errors.IsType(errors.New("foo"), &os.PathError{})) + }) + + t.Run("unwrap wrapper", func(t *testing.T) { + errw := errors.Wrap(&os.PathError{Op: "open"}, "can't read config") + assert.True(t, errors.IsType(errw, &os.PathError{})) + assert.False(t, errors.IsType(errw, errors.New("foo"))) + }) + + t.Run("unwrap error inside multi", func(t *testing.T) { + merr := errors.NewMultiErr() + merr.Append(&os.PathError{Op: "open"}) + merr.Append(os.ErrNotExist) + assert.True(t, errors.IsType(merr, &os.PathError{})) + }) + + t.Run("unwrap wrapper inside multi", func(t *testing.T) { + errw := errors.Wrap(&os.PathError{Op: "open"}, "can't read config") + merr := errors.NewMultiErr() + merr.Append(errw) + assert.True(t, errors.IsType(errw, &os.PathError{})) + }) +} + +func TestGetType(t *testing.T) { + t.Run("flat", func(t *testing.T) { + e := &os.PathError{Op: "open"} + err, ok := errors.GetType(e, &os.PathError{}) + assert.True(t, ok) + assert.Equal(t, "open", err.(*os.PathError).Op) + }) + + t.Run("unwrap wrapper", func(t *testing.T) { + errw := errors.Wrap(&os.PathError{Op: "open"}, "can't read config") + err, ok := errors.GetType(errw, &os.PathError{}) + assert.True(t, ok) + assert.Equal(t, "open", err.(*os.PathError).Op) + }) + + t.Run("unwrap error inside multi", func(t *testing.T) { + merr := errors.NewMultiErr() + merr.Append(&os.PathError{Op: "open"}) + err, ok := errors.GetType(merr, &os.PathError{}) + assert.True(t, ok) + assert.Equal(t, "open", err.(*os.PathError).Op) + }) +} diff --git a/errors/wrapper.go b/errors/wrapper.go index 5fa5495..324f4ef 100644 --- a/errors/wrapper.go +++ b/errors/wrapper.go @@ -2,10 +2,10 @@ package errors import ( "fmt" - "reflect" ) -// wrapper.go defines interface and util method for error wrapping +// wrapper.go defines interface and util method for error wrapping, +// it also implements Wrapper interface in un exported wrappedError // Wrapper is based on go 2 proposal, it only has an Unwrap method to returns the underlying error type Wrapper interface { @@ -87,112 +87,6 @@ func Wrapf(err error, format string, args ...interface{}) error { } } -// Is walks the error chain and do direct compare. -// It should be used for checking sentinel errors like io.EOF -// It returns true on the first match. -// It returns false when there is no match. -// -// It unwraps both wrapped error and multi error -func Is(err, target error) bool { - for { - // TODO: put this before nil check allow Is(nil, nil) returns true, not sure if this is desired behaviour - if err == target { - return true - } - if err == nil { - return false - } - // support both Causer from pkg/errors and Wrapper from go 2 proposal - switch err.(type) { - case Wrapper: - err = err.(Wrapper).Unwrap() - case causer: - err = err.(causer).Cause() - case ErrorList: - // support multi error - errs := err.(ErrorList).Errors() - for i := 0; i < len(errs); i++ { - if Is(errs[i], target) { - return true - } - } - return false // NOTE: without the return we will be looping forever because err is not updated - default: - return false - } - } -} - -// IsType walks the error chain and match by type using reflect. -// It only returns match result, if you need the matched error, use GetType -// -// It should be used for checking errors that defined their own types, -// errors created using errors.New, errors.Errof should NOT be checked using this method -// because they have same type, string if you are using standard library, freshError if you are using gommon/errors -// -// It calls IsTypeOf to reduce the overhead of calling reflect on target error -func IsType(err, target error) bool { - _, ok := GetType(err, target) - return ok -} - -// IsTypeOf requires user to call reflect.TypeOf(exampleErr).String() as the type string -func IsTypeOf(err error, tpe string) bool { - _, ok := GetTypeOf(err, tpe) - return ok -} - -// GetType walks the error chain and match by type using reflect, -// It returns the matched error and match result. -// You still need to do a type conversion on the returned error. -// -// It calls GetTypeOf to reduce the overhead of calling reflect on target error -func GetType(err, target error) (matched error, ok bool) { - if err == nil || target == nil { - return nil, false - } - return GetTypeOf(err, reflect.TypeOf(target).String()) -} - -// GetTypeOf requires user to call reflect.TypeOf(exampleErr).String() as the type string -func GetTypeOf(err error, tpe string) (error, bool) { - if err == nil { - return nil, false - } - for { - if err == nil { - return nil, false - } - if reflect.TypeOf(err).String() == tpe { - return err, true - } - switch err.(type) { - case Wrapper: - err = err.(Wrapper).Unwrap() - case causer: - err = err.(causer).Cause() - case ErrorList: - errs := err.(ErrorList).Errors() - for i := 0; i < len(errs); i++ { - m, ok := GetTypeOf(errs[i], tpe) - if ok { - return m, true - } - } - return nil, false - default: - return nil, false - } - } -} - -// AsValue is in go 2 proposal as workaround if go 2 does not have polymorphism, -// however, it's pretty hard to use and user can have error easily, -// we decided to use GetType -//func AsValue(val interface{}, err error) bool { -// -//} - var ( _ error = (*freshError)(nil) _ Tracer = (*freshError)(nil) diff --git a/errors/wrapper_test.go b/errors/wrapper_test.go index a0f11a8..32443ba 100644 --- a/errors/wrapper_test.go +++ b/errors/wrapper_test.go @@ -80,108 +80,6 @@ func TestWrapf(t *testing.T) { assert.Equal(t, len(errw.(errors.Tracer).Stack().Frames()), len(errww.(errors.Tracer).Stack().Frames())) } -func TestDirectCause(t *testing.T) { - errw := errors.Wrap(os.ErrClosed, "can't open closed file") - errww := errors.Wrap(errw, "wrap again") - assert.Equal(t, os.ErrClosed, errors.Cause(errww)) - assert.Equal(t, os.ErrClosed, errors.Cause(errw)) - assert.NotEqual(t, os.ErrClosed, errors.DirectCause(errww)) - assert.Equal(t, "can't open closed file", errors.DirectCause(errww).(errors.Messenger).Message()) -} - -func TestIs(t *testing.T) { - t.Run("flat", func(t *testing.T) { - assert.False(t, errors.Is(nil, os.ErrClosed), "nil does not match sentinel error") - assert.True(t, errors.Is(os.ErrClosed, os.ErrClosed), "sentinel errors match themselves") - assert.False(t, errors.Is(os.ErrNotExist, os.ErrClosed), "different error value should not match") - }) - - t.Run("unwrap wrapper", func(t *testing.T) { - errw := errors.Wrap(os.ErrClosed, "can't read config") - assert.True(t, errors.Is(errw, os.ErrClosed), "unwrap wrapped error") - - errww := errors.Wrap(errw, "just wrap again") - assert.True(t, errors.Is(errww, os.ErrClosed), "unwrap wrapped error") - }) - - t.Run("unwrap multi error", func(t *testing.T) { - merr := errors.NewMultiErr() - merr.Append(os.ErrNotExist) - merr.Append(os.ErrClosed) - assert.True(t, errors.Is(merr, os.ErrClosed)) - assert.True(t, errors.Is(merr, os.ErrNotExist)) - assert.False(t, errors.Is(merr, os.ErrNoDeadline)) - }) - - t.Run("unwrap wrapper inside multi", func(t *testing.T) { - merr := errors.NewMultiErr() - merr.Append(errors.Wrap(os.ErrNotExist, "can't read config")) - merr.Append(os.ErrClosed) - assert.True(t, errors.Is(merr, os.ErrClosed)) - assert.True(t, errors.Is(merr, os.ErrNotExist)) - }) - - t.Run("unwrap multi inside wrapper", func(t *testing.T) { - merr := errors.NewMultiErr() - merr.Append(errors.Wrap(os.ErrNotExist, "can't read config")) - merr.Append(os.ErrClosed) - errw := errors.Wrap(merr, "just wrap it") - assert.True(t, errors.Is(errw, os.ErrClosed)) - assert.True(t, errors.Is(errw, os.ErrNotExist)) - }) -} - -func TestIsType(t *testing.T) { - t.Run("flat", func(t *testing.T) { - assert.True(t, errors.IsType(&os.PathError{}, &os.PathError{})) - assert.False(t, errors.IsType(errors.New("foo"), &os.PathError{})) - }) - - t.Run("unwrap wrapper", func(t *testing.T) { - errw := errors.Wrap(&os.PathError{Op: "open"}, "can't read config") - assert.True(t, errors.IsType(errw, &os.PathError{})) - assert.False(t, errors.IsType(errw, errors.New("foo"))) - }) - - t.Run("unwrap error inside multi", func(t *testing.T) { - merr := errors.NewMultiErr() - merr.Append(&os.PathError{Op: "open"}) - merr.Append(os.ErrNotExist) - assert.True(t, errors.IsType(merr, &os.PathError{})) - }) - - t.Run("unwrap wrapper inside multi", func(t *testing.T) { - errw := errors.Wrap(&os.PathError{Op: "open"}, "can't read config") - merr := errors.NewMultiErr() - merr.Append(errw) - assert.True(t, errors.IsType(errw, &os.PathError{})) - }) -} - -func TestGetType(t *testing.T) { - t.Run("flat", func(t *testing.T) { - e := &os.PathError{Op: "open"} - err, ok := errors.GetType(e, &os.PathError{}) - assert.True(t, ok) - assert.Equal(t, "open", err.(*os.PathError).Op) - }) - - t.Run("unwrap wrapper", func(t *testing.T) { - errw := errors.Wrap(&os.PathError{Op: "open"}, "can't read config") - err, ok := errors.GetType(errw, &os.PathError{}) - assert.True(t, ok) - assert.Equal(t, "open", err.(*os.PathError).Op) - }) - - t.Run("unwrap error inside multi", func(t *testing.T) { - merr := errors.NewMultiErr() - merr.Append(&os.PathError{Op: "open"}) - err, ok := errors.GetType(merr, &os.PathError{}) - assert.True(t, ok) - assert.Equal(t, "open", err.(*os.PathError).Op) - }) -} - func TestWrappedError_Message(t *testing.T) { msg := "mewo" errw := errors.Wrap(os.ErrClosed, msg) From e0f9a5fd1426a0c0522d0a526a9fd9daa699290e Mon Sep 17 00:00:00 2001 From: at15 Date: Fri, 21 Dec 2018 01:26:41 -0800 Subject: [PATCH 14/34] [doc][style] Init general Go style guide #103 --- .github/CONTRIBUTING.md | 7 +++-- doc/style-application.md | 1 + doc/style.md | 66 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index dce2759..5a97071 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,6 +1,7 @@ # Contribution Guidelines -Format and test the code locally +TODO -- `make fmt` -- `make test` \ No newline at end of file +- [ ] add instruction for setup go environment and fork set upstream etc. (actually this is no longer a problem with go mod) + +- Read the [style guide](../doc/style.md) \ No newline at end of file diff --git a/doc/style-application.md b/doc/style-application.md index cd5d4ce..df99532 100644 --- a/doc/style-application.md +++ b/doc/style-application.md @@ -5,6 +5,7 @@ Style guide for writing application using gommon or libraries that use gommon ## Error handling - when using `log.Fatal`, add a `return` after it, it won't get executed, but it makes the abort logic more obvious + - if you are calling `panic` then you should not add `return` to avoid `go vet` warning Good diff --git a/doc/style.md b/doc/style.md index d6bac61..a61f28a 100644 --- a/doc/style.md +++ b/doc/style.md @@ -1,7 +1,69 @@ # Coding Style -General coding style +General Go coding style, this should be followed by people developing gommon and [other Go projects +under dyweb](https://github.com/dyweb?utf8=%E2%9C%93&q=&type=&language=go) + +## Package + +Based on [Go Blog: Package names][Go: Package names] + +- use small packages instead of single flat package + - a large package can have many import and is easier to cause import cycle if it is a library + - it's easy to merge smaller packages into large one but hard in the other direction (TODO: refer hashicorp blog) +- package name should be same as folder name + - avoid `go-mylib`, `go.mylib` as end of import path for `mylib` (we know it's go code ...) +- use `alllowercase` no `camelCase` or `snake_case` + - `camelCase` cause trouble for some filesystems, i.e. windows users (even worse when they mount it into a linux vm) + - `snake_case` normally have special meaning in go, i.e. `foo_test.go` (it's test file), `fs_windows.go` (only build for windows platform) + - this `hardtoreadname` also distinguish it from other identifiers like `varName` `funcName` +- rename your package if all the usage have to rename it + - when user rename the import and copy paste the code to another file, editors can't fill in the right import + - people can have many different renames even inside a single project `httpUtil`, `httpUtility`, + it's better just give people a name that don't conflict and can use it everywhere +- use `util.go` instead of `util` package + - a lot of times, those `util` are only used once in current package + - you will have a hard time rename all the `util` packages + +## File + +- put definitions on the top + - constant, package level vars (sentiel errors) + - exported interface + - exported struct +- group implementations together + - if a interface have multiple implementations, put them close to each other, but **do NOT intersect the methods from + different structs**, this only makes copy and paste easier +- keep file small + - if a file have many struct and many methods, you should group them by functionality and split into smaller files + - if a struct has too many/lengthy methods in one file, you should consider decouple the struct + +## Struct + +- use struct literal initialization + - you type less and rename variable is easier when there is no editor's help +- do NOT use embedding for struct that will be serialized (in JSON etc.) +- prefer function over struct methods + - TODO: explain this ... + +## Func + +- when pass/return a pointer, use `&` at call site, NOT initialization + - pointer means ownership + - pointer does not means no copy, sometimes it causes allocation on heap which can be avoided + - [ ] TODO: refer to Bill's blog and course + +## Test + +- use helper function that accepts `testing.T` and don't return error + - your code should handle error gracefully, your test should stop when there is error/unexpected result + - this may cause error source line harder to find since go's test fail function does allow pass skip + - [ ] TODO: might open a issue in go's repo, or there are already issues like that +- use [subtest][Go: Subtest and Sub-benchmarks] + - this allows setup and teardown using vanilla go code without any framework +- use `TestMain` for package level setup and tear down +- [ ] TODO: add godlen file, add refer to hashicorp test video ## Ref -- https://blog.golang.org/package-names \ No newline at end of file +[Go: Package names]: https://blog.golang.org/package-names +[Go: Subtest and Sub-benchmarks]: https://blog.golang.org/subtests \ No newline at end of file From ea986b6389fd085c5a74f4dc615cc79f69c7d36a Mon Sep 17 00:00:00 2001 From: at15 Date: Fri, 21 Dec 2018 20:52:40 -0800 Subject: [PATCH 15/34] [doc][style] Add example snippet for general style --- doc/style.md | 205 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 178 insertions(+), 27 deletions(-) diff --git a/doc/style.md b/doc/style.md index a61f28a..b0d0842 100644 --- a/doc/style.md +++ b/doc/style.md @@ -3,39 +3,188 @@ General Go coding style, this should be followed by people developing gommon and [other Go projects under dyweb](https://github.com/dyweb?utf8=%E2%9C%93&q=&type=&language=go) +## TODO + +- package + - [ ] `types` package, to export API and reduce import cycle, a package the defines struct is really needed + ## Package Based on [Go Blog: Package names][Go: Package names] -- use small packages instead of single flat package - - a large package can have many import and is easier to cause import cycle if it is a library - - it's easy to merge smaller packages into large one but hard in the other direction (TODO: refer hashicorp blog) -- package name should be same as folder name - - avoid `go-mylib`, `go.mylib` as end of import path for `mylib` (we know it's go code ...) -- use `alllowercase` no `camelCase` or `snake_case` - - `camelCase` cause trouble for some filesystems, i.e. windows users (even worse when they mount it into a linux vm) - - `snake_case` normally have special meaning in go, i.e. `foo_test.go` (it's test file), `fs_windows.go` (only build for windows platform) - - this `hardtoreadname` also distinguish it from other identifiers like `varName` `funcName` -- rename your package if all the usage have to rename it - - when user rename the import and copy paste the code to another file, editors can't fill in the right import - - people can have many different renames even inside a single project `httpUtil`, `httpUtility`, - it's better just give people a name that don't conflict and can use it everywhere -- use `util.go` instead of `util` package - - a lot of times, those `util` are only used once in current package - - you will have a hard time rename all the `util` packages - +Use small packages instead of a single flat package + +- a large package can have many import and is easier to cause import cycle if it is a library +- it's easy to merge smaller packages into large one but hard in the other direction (TODO: refer hashicorp blog) + +````text +// bad +agent.go +raft.go +http.go +grpc.go + +// good +agent + server.go +raft + leader.go + replication.go + store.go +transport + http.go + grpc.go +```` + +Package name should be same as folder name + +- avoid `go-mylib`, `go.mylib` as end of import path for `mylib` (we know it's go code ...) + +````go +// bad +// file src/github.com/dyweb/gommon/go-errors/wrapper.go +package errors + +// good +// file src/github.com/dyweb/gommon/errors/wrapper.go +package errors +```` + +Use `alllowercase` no `camelCase` or `snake_case` + +- should try to use a single word in first place [] +- `camelCase` cause trouble for some filesystems, i.e. windows users (even worse when they mount it into a linux vm) +- `snake_case` normally have special meaning in go, i.e. `foo_test.go` (it's test file), `fs_windows.go` (only build for windows platform) +- this `hardtoreadname` also distinguish it from other identifiers like `varName` `funcName` + +````text +// good +termutil + +// bad +termUtil +term_util +```` + +Rename your package if most users have to rename it when import + +- when user rename the import and copy paste the code to another file, editors can't fill in the right import +- people can have many different renames even inside a single project `httpUtil`, `httpUtility`, it's better just give people a name that don't conflict and can use it everywhere + +````go +import ( + "net/http" + // good + "github.com/dyweb/gommon/util/httputil" + // bad + httputil "github.com/dyweb/gommon/util/http" + // even worse + httputil "github.com/dyweb/gommon/util/go-http.v2" +) +```` + +Use `util.go` instead of `util` package + +- a lot of times, those `util` are only used once in current package +- you will have a hard time rename all the `util` packages +- if there are too many things that can't fit into `util.go`, better make it a dedicated package with another name + +````text +// good +pkg + agent + util.go + +// bad, a single util.go in the util package +pkg + agent + util + util.go +```` + ## File -- put definitions on the top - - constant, package level vars (sentiel errors) - - exported interface - - exported struct -- group implementations together - - if a interface have multiple implementations, put them close to each other, but **do NOT intersect the methods from +Put definitions on the top + +- constant, package level vars (sentiel errors) +- exported interface +- factory functions +- exported struct + +````go +// bad: sort the following content randomly while still keeps it compile + +// good +const DefaltSuccessRatio = 0.8 + +var ErrOutOfResource = errors.New("out of resource") + +type Executor interface { + Run(ctx contex.Context, task Task) error +} + +func NewShellExecutor(cmd string) (Executor, error) { + return &ShellExecutor{cmd: cmd}, nil +} + +type Task struct { + spec Spec + retry int +} + +type ShellExecutor struct { + cmd string +} +```` + +Group implementations together + +- if a interface have multiple implementations, put them close to each other, but **do NOT intersect the methods from different structs**, this only makes copy and paste easier -- keep file small - - if a file have many struct and many methods, you should group them by functionality and split into smaller files - - if a struct has too many/lengthy methods in one file, you should consider decouple the struct + +````go +// good + +func (s *ShellExecutor) Name() string { + return "shell" +} + +func (s *ShellExecutor) Run(ctx context.Context, task Task) error { + cmd := exec.Command(s.cmd) + return cmd.Run() +} + +func (d *DockerExecutor) Name() string { + return "docker" +} + +func (d *DockerExecutor) Run(ctx context.Context, task Task) error { + // create docker client, exec inside a container etc. +} + +// bad +func (s *ShellExecutor) Name() string { + return "shell" +} + +func (d *DockerExecutor) Name() string { + return "docker" +} + +func (s *ShellExecutor) Run(ctx context.Context, task Task) error { + cmd := exec.Command(s.cmd) + return cmd.Run() +} + +func (d *DockerExecutor) Run(ctx context.Context, task Task) error { + // create docker client, exec inside a container etc. +} +```` + +Keep file small + +- if a file have many struct and many methods, you should group them by functionality and split into smaller files +- if a struct has too many/lengthy methods in one file, you should consider decouple the struct ## Struct @@ -61,7 +210,9 @@ Based on [Go Blog: Package names][Go: Package names] - use [subtest][Go: Subtest and Sub-benchmarks] - this allows setup and teardown using vanilla go code without any framework - use `TestMain` for package level setup and tear down -- [ ] TODO: add godlen file, add refer to hashicorp test video +- [ ] TODO: add golden file, add refer to hashicorp test video +- when assert error thant will cause panic for following code use `require` instead of `assert` +- define anonymous struct for table driven test ## Ref From 12b9e04ff6cc80c821d7447877b2d62e08b9fcb7 Mon Sep 17 00:00:00 2001 From: at15 Date: Fri, 21 Dec 2018 20:53:02 -0800 Subject: [PATCH 16/34] [errors] Add Walk and WalkFunc for custom unwrap - user might want to have more detail inspection on the error chain or error list beyond `Is`, `GetType` etc. --- errors/inspect.go | 36 ++++++++++++++++++++++++++++++++++++ errors/inspect_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/errors/inspect.go b/errors/inspect.go index 14144b3..50b4c6d 100644 --- a/errors/inspect.go +++ b/errors/inspect.go @@ -109,3 +109,39 @@ func GetTypeOf(err error, tpe string) (error, bool) { //func AsValue(val interface{}, err error) bool { // //} + +// WalkFunc accepts an error and based on its inspection logic it can tell +// Walk if it should stop walking the error chain or error list +type WalkFunc func(err error) (stop bool) + +// Walk traverse error chain and error list, it stops when there is no +// underlying error or the WalkFunc decides to stop +// TODO: might let Is and GetType use Walk, this reduce copy and paste ... +func Walk(err error, cb WalkFunc) { + if err == nil { + return + } + for { + if err == nil { + return + } + // WalkFunc decides to stop + if cb(err) { + return + } + switch err.(type) { + case Wrapper: + err = err.(Wrapper).Unwrap() + case causer: + err = err.(causer).Cause() + case ErrorList: + errs := err.(ErrorList).Errors() + for i := 0; i < len(errs); i++ { + Walk(errs[i], cb) + } + return + default: + return + } + } +} diff --git a/errors/inspect_test.go b/errors/inspect_test.go index 0d2c8d0..b616b90 100644 --- a/errors/inspect_test.go +++ b/errors/inspect_test.go @@ -100,3 +100,28 @@ func TestGetType(t *testing.T) { assert.Equal(t, "open", err.(*os.PathError).Op) }) } + +func TestWalk(t *testing.T) { + t.Run("stop", func(t *testing.T) { + merr := errors.NewMultiErr() + merr.Append(os.ErrNotExist) + merr.Append(os.ErrNotExist) + + var errs []error + errors.Walk(merr, func(err error) (stop bool) { + errs = append(errs, err) + return true + }) + assert.Equal(t, 1, len(errs), "WalkFunc is only called once because it returns true for stop") + + // https://stackoverflow.com/questions/16971741/how-do-you-clear-a-slice-in-go + errs = nil + errors.Walk(merr, func(err error) (stop bool) { + //t.Logf("%v", err) + errs = append(errs, err) + return false + }) + // NOTE: it is not 2 because multi error itself is also an error ... + assert.Equal(t, 1 + 2, len(errs), "WalkFunc is called same times as length of error list") + }) +} From 62455bde02b56cb59ec7614046cb77c28a811fec Mon Sep 17 00:00:00 2001 From: at15 Date: Fri, 21 Dec 2018 21:03:43 -0800 Subject: [PATCH 17/34] [errors] Use Walk for Is and GetTypeOf - this have a bit performance overhead, but we no longer need to keep the code in sync in three places --- errors/inspect.go | 75 +++++++++++++----------------------------- errors/inspect_test.go | 2 +- 2 files changed, 23 insertions(+), 54 deletions(-) diff --git a/errors/inspect.go b/errors/inspect.go index 50b4c6d..a79ae7d 100644 --- a/errors/inspect.go +++ b/errors/inspect.go @@ -1,6 +1,8 @@ package errors -import "reflect" +import ( + "reflect" +) // inspect.go defines functions for inspecting wrapped error or error list @@ -11,33 +13,20 @@ import "reflect" // // It unwraps both wrapped error and multi error func Is(err, target error) bool { - for { - // TODO: put this before nil check allow Is(nil, nil) returns true, not sure if this is desired behaviour + // TODO: Is(nil, nil)? should we return true for that ... user should just use if err == nil ... + if err == nil || target == nil { + return false + } + + var found bool + Walk(err, func(err error) (stop bool) { if err == target { + found = true return true } - if err == nil { - return false - } - // support both Causer from pkg/errors and Wrapper from go 2 proposal - switch err.(type) { - case Wrapper: - err = err.(Wrapper).Unwrap() - case causer: - err = err.(causer).Cause() - case ErrorList: - // support multi error - errs := err.(ErrorList).Errors() - for i := 0; i < len(errs); i++ { - if Is(errs[i], target) { - return true - } - } - return false // NOTE: without the return we will be looping forever because err is not updated - default: - return false - } - } + return false + }) + return found } // IsType walks the error chain and match by type using reflect. @@ -72,35 +61,19 @@ func GetType(err, target error) (matched error, ok bool) { } // GetTypeOf requires user to call reflect.TypeOf(exampleErr).String() as the type string -func GetTypeOf(err error, tpe string) (error, bool) { +func GetTypeOf(err error, tpe string) (matched error, ok bool) { if err == nil { return nil, false } - for { - if err == nil { - return nil, false - } + + Walk(err, func(err error) (stop bool) { if reflect.TypeOf(err).String() == tpe { - return err, true - } - switch err.(type) { - case Wrapper: - err = err.(Wrapper).Unwrap() - case causer: - err = err.(causer).Cause() - case ErrorList: - errs := err.(ErrorList).Errors() - for i := 0; i < len(errs); i++ { - m, ok := GetTypeOf(errs[i], tpe) - if ok { - return m, true - } - } - return nil, false - default: - return nil, false + matched = err + return true } - } + return false + }) + return matched, matched != nil } // AsValue is in go 2 proposal as workaround if go 2 does not have polymorphism, @@ -116,11 +89,7 @@ type WalkFunc func(err error) (stop bool) // Walk traverse error chain and error list, it stops when there is no // underlying error or the WalkFunc decides to stop -// TODO: might let Is and GetType use Walk, this reduce copy and paste ... func Walk(err error, cb WalkFunc) { - if err == nil { - return - } for { if err == nil { return diff --git a/errors/inspect_test.go b/errors/inspect_test.go index b616b90..849f041 100644 --- a/errors/inspect_test.go +++ b/errors/inspect_test.go @@ -122,6 +122,6 @@ func TestWalk(t *testing.T) { return false }) // NOTE: it is not 2 because multi error itself is also an error ... - assert.Equal(t, 1 + 2, len(errs), "WalkFunc is called same times as length of error list") + assert.Equal(t, 1+2, len(errs), "WalkFunc is called same times as length of error list") }) } From 4779e1364fd24f1bf06a76902c907bd1b9dae67b Mon Sep 17 00:00:00 2001 From: at15 Date: Sun, 23 Dec 2018 00:51:05 -0800 Subject: [PATCH 18/34] [doc][errors] Add error types from k8s - mostly similar to http status code - use struct that contains detail, cause for individual fields - actually this is very application specfic and should goes to webframework like go.ice ... --- doc/style.md | 18 ++ .../2018-12-22-error-types-reference.md | 212 ++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 errors/doc/design/2018-12-22-error-types-reference.md diff --git a/doc/style.md b/doc/style.md index b0d0842..d7d2976 100644 --- a/doc/style.md +++ b/doc/style.md @@ -104,6 +104,24 @@ pkg ## File +At start of the file, briefly mention what the file does, it should also include some tricky internal implementation +and warning for new developers. + +````go +// good example for good code +// controller.go is the glue for reconciliation logic, it is generated by operator-sdk, +// the core bossiness logic are implemented in placement.go and operates on object directly so they can be unit tested +```` + +````go +// good example for bad code +// fsm.go implements a state machine using nested for loop and if else with unhandled errors, +// it is called fsm because it imports a fsm library +// +// If you tried to refactor the code and got stopped in the middle +1 for the following error +// 25 +```` + Put definitions on the top - constant, package level vars (sentiel errors) diff --git a/errors/doc/design/2018-12-22-error-types-reference.md b/errors/doc/design/2018-12-22-error-types-reference.md new file mode 100644 index 0000000..3b59aa9 --- /dev/null +++ b/errors/doc/design/2018-12-22-error-types-reference.md @@ -0,0 +1,212 @@ +# 2018-12-21 Error Types + +## k8s + +- `Package errors provides detailed error types for api field validation` + - https://github.com/kubernetes/apimachinery/blob/master/pkg/api/errors/errors.go + - https://github.com/kubernetes/apimachinery/blob/master/pkg/apis/meta/v1/types.go +- `Status` + - `Success` or `Failure` + - `Message` human readable string + - `StatusReason` + - `StatusDetail` +- `StatusReason` + - `500` unknown + - `401` unauthorized + - `403` forbidden + - `404` not found + - `409` already exists + - `409` conflict `the requested operation cannot be completed due to a conflict in the operation. +The client may need to alter the request. + - `410` gone + - `422` invalid + - `500` server timeout + - `504` timeout, detail contains `retryAfterSeconds` + - bad request + - not acceptable + - `415` unsupported media type + - `500` internal, causes contains original error + - `410` expired, i.e. watches that can't be served + - `503` service unavailable +- `IsXXX` is based on the `StatusReason` +- `StatusDetail` + - `Name`, `Group`, `Kind` + - `UID` + - `[]StatusCause` +- `StatusCause` + - `Type` + - `Message` + - `Field` +- `CauseType` `a machine readable value providing more detail about what occurred in a status response. +An operation may have multiple causes for a status (whether Failure or Success)` + - field value not found + - field value required + - field value duplicate + - field value invalid + - field value not supported + - unexpected server response + +````go +// https://github.com/kubernetes/apimachinery/blob/master/pkg/api/errors/errors.go + +// StatusError is an error intended for consumption by a REST API server; it can also be +// reconstructed by clients from a REST response. Public to allow easy type switches. +type StatusError struct { + ErrStatus metav1.Status +} + +// APIStatus is exposed by errors that can be converted to an api.Status object +// for finer grained details. +type APIStatus interface { + Status() metav1.Status +} + +// NewNotFound returns a new error which indicates that the resource of the kind and the name was not found. +func NewNotFound(qualifiedResource schema.GroupResource, name string) *StatusError { + return &StatusError{metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusNotFound, + Reason: metav1.StatusReasonNotFound, + Details: &metav1.StatusDetails{ + Group: qualifiedResource.Group, + Kind: qualifiedResource.Resource, + Name: name, + }, + Message: fmt.Sprintf("%s %q not found", qualifiedResource.String(), name), + }} +} + +// IsNotFound returns true if the specified error was created by NewNotFound. +func IsNotFound(err error) bool { + return ReasonForError(err) == metav1.StatusReasonNotFound +} + +// ReasonForError returns the HTTP status for a particular error. +func ReasonForError(err error) metav1.StatusReason { + switch t := err.(type) { + case APIStatus: + return t.Status().Reason + } + return metav1.StatusReasonUnknown +} + +// https://github.com/kubernetes/apimachinery/blob/master/pkg/apis/meta/v1/types.go +// Status is a return value for calls that don't return other objects. +type Status struct { + TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + // +optional + ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Status of the operation. + // One of: "Success" or "Failure". + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status + // +optional + Status string `json:"status,omitempty" protobuf:"bytes,2,opt,name=status"` + // A human-readable description of the status of this operation. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,3,opt,name=message"` + // A machine-readable description of why this operation is in the + // "Failure" status. If this value is empty there + // is no information available. A Reason clarifies an HTTP status + // code but does not override it. + // +optional + Reason StatusReason `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason,casttype=StatusReason"` + // Extended data associated with the reason. Each reason may define its + // own extended details. This field is optional and the data returned + // is not guaranteed to conform to any schema except that defined by + // the reason type. + // +optional + Details *StatusDetails `json:"details,omitempty" protobuf:"bytes,5,opt,name=details"` + // Suggested HTTP return code for this status, 0 if not set. + // +optional + Code int32 `json:"code,omitempty" protobuf:"varint,6,opt,name=code"` +} + +// StatusDetails is a set of additional properties that MAY be set by the +// server to provide additional information about a response. The Reason +// field of a Status object defines what attributes will be set. Clients +// must ignore fields that do not match the defined type of each attribute, +// and should assume that any attribute may be empty, invalid, or under +// defined. +type StatusDetails struct { + // The name attribute of the resource associated with the status StatusReason + // (when there is a single name which can be described). + // +optional + Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` + // The group attribute of the resource associated with the status StatusReason. + // +optional + Group string `json:"group,omitempty" protobuf:"bytes,2,opt,name=group"` + // The kind attribute of the resource associated with the status StatusReason. + // On some operations may differ from the requested resource Kind. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + // +optional + Kind string `json:"kind,omitempty" protobuf:"bytes,3,opt,name=kind"` + // UID of the resource. + // (when there is a single resource which can be described). + // More info: http://kubernetes.io/docs/user-guide/identifiers#uids + // +optional + UID types.UID `json:"uid,omitempty" protobuf:"bytes,6,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"` + // The Causes array includes more details associated with the StatusReason + // failure. Not all StatusReasons may provide detailed causes. + // +optional + Causes []StatusCause `json:"causes,omitempty" protobuf:"bytes,4,rep,name=causes"` + // If specified, the time in seconds before the operation should be retried. Some errors may indicate + // the client must take an alternate action - for those errors this field may indicate how long to wait + // before taking the alternate action. + // +optional + RetryAfterSeconds int32 `json:"retryAfterSeconds,omitempty" protobuf:"varint,5,opt,name=retryAfterSeconds"` +} + +// StatusCause provides more information about an api.Status failure, including +// cases when multiple errors are encountered. +type StatusCause struct { + // A machine-readable description of the cause of the error. If this value is + // empty there is no information available. + // +optional + Type CauseType `json:"reason,omitempty" protobuf:"bytes,1,opt,name=reason,casttype=CauseType"` + // A human-readable description of the cause of the error. This field may be + // presented as-is to a reader. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"` + // The field of the resource that has caused this error, as named by its JSON + // serialization. May include dot and postfix notation for nested attributes. + // Arrays are zero-indexed. Fields may appear more than once in an array of + // causes due to fields having multiple errors. + // Optional. + // + // Examples: + // "name" - the field "name" on the current resource + // "items[0].name" - the field "name" on the first array entry in "items" + // +optional + Field string `json:"field,omitempty" protobuf:"bytes,3,opt,name=field"` +} + +// CauseType is a machine readable value providing more detail about what +// occurred in a status response. An operation may have multiple causes for a +// status (whether Failure or Success). +type CauseType string + +const ( + // CauseTypeFieldValueNotFound is used to report failure to find a requested value + // (e.g. looking up an ID). + CauseTypeFieldValueNotFound CauseType = "FieldValueNotFound" + // CauseTypeFieldValueRequired is used to report required values that are not + // provided (e.g. empty strings, null values, or empty arrays). + CauseTypeFieldValueRequired CauseType = "FieldValueRequired" + // CauseTypeFieldValueDuplicate is used to report collisions of values that must be + // unique (e.g. unique IDs). + CauseTypeFieldValueDuplicate CauseType = "FieldValueDuplicate" + // CauseTypeFieldValueInvalid is used to report malformed values (e.g. failed regex + // match). + CauseTypeFieldValueInvalid CauseType = "FieldValueInvalid" + // CauseTypeFieldValueNotSupported is used to report valid (as per formatting rules) + // values that can not be handled (e.g. an enumerated string). + CauseTypeFieldValueNotSupported CauseType = "FieldValueNotSupported" + // CauseTypeUnexpectedServerResponse is used to report when the server responded to the client + // without the expected return type. The presence of this cause indicates the error may be + // due to an intervening proxy or the server software malfunctioning. + CauseTypeUnexpectedServerResponse CauseType = "UnexpectedServerResponse" +) +```` \ No newline at end of file From 7a8735ddd48e8e2456e389c94decb727be1ef2fd Mon Sep 17 00:00:00 2001 From: at15 Date: Tue, 25 Dec 2018 21:38:36 -0800 Subject: [PATCH 19/34] [build] Add network util --- build/go-dev/Dockerfile | 4 ++++ build/go-dev/Makefile | 5 ++++- build/go-dev/README.md | 6 ++++++ doc/style-application.md | 10 ++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/build/go-dev/Dockerfile b/build/go-dev/Dockerfile index 9521b67..39fde7d 100644 --- a/build/go-dev/Dockerfile +++ b/build/go-dev/Dockerfile @@ -20,6 +20,10 @@ RUN \ vim \ zip \ unzip \ + tmux \ + netcat \ + telnet \ + tree \ && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/build/go-dev/Makefile b/build/go-dev/Makefile index bdfba7f..0bf0785 100644 --- a/build/go-dev/Makefile +++ b/build/go-dev/Makefile @@ -13,4 +13,7 @@ $(PUSHS): build: $(BUILDS) -push: $(PUSHS) \ No newline at end of file +push: $(PUSHS) + +run: + docker run --rm -it --entrypoint /bin/bash $(DOCKER_REPO):1.11.4 \ No newline at end of file diff --git a/build/go-dev/README.md b/build/go-dev/README.md index cdbfa64..f40d99a 100644 --- a/build/go-dev/README.md +++ b/build/go-dev/README.md @@ -13,6 +13,12 @@ you need a full go environment to run your code (looking at ginkgo) TODO: example using multi stage build using dep and go mods +Use it as a playground, run the container in interactive mode and remove it after you are done + +````bash +docker run --rm -it --entrypoint /bin/bash dyweb/go-dev:1.11.4 +```` + ## Build ````bash diff --git a/doc/style-application.md b/doc/style-application.md index df99532..f58c82d 100644 --- a/doc/style-application.md +++ b/doc/style-application.md @@ -2,6 +2,16 @@ Style guide for writing application using gommon or libraries that use gommon +## Import + +- group import, ref https://github.com/jaegertracing/jaeger/blob/master/CONTRIBUTING.md#imports-grouping + - std + - third party + - packages in project's `lib` folder, they will become third party eventually + - internal packages +- use `pb` as alias for imported protobuf package +- use `dlog` for `github.com/dyweb/gommon/log` since log is used as package var for package level logger + ## Error handling - when using `log.Fatal`, add a `return` after it, it won't get executed, but it makes the abort logic more obvious From 79d90a132118521794907ab0deedd1c2b597cb7c Mon Sep 17 00:00:00 2001 From: at15 Date: Tue, 25 Dec 2018 22:07:20 -0800 Subject: [PATCH 20/34] [errors] Compare reflect.Type directly Fix #104 - `String()` is not full path, so you can have mis match - this was originally learned from https://github.com/hashicorp/errwrap - interface are comparable if the underlying type is same and comparable --- Makefile | 5 ++++- errors/inspect.go | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 5de9dfa..8dc9992 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,8 @@ generate: gommon generate -v # --- test --- -.PHONY: test test-cover test-cover-html test-race test-log +.PHONY: test test-cover test-cover-html test-race +.PHONY: test-log test-errors test: go test -v -cover $(PKGS) @@ -95,6 +96,8 @@ test-race: test-log: go test -v -cover ./log/... +test-errors: + go test -v -cover ./errors/... # --- test --- # TODO: refer tools used in https://github.com/360EntSecGroup-Skylar/goreporter diff --git a/errors/inspect.go b/errors/inspect.go index a79ae7d..86aafc9 100644 --- a/errors/inspect.go +++ b/errors/inspect.go @@ -43,7 +43,7 @@ func IsType(err, target error) bool { } // IsTypeOf requires user to call reflect.TypeOf(exampleErr).String() as the type string -func IsTypeOf(err error, tpe string) bool { +func IsTypeOf(err error, tpe reflect.Type) bool { _, ok := GetTypeOf(err, tpe) return ok } @@ -57,17 +57,25 @@ func GetType(err, target error) (matched error, ok bool) { if err == nil || target == nil { return nil, false } - return GetTypeOf(err, reflect.TypeOf(target).String()) + return GetTypeOf(err, reflect.TypeOf(target)) } -// GetTypeOf requires user to call reflect.TypeOf(exampleErr).String() as the type string -func GetTypeOf(err error, tpe string) (matched error, ok bool) { +// GetTypeOf requires user to call reflect.TypeOf(exampleErr) as target type. +// NOTE: for the type matching, we compare equality of reflect.Type directly, +// Originally we were comparing string, however result from `String()` is not +// the full path, so x/foo/bar.Encoder can match y/foo/bar.Encoder because we +// got bar.Encoder for both of them. +// You can compare interface{} if their underlying type is same and comparable, +// it is documented in https://golang.org/pkg/reflect/#Type +// +// Related https://github.com/dyweb/gommon/issues/104 +func GetTypeOf(err error, tpe reflect.Type) (matched error, ok bool) { if err == nil { return nil, false } Walk(err, func(err error) (stop bool) { - if reflect.TypeOf(err).String() == tpe { + if reflect.TypeOf(err) == tpe { matched = err return true } From 66e18e136f73f0e8e6d5dc654f7e4b3a62cc1fcc Mon Sep 17 00:00:00 2001 From: at15 Date: Fri, 28 Dec 2018 13:32:43 -0800 Subject: [PATCH 21/34] [httputil] Add type for method and factory funcs - method types are based on https://github.com/bradfitz/exp-httpclient/blob/master/http/method.go - http transport and client factories are based on https://github.com/hashicorp/go-cleanhttp/blob/master/cleanhttp.go --- .github/ISSUE_TEMPLATE/.gitkeep | 1 + build/go-dev/Dockerfile | 1 + util/httputil/http.go | 65 +++++++++++++++++++++++++++++++++ util/httputil/pkg.go | 35 ++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/.gitkeep create mode 100644 util/httputil/http.go diff --git a/.github/ISSUE_TEMPLATE/.gitkeep b/.github/ISSUE_TEMPLATE/.gitkeep new file mode 100644 index 0000000..0e41fba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/.gitkeep @@ -0,0 +1 @@ +# TODO: add issue templates https://help.github.com/articles/creating-issue-templates-for-your-repository/ \ No newline at end of file diff --git a/build/go-dev/Dockerfile b/build/go-dev/Dockerfile index 39fde7d..1e08952 100644 --- a/build/go-dev/Dockerfile +++ b/build/go-dev/Dockerfile @@ -24,6 +24,7 @@ RUN \ netcat \ telnet \ tree \ + strace \ && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/util/httputil/http.go b/util/httputil/http.go new file mode 100644 index 0000000..780d0f0 --- /dev/null +++ b/util/httputil/http.go @@ -0,0 +1,65 @@ +package httputil + +import ( + "net" + "net/http" + "time" +) + +// http.go contains util for creating fresh transport and client that don't use default transport +// It is based on https://github.com/hashicorp/go-cleanhttp/blob/master/cleanhttp.go which is based +// on how default transport is created in standard library + +// NewUnPooledTransport uses NewPooledTransport with keep alive and idle connection disabled +func NewUnPooledTransport() *http.Transport { + tr := NewPooledTransport() + tr.DisableKeepAlives = true + tr.MaxIdleConnsPerHost = -1 + return tr +} + +// NewPooledTransport is same as DefaultTransport in net/http, but it is not shared and won't be alerted by other library +func NewPooledTransport() *http.Transport { + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + }).DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + // TODO: set MaxIdleConnsPerHost like https://github.com/hashicorp/go-cleanhttp/blob/master/cleanhttp.go#L35 runtime.GOMAXPROCS(0) + 1 ? + } +} + +// NewClient panics if transport is nil or http.DefaultTransport, +// You should always bring your own http.Transport instead of using the default one, +// because all the third party libraries can modify it without the application knowing +func NewClient(tr *http.Transport) *http.Client { + if tr == nil { + panic("transport is nil") + } + if tr == http.DefaultTransport { + panic("stop using default transport") + } + return &http.Client{ + Transport: tr, + } +} + +// NewUnPooledClient returns a net/http client with a fresh http.Transport that has connection pooling disabled +func NewUnPooledClient() *http.Client { + return NewClient(NewUnPooledTransport()) +} + +// NewPooledClient returns a net/http client with a fresh http.Transport using NewPooledTransport. +// The transport is a connection pool, you should reuse the client instead of creating new one +// with new transport, http.Client does not keep internal state in struct except cookie jar +// +// If you do need multiple clients to reuse same connections, you should use NewClient and pass a transport +func NewPooledClient() *http.Client { + return NewClient(NewPooledTransport()) +} diff --git a/util/httputil/pkg.go b/util/httputil/pkg.go index 8819151..322e002 100644 --- a/util/httputil/pkg.go +++ b/util/httputil/pkg.go @@ -1,2 +1,37 @@ // Package httputil provides helper for net/http, i.e. unix domain socket client, http request logger package httputil + +import ( + "net/http" +) + +// Method gives a type for http method +// See https://github.com/bradfitz/exp-httpclient/blob/master/http/method.go +type Method string + +const ( + Get Method = http.MethodGet + Head Method = http.MethodHead + Post Method = http.MethodPost + Put Method = http.MethodPut + Patch Method = http.MethodPatch + Delete Method = http.MethodDelete + Connect Method = http.MethodConnect + Options Method = http.MethodOptions + Trace Method = http.MethodTrace +) + +// UserAgent data are from https://techblog.willshouse.com/2012/01/03/most-common-user-agents/ +// For UserAgent spec, see MDN https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent +type UserAgent string + +const ( + DefaultUA UserAgent = "Go" // TODO: should put real version number into the constant, need to use generator + // TODO: should list more UAs, including mobile devices https://deviceatlas.com/blog/list-of-user-agent-strings + // TODO: os and browser versions are too old in those UAs ... + UACurl UserAgent = "curl/7.60.0" + UAChromeWin UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36" + UAChromeLinux UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36" + UAChromeMac UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36" + // TODO: add UA for mobile device (so you can see mobile optimized page in terminal? .... +) From 166abde11e677d75f25789bd66846f1906f5c603 Mon Sep 17 00:00:00 2001 From: at15 Date: Fri, 28 Dec 2018 14:12:05 -0800 Subject: [PATCH 22/34] [httputil] Add Tracked http.ResponseWriter for log - it was originally in go.ice and since go.ice was not under develop and has too many dependencies, I end up copying it everywhere when logging access log is needed, time to bring it to gommon --- util/httputil/writer.go | 98 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 util/httputil/writer.go diff --git a/util/httputil/writer.go b/util/httputil/writer.go new file mode 100644 index 0000000..1198095 --- /dev/null +++ b/util/httputil/writer.go @@ -0,0 +1,98 @@ +package httputil + +import ( + "net/http" +) + +// writer.go implements a http.ResponseWriter so it can be used to generate http access log +// it is copied from go.ice https://github.com/dyweb/go.ice/blob/v0.0.1/ice/transport/http/writer.go + +var ( + _ http.ResponseWriter = (*TrackedWriter)(nil) + _ http.Flusher = (*TrackedWriter)(nil) + _ http.Pusher = (*TrackedWriter)(nil) +) + +// NOTE: CloseNotifier is deprecated in favor of context +//var _ http.CloseNotifier = (*TrackedWriter)(nil) + +// TrackedWriter keeps track of status code and bytes written so it can be used by logger. +// It proxies all the interfaces except Hijacker, since it is not supported by HTTP/2. +// Most methods comments are copied from net/http +// It is based on https://github.com/gorilla/handlers but put all interface into one struct +type TrackedWriter struct { + w http.ResponseWriter + status int + size int + writeCalled int +} + +// Status return the tracked status code, returns 0 if WriteHeader has not been called +func (tracker *TrackedWriter) Status() int { + return tracker.status +} + +// Size return number of bytes written through Write, returns 0 if Write has not been called +func (tracker *TrackedWriter) Size() int { + return tracker.size +} + +// Header returns the header map of the underlying ResponseWriter +// +// Changing the header map after a call to WriteHeader (or +// Write) has no effect unless the modified headers are +// trailers. +func (tracker *TrackedWriter) Header() http.Header { + return tracker.w.Header() +} + +// Write keeps track of bytes written of the underlying ResponseWriter +// +// Write writes the data to the connection as part of an HTTP reply. +// +// If WriteHeader has not yet been called, Write calls +// WriteHeader(http.StatusOK) before writing the data. If the Header +// does not contain a Content-Type line, Write adds a Content-Type set +// to the result of passing the initial 512 bytes of written data to +// DetectContentType. +func (tracker *TrackedWriter) Write(b []byte) (int, error) { + tracker.writeCalled++ + size, err := tracker.w.Write(b) + tracker.size += size + return size, err +} + +// WriteHeader keep track of status code and call the underlying ResponseWriter +// +// WriteHeader sends an HTTP response header with status code. +// If WriteHeader is not called explicitly, the first call to Write +// will trigger an implicit WriteHeader(http.StatusOK). +// Thus explicit calls to WriteHeader are mainly used to +// send error codes. +func (tracker *TrackedWriter) WriteHeader(status int) { + tracker.status = status + tracker.w.WriteHeader(status) +} + +// Flush calls Flush on underlying ResponseWriter if it implemented http.Flusher +// +// Flusher interface is implemented by ResponseWriters that allow +// an HTTP handler to flush buffered data to the client. +// The default HTTP/1.x and HTTP/2 ResponseWriter implementations +// support Flusher +func (tracker *TrackedWriter) Flush() { + if f, ok := tracker.w.(http.Flusher); ok { + f.Flush() + } +} + +// Push returns http.ErrNotSupported if underlying ResponseWriter does not implement http.Pusher +// +// Push initiates an HTTP/2 server push, returns ErrNotSupported if the client has disabled push or if push +// is not supported on the underlying connection. +func (tracker *TrackedWriter) Push(target string, opts *http.PushOptions) error { + if p, ok := tracker.w.(http.Pusher); ok { + return p.Push(target, opts) + } + return http.ErrNotSupported +} From 850f266d6c4d1aba69ae215a9fd4b08ca487e91b Mon Sep 17 00:00:00 2001 From: at15 Date: Fri, 28 Dec 2018 23:06:23 -0800 Subject: [PATCH 23/34] [doc][style] Add error handling and variable name --- README.md | 8 +++++ doc/style-library.md | 2 +- doc/style.md | 77 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 45661df..6577bba 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # Gommon - Go common libraries +

+
+ xephon-b +
+
+
+

+ [![GoDoc](https://godoc.org/github.com/dyweb/gommon?status.svg)](https://godoc.org/github.com/dyweb/gommon) [![Build Status](https://travis-ci.org/dyweb/gommon.svg?branch=master)](https://travis-ci.org/dyweb/gommon) [![codecov](https://codecov.io/gh/dyweb/gommon/branch/master/graph/badge.svg)](https://codecov.io/gh/dyweb/gommon) diff --git a/doc/style-library.md b/doc/style-library.md index 1555352..6348dc1 100644 --- a/doc/style-library.md +++ b/doc/style-library.md @@ -2,6 +2,6 @@ Style guide for writing library using gommon -### Error handling +## Error handling - DO NOT use `log.Fatal`, `panic`, always return error, if an error is added later, many application won't compile \ No newline at end of file diff --git a/doc/style.md b/doc/style.md index d7d2976..083af3b 100644 --- a/doc/style.md +++ b/doc/style.md @@ -8,6 +8,11 @@ under dyweb](https://github.com/dyweb?utf8=%E2%9C%93&q=&type=&language=go) - package - [ ] `types` package, to export API and reduce import cycle, a package the defines struct is really needed +## Reference + +- https://github.com/golang/go/wiki/CodeReviewComments +- https://golang.org/doc/effective_go.html + ## Package Based on [Go Blog: Package names][Go: Package names] @@ -206,8 +211,31 @@ Keep file small ## Struct -- use struct literal initialization - - you type less and rename variable is easier when there is no editor's help +Use struct literal initialization + +- you type less and rename variable is easier when there is no editor's help + +````go +// good +task := Task{ + Id: RandomID(), + Parent: Ppid, + Name: "clean up" +} +executeTask(task) + +// bad +task := Task{} +task.Id = RandomID() +task.Parent = Ppid +task.Name = "clean up" + +// even worse +task := &Task{Id: RandomID()} // don't use pointer when initialize struct +task.Name = "clean up" // for fields that can be assigned directly, put them in struct literal +executeTask(*task) // don't create a pointer and de reference it +```` + - do NOT use embedding for struct that will be serialized (in JSON etc.) - prefer function over struct methods - TODO: explain this ... @@ -218,7 +246,50 @@ Keep file small - pointer means ownership - pointer does not means no copy, sometimes it causes allocation on heap which can be avoided - [ ] TODO: refer to Bill's blog and course - + +## Variable + +Use short names for common operations + +````go +// good +b, err := json.Marshal(Task) +w.Write(b) + +// bad +encodedTask, err := json.Marhsal(Task) +responseWriter.Write(encodedTask) +```` + +## Error handling + +Return early to avoid nesting + +````go +// good +if execErr != nil { + return execErr +} +if cErr := container.Err(); cErr != nil { + return cErr +} +postExecHook(task) + + +// bad +if execErr != nil { + return execErr +} else { + if cErr := container.Err(); cErr != nil { + return cErr + } else { + postExecHook(task) + } +} +```` + +- [ ] if there are many checks at the start of a function, might put validation in a new function + ## Test - use helper function that accepts `testing.T` and don't return error From da470e773e09b218fe9d91e053dfb035ed0b136a Mon Sep 17 00:00:00 2001 From: at15 Date: Fri, 28 Dec 2018 23:08:04 -0800 Subject: [PATCH 24/34] [doc] Add banner for gommon by @at15 --- README.md | 3 +-- doc/media/gommon.png | Bin 0 -> 263044 bytes 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 doc/media/gommon.png diff --git a/README.md b/README.md index 6577bba..887c79c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@


- xephon-b + gommon


@@ -12,7 +12,6 @@ [![Build Status](https://travis-ci.org/dyweb/gommon.svg?branch=master)](https://travis-ci.org/dyweb/gommon) [![codecov](https://codecov.io/gh/dyweb/gommon/branch/master/graph/badge.svg)](https://codecov.io/gh/dyweb/gommon) [![Go Report Card](https://goreportcard.com/badge/github.com/dyweb/gommon)](https://goreportcard.com/report/github.com/dyweb/gommon) -[![codebeat badge](https://codebeat.co/badges/8d42a846-f1dc-4a6b-8bd9-5862726ed35d)](https://codebeat.co/projects/github-com-dyweb-gommon-master) [![Sourcegraph](https://sourcegraph.com/github.com/dyweb/gommon/-/badge.svg)](https://sourcegraph.com/github.com/dyweb/gommon?badge) [![](https://tokei.rs/b1/github/dyweb/gommon)](https://github.com/dyweb/gommon) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fdyweb%2Fgommon.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fdyweb%2Fgommon?ref=badge_shield) diff --git a/doc/media/gommon.png b/doc/media/gommon.png new file mode 100644 index 0000000000000000000000000000000000000000..fdd96bbe7ed299497e19936543be0470d0a6d3fc GIT binary patch literal 263044 zcmdSB2Un9>)HduCb<`Oc2LVyaNTf*sL5hI@qk<@1dgw}%5|GfO1s%o$2u!3231B0k zNeLPtG#exkiqbKJC?rIPzz~EGl92C?<$2%d{Rbaw&9Ihh-RJDH&px|c``TnU+FSj$ z_t4%QJ9hkb{+xx=jvaezckI}y`^zriU%0*o!N9LQSI)s=cI?>qhxorAhG|H_jvasQ zIB#*v@XP$Spdhro9goxvlc<=}5!%_{fAJ0!-`s1bS zpU=;W|0nzBuG4?+@xMgKojmy8O{Qf}n-=Ardw5Z8YI!P_J)bz=Sy8pHP*$~Eyu85V zny}I$2y_zFw#yGy8L&1-LNdJHPX*iti^Ht{KnuQh{xwRRX0}x}ppAfk{6l^Kv zamXHlF~V&;xo=D8lt$3NAHkd9wK;)|ZE^?7YH`T3O&<9Cp5_6!AHFQJQRcf~T}iSU z%*1%=DD8(FMfeL7WgOiL>i#K#dFD+-4ZndC&MZi(bj=)^8YaMHJj%17@~vu!WJV8J z3$@$Y-G3?hDzMbGRLhIfUzYSw+BM?PbUW!R!x9w(!dc`*mdsxpwzKnbn^^j7n6UJo zb02NoCyIC%U9@}W_^Rc2|NN!`%)djEbAzm=JYiLKEVBk!=(*Yb2C+5m*m0aFpj+Ep zm_iZ0jECf%DCZI^q$PIe*vuPZdf@rE-uLWiDHpDV$y3bbAH!8CH4Z(T67p_Pc@ETt zxBwX8jvY;>zsttuq#c8uHPz-Uk}FU#z(P-BcaERZjk_v5b^e+AzUrpcwBl3O4ffRp z1d%vlNMS_8$g@Vl8kq!g7+v`ZSgK$BhO2nV9oKr9A}6=A_F3(^oGomqeFhVWQjw}H zA91~vRh>?ql-(ueM(W$(gdNISN~qMoGWwp!z<$0MdZ1v|T_j|q%mrQZ`z z&E%f5_x5qO^%RT0q4!-Ib+w7s3@ecNjQ)UJqc==d`}W3$(}g%M4gJXQD~ZQ{cxK)r zZ)CbjRAR5+8lQUL=xKB@f9YQ3(-ODw;jkIj;jAwQYbesF{VG}(tkB5a%2$!+U^mi! z1aWT4_oIB*@-Voj$Jhp6>xng7eX4K_7rYP)js!J53%2o@=f_#vyT}D$bV}hw+ z4O$BOD?pSHju8$ro<-wJk*Y1(;HgMH zPdl?FJ+$8ICPCAtrn!ehw=#2!2ak#Owat1dwvrt?8YUT^-?A*X;S3HoRN|PFXejXb zlUPSLg?a_gNX^t9V(x2BHQ5SOo-uuRs=bI{(i{@Ucun4?9Aaf(!)4x^?D;96HDWro zmtwDW=Gs!U%t&`S-ZP*5bqeCkBcJjsfH+QSfYSymVsy(@~&+)y�F#!vm+P3_%V z!|Qk!UFw&?Oa?ia!~%!8X8PcpLjcP_VC#>`I)-*@Tf=smr46+u%t7z8%39-l$Bosi z<`Dr})ySvwJ>;e!x*cdhZ}9_aA1LZhyRxCE(nDK^itEnNc_JYjMe3pX*3QiQK@s6% zM=tRtjsUy(6AS;xJpNS~m;;HbLraW`d~DsGR1B^Lje*1G4-A9jBQxTbvzdNYND$`* z*#~9E-!z+#ugjV~#5>0bSZ5nVyAujx*0j3`lFQWp9#1xAY*?Obh!r_(^~<-U0oy!> zV!t0I7B&dg=PZ*GCjOYW2pXHYU+bA>22G8n{yeK2pWU) zE9#0YCp0P3V3zov*RFlHtAC%|Za3ga*^S66P-cE+|61fa@}JAtQ(Yu72%M$?sN_}9 zgvwB>I^`MHqAu548g>|>w!FsKUx(&@`j=d!EP3)Vu->%=$8SzhrW)*7Cd=aiepE+F zyZ8h|-(p5w+M(NDUP-aJI4krMbMi_7zZV zIiG_e?~@njVb5K@d)a-xedOk|ogKDj2C?B(f*0^O6|`$+_KOd51~!@JdW$B@d;JDE zSs;Hd*#jJ-96skLj)K_l0o6ktZ;|N9NJe8Jh`Vt;?+B^J$TAT~&Q^K=zc|+@$R-z{ zw6ZRjGY?EjT8`(mAPcYg9hw^YAkGSHZ+tf=M@HweVdfQ&`MqT4 z7}s*!5TCezx%u#=E~pDZ_G*F(m%64*GMNUxzgG1>=94)BiV~+8dys|BE=oB}mh!El zr5j`5Mqeq4`7s| z-xj4zOdFqRs#iOrHFiJgr#@;Ck8}1rE*jBi@Sl3?^u6JO75+=S%`!G zQ~X-&nBnvRib!tiASC!>h!6+~yO&2P zrO1OyjSrb4AGR2Qf?#F z&QYh}5q-@|jXl~agV=Lvuj69Ulr9&E^V3I8-%NeJ2J>&y#S#5m*?gqEdxvphniR}P zAS4%5Xa>(SF@J1jCYj{xxt+)qD2;@*{R(_~u;80<9rs&OYCVaM*hB*wlFI73J$>Ru zT<0P4u8!CnV*>NuaL{gQ0X2bsn;q1fP(c%?2LI<~>g{tdzVVoUOO!&>;EWN}fRs1u z16iafs0}U2yR>>YwP9)ZS^L}aPV&2VSmeMkvm3=47>*Blu_0T6O>_tQ9i-R<@RZo8ko%vg zA{w|#ZcOHuIK=%Qt2OJ(s0=Amz@-GAU!OxI$H&mXgzYm_LU}fGUiCHSK5TN{6_wCa z&#&{)uA$Ck)%(d&%Co%nY^b`$!oH6plfKlJ-iu#k46FK9m-L?@S&ZjDco{||ICRfu zl6&^oi2@n2bH*aoLNtgw@Y1kLog;-RnCt8UBJ0F!Lg2;EAIPkY7u)^9dn0~Ur41!F z#^NN-0D}6ve1D)R`OLnbZgmd+$Q&?uMAYESRcMAsVm>UH{>cddf{=4OX0OS=6=Q3F z|B0S5x-o{?O!|Cae8sBl%EABpHm>osD7g^n6S#Oq8HhF_WdWNTToofCXO?(}bP>H> zizp@7UXd)r>P5&x4B!tT(VwTtk5FyOMrR|{X$OX%p|s*?sSDR8m!;46^A{9&ccX#ZGPuD6Vxk^NO^<34T5fXabIjOktvoD^drtKgEz@WP#@qm zUAyqj>p$S-~+4m^M5p+@DaW>eW^KNnB}u>O2l+KC%g5*!3R!86);5jgrGw0z)4x?iFQ;rS8BTy7x+H@kR8-NLV`j}9t|5*r@8z*nVsZtWPxztlJm(5G}^9uLp7E?j) z1Dp+J;kx`N3K_Up)~7_a(uePHYVNf)l&P`0@a0}m|VAkqeE6=gDsz8xun3z=0? z1x{#uhfC$Z#wF7eR-cnP_H7BbNwXZIU>?_8YU&`&2gur(>5VCA#fL{+V?^FEq<0rV zwI(t=KA8xc?u2gm9pdGX@fzMb+@%oZ+@vqaf%CV5Va=*A%Rbv%9-@QC#^(LJ2EwgH zrSf!#VMqHkZaG*iXHWmps*7-6==1T)%Dobdr09hfvhi0+c8-D{1|M0U+`^n}j}012 zXOkLM+~YC%#@@C;Lr)^88?T8gA#DvMETWo?`)PYG<*xm$kvX5xnE7PbRJ8HRKbMr| zg`0dB)SMObCuMpiht+$*xy85eZ>3Bo34~VI22}Ih7d9SaZl3Lo$18uxmLAJ+s_s$_qX1$A?%|+N)rZ812w`<>GYjOhVlGhNW z3AQ=F>X+<}VHE_iPQxQt?oO7k&ISMHsW*v(?cGjc1m7|f8<6YQ=X*RM$XQ*@va>O? zB%z1HtI(9wDh{N*KwL@mLV@ehL3qryO%4IRa)+s=tdueBLfmsBwXFnlrItmn+3oD? zph5dg0^{Zxqu?`&p1DkLxMXIPovv=2_CaWTlh%-*$PY6SSxI`OW@B_dNt^s|y&}3l z2k(b)T442kvI7qw66}$}!L5`-NX1|PsBkWbe7JbG_dP=3>hV2f+Kr+AXL`=S(g2{|$Z-^x^aK z;erbeTC@Xwn6f0>qvmDMtZ|_7_h^2qjvb8i+W5em`#?@+y*KNP*{HFC1ka@i0vGny zG#x2V*2xvbLP-*bn^Sq-Ud9J&jIVZ%>oon|uFc`1>hqWK+QoUz*>A;PjE!MBLYZ#a z;k&Y%O6aJJb#~ib(a{mj2jUP!qfm4-QlwJB|78h$gh{#(#reHKHt0f9km*eabsC_) z%K6nI=IE$Vj9VmHb!lSPx8 zD+{r?=@){i4V&_HgU?cYX@gzrw8LUha0k}*YeG7o1OAyA+uICO@i_!|%p#-A)|FOT zu$nxW!;D^S8_Z@Z6+|L@WrnsCC9p@Ra9#o{U72L%`+ILyCoIQ=U?1C$2$Nw2H(}o8 zXW*U^=3b7%?~vAKPTqoEc87+*7zT}gg}Dup_Bde}BSUTmo8nt0$D1+^Z8Lfi6G)9w z?lul+JcxW`VgE+>7h~hnAtUjH+I_#!z{Z7I-+HbPPW5#n&W71zJ`Mc0GN3p7!fwP{ zrpw6uuBlB81a4p+>=jx-Q!q)K4u-719xnLcdl2#@LI+o;P5Woex0r_rJF(2wKSS5a z6KtQlV3516OA_S6v!QQRxLt}A{!$K0UM{g)os^78h%sK0wRZO|)qqJ14Q~#;UgHF< zv-cQQ49uBRs#~>ecF(!8V~*~7rlgI%BCd{-|N5%PjVIurmy~!LK%rQ>^{^%7irm)a zdDva%W6VunRKQ_QKrLOiwJr9B{=s}_$qaefMC8KWYM8yLc1|hiQsQ3=*5iqvHiEC1 z>{=+upi~aDtS2NSFTH{PW?h4Qvh2HDV>Q<(vc{Z1=&j|&_r_@7kvDyrBupynK<;W* zp;YmL|0&NTE#B4RiEi}FvWBDybGl3Lm6}NE;9=#2tCz)*@jIl#w=ZPiM?a4PD4-%` z#KzrsY3b2YZ~U-sqn4;0PJK1UQ3Ak$hz&}F`yh*m`or)?KE;ki|rgEplda((T?Nr}$s6|T8VQPi;$CP!jBTlF^vfRdJC^fM#& z9Vz%fOqt4B!t0H8LhMLR0(VlPY+w4s17zxNo7E(7kv8f4_1+tz0^hFm1NVLgPVKCV zRXS3-6BjHIK|KY)gIWYl5puJwLi0( z*mj$Cn6zLq;-pq`&og)-QFk&2yrk8mQ3JO-(L01F54;!+XUv>b?HmULRw(K^m`K>h zM2b2tKUSC_ume_kdNEOZ$*fO1S3P{nSnC(ctr~r77GuWkQH5xOoG_lf}QNB zF)NDUofGo@vzQW?LVie!n;u84LkpBNyN-4ODJ>oB^Om!a)b<27Dq)|^8W!6J>C;W3 zc}U;>4&Nv{Xe6a?6kB5$$0v*j%;k_%6+Hn}`#XG1XrF%~cXj)`VXtpnQVI2UD7Vhy zN1x)lTRz;KvYqU*_Z+zNZl$2^X<*5$-~{yidPN2L@z%qu5fh^;4i)+xHBO5u$FPGt zmwpDcEKk`}`ATIJTqxmidVup0sEKTh7WDI&q-PbHhX~0HcIfc3JJX97OYX|%Y^L41)}`tnN}8l@AL;G3GiflZ?gtl zQB(}IFEH8!LOYHF&5ynY%P_4Hlw=@Kq{GZMzN1BW>opwdE*QjAZ_Yey(Vs9_-!%Rc zahkCcwZ`T=z6zFnLb8c^OFtgm-BJ(a&AH0=CqVnsSQ|+l3C(amJev1L z?^&*o68T{m@-w#xBuZN_J8i61lT#jFlqVOncTR}X1ckg_pE*Qn*0+m^m}-5j?;3q# zKqDG0s*TN4sy{}1d~V}Oinn`F-Qy%*#!E6})guUEY*WoMdY@q(lkT**d1>)dd5Q7n zFQ5DWa~XZI4l%r!X{`P*I79GYxIhxuUoauG*xKk&+Cs%cEbg0lZ6vSb(K%m zlA=v?GN{S!3)W2u(j!N>H+)U8oJXXl$6FjSFYe*8g$mH3u!qa6cFJqBvb!5gRYrHS zW8iQ*-@)FO%@a_^@&K>Q)5lEOS`|T%x3Oa_EO$j6G<)@P1?(h}FiKx=*LO5hgTv9R zm;@3>Qh7zk2NG-C{RWf6Eoeth``Y`ax<^cWNWLm^>w$Y8YA#YbA!Jo(8tblc4tnl~ z97Du2q)|m@?XyX9@A1M%?YoOB?|b&m9>XoW?CRe&^Ja*kvf*ccIu+>3Wh;p;EM)%U zARFX@Wfh*xsk<|$v9BhagMpPNP_e~l6?4i(0ShyZ#}DHpN>tv3%yN;YLvpBB)U z&LrhrO^8@oT&+KY(p-!aCw3cqzXF1j$@rf?Hvyr^pb++J=^&kss1B2vpONCFESPyF zIWVk>S)>9{B%`-wNhx>9Bup}D?KYXJ4D%dVNXFIjR6HtM^Ppng~(M!#>GCN2bsWyK5 zxpK>UAZGPUTv&YP{txJ;7CV?{M-BGZ(!=(51fCJBwI1{OmuDIUjtn_T4d4U~&EIaL zjFRgD2w=isX1bIcrjP5aw|0UJAENbo~<;Ty(9lkn# zQsZ2{P_UJS0$^)pnmflE6;-`?{pw6R@sptIVY_7c9dGCSW^tO+aqeqEhSjk#R|Bd` zB_L$h!%OBF!*;{y>P7;*>HZcQm1{L#*x~m_AC+?YlSrF3I#`}5kKYYgS4`@8#!o1n z{JLMrWePTlpBVZ*F(CRMtqqFKytlKw=IIRU>U}Tju#Y9It6oVtS#=B$6pPri@iB`` zT7~BOpi>6sIYQKMfr~U}l4^K1Qp;!m*6pjQljenom2=OQ}kis+*~;a&~Kq;8&ix-f}jG6O>{dIMm-FEIK4fmSR^&oO3mXSK3Q3H|Q$i%PU zb`8p=hgOp;eC1o8#xhxR)<&)I=Zi%<$0A)8H!`ZyL#xs+g{58X2%V1iH8*!kJ#M;Q zMh3!mD&51@;YRP^6wn8agYF(Gc`q0q52ZsO)L3yN|9d0;+S{O3)@vZYbf6N1gDoit zu)Cq%31lf)K6p=TZE#AoflVyzXt0Tu7+Cc_N~dBm3peKR-+xvMVNrfJv(mV68~m2 zHTlv4_ASzifth6O1{y1kyw3^mKJ->4Le1Un6^!k1scq|tcPmD~u^~SgWQ{eSt;oDsM7EKH?o={tV>!}(xJ<*0+fVHsbyc&x8%}2Uz(K-;V@O`pNPb- zIrF|b&Z37l)eaVSg?0)-ocHk7G4Fxaw*|{igsZid{xq9ZpGzg@qC_hwbM<6HxItE$ z(rv1VP9*oG-oR|2H@C7S+bDk-G}ZOt-zin|BtDr;0Yzm7_VOwx-6<4-Mx|MTd`@|p zr%m;UR;5H;vjM(JrX?n9F&#yc#G&JVamsvSOM6LwJ-N6XgvnK=RZu)I24 zV5U^t1yYMCb5uhF@4@%}eHomFm2pplAOebKUiS)0d2fe!``_TKB+&E7zNNmGY#Zf- z!fq3xo>DzHz2i?#4?tUSo?Z2HB^2eb9)75*5@)Me!Wqg!{^YLiad24FZ8~b_s2WJp zGBVfOFD8mHHC*6vy@Puw-jU&hTB&pHNHF^$cH|EIFz?cWs{9v9%k6KrvnQzW^Go0f zNCBnw!QQ`V04>5>zDV*8lYC>$^Jnn0&qkZ%wGSNnxQAL+nbPf)mRP0hot@heaSPTp z^~ZcZeEj55r%t19ATEU?1Ghw(?K+$*#Yt7iqgaV3ziet}JCOI@Ll zYv`a!3mk!uXwJoN8y1>NCng=@8kuJ-WJjaBWGO%4T8B8DjKIn15SXnuS!u2;X#Nbl z5v4Mg1qzv1(F!N-PSF-F#kJfwR>w)~tzOcPigIjMJm9R9&9KU3y`HJJip+w#5joXP z$NKA_qQFz@eiq34_RO-6nEa@umSctHvaMm)%fG}ii2t`P%TRAdu^JZ$WTDiJ-=ZNl zX6a6sk+mGTxZ=DcXgoSMQOVt{^gU@w*eG~K_B*QxZqwdi4&e{nQ=$r1fmeTqF4x=z zAc3zcg0@t8+s(^!sqJQwE6kDh(odm^r6ZSZ+`Uuce^5NkXRcl#{=L|Ic5q19W15A* z6Caz6$2_R?Y^(CoFaW40mxab35*XA<3ewaJf1pFVkoWdOuEnypk1lVEyabe5)tJ3} z-?DUs9v7`lZ_C1mb#?CgZ3g_HRt(YhpZuCHeGJrHcLieGRVqQYn<=z$cd~``cuUi} zjuw&%>L&BL+Xei0{=fWl5%N)@}_sc(ce8ohT0ph4w!b9gA}pk!Fw)l%>ezxA1?BmC;OYu(evZJpc{ z4*b~MGZdA>)Um3&U5lPKWQ|gi!!~ZVcQ}v;LdOVxZ%|U+>Et+(sSYPQF$Mao>VdL9 z65*;Fy~T~tqjy!t;JI8gaCQ9BXk7)+Cr1J%K_KQ3Gr-{`s<^61 z4HIN6N1}U-05AGgT8{c4NR?6zaE;tSG9Ht>rveQ&c1@hf&E#JlhMG!n zRw^KN2DsOqM-UgL%OBae`zt~T{D+l9=TpzL6Wkq_fQ}<}ew2!NjAo8PCF--LJ%||{ zeN_#|8UK7rkB^q{Q)2z?K;oF9c7GBFHGjjef#NUsamt=YHmbbD6D}45aXqRYICpbJ zGaX4)4~e2Zq*)dKM^7j#>CpaZYi&`&pW`o*bw}E>!8QsI2Ssj511IY7t+Rt!?W%}q z=uNePNPs%C)uK(?Z8=7eaiMNLO9U@f-#6Yw)-`M3wd+cprJ67$L8F-XRWL^{ z_k8Dz5BmPz3P9abzE!X!+T9mpQRr$eOQAnu-Pmqf`>>d_Y<>*kzcBOs?dV#ca|OCx za3wHcN~j+qHjmP;iS=LZ|FxQOw1Q@b!|Wa4VMzwBEX!5WodOoDp>w!~VD9lOvn~bR zxngwCm{YT`dUCw}b!y_7j&kRMv>0z)lF~|weADe`svHiPiIOFy4nADMzX-tOhH8gY zRT4)Ij2WMgOF19cf+05A+oc{wxHGoMV<<1>+`_s*tjn$}$_W%YCNm6k#%+AfJ4vsT z%k$X2$Y56_=bf!$wFiJ?Eqq;xoNe7`nsAYqLJjxyHu7s;&h?h+9cH_;TSIDmB!I(w z?~oh2haADlq{KVoY9u=4ZD9%r+?r~M5dag*t;fzkyPOqB1SlMY*r124U?AdE8B*GI zJIrmh`WX(qm1LfbM7^hcN z54@RxU7mKceiAhHVhDfvB=Vhi;Vm;M9%Z=TH`dl`%5SJ4l8|V)Jg!L@#nsLWQv-8o zxEEGF<nvF+jzi$Lr8QSjSO?}qIhJ8-d5 zNhD~{^^8GXR2)DVa`*Oe^((^vSJu6O(N0A&l0ij1kdODaIxY-VXwDj2kvMlxI&_>Y zbU(XY2~aiHyIsVVmDfLAJICT^e5DZZM%Je33<08Q{ ztK;+w^HwOeW?h@RX-bIzeA4dR8i_n0ERA`OWCYw*sRal4$hSffr=Z?e`O7O0(@9U< z)vC3NA3R(nkW#$QKl39v*wg-i$(kee3MH+UGa0%ucBa92MtiIJ^&gz`SlMblrBV|X zGjfUUm|HnIBO;}@go)=Z*`NU4{K>S^!xZx@f$IV`7EKU`0NZc1nK49e8H8HNl=mhS zxg<+{K8OZG##baA+!e}_iWR!SQQL`(YP~MiOY`}o4m}%w^4!(+3bciBW_IKDv*?bt zjSmFY9O}j#xVqE$u98oxZ-l!U*5;dvngF% z=YRTE>lnj^d7e~4f{|Tb1sbkWYHS%tl5W$xbYhv9o*S<;`8yDYqO9F-mPX;B#X-~n z5UW4|H)#KK1ws5&3IK4K!*+X1;Z)V_v89fqoWiTqz6U8k*#IQ0gXS0FqNH7StBipw zbArx48%2a2<&u^XQvlLIs{tpl*iR+cAL*%(7|sZpZI8WF_LG{2nhgg%>%DHf(D&J4 zd__qSf~fq@yf`I*bfmEzV96GVR5(oN?sr15*&qLgQ!G~j1T6sY=bV=+JVYw+AOZRY zTHfa4!j1~fs6kRL(4~JXJBJhuwbYv*C>qMCJ=AReu0-&0fq#hxW+6NKBTqF~<&Mdr zD9ZI&OQtG?{33-cSnJGPOf^w9tNNA7m=-E-bv+NopG$7G==3Bm5Fydy3>SX_^XS-0 zw!j-xo}+Hk^;Q{x(B}Wq0jgS2Hd(kHr)k5rI_rCg-3}_09din6%8FSKjm+DS?rwo>0=P`pM%gim-pJ)_I?PetJo?$_LTrW1$yWfv;ZT8g zBql1ZWPwu@Wm->iuq|{JiSvfc85Y8I_t}Mm5?-fn-x{{d?`T4%VVNs_au9&iMNEac z(CTfyT?Q7ebp-#tJ1XX%JtUC53UJ-x~LBc88gU_ri@lUrv(O#qipfe^xJ=C0L1x|a?seMvOT*XoE zeXS@=`erZM?=@|WWs+DxvS7y>0RP9x9|PARRXHep0L(xnQzW%)Uj?|KWMv3VTkXw2 z_X4`rF4xjbju$*FD(d0vd?v_e4&kX$gxS5I_SsABQ1kj>Pj{h7pv%p~)tF_)GS0Qv4VpHtu)(SC%4AhZG)N zTY;zNnhj=@LjYcCm9Gr%I?(ZpXswP_DSxuMzNWl;gD(XC$b3BbTg=t+zz>%l?Mv-i z=}q}bo{Zt)0@tQWW8}lID!-^?`9+JdDtp&~Q`IoyfL@pk5n;VdAEz9K8eB_M_DjHz zcGZ@3k!Gh0^IoOtat2IWXekPt{B~MPhu@u%Pr5GZ?(roukfjR0dBhU9G}-ne$`!u*^y*h%YW#BG)078 z$dr!?S}+^m(n1-H3h!BttxBFnnyUZGExDjnYhUORQ_P&LFNoaxo@DLrIY>Iwxc6QZ zr@b}!zP+<&_wWkr<8ByME47;vpQ*&#qwt-Tf$7ciVsb2&k>j6)S z9Pmm&0;l4vUQKvbQBqL_z9Kxsa8QOVzPrTzX>IyQw6V9VPSg`tGk;@tDWupO_pg!+ zZdfUs{w!^3-GU7@FIQx5w+-8Q2(4J+rjiHdThHbatP>ax2`azv85A&AXC8K44>wqR zvYECBYViK*W;B(82#a@FYfRBY1TgxBrCp=cRjYp7M=5-;=4+qZ z8N6#S+QD+$>nQy`%DEwdi_L<%m&;MtVxoDg!?z#5nBlfuzPD*RdM7`-}4~EffLg}F{b~~Ogf@o!}`^vLLH7x=q5cfcZ7zy)m z`6fXKFQLiE$ZqZ7a6u|zlv$^Wm9Reb$UbnuiP;1ocm{=XQf|#A4TY|1_@f(B_&wNa zf&o>P)?*xQ<8CW-VE{qRHMLsZ>W6*wRD+Q2Rz0-~i_^ zs!R}*(mZ6qxBPGX>tqUrT>lGhBJX`cTxp;_VwRoX^Q;V-CS& zTkFcY#7K|jw{Rbyuk=9RfW{^|FIXG=1nXNU*n=hgW>$c#d`hgL4*ks>X`I0Y@YtyE ztX!;8R7_w1B?b%LU$gCEl=+9KEf6N*SKpBn8KE@mu zX6SAG{NDnmY2^-Pf7fz8a8ahr;3vmu=raiFa!ei2_PN%5>Km+)zq7>ea!Ih=RHC)F zR~OeiG%f{VfJ*}sz8ZSet-2yZ(om-yr7KG?F)STNc*iv9O})Dl3%u4XUwb=Kjg5s& zd`IoOdj1I;r!>*{G@Y*||F%Ah`pFQcW&uUaZQ)rYF|@=A#8&O?0cOysR&OP>C!VI~dsg0|3;}Hv z%0y8lhs6f%E~FJDPA=2C%~HZX>RvVMOz>j3E6ZAJG@Jhre^=_2d*Re!-@DGebOqMO zWKej*s9~1$enn;l0nft+hQ*hrjwhZ!f^-snBtVfqvdU+#`Yo@%h+~+ znQ54X^UKA^PV~N9_($oPw|UQYP$P0?*+jWHzwTZM-P{o2#Q1O64x6lpuw4-y5ybAq&^toEmqiEhBnbo&pbW^P9ef@v5 ziGS}PK#p?OKr(Jkm@OW8$U^b9eCZAJu&@LEkPWo?guvVo{&ma+Mi8n=Ib3wU52Vi(XCA!ns^ksOM^AP*i z#Kd^W(rI8nk9*&atvQks4nVXAD8jQI=KJyiD|TA=4OMShmLXr#J*t-&{j5sPJX#xf zD`zZ7CV|ky5n6_Zyay&iD$v2`iZS29)d0JPk(H6t7DTap)>Q!*^=-V~8n zF)EzhC>*0w>H&;0=BM}p?5dd^_hiM7Sh$TSo{4jq9$eC#sXgo3apNa95RPQp@W|DnZ+Q)VAY>mgC zh3~%1k2%`sczc{pPEROQi_)gK41HKJy+FLXPz18F7=%+dx*MTt2-~BS0vLJ9k(k>! z^f$Z6+P%Mb;X%MUOViqYcuN1qQku<(e%B#N!82Pv4HYgs8Ia%oUdICY=klCM08c8q zys4e2+-cT#}*;jojs<6ZjNehRQCBOrNv{_DjHmkg}+mu0o* zDe-}+rKyO2CZFx7eEbpGPs6B#QjIZE*OEek2-C z<}H&K_6Zs(p#%sjzW?H{$3sjHB?3L0iIw>j+W6Ym%~`1*0hjH6>g(u$e`7K*Hei+i zc3%zGq6`C~mfa|GRyJ3Jv`6}7wvA$dVGDUk=xhG29Vd?Xp*v-G0|fY*ol;(b+Zuo9 zHG4wXm@*y-faKS1Sbg!k*Sf)37+K4W!$5m|1nM|HXfcG5;O79mDLDx7lZWjAG=KihhPgQa_CE;v`*avEyXBb*u+f1+LbF11&ojmN9l?LqcgompU~`_K z%yT9l+pTVWzS)xE9cY>Alcb7p5PWKC6nOi{0_%GCe|@(e{W-TW`ip(Q7=n6CO+(Vz z5q(mTkAs8HA+{iIa)UI-iLxsD-0;)!pWbaHjpp|PO`0nVhgpM#aSz`~_WJl}oDx9% zHtOL}*5l&)mx0Fp|53@OQS!y0i5o)~f%nt@V96W+_b(-c1Tl7lnk~!@PQ8Dy+h2z= zyD<3+8@itt$XG;nl}E|iEFdaZNjj&w}eV&jPawcK-=gJLx(mMQ&@I zof?Rnw-o)|nEIbm(k<-QeSL|dA^rG$zzPlbz|X|LcX+(sRq^1$z%#^KEfg9=y3?g( zHv;Y*4$E;>;0i%=d|6U=ed6X18zs=)E3z;th#4Z5@h|xSK-0f9w!WN^qJi*XpvYLV zMU zz}TusT-^(o=P(mPs3p2Q?(-la^@;&SZyZ zEL4(xVgmV1qxmX0Ec<~c3!;Kg{)w=NwmbHn3*f}) zCr_bdE$nmTWmxZo)?87+jJ~f8f+mfUnoYq3O|YzdhXKOo?{vN=Fyc;p*dm6Mz<+&p zOW=5r?B69=*cf4HjWVrKixnJP&{PiTCI|x~=YD42=^iqZr$}1XY5DX@UrS2L1!u|2 zKvXynhwUy5JY3X`_611S_wT>^R6#Eq@rZf1C>Me|@%PIDkSdv~_4!&XUPcM9@*2n!~1_c2Iv+ttwMY zOk@C?xtIcjFi<*6SjEW@D8r!k-m5}IlV2T68w&CvD$^5e0 z%y&DcLc7;vlzr`IycA$2q-7g>J7F5Y7SBkbzttZ&RiSytbU&T%4_=$Y8YPmiTWX7R z5Lb@kg8V#)z|?v4AoO8ul=Y;Sv&-(1By`a95<#lHGceHr7pQcYwN&)lkcr;EoXv~_ zObKv3^)v2%X#|TcMSDQW{$^jJv%f9{!SYzhJpG6~e8FDV6oR11x9Px;TW5i})p7t! zQhPB^7e(GlA;jKr%i~?^R?m6}OyCm$4k<9$@buFoG#2KHb#sFE1W~!O7_XkxsH?*G zzt`eSmO1Z}Y!)Y4eKeH;I$X$u4X-(lLSs9TzT&4;6qOD2CUU~;eO-OU_BIdwiZ;kJ z3GOk$TVSR2y}(@12w_Qcc6OiweZ{CKkhgK2yjxjG(`Pj_V8Bh%sDT`~-vDRVbsYF> z0X53Fo*{rb2fymn#7{F<{UjrC3Z8^B%_jHACDeN&Qt#~I zb(9nz09PWFv}ljVsMg&1j1*I%IVaGR&I-T&qb4BJah86(xB}miHOU6}5G#%1xsZis zc(Nrp4b1u1=a=Iw2Kx7$90q!yb}mMVwIxV`P5JIpIH>(_qet2q{Xlu~{Y#6y>RY%9 zV64B$N2n7lcsyp?75tEHu!-#4bA(a_bLucB@HaeW{%Cy^EA^~tl&Fnw1(0FFJ^XLL z#-F}%#K6J0vom!4fhy-#Cn|xb+ixz-YrB-G@Z9@f(TayO1cEp}0ExZiaJw6!C;LIUvAV^_EvoeA<@`l>?gTUMgE_DE6X|4 zu$Z)?^lPiO3z>CZVYY!{D+vDQE0q8YjEywXfq_uI4fn!ZP>r)&nlh(qI2C+iN0S=m z9>jDV161)7J*|S*9(7=!Q_tLcq+KWiVfi_G51QwRQlY+j*T$}9S-x77Chm!uG+)(k zNbwHyC*T=eA1?5$ucFvVh-+2&c>g3u@tlwv|_uqEJ<@a@!16JDHxbt{9$!nH?Bhs z#0I)1ZDPdp)AN7C{%nU#TO4Z0qEibodY~T7ESm#JI7QLC59Hs(3N$TX_NJV9z6xN> zbZLkh-k6flztUEwnbnh+_TT!IV*MeJmoi%UFaTpuJL*K$vG0uWjqV|cwZX0ns$>a# zdqg?%-8fZ2yLS0WMvYKUA0BtbZd&60|@DRZxHtap|x!Y)KZppow^?d0KL9(xfeW}aak2t$XT1=guZ4h6__o-`QZ+sNMlWe}!D$&zG4Z4SWjlr#K2wz(6&Lw|RTmPO&sY6c9UiAwe6v5strItnwCo zf5v@i0g&)G$5H4T4EIS>1=rWxwMycTqJgRDf#5{$J#s5*&DuQ-VC}kmYWn}EdhfU< z&+mQQ)>^-tMI;ISNb^IHnzIU?F`ysqS45N zzxGFCT!Yq%S7SkItcj%dRWLiUu`{XFjJA%p;G`(`_x0EHUH@kD9&g5xw^5Hrnt%xa zoZ7!lU*}4&XUA|&N;QBxps?pR)m=DYxj-{1#)|XYV7^3cg!3M#p44vC9rm>Eeu|W% zYz%WgevQma1>w49?eEip=*Z|PYvN4juzF##@`|VQpLC!n!6#E?%lDB#*81&Rncpii zBI^l!=PF`ew27w0a-okT0cz^E1N&)%9IkXNY-A$$D~3zdr;VWI6?R9PV6Fmvb4Qzc;k_HH@@4 ziPH<)x{bcTS$aUB%-|M2l^jhVBz;8321Cr{g@sf~g3XETp$^i}l*eu^_~+A3Lc}ARKFS_ukmXoIUd}&58USWLp0D--lv%o|%RIxcVyaZ=l1a`?IGmFP_!FAR zIls&VBkB~wDD_4Rd)M_|hOMjnIQJVvAGf(15Jma4dU8aYR4->$7KYzKi%iKUmnXS@W(vj zkd6h~)%aYM>m{C#s|mN!34JM@x|iiu5@lOM9dW~Ch>$tu2+gX zw>d#scc;U6KQMXR57a*Gt3&ifKaVJJk6;+jWtupQ^5hNEj`QgC{TDnGW<)K5#tv!c z&<67ZtVwWn9H*kO>M)7g#FNci$u(biK~x+E1BIfmX4TR^kZ)C$m(0{dmU;O#-e~C( z6vN#^TlCl0MHG4Zt$^M&<{%x*=l42nv@h zZ8oIrp*;0hL6{0ZZeb<2!Q`RYVH(NC>2mi(QhwG2UcEu2&_=2~z2J3}W6_4wjhaf^ zlGk|inLf*sM}f;J<@LwNwYmr|DQC%rfA6NC(j77R!fGFp7^jANht(Iix8pq$+MH#Bh@@{t24zV~?q86@l%Hd5ur>T8Iz2D!Dy&?+5)NxwSL=UmlF zn3AERo~kWImp?_V;zeG%i1t(WqadW6J^O(Kl2Hr2ImSb^8XB~?XJNIpo#)WTTf3hi z$08tDQE@*?yjv*ML{dH4wr#cZz!eX_S|=zIo?ml;e`Q+wis~s6E{_YL-Zsj$L%U;i zg3ND9eHRVSBam}etEn50LMKkMUJ-Ni+GQ#u$wfo1wme{Y)@x&f%p#pQL~8H_vC&g* zbB;;iRRA5*MoRx#n&>uE!oBBJH|yM%mCULr=C)5>JxtG4%94Q(GzwmR9jwBb;N~07 zkZaaMiFy6s2Dqi@k~kj^)Vv^{a^U!m&}bXVYkcoiN&Ilo;O=Hkx+mJfv1(e{X|eIA$3!`0P<_?mv#_O=D_LEJ>*PzTmpCLW&Bp17#~~H~ z8Ao0YDlB|CByypY!P}bQ(<9(f-KCB9cR3IxvdLFtd_Rj6&i~wQ(WuY23Gs-5d4~*^Hyf0iI)@EC^;4j~BIOlK|6sv1 zp-)_)YjQ+ZaU@*}X8Ysk)1yyX86p)=NOu}*tu+Z!5>}WTUPu(!d-8CpR%7B~UMy`~ z@EPl6oZItxA;La@S83i~y&S+AC6hWN>sOYtm56kAb;a9U(j*e6|8YrH_vNX4f$#btL|m?FLC?He)ui}IFMMC3ywnO|zhO=9 zl)Tb>IdOyzOcy^)DVyC`P^ScK{YRn=ky$nQEj6%Bt8Aw-#-kstfgux#PMvw8MD<-p z{#KTjjf^;@ZPC9=|4{MA*B99EmdN>Lawr@l`hn^5+D8Xkktn@~X5a+srTEsi8I}2@ zH5ZPFr_>pFSpq*%>$b8!W~k(wnpn2NtP{l#u>IVc^h^r6?ipitA&*(@X} zdyFo+jMT_z81>mExU;7JSA|N`er|0>TEUZn=n|I4!2r$eY-FLo&-IXVvO3Tz=i!Wr zHpuZtbAARk`Z42kz*CEH2(~0SP*~+6J_jKHiV?TvW0)vhKWa|Sb$r1-ywk55zc?dU zIJ7YFSYVox*d-?`J7qt{YqXlv%w|mh5Xv$fwq6KsvHH?dkrJZ^%k(`2M!l{1J^66z z4wApJoU&Er{p=py^~e$O^hR;M~V{45N;1lgGomCV9P9bbTDpuGy;}I zw#qIN(de~qhJGG#%pf3C>*kaP4(){c;sGvy{=uDEM? zd1aAcLa$A3CRpxt&~SD*bkJk4o6z3>5Q#t#?X|6VkrKm(%nx7fUuL)RQRDEelM=Beu5hSvDiJkuVfh?y(Z)Eps!mgYgH^0W z{Q;pP=jrRA=S8EE)!+SM6E{+)nQ+FCsh*&nVNn9k7do& ziZfiqamO-NWK#d%pHmqKX1HZsb?#yJ$zBs)+#=6d7^a$M7Q<6l_+DxKvE3l{B4vprFQJZiDF#vhjXLZ&Ynx5*UV|e{b zygnce)EaSNp6O!=gBf;6P$I+!tm&vA~@yO z*(xYra<2Ly0|6DCcT7)e4vN>Yp7Ktx5wIW7`D!1U3>2f?A;2&c;niY0iSS$9p@XGX zW*_rM~u0u)-DP!ybDIKkhJ_P)5Rhjon> zblcj`yX#Ljjus`f&&RLXBOj9m7Mt$_{|?adWQCScXv)e#v0|cV)CnK|zDdE;e0a2} z$`!^_BZkq4{>n{oyiL=)2=WuWRJ%j6NuAxOhvTZ}A+7lhc6o}aQ9&9$pSkmfaNvi0 zCQHTKDNN(7Bo^8Hi@sz}SPO8N(o}_wy5gU*v4A;Hs^q6i3X&@lVn*?q%<|0 zMQ@>H$-g>|=)hqNO~aa`Zy9%r%pgiAh2Q?}mY{E22L=|v|O@6gS*B zNM7T&>ch8(&3@O8D_C2JaNQQ8+bABPKBnrZ^z)t<;prRc)IVXzqp5eP4oNzQ`AE;7&RMWAY4x~OB{^7$BKEb{&o2@DJY?IKC%8d z`90BlpRplo0t{tCjo*8qT)42pCWNBG?Lbrd&1Z}-#AgdeufWG zQf(>Qxrr#>*4%jA5s-MZl?^YVW)bv#2|1&qqep~w)M;wCRi5ime5xsexr}z-fI~1R zP$^8>?lOQLpUseKGutoXm6yB73L{J!<8t}|*K+*^sI?5XrhUt2eVdTO*7nIUR!}I# zGAM-wylcm+hCGE7)58Gn%yz`{rx%sM;Wq;AGOPErQqr0=^5;8RsX0ElJE!3(K_LnwLbbH|- zOF2qJ{Mfn9Cl5I5C&vCNm@tv7lC^li1QU%HdZ9sD@qPY0d_Nr+t%gLej(0){Es?mGL~ko zgGLtt5=vq~vOLoXrV!unAxBG4&akY87kAyFSTuTs<^7G{*<6mTP*F-*UoFv&19-k7 zjg*OOR{s^4B&Bbw`QPYsr7hzACK9ElYdx;$1u~06z|7lb4C_t`Q(C+{6Xf5U=_BCF z3FR?P96wST{6;JX&qyl`-(MFdT}?|}9$uItf=;1q$fW3jytHU}Sdezqw^5Es@Z?wf}Oc3-9QA4&K4g2rWBJb~(i=j{jc>nU6pfRT;3IQ?Cq_mr?1OjjYmwTlz z2);~qi3{hLky7FR7Ep5$Ty4KOw$+6^54>8D^Mz`=FJ`$08sxQDNAG7$Nyyy0<9}v4C(`X84rgwoDFI;<==sJK+-K{d+awX;tponpm>ma1XaZm2XGF!ZlVzt~Z)I z*pi1PpR_~jsqXbfqG=kVnklLMm8Ft2U%{XM?Klm3F>yLkdsP;**M2<=SwKq8RTeHs z%yv@uEZ}o2h3V9X)}gO%XLh3=gWJh{tT12gm6@7gWuiPH${WcU57}L$R+mXW(lI_A z>BZg!o}GDQv3S!#X6m7c)8R~XL($K?SQd~8H5uj#Gb|ca$yy#2|LA5oHud{?D?Qn0 zLr2N~xU&UyZkD|`CYaQ|YVCdr|2>ew1zzjIUJa=<$MI!RtEgT%NF3_j0F~$^I?*;0 z@!K*@lM~!7;isOy3R1O-JFCs$3JotVzoCv`vz5b>7ELk(QS8Plcb~yZsdk`w#go1q z8W{jYgT3LMUjE_e#QQmKS39Z7B#8n{!Bz@yTi0H|;DmJN_tc@TP8VA1 zoI5ayM`N47j>|AkhrN61lBWol7e;Xe0<@H!L-+U$0{gC5qT!l(^}pS3oFi*U@&yZ? z5CsyVW2d?-`mN@Yy^gAuB&!$qqyR1TBHjEs5=(@NnywESEtM*z_vbjD(5RNo|M~wM zQI@7T9b@5bA*=oYVL0DI1=YmRfe~ypi8yqnA5D2Z;(FE82;~-C@20o{<(<(DzPr*j zKAWGfMv*+m>B1(0Z<|kJIyNIi!6ppeEV{vQBt0|?h~2e+qwP66#6oYdNi4FrR6k*3TCrQ8cbRs|{1 z#u1SK2G$CY;^Cadq5dS*eO1tMlO|h}cuAOh_^wSB_yNK-=W&muE%wICOBs@NrH(B& zM)FV>y{|h=R&_je#3fZ~ri;FgI5&qRx1j$W>3N(3 zh-pi^UqPXO*iXB#$hvLwl6gC3dxNw6JlsbOExOWITT(&AI|=!h86*7ZJ=km;r<<|Q zZDTra4mc$zFD?76yBCZ$?r`9-TZ*QORiKowjxo_q=Kew-m5i z=>!wb-h}>K8A{XTwaXGqGkbP(<*%Lco>DC|pKPg>0V%|OCOS53*z6zO9nXZObe0_3 zHGB}+l+g;p-^2+lwxaW9JUS5ha}hfkpdznwaDR+8b^G{eM*47?M~!cIcCRlOk$$YM zYtZejbguH`8pfh?xTQ&66Ju0o*5WE&;LBXb>Hrc`f~bSwNl%6(6;nDEt4w;{YTH8aCK&Zk}Hf^s~5#)>ao5jDWQ@QqB1n+pN!zp8XCteq$$&)Eom;^UW zr{(0wCaU6OwgOE~CBwo@N$7(-v6@xidAsvsGriM4HKk_b11Ma$M{2~q)&(-8A+O!c zw6*yQuh;Xbv9Ue9p2?w6X2A<3af*LI7lwVSxG_yHC5NYO3zd8Liv4ri?|$~bnl|-L z)&E^GQ)tCM&;`H7iyL*}oyTzJe(009umiMvD)hvrMPsi>8x9{(x=XE4M`nELesTg( zs&cPn5~O>?1Fx+3-qtQwi+lz(0%k$gdXctcg_4q#o?4;gN1v*DYQB=EApA`>mm%@O zws*HC0SoCsIHG|u1(VQSxCL(-`C@fq^Q9Xe#6TC)zuQTaq#i!~g!Zq~K33y}9th zUL;*o`D;G56-ciK5KbKd$}9GTy^$9^4j7^?AUi`BJBLpZ1*yjibLyjPA)yVNmcih5 zz7CKvHtTz{(woQN@6$Gh@V9P`cq9s*7JUxl@M^|L^|6MZ5~1^{pJ@;8 zTx0(nStGj1Ae&I7czEWtrrdg(i)ZPh2`iNi#hQxDzdAUagX4wEHRcHs;c)Q&o!~Ma zlr&MiJufs=JIwywDD-+&OHHgrPCr3b-`siafW(H|;x}je34*=@tD?dUGs<0@sf~*o z?&)|2&!o|RF+Shu>u$O0r3#N1xX=i%q$z*EQ41Jp_qF9%r8=&xcB`mSe~;`n59w;S zJe+YNm|NLbEP-0MY|ANn%h#C6`EM3aLzR_`4^v@q%XJt>z_5Lu z-9uC5Mw>GxiDoHW_VEOgYWqqQ4C0U=?~K38xE`6s70eH-^*UYaUU55u>2dOk{5@tL zx0PS2?0L#A(ixc^FUYO9!Jb+&{ITMP#=cW`6*!u|1t^XQg$dhNEPE6`ttmacfxeaV zE_Ay5+sfNM-P{0Od*E$TIDOB)soNeP`%{C>F{^zUTjYtXp!{8{mPa^V1NrKGo$H^k z79UrqJ5hs`{kqaKFZz5oV`aF>0CjsUHcx@ZwIp{%U5Q5iJC)g78+#z2ej~hvAWVF{ z{)%c|J#)+5(GB5i_{-O&5Wzd+S)H;})e|9Z!z^*mzq`K-AL7v;gR9#Aok2G4c+L*& z3!q0#oPO(KmR~Z1;H^UkYhI?_CqM{6@!X00_W9FxYRf}_`v(+gfNR!qMU+n!oZZ-O zSmv&&`3M1Yc@6uRf11wRBzBT=BiPXL7=Pmhy~5(S=eqZYeyKZciu(CWxz~K+`iT^} zG1!b9-qyqVI3LFJ-S6_U>4y31SyM5RYJBQeRpQ-L<&?cbZU>vor`7<=l#X^cZ_))w zqoIM)zXsJ`nf*fVrk!HL@KXd&;G!bSbo29K9mNe5TB}OfjYRLPm496J!RKNfIZH&M zU>+XqbUmb=+iRmUv7G24MsskCgUF+~@`FuFgs8PA%iT67K;FryQIa$6oC0v0w%+ zMBt89lx6i=HXW`x#_!hF*MxdfF!QBVLi2pJ(#8|G(h^V-VjR3`>{QvXH-06?w|*ll zM6{l-)>Wdev0~Og(HLSsE4NEzg0qN37-k+ZyuOSzkpV~d=;ty<8mzQp8Vv1P#O7gr z?co{n$UH#l)VcU-CQiuRP%wX?+pEdtO^Uc@;Rohd z>Lcc*#s%gV#AYa8?*yi~?}Q=$yp_ZoT_uQG+F=rbapinUw&6N3>yRT!dWz~lYf8x!J7+Lv5V- zf%#m~h*EmNvARJbA(dYr`umjm!kL7wl&cn6$H{B!w?sO(p8P}-(oXwcl_1IgvwLL~ z^_+Q{QLa4>wy$Jn^}X$|nHCOyrJ-qdMESb3U&U{trLT&f^6cIk?0Dx*$0|v{(EZ#` z8ii7J!(A~~s;SHTWGGu>w#fgm;emfuNl!)tv|Z!h>Lp=e;|-zNJem2$f(ZJHF+nsa zf%d_9q4q%=Ds6)sc{l0jnmRHJvqo`XJH}z8o)yN1UN3oN31ov#GQHSbiL^^o}7;) z)@k;C266AkWK_eiearjW;}&yXHx$i!grFVk>7nE6dmppFG4F^7^ltGZ=JBhz-%c*VoUj2t{ZKeHbSO}C(WfikjA6D0^6ID3H!A$qvs zU*+^yvq{gVmspHBx%v2*Q`PVGwVo9vzmC0@wN}ZrueS$P?`OH{M^rv4PlDXTGALYN}{nfMPVU)j-|Ax|sj&(p5 zyaK^&?`WS4qPju7A7fv={s+gkJSk4O8i7l3POMPaIjFfWapmiqh|=|Ofe7%xs^Lu0 zKf@08^1hz0SYGm(Mnc-jl*;!0U%CITWyJ*4Ezu-5k^eT#;dDZV>2dNubT7DdM+Yw4 zwwI9Gnz0@5!PmfD7VhHu-t27kZ#tLz(KugCH9IxF4ff&V@5gOZS>vnJ;8sEBPjPH1 z7LXtJMNQtpwa>8S=hLMOFipA>NLCCx^a|(6Pl{~G`b>+TzR|kRG_eom3ynyUl=_c_ zn}x3zWUVOFl&4F_jcd|BI6J%}7&onlvSlM}pwOMT1)C%}a80!7+3?~xVMUJ_W41U8 zF2t}&73CgK7v3r`t+N$Z3?dd*^*?r3eXgjCBJrB&9UV!u&abUC#jsy&YpQs{s3)E$ zsa}hpsGqUfMMmJ=hjTVVfJ7rRGI2@saiB&wVxTap_VZhi(Vqtnp(cOhc7J7Y$xQSO z;Tx*?g&_YXRi8#k22iJQ;p7b@o6E3PEqK+0L`ZStnNih*g!!alk2KiI@rSK~+?(u6 zF85!p+=^CI_?O*1cGz8(`)SQW>e~@LycO#w8XYB*m(+?t`qWH1JiQ>s07}lmx3+o$ zfI^W=&&*W*pi z#9?>v@}$q`P6gzj!l)E0GIVx(a6Ieu>g>*Y zvo@#4m)Uajip8py+|eZ$;qSkR1E9?p z!$$@y<>m>5)?6uJD+mzeY4^Pp>Ds+*wI!O#6B|h}uQG|Xr2v$<9c!MzGdmz^-Pgvu z*Lmzj@dK|h(@|z48&OwNN?wt7#q-x9M86?eb5OYbusP}~kW$ha8!#>z@ktosg3e^+ z28tDBpxG3Is&DOdlI?e>$0fUy^E0W&+~lUD;X?Bk=jiW{2=69F;YnCO+FrN5&bHA6 zPk3i~nok0d3exOa&F~*h|ms#!KmClr`yHXMdjb{6a-Y zv^$6RTwY%eFz69=Lw&GbpspBmt}fPNf6d8c%G!><=Kks~XK}tn064dqI?6*nLn&pV zP$fLA;C*LTsySQ9KjWUFmyo@?{ov;L$=p1-y0~<)MLDFjNUeJ!S5<{(D+4qnh{Ou( zP}wPu?3voev$*+$n36D=EBdf8)6R3_r~{|8IO*uhGG(iFC8TKW<#36 z*y4WHM%toAOo#4e6}Y!ChlDQWS^fh+Fb5-mFdKjuEb6+=onh`#x}mS~@6YBalz7er#}jlsdsNi#;Y zR~yp8cZ{@M>6w!y*f8^G%&g4Lb}sh1G_JP4wSE4O_)*rROC1Yga^YgY;6=|9~2g?WZM7G;z@H9LvAqM=yqN}G70 z+$9Ll5kZn-=a$Q3Y_f*`feG>;^7DT0TON;9teYOESmZTfw5n;O8#Nf&lA@vXQo}M+ zjm3<^f(k$1f4UK`?{56gl5j2`jGtErKih78%*Qw$x4CDyetU8Me0u7cinOIXa0^Af zX&}4{d^NL*cM-dBN@-s#Tm*qY=kz!~x||SGV%tcKxyFfBmN}U5gkUF~9&b*ekBxta zK=p>p+6O(n2YN@*h;^>EU4fKSfOO~d`1Ggl3#`stslkgGZqhE_CSgmc>}HW#tj12a z{^fV&UoB;0rmy0jczEDDh;SG$w~g{FH%B;*b&v#tOXXF%$EP-AMKot?r{_mtvvJJw zav%Y0`)CRNW%kK^W@KH2y}i)nBM~14ztr$+@{4{D-kKcZzc$gsX=KKl}PbG7sC1P%KI`jqNMDzMq-E2JiW*D2!w+sx%GZcGMx5MkWU$J%`0pR5Sv0 z{K8~%F)xm9T+SYLc~EDI?yn!%M{vepB<*y`uXy{MJY&U4?I71|wNzwLQP(1u;*$~NtV^=Cu zp>>3Yq!!WWx1MU}I7dnN?+}qjyL#sXSDtma91x9XdqrLk0W$yB90!-Vf&`V*MMPw} zMa$y&fMKa^?9nr9(O=XK*kU5kY??VrnYi)I^8d)ad|dL~?;{}mERa<#!Xut{hlsNC zvr1}_GNs*&&Ot02)LU94q9}!4VoOfy_WFH^%NID~05NMW3qQ-531xR7bIhe{K#Rp; zN^zg8|H3ww&aSis@cyb}s5s6iWYk7Fk1p$jGtmbi(r&_XoJ5&o+k>sEYf?^;;1=>k zkq0ZTp;7mEulpev)aB6);A26#?P@pI7p_P96I>N)cZX+P{}(88t(2SRN{I$&onNGs z#t}^T!m85A?#fTDowxEKf}A;D!LmkP#d{(i_OQAsM$3KU!Bf`!B~oM?+&G@Vv?-F1 z%n!S@Z-P%$5f{>74YOP=dK{9WvOGTa3^j`q@R`?XrMB} z6o)teeTwTOd2RAmNTW#u9X`Y$!Gn3U7BU=%md(ADb^k?p`+8}>JPmyDviS|66t*mmBHZpkDb0w`K9k%aL{c%*(!r&KMGwu~0FSj}*$6wp9H4;i+%#4D&xK}=` z*3oHCI-ln{gB*1L@4QABKYtPYZu?hLRiV*4{qP8U8D(NM%NKVRay#lgAP~lpg-FOM5O`!qj9C} zMiZfkg7?jFrG1ft$!uN@x6bka++%DdT4lZzL`#e`C&J5_RnW06gE~odQZZLbDTTa9 zg__IK!mWZTe$F}}QB)QU$w-RGF1v0JVgn(b2jMr+JqF8}6E4sI(VE-FOR6==e+ZJv zS%Cd9`*0A2tXVP^bW$j3L}Fw@%D z?&^p&h6^B6nvtF$!-6>}UWB^3-QmLNlHpRetYL8`*+`0MI#t-4SGbo#Ea>*i7qox> z&GlcnwO-}YNYc%adN75P+E$tzqeTunP&(ft7InEaYLGup=Qu{~3LUqHC&^zr2rKN( z#p?AgPcG=FU~ngkdTif`58T1{N-hN(Dw0TX+j$zle;x{5E^yJ%utQFi*OsuQXe$c0>aIcMlgATv4t=tx!4p6nslnd_k0}=<9 zFfW;Ufk5XClQsOqxO>M+eSvPii%*^(m1~3n>DcD|YboFQ45fiyxci92C_9Ku^-3`S z7B-ZxrD|V=@$8*VpEcy>zhNi2Ar6rrBBprNR6<)^l*oJZSHZZK*Md9tUgxdj>IHNu zMEl!mgY1&dj@MQLLA{eD1|{T5BkhTuLGIiuoD?g?3cT-Z4caJAQkmThC$<@A@XblY zC^&?hQLwEV0OQS*L5q|RWWTGwMA{&&i&YkK61;?(*kYJU^f?bR@nb4`(#$x%U-MDD z5r3FrzuU($0GJh+P4r%)@{tF&4Ukk1osMxo=f)#K2XlQN$njg?Wto%+6+#$p0=&ffHJ-i(o?XZWk7agS_ns{Y8(tpz2c%@`=GNNnP8*GkiLyRZ=5( z^#g?jDOv{3=dD(>v;FP7va;IsKWlsFf_<8H$n>d@P|mb=yz*BARMgx<6&Z^8`OHWh zEKnupx5yZLnq-F0Z?S4Mu1X8V0M&mImq5D-U(POHo6lF(+``m%N zjXfb?JEV6U`n>m%cZi5Oh8l=w#g;2jDk(FJrb;~Pq&bVuSXYA$R(AZvB4~6U-WEvBDV7IONKt0zeX7!L zUjzaZAjPNs#x?}QDZRn2FWv1~G6V2WJ`m5#cy0Y?dSJfVui;4)_0av^K_s2W9j|+j z_pcuhv2|CZCDkpMxsIDQ%G217hOfB&9(TgHX7|RtPr=0hoo@fU1Hu|2aqsCDsUOL| z3iN>pc|1)u+d4G<-!4niQ`uvMYwj{a?>fR>|47arY-&+w!>L`Qnf8GGIR0?eOWpg> zH5Y9PhfV?8Pq>ZY8Tt8 zJ1y)J%o_Lfs`RgC0HvRHX1j8D8*k5eV1}SEkVk(iHzvd!rV*0d;9)sAw63%!s`hMC z_Mk-CMeB!*pCz@GYbDeJqv60_J!UM{xVZQ4Yaffnii(tdY9O?UG-gvu&H=bKXJ#b< zaJSy&XDxN)WgyzEh5)*j*63t9KAhfKdrz5r0VH1a%-vCUCuLM9R(T3D>+o`uGvrfb z%$hBHIEXYH26#OsE^&tZK?$=uARF#wAzM<0XE7@!Y2yFFp3rml$}yIlhn>mC8*>r` zAE?J+zla{?%zljr7VRpO*-QKP*ynRVo2_@eN;8>-ks9TZvN zAhGc5hBjvu#Fvmidi?;w8_hp6jyZgk^$}cSaIM$c$XZ?JmT%CZyOyg zIpP4C6_TJ_3c{L5L_7W6Su61w)IDg6*$G-Q0T`Lnh_7#v@~5Eovt6`2&Dk3B!i#zG zh9$e5|Gg2h0z?g2ed7V`#1ZLs%O1!(yWSnEN>1;-o!vV}H_uU-CXL^rh6VC_^WZ;| zd>ofOL8<6|tet)A+Haili}hy<$&E+JL~f%m47yCg)-SCk6pbR$PC0}qO&v3a&lyho z!~d`V4;g*|2_{%H=#qr|xvk{ok9CDk4^9_Po~h~hc6L9DxpLn=2@2%0KoY1Br(kmu zt_9Q>&ypU+UXd=>1leE`V$G&AMYS-@;}ZUW_lgeZo{B2SXNyl`&lim$Lu0)IfjE9` zF@2F3CgL0$F3c#nx!ZR1uJ&+K_CyVu^gl<&Go28dt03%Qq+O~-)lMgsuTiL$r1;nY zJmX-6lC0o9sQOALI9tLh!`)@t?HcLz5wlA!jg6WA8oJLV4SR+`isp2$SJl0;cG5Ca zoOB1}e{LWG=xLQw5VHI$=@ttb4@))z?ddoM6!nl(o?$tE;2h}* zIUnL;t&p9i@b{hZdGn9ty$?AgEb~_9auZ!M%u-R~YklfPlRpeEs@N2pWmX~(uNmPu zs8YrMjT&YX$hV$mP{qmQ@!L%KQoKbOMzi4g8GTM=&%QLDq3K&Y9ihdKTLFFUQup}8 z{Mc7G%Pb59$@5yaPkU8|C02|jK5 zlmQv_c(*H1<^Ugtv(ZQ%4mfni%6n>o2B1NX4+vz?VOhosR^`b)QbUX-#LO1AUZ78* z?2spl1=G$h-Ke<2ZZ&Kn?(>NY-@j;QJihL+pUVX7u?G@R(^lr@POKfyz6Y=j!}3aM zjid=`#xifNdE%TL%@4!%XUh+ge~65Wb_-07(Ipq_64cF&QI$+%^+sl1Y1A&F3Sy1n zXAd_$iK8b$s#mYwgg1J^e$bu_+`u%`K4( zG7|7V-;&`SGB*J>t?{H-VsSLBs$J~5&cS2WZb!@S)sAM&5g>t zfE1=r?eqZSB5tMxEWX~Eha=K8uXlS6dAQ);PQS@l^Np>lOa<|kk(PA3YgO;qP;H6Q zDPx&2%eGM&)=^g>{A3S(jc~xQ^y`|^gODqOk=mjQwci5nYyZ!z?3+jN#OBxT0t`!= z4CjV@A0^pR5Rw=j>w?Mw5J3C8?dKacRKxR*&;QKtv+2zOp|G~&kKW0#h z5bJA|7gX1LiTnDxxI4f}#Fhp#_Fn`xAmi;gGt02OBQMHwmaYRNJ*3_nfsG611fTk( zls1)1Pm=%8*gl;HH1Zezgz?g7zfWfXkSfr^VR8G7?chn=$*}1w&?=&?6D&~X@NCmj z8178(psI;Mb_b7g^;?bfzi-(AIONVyG<9I-bZrmqZ;T#!)bB3ucst~rhvS26gPiRq zDQexi%$%|zWB8XRXKfp1Y@Dh~+Xr!8^YfMP!;u#`Spb){U1~$W1$d|v2_oQvz_)1q z|IfFw(m2_jU)COFz%SNzGu$UYgj<+h#oP4k^MWI-pCkZ$=q=G1C=@!1$z&R-qn-xZ zKN=kZa+LzHDm}1V-5M;+t~RSSJ`}sX$TIU%UK1?sjcU>4nUza}Aq>f#Kx<2e)Q7k> z>-U8JhnZ@jnIfAjg#X)lVWXFMBn+>*&mJ{wVbXX|H56Z>GhYZPO5>`j)SJPyx^2$HqaS z>zyYIW53AdXOVx_C4Y**a4PaQI6WaLv|AS-^aK+ZJOkEA&vH<|=b-9s0B9v@>xCEk z5nMG=jY6#xY~LxnETzM2wU8nR03x`m8k~@n#eUi>i)l%9rnHLdtH@`iWTw<#RYm&$2J87HQF;JY2- zHVS!R9qntz+iW2=BfB0%h*zo0Fp2NnP2c$~?bmhTVYpGQOuCQX_yuxW`a}&Nf#uF+ zP83&RvlG;AzNZ6tJll-3fNlL!p(1Mak1nhzIn{l|yTAivDMHH|oq@^}d_)`pRw|hN zOHKQ!zv4HtUn~jpGeTOPxo|&*eLFXqTg68o{94yE9H7T;Gq;B=46iHGNkqSkgm!XK zxGWLM3HVTLm2O7wkE?an zx*64qvSWJi{pQcOdXcm0$qhYv+ow`T2#SLsTX_gEW&>A}jg*WOzn0_w@OPNU-a6e3 z9`w|o3#JDFsG((lX@^%JbSf)AzWsAO!}gT->ad*f3bOO7)GJnt&nx@*36hP|^v*D| z3{#S>(~F%4HUbxJg0z^#z;2;Dnr%_n3VRLfL5cW%tTw;-2YM`&^GW;a6`p#*B(L6W zJ{P8Ddy{W$-DGGfAe%n!aG;Bpf(b@iqtrC-nO2w8)}AbS z`)kl6w_Zi7d!4jy%_E^}C1s}yN0#%m9@PcDU-UCj!v$;DMIJhg{)5BOj>s~mV<{kv z_y)2x-Ma3ok-hQ9ne?=R{n{T8r77__Si2$tdcD&%^3~$bV%R`F8cWakY~x~>Ugo;g zIdPeU8VXEY3-#^>vx!>pR{clBePc(%{_}|RzDw2c_#$v2;xt9(QSvyiyN>0NO+VzB7Wfo6d_NM z_T??58Jzu5W2ikO!(w6PTi!5p3*F+AEs-qvKz0Ml$YHqqOwnVtn_4gHY;-4!j&eeA0KqQrT_suUD;{?LAEo&j4X)AhJ_<&&Ys3f_)KqdZ5JV zN6o2Vbvrl{;|e~!IY>-50KeP1PWVEhRmx8(6|eQG%b*V-BgIw3Yu6$n?uzX_D%;JP z{8HW!6YY)ieD&te!11C#I3KjX9-Fccr~r%!JM4P#?|;ZbdMV|4SV(*@`CItP z-A{$i1YSn@ZwF%D4-NizvRzq!;XTk~o+>`qY>uAw)1P>1|-qsBnSLz@%jc|e#) z4NbUvW_U-J8*&lJjeY<%?}ga!(Lv(SnRu~YK1Nf{nmvqNiJr6 zc`I#-RxBD3D^ei{&Tb@(Vp)lXaZZ&Q+$z=P1ChgymHZxn5S7s#$-{q&R~cJ_cMFp&>yg z6k8KnO8;7fSh!HD-_A4lrx3=mH777N2s8&zHC0hjd05^4998Zx)tLfUFFUBQMpaB zH}#nL_;81OI=8YaFo-O{D_mRMS^!s|a`%s}MNwA-$P(lQl+_AeLSASj<;X_@Y_ew3 z#6|3(eBo|#?+9$cMOrRYnFFW=7&F|)5g`m&P>A_dU7Xlvoww;Z3m0hdYSle+&WgNYs7i508roPS&IL2+) zh+7#pSBU0|{F-?0LBAKws&*imbYohGrGUWm9O&p{?1`(d8!_UIi`qj|if4>$N-)0& zQOE-0Q^j+!k-JA+@w6_k?r3Ks`_xa|VN z^4(?#w1>$Ca-mi!o<*UsMnnD|7)Ntjw(lYEyG_eR{!nDJ_y5P#l?O8Y|9^c{`gBZv zQk1Lm>7&Du4!2PfeUx)^&ykAAIWyO&R4P|KiVAb)c#q9B$L36#8s-|pEHPtaa?E1J zZ+yPrzQ6rrf7$DJKA*40^Z9%{pU=>Q?f2){&Ixt4hnZmM+MzSU3d^y=iFv)kYD+RE zT7t2lW%~UR2|v7y?vY?rIlRAcMb6gV0Hqn|0vmYis4zepP$UJ1hkY%3mEH^8&XdK@41L&VlO--$Rl+#TmN13Ki$cy+Ds`F8~_6av_DF@p4h9PZ|-zJOG8Ngc6l}%aO3Se$`RYv z@cQ#99uk?3oww~hb&Dx#JiFT^)C&p|kH51|L>;!jFry|%8nfTb_)L_C8wG8ye(<6A zW^rUp6uosQWiktP4}bRFYa6#iHrp7_9=|lW{ahyQy1J`$oZ8pt-dGwcWzx{6X3f_6PMBQuvMb|61SYM%cfp($y0!?p&z8C>~ zeQSmgI!WG>v$zM&W2Q-oN3JSc<-BfUO~Cy-E`O3N9L2ar;=P#HWxVw}8-U&uuy-=F zdpwhD#x+p!XaON+Z2G7))J2)RAict_T1LzcD|)T8nq<7vLjTr5v5$25F7ETwP=z!z9tre4f%cEcwLK2s#edaqz?*}jiE05EY`Jz*sV?9$F%PMclTMw zl}A^9%+=k!;FNqfo7FyIk56`^ezBq+eOiAulrMiV0`%(6v}q^(%a5Ss8Gj(pm43I2xE`HZyv1xQfN|k@x%8CgSj)y?b z`@~Jv_n5DSZ?{p5iHe#s=)?ib2}RRRY%|2>HYa9#lq&0v_naK>&gjQ9Sfnrp7OT8s z3^C!JE9%tgB*M&Z7cz8i%ICb(`2eo4BTE9o9$N(=B7=t?Zn;k)B1#a4|*CG zPe3kdjm$2E8d=I5DvBO~{8%1-n?0vJIr=hHd zQ7+2#_u40|3T38(!5%9i43%b`Yr9j;`eRg<+Y(`Mfptq@_=&Wwy*=B=xC{~~x$wS5xdawedyV}<;NTA24fe+9FrHB4q!b=0`?S<&>3P48F4 zfO70#5}t@qRhqoCX2KPA*hpS*IyjjSM6sMbWG!7Y5AV43lh|nnM7#6pr^7SrUQWEmSG9v}KmX?Kh}ttdh+ZL0ymgY1p#o8ds=C+wk) zCP@5zf`lFI_>`ymfmn!f(WH3`Tt}BDm6nCkywB)-3{P8|v83wlwWI_La(eIBM>>!Z z<24)UIj7J$jLd6Z}H3O>b=|5Cmd7^nIt#|%^8xgrKcM|YjcPi$F2$NF`pkDRR9X!;wrxOPTa6)+EC$+Fe_SQ9YFyT&dh>@igL#l+^=sokW^st~Hn zekVNTDaIVIz4UcJrk`Mm&}ivPt`l%91b6IHT%OPGSdno+0Vz>ZK@Ojme$Z<{ka~M~ zK;~bh(>LdO#(F3iY;6CP{E`~zCC$lgm8TAS1vp%sv)D>%H^r^Vhi)B&NS37L?eFA7 z@y6}ADAFa}tnUxn&OE(8vTUvVE}t2EYUQZ<<$*W3+WDtm+)W(}t{L1`x_S3tY?$H*;E-n047M0@zaXtq{J;KjM!4Q5;|s+L-V*$0JM!Y9F} zIm-l_+0MzWJk5h&GEdIE*-#C0#h=<8Gqdbg)9M4w%laPVuSh%lbvw+xmR0Ea#}y@V zk6Q^FnkJQxwxJBq*AL}`xbcdW+O{NGFMcieSmleNu~K0rvNi_n{#dK}G-cYnkdrpS zY`l>uFxgYxOFp~Yv+#3pz2(x@&f7JAJrkz=t;@1DK0oJbWvZM$`^U$U0x56MbXic! zooTWX{o~!K-5}c%8`*OoB0}sC}7f-muZEBi!~o3s{KhHa$n@wjZS!g?$HjMu6(lo&4)!!->|n&VKN0}vs$?; zfyF6v&m|l($fdXbfx1y`3!RHrsw%%a9Y*e3f5|#QO##(`khb;84tt7M^}ciiu)&DU zRXli`@aqbmi(;b7PzE0xnP;8~!c)I35XZ+%B~>#M6jjJQG*)ERQ2XXNJkq*NRSKH= z_VXsdKa$_@eb(WLP2-VqcBMAv)C)z=73bHg)P`qnpZ=WJLK#zIA-G;$NA(Z26TZq| zU_oYduo=a8w}!smKA^mQtnK$Y)h%=19`yh2g48zl^48!0s5tOCYgyJp;?tvhASpRf{zDw&g#nBbl%(-rV>k+~tHjh?>U zMprmz3D(eqa>F`su-xlEt>MdV%3#cDAexvFbiGoJwaiZ{FlOD_vj0tSpH!c1U5-eX zsmwoc+lp2d%$@bo9|o^gkF+z8!_|piofmcIPyif)6l&)OC8`=g%LzTzp4!rIe3vBe zth}|XNn~afz9ov?IxmIKIkik(paB|y%sClXPc<`81Ufs6I7tO}*I5_o_br^H>SH+Q zM*~6jpthj!}yDjNUm?oKhNKCp@@SDK5Pe_|u)Wv1sy76Bg&CZhN8 zc#$!LEPRMsjuC}$2}bI|&+Mee0~tDNx@qL{wT6_9H<`H3F653mjhJh@UtD6}ol1Db z35qbPj68CGNnDJqpG&yTpBs+N`SE^;iC>WtC1mQ~_@S=X+U&5N@g=(@e(zk|c(rTt*5hsF^#S+5{36uE7hG3IOvDc#QG1K871VQ>~xBwAjEanoUH!Khyk@|u6*4J~?C)bXz2tEsLY?kqeG~GDg^k4Un zG~*=k>B5O8CJtj=WDNzxT_d*4kNO*UtMU3Xt-LX0q9CPQ3D>uPO1g6GaW^4Z^cJZo z(r`HZ`bBYRUq}XKHB*YtpcCJ=z&Yrj_o=Z^^Z5}9zggjOQcjN|N z!AK+c!-(RVQ~ID)X3h6d|2S3&VC$(fci{9J?X}>FKqx%F&xajKhud*&l=X8~Z`|-{ zgR4@dmmHi6v@av#QbW-$7>6{M@jXj|Ps-vqEPv{?Q8YMd$miJ{WNttRDRpO~BTvtT9F-_> z+mTWltSr{n*`q!h`goSz^d{O~KYm%2KRP?IGLI1Y3<6|qHa*Snv#hLs_UbDZIRA5@ zt}50_ki;53N~BXmfv0*HG3; zq8J1U6`LY&fjXJrpfGJ2CQBIz3~k}b+>LtuC8K5II>I{=M#Rt8M2f>9dE)f@w+LHu zc6awLZ~9VoksM@paQ3_Iut5j@X!R;kxJ+$e85t6-IKFK-jU$z$);QA?}eL1oJ z(Q)Xh+c72&FI1AovW=no7BJ@&2J-~s$>JMAT6#1F1b~YJCF@)!2zpket19nD1u{($ z4tZT(K97erAt3>IW{(6PGN2okr(Q5yEi9@DYc-;9=&^7!5EgsXZ9@~vJzKkHVnE@^ zxa=*OW;F8^GuE(<)3b(%k5?^Pe{AxQ?v+4YRQoO`m^n|IRJ`npm#>Kh^#5G2pr}Kh`K8{x3OI$qZk`rFU6$<=Vw7n zXD%7UQpWgm*?i*S3@!cambtmG;}^6!8l%m5o~`oCJ)rBhA2sr_D{A1yePw5~p8Fo#;@F}PuZv?Kz=_cn6Ie$*ovqUXX5^BJJ3 z1(GIxJ^(F`fKf478^o?9Kj_A#oBSlY#>|-eLl<;Phwla9Ys5MTvAxFn4(@7&D_P1) z79CvKnZIRyT>tuCC}YAjng7Qk+_APy)fkrYp2*kQZzOpk%Z+EDce3Ywu`vtFGKJGe zJot&k?Hgfv-@L}{_TKmr;Gt6kjCb(4+$2DJa-8~YFWMcH?VL}r;fBU{97rMzYmXpf z)@OYyL5kQt36%OTve$VX{Jz*qaV2R+NRN~aLMqbR0Lq&Of5)*`(Jh(sRwyVw_}%2p z6Cm}Yh1lSMQD+G<+(e*XIg%Ac@fi<|a75;ELuj2Bx17WF?Y|Iktc<^dyz)*Pg(p2S z7W|;DW^vDnglCj9FK)$H`i{G1s}4jO%NOIKJ;+c$sx~gJpHNt}@uyK)jS_iaiVOud z4i@phf7_esExn-W^mPaBQ-;2Pm(&p&>AuGA{f$}#Xn2mwXKn~XQ2Y`%Y99pQZGmS2 z1TKAb{iH*KIagpJ8&*I@aveB*>|q_Gt?a#wHSmNTRj3KaU3w;e)*hT)zwG)iGBxaz z+FHIaT$V*93nc|_L!7Vm&QTqxkINR7V}2XF4z%2w=*($ws8%HjGNuHj)dStBE9(kW z>s)*(5EaAmr^!&ARNa>nViV84_&~Fv)i3)&Vs`HPlkkK{Tu)%N0La4SJ?l2-<>V@8 z5S)!bzXK6tBiTCWM%^Y43F3EPrFLU{`>Uh!7~v55i|KusAs7HZ>g*C%?kp#UENURq z6uYI0+eqIYo!)Xb-u%Ave~Z2ect{wPg;yS4D13At)H2faW1Hh_@hWWAxZx__r15O9 z9a|Deyh^woj$2qQv8uOc^D%&yyX=KPO=@=ULnr(RyhD(M@DhGoGyrOoSP9{^PwyXdqOlOLZyk&a=#ogQ;mOhThMRArfq2;%jcE@_ z&lL|qo(W$IS}dDWqr$mi%!Fm<7Lu6q+VOWcSHlpMl3*@s&N4$X$ZgApwTnb}ZFbrc ze(I+lnl$%@VUVX@+$uWU;G_m?e^4o}YsqLD^~}&m9pTL}QCM4{uUyU2{BhQyQ+j;) z64JWkI5lVNjoRqB+-PjUy_I~E3@NRhH|3FEg9jX4sz#&a=ha8gM)`qSdV7S4ZfXT2 zDyJ+Q?PVXgUY&n_F*Jl+iUq2i_4!_Toq(hf@-g(RZ4vhVSTOjhTuNdxS3R?ZM{~g| z^lnAm&K>+^aaD)@C}O+oG^cz8+@Xs{z^>k|6t@dVd)dBtK2`su`_}B2M zGgQCAb-Trtk5YPTv-3gBKCaREH@viRx1aJ5LAY?OsmQT_$;(tp#}F9UW+r8zgogNm z;#tg+B(@O1|I6f?6Oy7|p(Q;g z9gYfmw+$?YbpBaFd8F${&kaj%y+5Zm`6FvNLrlhm>VvWHc0X3LyCuqwYYTYi3ikhm z{O|^wj(n@ zHruGJ?yVtL9)NeQTvE1zWjRnF>46|OVPyM)-yT2GHdK9TiaAvF&}q#TgHdpOFxuaQ zdVDro0cFs#5$XS1B6GZj1GrcU(5=&dQaxc`P$MB%$!y&OLJ2Vew4nomRnJp4a4b56 zhgf+72DERm1E-|;C}j#xORH{(qp2gokC%!%wiU*FfHwHo>IlXC3#=V2^gOn8hlR9f z?~gHWzzj9*zqNCrVd0|*>$T})s zz*+K(p#a!Kkc!ZD(5Ne8CJ#duEj*0E$DlF79#e$1m3`eAu#^0Z=!YPvTypdiqoG%y zbYH8IH^ZQqCrj_yDZ&zvNP27r8mL05e$?A|G>ek?&uWIO)ZOH@5GW! zUO>zznac3QemEjd_7oSj^C_*9l&ZXMWAx}XGVGl!XylvOKMgarBnaJQe~Fl_tVCo3 zi>asCAZQxqUI6ezkx|7y)#iJ5sClpQaw5 ztk;7mUfVq=S2WOIWh&;UZ;Km{#g1wwkgx=x!GQZk7o00lzGpEWL`ku^1)}htzP+23JU@LoMyG_BPYH~7^(>j|qAr47{ zxIk&SK0J_*&Ya}p(W=bUia?E#gAfucm{S>s%oAbX0bH)JTv;OQ=oK}c_wSH86I{(R zlx#z|3F7j7X+p4KKuo7b^@@z?!u%LCY$REiV*IxE4(L8Hmz4_PPY8TJmGU9g{M&g6 z9mwTxG6WrYahHJ)YHctZvL|k_oSvpc)s$@w-)tN!V$i7YD^(K=wNF7ZEG#i&`&}jn z$MO=5Jw&e=Xw0vV9eYc}aCBSOzY9LlptPIiGf4=zEmUleODsR)e-7aYlcW;jXq_p{yt-91a_%ioIFZB2r<68yBOQW zek(tW+=5yH=aMR_9sM(}(mCXYY8Nc$&6ts^?B5QRtpQ=pj z9;SCTr^6m`p1#pqBYwMZ0tSw#r0*e0=R^l@9IEezQTY`_puvv&;=pe)3itY9_&$9x z(!BQ2z5=bj$sgG|cHj?7GpCSyowH`O&GD%X>vnkt%ZgA*W7f}=1JoU>U(ezr?sERn z_I~A~H!Q1$51K6ryOn1~1eJkhiWCYcsvzv>dxAM$Jqi`W${t)7J!lkZ7IVuM1G1FG zYWGpHnQ)#p@}RImU&Eo50AvSc>{tnJ@w`3-4)tJb1eIoj=ScaLc|d9Iwuz`9ret1xH(r!brw zAxW&aNmt~QPrtZTU9OZEFY#AS@{!eyWb5!k=lt5naj9xV;Y>^t;jdtwjmTk|3cZjU z_|2E(soJJtA?yq%Jqp81*$A`f@s~z@%8{cgwrWN?tVn8O;b^`?dUU30m)5s-lj9#% znOafttc}8kyv%wFvLd?*6w-Ob-I*HL@daj1YYuUt&WD11y2rKF1a^7;<%WskS9iyO zU&*^W=NU|wM??+$5p!Q*#}RKI>|)&-0V5t*W6I7fL?0Ellz6(f7J2h5e1OsY2$05u zG(1=6Nrd0%SZx?rg@)vo^0j%h+*OLnc>XchV z5%M=o2HuE!AXBk&O)sARx8Hz_6Y3w=RbTY$ zgi5YdVYLW2og@|SpI`f@QyQ__`JhaXJ1yn+7X;m!b+gR$?89b z`-^e`(E<=R@+QJ3Po<<07j_-gq|x9R14pGbcJSZ{Z-OXjj?W{j)M$a6jWL%7l znFyF{*{ZvI&~WwFiF9VPjd0sxpB-keFo$IehKvzfhR?v0KVIn^({`$A5lRXYDpo$? zQ=Zmh1=>ca09pW#Ps^z%{J2u*) zQXHPGGC8MK{Qyj5$H<|jS8H{Ss$*DTYpOnKpW9WJFQc@Jvi|jkh+LLBioz~S^(9_x z96fOjq++v}>$!#;UpnFw8K1>KSy9*)(+QyoU%%*LOLXb3&KqYTF19FZ1Z>pT+@}5} zPSEC-$V8Fch4tJmfGxv^>eo=PN|PG*^S-IAQ4PQGV=z*Iuwl4`4n^Yj{BobQk*$2X zNyfrtWr#fUdhl8DLZwM67=%i{nv znQy_VcF0&%WS+r9h=i7_wyvk7ZZ>luMParDFJN*U(CeXaWfoqCSK*Q2`BRS5Ird%S~Bbyz(Iu|R*=IY3#r zBikCWl)yTTkz(OAqvZwNzf_R6xV@b1MA-?uGANlXVAF4UV5MH!&TMmQx^SM%8Bn9k3` zwX7R!CUR-1MP?Lzq9P@>7b2$d`+{~Ms}oPRK{+AHyl8v1^0f!&j!nNyxH*_JXg?nQ zvSAHWaBkBK8|a_-FPMq^QON2sIsQ*@B$JWwkW*6p<`m&C>GmoMNjEj4E^?Uc(+7Cem6-PmYmK9X_PyN1X4R{Z%? z`*XrYNuc(E)Nt?}ADMyq?RL^S1vVR_4*SOX!jc93G(X;#;f>U!-`=yKNTyd%jvU)4 z=@D7gGbFynkQS{~$U4So1XR8-N8jeN{?zHpCb_(-9xgtd_%GvSN#fVLECymnxNv8L zC@)7;vA~FMbwO3Q>E|aX(*wd##Kl$)Hq0ZB6YEV3$&mRTR@Kj(c=K%zk8cqDwEP?@ z)}pRa8+u5)_)3P_P{?Y);<#;Uth~2QM|`$vaWyesHW)EA+Wb+lsE#Z|rnWUh>OshX z^h`gU=`Th2WG++$Wn_!@ zrI(C|ug(Oz^%56xS>wWz7&0;9$>egjT5#c)AP z++dq=0YfGy9$qU(yZaZ9o9Fo#CEAO}2k7`&i=C83>uOpVASfm<>r|#I;CDgs-Q(kG z%e4lVY{zRH$n-v~F@NO^XGAtdIW%0QW0-t=YzLvkEOZU|dH8NXi~1YtkD5E^J!y+Q zi}!-j#uW>w;5Hs|VEAj8EPkzGKG;)v!8_tniqKz-6S&ttonsDQB5zis@>3LzSnX^+ z@gSSBP}W>sdsbfcF2VGYO`?}{2fJ&0V4*IM5DX_F8gC4FG z^gSy=HAls5w zpFFdI28q=cht=|V&|0lD6C3vCj780Ca$bzz$x<&+20BYy-^w-8n?x~@wW@F6u%`FF z*HPi*lSYrGw7d66z4$os!;TQnnjV@2`L5r|>&!_klxw5@4#H`RuniU(R%-Rcj*6D; zk*=yYEa~{r`>7ORpFNzBMSacC^sMasPRIxcE;BsVQMD}*i7x-ZWES{Xmi&nw-!%#o+BU#KeZJ#+ zp`SqCRXaEf0_#jwHNICxCQ4WYeH%UA4}(`v?O2)jzI+aGW}oMpcz0E(tsHVrF=H=6 zM=Hz@E3fetCu>7fu*#xt>o*GXSzK5v{BoIDoAiiP5lCHRJ10?Mz2Wgo&2eGtTGuAs z9}y`WdJ(FJ-%vHTO)c=*f5H{N;HKb1c0%lz^1AF(FLHD0k3Ele^B{G-Pk!h;Vfv!c zZgHc}Vs|Am3~23q8C@UVjX9HhW^*Szb4S|h#2|IKGJk7A6 zvHi(3!(vW0RI4kS=|44?k9tS9^;s7Y%(moc?2k}m3K?(3G(DwCOWs&%Lu)5S6X38^MOtzJ>}WuO<7Iys;Stdsjwecx6sG862o*q(sO} zNyyudAVL+N9z_AGO*@&oLP2renbYzguHfqDzo;vKP}jBxl3(Nu;+4oAlYISliyYDn zu)42f9I6)lvqc!0g9x00qIh83K$2QnRu=IsD6R*FNE^PkTE9|~q^)AS7nU+?>Jm)b zc1`~5VdLZnL^LL>vz7|4`Y{6aplVy!vfKM7b?k>1?US@;M=3AcLxgFE8+z4-iF4zh zRDgN~1bjzW24!sBwe@f6!xdc?POz;a^Xh8pS6KJDSm%~_ANwWV8mTFFgv?P19piAC z6^-%ZD%B0x<3615!a&2ZaP?3}D21*T#l#3&QYANvS*>z*)uNTooQs-Cp!@j41dMt@ zqG*1M=L&O`DUP~fsh6{$#_*?pUS07L)Y;G)zB_rnwv;7#bC{gYj)VfP)NWDR>eFc> zxT7Q?t4QsBoX*$xelaG;?@(go{UhJZW4j_ZN|(9sWy2yUcaXMv=O|iPsXv`2#gRWn z6~!1ls!j{3ZsI{pqxOMixM)T`$j;DsM|Nm7_VbKGkhP5*E>8p*Ktp(_j%yd zib%=8{OW4K3fFy-pk-A}tIsjKe6OSW(>p)DSVn3$?L~x)bN)gEH?((~F!}xZ9ZAQl z_E--+*IQvm737f#40ABrInFqpnlKbNYV$0n41XnU{B}YmU)@<@2}w|~%OCy(ZDbU! zRLqz%XP0z&;NOW$shE zL^ILz9W`6;dT7|%FtXGjBv7A)lV(4ul{G%s+#eqIWm_>(YpvZA&8x;FaK3@rO?Vxv zP6|aVs3jbfnV33JJ3T&g9H4^xS(-#8c@O5qq6dcml!9B&YF^ilY_v&)SEgk4H8vK7 zz2SWqZrfnv_%8%E(bQ)y6oC4S(zFUs%$M>AbVGwRSH$qS3gcMY2`c-dhk1eo=tykK zQx14-b}B>USX$4M98yiR;g1VGOu%(#KCy(DE#X-5!| z;LgLz7ny>FNllk|1B5H5D$IK6nsLbHCHt&Vwz4h4aJrw2)9N+*l!8M>W#t`w?T7^# zs<^@q{@+gz!rD^5AvC^83ga4acP#kPL}y{xF7Dzu zjXH)yl%t9FPl=goVEVl;$%0We*)mrVQk|C}$k*sE*h1@OHp&CL2+{G}i!NkH^^dl*kn)p4iB{Yj)oemL6GhJ0pG{EqkQU=}u6XRD`5n_1gh{5irC5WXCgK=_B!Y zU-uoM=7{ph;Ce7oXulu!RYYR8FPj^FZ#ldkxjRB zN%ogbw&fJ^PEvazrbBgePE>1XrlmlsM&dNV81Z&W-Dp4_mP7^KUc{~1aaO! zzr%usbT^W8zRhvyM)>Nx!UVrd3+3oUBWZX6M9gw7vF{|;hN`N7Km}>E8vmsojB1D3 zs#*)mS_9%IKgjuvAeS{e6{pNWk#x+$+H~KxJ8pSqsZFI^2@Nv}C8ru763Vaqpmt>QNPaqVx6CUaS>tX2LlAxzc432E^YB&|u9iG~Y zx-EmN-Tk#@C(z|3{sbZhFB$MgHvN29Hya^C_{(arXRMD1m5w(3Gcl&6(UvhkQXju) zs651Od$EpJnM*dl%KQxjhlIDb$)0&wmS8LNL9gI5S^KGONrWN}bO!{1o>wce*Kgtiitc-) zKF7wIQR4dEE;L5_n!Skxq3aBUIn+$j->)$ABCc8T1VI}*eer@f~# zmuQu`7#obTkYUw`o1@EYuN7jc2k2kHH9`BfdcTxAD_?R>jo5qD!E73(PY-UtAt_dv zXkFVf*s0vfP=43Afnj(+61h`h#69`Z4#FnoIkh832ybKRQ!{{e6ymeDz*o6`lwj&2 z&hpUjTDWtAS2Qf=Gdeo<5wTagoUDM*;y_%fq_PY+?Tf$r_I1a(6I8G;!CFw5&=q<| zJj43%M5g2c8t(|G5v_IwvvT)|Xcc^Mwmwo7f>P><2^5~a$kMlskDGsmA3?k9o6h*9 zn6)Q9qXUilI+wXusan9ky^8q{Ti%EPqn1Kvd~L3*Pw2cEk;RLAS!0MsD|SF;fB5yi zJ0NLKAVlS?cUDCTG1izlpNfu0u4|APQ;JiKN7^Q277Q=M*ach3=4%@YIj~XGf7U9ndkYiJksSAx5PDfMjq(W0v;?J&R@$b{NU7(z8aZy1qKq- zTEADtHDKMG@w$nX@$xU0j_*3Qu+Dn9@@2}&)&tq5>%>)l8r}*p#`{%P>?T3EB!+uh zA{-RZpA)VVTfN{+eBI?WVJ38jV~`0QbL8pr%>uk7ssCE0H@{;cf&$htDoA+fV>RRC za$QFky43W-L=)uHLIkR0@>_)h;_3ZQe{~G_36m_oGtJ!KAFZ-`Xwc>B!on9@yhG|^ z2w7dHpo*nNy>o;A`!JN71~3b4yoj1pm-ZXFVb4d+)?T7WTUUP_`4GHF@!RtoM(X>w z#SCF&BSTwVE291{eX#tSw^|W$>9`?7#Em=^0OZMe?=Z;TM$PvNc^6|tc5~r}2%h=! z7$&yn-~N3^qW^1zT>tXtz4sGBp}3q=GFD!^#I;xm#Tun&l@&)$ymdyia>a>?D>_`I z>BoM@9JseKHdSJ3A{i>ngVrV&^LktGjVg(KZl%wr_b@N+gs_t(%zTz*klvv?dLuue z3uhN!n9v|Gl(bfNUV0lXO!^8YH$ZH}^aP@BJFpUjLQBK7mt^n}PD=s)vWBArUMspa zeTFD-oMcO0GnqYD+e@u%{h@a1!Iv10P&JS}0`BeP@3FsV`Q{GUs`Vmkn z)?cw3kORW*=BWf?Dmy{XHG)6M#1;Ks;@hitNz7VW6v+5qn%DBOT8*pNp5Y}p{$$5c z%KEAb^=7TFt$kzP^e$k*F7;_bv#^4IqHO@N*3pYiJ~iO{z|q z_!L8P6$t7hTi3c z#PXDlS$=zbP?5VBgl5FCC}Eo=Jv!B4buKzoX6Iikc*r(t$wNr})8^B=3VeA-PdxFB zaLSJU{xJjR6Be4+IS4k>&vKA9+~!NcDFV*eVdYchQi6ZjXhjXL&;Wn?;QQ?J9q}=j zo}>iNJg}FD<;sZdd`OM_Hr}pFMXzWErLYD~WV1aq&>Ke$GoV=&$=mu)%Rc^#-Cf#% z*FEfbtFXdLVI#sQud`wcmOqgDWq>a{S7iuG8jmM^*ybb5h4IRjT%@P1*r_|3Pu>8F zse!Bn=*i*B-C=f!ipj}D{F(<<03-OWqbd!HK+Kxhk?Rh|F~Jc<>@q@uc4hfVdcJxm z)4asm`po1!2-vrcIz5zGJ#`YLYQ-~4IDL>=x~!bL7SX_ZKg3xauF1p8FFuL@Z9A&f z77^oxn{pEKL}r7X5TF*?LBIkKeI3qpcUt8C7I7aZ*eI-H)zZ#>t-ct3DH@6Aso~A5}y%GUe*-`s@msN7@%DEnlGqU#zdXB=*Com#H{oR^Psh3;YM(P|~Lf zX^7lK&CpLR3Vy!STzE&j>Sqsk&tfg6pE=(1b1ZR8 ze*5X65o3Te;7G)X@4c!;#SPiw!}Ah^F9s*i@OgDd zU4-w^HYkdQQ-+YA0@_&#T)Erpnk4s-5b9iNr;U$=z=~@ZxmA@GCh|)WFM{L+SGls( z#S=RY42=m(2JBN9m?iE$z%iN};(kk}$zN&`|IShMg@la{K6)w_FfZ(cmdrgu1Za|I z*5;6m_u8MwnoMY2Udc7$;k4?fHgE$(h#1v(?#k2v(FP}=%7cn!&y{sKp+_ADII2Ft%qq(q7bRd|h;GRFj4 zKE{*(y$yli#utSFJ|T8sd2S?5aoUmjoT%%0rN_4YapR-e8L=#vT!)_4h>qXnxm-Jh7s?qM; zZ6$#ZCZ7NrlUa8#@KS6jGDFCKhshc?#}|DseIW{t0nk`&YaBiG23r2$o;ir@i26N& zT)aeSfW~EEX`RgJpap~b!^$0QX*f(nuETqN2Jl`7tEE|W14mAs!cOzk6kz4U!kpry zWRYAyFEf8w_mb5yk8(nDLZvTzDMG(jSUcutwEMt;tZ7|8>lmza!94x?Fn?YPh#D}S z#jR5j#oa z&YLOg|8e?bZRl>{D6PyH6>sArbgs6rVB~UBt=f8)hu*-olGK($5Oqzr=j5O)r>^=5 zVhPL?>3m%aP9Y&R^zse=28|Sx`33S(K0ztL{!kSq%t+9Qd4nBb9rzr_cgP$B6pEek<1iD0XIZIO05uf;JsxV3>6F#jvr_3e5?UC?wx7}+x*b`k!6;3>SZ0_OSN~uvG?TS6-J62s z{gR%_c_vsiYg>EqnR$hHjQ>dHs=mwQ1kZqAm?{PA-JLeH7#w}Lp)i9rBpr5%^#z|X zG{Ed4qxpjwi-Hey8ya)M=>+1To$F0;+jB)Qfar54EaPSJvot=3({9{=-#$plq7POO z@-Qu>%!%60F8G6b5CvdP?9_`Bh@w7jSZ$^KYiyx5AdnvAl&uyTU(>13%3{2DF8H0z zeH{AjTEmGO$2f`n8`Kdx@AUA__SL@^Ilr#0o3%Z&fGEqKUw9Rr{fwXN$xEj{tfO1p zLOnpEIV)4wyS$R$#c+O)yb@{|l>c$W=Q-yr!+UoZ*^%rU)$>{Wr|Ww<6P#$L zXL$`hBzxvkK`U39a67S>E6WOY8k=ALZbkjQx#OFlxqVJ{$9i|dr&&F{uR#{24UELq ztV^ORuH~OLk0}cX9J$EimS{IZD`9UZ4h-M*h;`HdX|%vd&d8s`8G(i%Fr{Z&3eZx# zGsW;>sK6GXbVCPghtFb3YtrO@@n`biWSnblDV{_gb$y9F`{fh5bw_Tx(cw3X{G@B7 zT!bxn88wskSnm57x`(lJU)&0KApN1Z1ck4d7Af?Q;#@2=vocE(kh9ww$A)==KJ5|$c(J&ZM@ z!ei!%23nINm;v2Tw|wck)z~eNpXhv}4x;gnHv#yhT()*H^W>qS<{jk3dU}WtwR5e_ z1-hA0%5}8^S*tF*Nd8EkhU@?Ic<0prA>D4OiTh#zT5NcLE*u~^B9blJi?{6aT)Uay z9R;0U=nS-#Uk&x9)+TC{uq>`iRIPPFn&cP1{b8N<{>bmd7AH?FKvZq4|38{jzs zW~vvxXc{cqP^NfRp%z0c@Zczc)0VAd$l8yskgh~8Ip<82hZ=I~MEVi4_y%EGJ{q-s_d5iWszvFQJqvDBf0G<@Oot3;o zWc|&cuBpbcy*am63xVi|GCIF>Ovq3>HQ`up?}Q(a!f_BkQ&3QZKW=J=nv~t#yiVASAx|1FD8uuo^xySQ*{UJ6 z)FsbpBk!i{jaD>b-Z1WeLE5zGcgXe@hm^_Rz*DFihp?(?k2d4Or`_`!K+${KA12;M ziKoJBnk_&@YoS-4+sAiD5JW>pxsc(bU&4BuBy}XK)q56aKgr$KKINl+rbrH#m|{MK zu0Dg_)9H?UADqkL+Jg9sg|^4lYTfh}?!)AEfZC++Me}rxr11gv4kmp>vhN;BU7eFS z^JL`n^woA*!>PaZh>^uRD;j+{AWfC^5ST!5Znhqi+&;tsd9PB0tfc>VnZv9)6vmGmzWQwmm`|e_bWErFA#?k!MsmS?DaD^5m*Sp zNpD>FS5^Kwnr~~O9G5JgdEsoY$%x+TNX~Phei4tgXDQ}@YVpvv==m%q`6t0KoYHiN zYF|ij72K}bmC|ZWI;6C$p5iFb%`O-2)8`VB8!~oF1Bgwps|WsqQf=v9ZQMhontCJ;yhU>jO-g$0bK9v6>&$-TDvk>B0G~xSUvyywtDCvP=Tg8nnYe)8B zDSBrS*!xD<&ttie#70!zN{$)H*3!PTPxr4|PtgP6=d#KHw8PIB&k3)I_&C1T(}uf> zq1L4aU&lSWOz@Hb>MP-xwk@LzercxA0w|qVUs0fHgXgQ-$t_oXA@X_NqD$Fi@S#CI z(LMQkY>W1j*n;Zq-5IQ}tzP#Q${Pl_c}(X$^%K4XeAMBMQaJ6>KTX^ie}VM3mP@m( z)PJKwLkQsj_;U0b?x-=4*T>vFakp^=cQDi1@y{m3p>=;cFci#+gd{EVKCW2k#J ztjdb~k`d=>00kqGQhZ!=&yZ)c$x_DKQ^()It%BdOza`%dBz>3OK6j9Q&_Q`|cElcY zG5-l*xb_BpwdAQU@6Hb5w%VW4R&^RP4N(f|PkUmKno&jXP7J&p&(yvp*I2BTSxcUv z+{82BRt=66?xYGhubnGlX@oQ8H{lxVcP=<3&|KxRQ)gyvP*8R0pWTICyS85OtC#qsu$tJ)se}b<5`5betQU7z{-;{}rM`UfjhYN>sDLT)0^rISB?h2d5vbj!ao?pC_!H=G%W ze$LXPbJEsy4!rrMZVv4hIXAL)FV(A8fEe}^YM1iyw-;(LeYtOe#EB)$n*K3swHjwz zc;-B=jr7P8lPb{LRvkQ-&5JS^e$o?bpem4(X=z$x3-Hf+YKcJN0Gj>k74k*^8yG%j zh44s7dI7KfqJ(lUYuRCocqY7p7n4ANav34YEC0GO+*a-f|M0q1cde$utY39{?b6eu zn2FMN`EM2@QzJNa!f@j4B=IF9a=o+Pn>|3bcPxR^y%J4g?DW6+bmO{vn)DALu0mhZ zH=OD(X1+edKlA8a>Km9W(^J4@iDrYx_A6aqcx6WF`?YlC0MX0By4h$%ROeGZJ29l2 zicBY*?DzUaOAHnX1cI@ACYt!VBWP}j^H+VIZ?Y6Tw$eYf;?&%DkR{ct|42@G(fS!{ zGfe&ykiW?I>#3(4Ze2kuQCzYhYnaJDg57hx;?cvp;kIZP7W?R-`%25fXe$qM!rTn> zBH{@v0N~^c<*7%UxUAo~#_^)u9z)J_vr4OW0}6vDNQVWkkCBouCc5z_Q0s^L{@hJ+ z_UpTo!>V}V>qsgRr=!iNO!MfsGhuk~02srTmKHVapNw~9YA+)Qt--k+U&gNm+PC(& z{asrL-ZaqXL&UDshjDiCaRNtZ&83bpbu05&x2as6O!OaEDtQGi(%r75CRuaSyvJ_aP;jms+yeBo!sJV?|Nw-GaJ zX(kO<*OY$VhH6+MhWyA33&{fJj}?0+EvLW-j?HPr`1ZrwZp+e#;!Ta z!BWIg_$%g!SClXBe(BA3kr_OF`d>1b10(xmZU6HeUC- z)O zduH*WzjZ8tGSke~fS|3aPZ3YTUN@OAByP=ahW51!LcRS_A3a^#vlvTjW+30wv%$Ls zxNtBJ5)CqMu!L;eb9zJKh_Pte;slN{iT@v1>9Mx>KMp%MONO2Yl$-2$(eh_?=$0hZ z&nM$#%IoEcsH;4RS=-ka?=NhY1oXT1%U@pj;~vZ+XwQ4tXZiK%mA5hNKxDC7VMKk+ z>}#La5qo`i#A+-M9WOI_@L?mZo-!IZT|e4|37%M*3HH^!$0`eiz8!lR&2TEG1;)$B zET#pp9a7qLTGj+jvODbQX`SoihfD5&zDyhO%OWr0n)gP>E(J5VigulY5IcM~A){Ma zHIa>Ufl3z%n?i8;7Cd{C9Ytdr9U{`5wXE;q)Fs=(>H4rwOyG{}e%r17;2jTfV!JJ(U2m`6t8;EYciaG`k=c>wU?5Xj_PN{9&FjQ_CXzu2t~R6DQ!=3 zAX8UpI&(P-LA{GLAh|w>Vw|iZ-Bh`=^ZgMBQ9lXQY+NoHG#h^uW~0Fwo1(xanEagg z!`A|krkH0ec0|iSRi?YXN&edp$MK9@TP$8w+%f+<9#2wWjOTP%0A2*r*fywFNss53 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%0D}VK$H!vbdLndH-pI!76-wPUZMs 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 Date: Sat, 29 Dec 2018 00:58:43 -0800 Subject: [PATCH 25/34] [deprecated] Move requests to legacy #68 - the util/httputil is already doing most of the work - https://github.com/dyweb/go.ice/issues/37 has a high level http client, which might be moved to gommon and become the new requests library ... --- Makefile | 4 ++-- {requests => legacy/requests}/README.md | 0 {requests => legacy/requests}/auth.go | 0 {requests => legacy/requests}/auth_test.go | 0 {requests => legacy/requests}/builder.go | 0 {requests => legacy/requests}/builder_test.go | 0 {requests => legacy/requests}/default.go | 0 {requests => legacy/requests}/doc/history.md | 0 {requests => legacy/requests}/pkg.go | 0 {requests => legacy/requests}/requests.go | 0 {requests => legacy/requests}/requests_test.go | 0 {requests => legacy/requests}/response.go | 0 util/httputil/proxy.go | 3 +++ 13 files changed, 5 insertions(+), 2 deletions(-) rename {requests => legacy/requests}/README.md (100%) rename {requests => legacy/requests}/auth.go (100%) rename {requests => legacy/requests}/auth_test.go (100%) rename {requests => legacy/requests}/builder.go (100%) rename {requests => legacy/requests}/builder_test.go (100%) rename {requests => legacy/requests}/default.go (100%) rename {requests => legacy/requests}/doc/history.md (100%) rename {requests => legacy/requests}/pkg.go (100%) rename {requests => legacy/requests}/requests.go (100%) rename {requests => legacy/requests}/requests_test.go (100%) rename {requests => legacy/requests}/response.go (100%) create mode 100644 util/httputil/proxy.go diff --git a/Makefile b/Makefile index 8dc9992..be1b876 100644 --- a/Makefile +++ b/Makefile @@ -36,8 +36,8 @@ help: @echo "$$GOMMON_MAKEFILE_HELP_MSG" # -- build vars --- -PKGS =./errors/... ./generator/... ./log/... ./noodle/... ./requests/... ./structure/... ./util/... -PKGST =./cmd ./errors ./generator ./log ./noodle ./requests ./structure ./util +PKGS =./errors/... ./generator/... ./log/... ./noodle/... ./structure/... ./util/... +PKGST =./cmd ./errors ./generator ./log ./noodle ./structure ./util VERSION = 0.0.8 BUILD_COMMIT := $(shell git rev-parse HEAD) BUILD_TIME := $(shell date +%Y-%m-%dT%H:%M:%S%z) diff --git a/requests/README.md b/legacy/requests/README.md similarity index 100% rename from requests/README.md rename to legacy/requests/README.md diff --git a/requests/auth.go b/legacy/requests/auth.go similarity index 100% rename from requests/auth.go rename to legacy/requests/auth.go diff --git a/requests/auth_test.go b/legacy/requests/auth_test.go similarity index 100% rename from requests/auth_test.go rename to legacy/requests/auth_test.go diff --git a/requests/builder.go b/legacy/requests/builder.go similarity index 100% rename from requests/builder.go rename to legacy/requests/builder.go diff --git a/requests/builder_test.go b/legacy/requests/builder_test.go similarity index 100% rename from requests/builder_test.go rename to legacy/requests/builder_test.go diff --git a/requests/default.go b/legacy/requests/default.go similarity index 100% rename from requests/default.go rename to legacy/requests/default.go diff --git a/requests/doc/history.md b/legacy/requests/doc/history.md similarity index 100% rename from requests/doc/history.md rename to legacy/requests/doc/history.md diff --git a/requests/pkg.go b/legacy/requests/pkg.go similarity index 100% rename from requests/pkg.go rename to legacy/requests/pkg.go diff --git a/requests/requests.go b/legacy/requests/requests.go similarity index 100% rename from requests/requests.go rename to legacy/requests/requests.go diff --git a/requests/requests_test.go b/legacy/requests/requests_test.go similarity index 100% rename from requests/requests_test.go rename to legacy/requests/requests_test.go diff --git a/requests/response.go b/legacy/requests/response.go similarity index 100% rename from requests/response.go rename to legacy/requests/response.go diff --git a/util/httputil/proxy.go b/util/httputil/proxy.go new file mode 100644 index 0000000..288e883 --- /dev/null +++ b/util/httputil/proxy.go @@ -0,0 +1,3 @@ +package httputil + +// TODO: socks5 proxy from deprecated requests package From 29dd85434d01c1bd103a3446387228e9f626cb0d Mon Sep 17 00:00:00 2001 From: at15 Date: Sat, 29 Dec 2018 18:02:09 -0800 Subject: [PATCH 26/34] [errors] Add errortype subpackage --- directory.md | 9 ++++----- errors/README.md | 6 ++++-- errors/errortype/pkg.go | 3 +++ errors/{types.go => errortype/std.go} | 5 +++-- 4 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 errors/errortype/pkg.go rename errors/{types.go => errortype/std.go} (90%) diff --git a/directory.md b/directory.md index 72f9eaa..e616af4 100644 --- a/directory.md +++ b/directory.md @@ -3,16 +3,15 @@ ## Project Layout - [cmd/gommon](cmd/gommon) the command line application -- [doc](doc) documentation -- [errors](errors) error wrapping and multi error -- [generator](generator) generating interface methods, render go template, protobuf etc. +- [doc](doc) style guide and developer log +- [errors](errors) error wrapping, multi error and error inspection +- [generator](generator) generating interface methods, render go template, execute shell command. - [legacy](legacy) legacy code base - [noodle](noodle) embed static assets for go binary with .ignore file support - [playground](playground) test library and replay issues -- [requests](requests) ~~http util~~ - [scripts](scripts) test scripts - [structure](structure) data structure -- [util](util) small packages +- [util](util) contains many small packages like [httputl](util/httputil) ## Package Layout diff --git a/errors/README.md b/errors/README.md index d1666cd..9d7432a 100644 --- a/errors/README.md +++ b/errors/README.md @@ -1,13 +1,15 @@ # Errors -A error package supports wrapping and multi error +A error package that allows you to wrap/unwrap, multi error and inspect error ## Issues -- init https://github.com/dyweb/gommon/issues/54 +- [#54](https://github.com/dyweb/gommon/issues/54) init +- ## Implementation +- [doc/design](doc/design) contains some survey and design choices we made - `Wrap` checks if this is already a `WrappedErr`, if not, it attach stack - `MultiErr` keep a slice of errors, the thread safe version use mutex and returns copy of slice when `Errors` is called diff --git a/errors/errortype/pkg.go b/errors/errortype/pkg.go new file mode 100644 index 0000000..61f3c84 --- /dev/null +++ b/errors/errortype/pkg.go @@ -0,0 +1,3 @@ +// Package errortype defines helper for inspect common error types generated in standard library, +// so you don't need to import tons of packages for their sentinel error and custom error type. +package errortype diff --git a/errors/types.go b/errors/errortype/std.go similarity index 90% rename from errors/types.go rename to errors/errortype/std.go index aefee35..f2588a4 100644 --- a/errors/types.go +++ b/errors/errortype/std.go @@ -1,13 +1,14 @@ -package errors +package errortype import "runtime" // NOTE: this is not the file that defines a sort of structs .... // TODO: error types, user, dev, std, lib, grpc etc. see https://github.com/dyweb/gommon/issues/66 // TODO: Is function should do the unwrapping as well -// TODO: should consider put it into another package like errort +// TODO: should consider put it into another package like errort, now it's errortype func IsRuntimeError(err error) bool { _, ok := err.(runtime.Error) return ok } + From 23165f689a914358fabeb001fa02b06063a622c8 Mon Sep 17 00:00:00 2001 From: at15 Date: Sat, 29 Dec 2018 18:26:01 -0800 Subject: [PATCH 27/34] [go] Init go.mod #74 for using replace - for using local version of gommon, users need to use the replace directive and use a relative local path, this requires gommon to have a go mod file, otherwise it won't work - [ ] TODO: add ignore tag for legacy packages #105 --- go.mod | 14 ++++++++++++++ go.sum | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..be42f78 --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/dyweb/gommon + +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 + github.com/pkg/errors v0.8.0 + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/cobra v0.0.3 + github.com/spf13/pflag v1.0.2 // indirect + github.com/stretchr/testify v1.2.2 + golang.org/x/net v0.0.0-20180824152047-4bcd98cce591 + gopkg.in/yaml.v2 v2.2.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9323dc6 --- /dev/null +++ b/go.sum @@ -0,0 +1,22 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/net v0.0.0-20180824152047-4bcd98cce591 h1:4S2XUgvg3hUNTvxI307qkFPb9zKHG3Nf9TXFzX/DZZI= +golang.org/x/net v0.0.0-20180824152047-4bcd98cce591/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From db40028047e4ec8ed4e86a7b842b30f1d06448fd Mon Sep 17 00:00:00 2001 From: at15 Date: Sat, 29 Dec 2018 21:03:50 -0800 Subject: [PATCH 28/34] [cmd] Add add-build-ignore Fix #105 --- Makefile | 2 +- cmd/gommon/main.go | 63 +++++++++++++++++++++++++++++++++++++++-- errors/errortype/std.go | 1 - util/fsutil/file.go | 2 +- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index be1b876..454ad20 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ FLAGS = -X main.version=$(VERSION) -X main.commit=$(BUILD_COMMIT) -X main.buildT # -- build vars --- .PHONY: install -install: +install: fmt go install -ldflags "$(FLAGS)" ./cmd/gommon .PHONY: fmt diff --git a/cmd/gommon/main.go b/cmd/gommon/main.go index a97be2c..7b14b9d 100644 --- a/cmd/gommon/main.go +++ b/cmd/gommon/main.go @@ -2,9 +2,13 @@ package main import ( + "bytes" "fmt" + "io/ioutil" "os" + "path/filepath" "runtime" + "strings" "github.com/spf13/cobra" @@ -13,6 +17,7 @@ import ( dlog "github.com/dyweb/gommon/log" "github.com/dyweb/gommon/log/handlers/cli" "github.com/dyweb/gommon/noodle" + "github.com/dyweb/gommon/util/fsutil" "github.com/dyweb/gommon/util/logutil" ) @@ -67,6 +72,7 @@ func main() { rootCmd.AddCommand( versionCmd, genCmd(), + addBuildIgnoreCmd(), ) if err := rootCmd.Execute(); err != nil { fmt.Fprintln(os.Stderr, err) @@ -75,7 +81,7 @@ func main() { } func genCmd() *cobra.Command { - gen := &cobra.Command{ + gen := cobra.Command{ Use: "generate", Aliases: []string{"gen"}, Short: "generate code based on gommon.yml", @@ -132,7 +138,60 @@ func genCmd() *cobra.Command { noodleCmd.Flags().StringVar(&pkg, "pkg", "gen", "go package of generated file") noodleCmd.Flags().StringVar(&output, "output", "noodle.go", "path for generated file") gen.AddCommand(noodleCmd) - return gen + return &gen +} + +func addBuildIgnoreCmd() *cobra.Command { + cmd := cobra.Command{ + Use: "add-build-ignore", + Short: "Add // +build ignore to go files", + Run: func(cmd *cobra.Command, args []string) { + pwd, err := os.Getwd() + if err != nil { + log.Fatalf("error get current directory: %s", err) + return + } + buildIgnore := "// +build ignore\n\n" + ignores := fsutil.NewIgnores([]fsutil.IgnorePattern{ + fsutil.ExactPattern(".git"), + fsutil.ExactPattern("testdata"), + fsutil.ExactPattern("vendor"), + fsutil.ExactPattern(".idea"), + fsutil.ExactPattern(".vscode"), + }, nil) + for _, p := range args { + err = fsutil.Walk(filepath.Join(pwd, p), ignores, func(path string, info os.FileInfo) { + if info.IsDir() || !strings.HasSuffix(info.Name(), ".go") { + return + } + f := filepath.Join(path, info.Name()) + b, err := ioutil.ReadFile(f) + if err != nil { + log.Fatalf("error read file %s", err) + return + } + // NOTE: we only + prefix := []byte(buildIgnore) + if bytes.HasPrefix(b, prefix) { + log.Warnf("%s already have build ignore prefix", f) + return + } + // prepend prefix + b = append(prefix, b...) + if err := fsutil.WriteFile(f, b); err != nil { + log.Fatalf("error write file with build prefix: %s", err) + return + } + log.Infof("updated %s", f) + }) + if err != nil { + log.Fatalf("error walk %s", err) + return + } + } + }, + } + return &cmd } func init() { diff --git a/errors/errortype/std.go b/errors/errortype/std.go index f2588a4..bf6217e 100644 --- a/errors/errortype/std.go +++ b/errors/errortype/std.go @@ -11,4 +11,3 @@ func IsRuntimeError(err error) bool { _, ok := err.(runtime.Error) return ok } - diff --git a/util/fsutil/file.go b/util/fsutil/file.go index 64c8fda..91ec424 100644 --- a/util/fsutil/file.go +++ b/util/fsutil/file.go @@ -6,7 +6,7 @@ import ( "github.com/dyweb/gommon/errors" ) -// WriteFile use 0666 as permission and wrap standard error +// WriteFile use 0664 as permission and wrap standard error func WriteFile(path string, data []byte) error { if err := ioutil.WriteFile(path, data, 0664); err != nil { return errors.Wrap(err, "can't write file") From ebd4181fe388bf65c88dbf30714adab163c5c82a Mon Sep 17 00:00:00 2001 From: at15 Date: Sat, 29 Dec 2018 21:10:45 -0800 Subject: [PATCH 29/34] [legacy] Ignore legacy pkgs using build tag #105 - this also removed pkg/errors from go.mod #74 --- go.mod | 2 -- go.sum | 4 ---- legacy/config/gommon_generated.go | 2 ++ legacy/config/pkg.go | 2 ++ legacy/config/yaml.go | 2 ++ legacy/config/yaml_test.go | 2 ++ legacy/log/config.go | 2 ++ legacy/log/config_test.go | 2 ++ legacy/log/doc.go | 2 ++ legacy/log/entry.go | 2 ++ legacy/log/entry_generated.go | 2 ++ legacy/log/entry_test.go | 2 ++ legacy/log/filter.go | 2 ++ legacy/log/filter_test.go | 2 ++ legacy/log/formatter.go | 2 ++ legacy/log/formatter_test.go | 2 ++ legacy/log/level.go | 2 ++ legacy/log/level_test.go | 2 ++ legacy/log/logger.go | 2 ++ legacy/log/logger_test.go | 2 ++ legacy/pkg.go | 2 ++ legacy/requests/auth.go | 2 ++ legacy/requests/auth_test.go | 2 ++ legacy/requests/builder.go | 2 ++ legacy/requests/builder_test.go | 2 ++ legacy/requests/default.go | 2 ++ legacy/requests/pkg.go | 2 ++ legacy/requests/requests.go | 2 ++ legacy/requests/requests_test.go | 2 ++ legacy/requests/response.go | 2 ++ legacy/runner/command.go | 2 ++ legacy/runner/command_test.go | 2 ++ legacy/runner/context.go | 2 ++ legacy/runner/doc.go | 2 ++ legacy/runner/pkg.go | 2 ++ 35 files changed, 66 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index be42f78..cb26c4b 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,9 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/pkg/errors v0.8.0 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.2 // indirect github.com/stretchr/testify v1.2.2 - golang.org/x/net v0.0.0-20180824152047-4bcd98cce591 gopkg.in/yaml.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index 9323dc6..cf2333c 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= @@ -14,8 +12,6 @@ github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -golang.org/x/net v0.0.0-20180824152047-4bcd98cce591 h1:4S2XUgvg3hUNTvxI307qkFPb9zKHG3Nf9TXFzX/DZZI= -golang.org/x/net v0.0.0-20180824152047-4bcd98cce591/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= diff --git a/legacy/config/gommon_generated.go b/legacy/config/gommon_generated.go index 62bba24..9324c91 100644 --- a/legacy/config/gommon_generated.go +++ b/legacy/config/gommon_generated.go @@ -1,3 +1,5 @@ +// +build ignore + // Code generated by gommon from config/gommon.yml DO NOT EDIT. package config diff --git a/legacy/config/pkg.go b/legacy/config/pkg.go index 8c131db..bc51532 100644 --- a/legacy/config/pkg.go +++ b/legacy/config/pkg.go @@ -1,3 +1,5 @@ +// +build ignore + // Package config supports go text/template, environment and self defined variables package config diff --git a/legacy/config/yaml.go b/legacy/config/yaml.go index bfaca9f..2893e6c 100644 --- a/legacy/config/yaml.go +++ b/legacy/config/yaml.go @@ -1,3 +1,5 @@ +// +build ignore + package config import ( diff --git a/legacy/config/yaml_test.go b/legacy/config/yaml_test.go index a9c879c..8d5b5df 100644 --- a/legacy/config/yaml_test.go +++ b/legacy/config/yaml_test.go @@ -1,3 +1,5 @@ +// +build ignore + package config import ( diff --git a/legacy/log/config.go b/legacy/log/config.go index d1aa36d..0ecf7dc 100644 --- a/legacy/log/config.go +++ b/legacy/log/config.go @@ -1,3 +1,5 @@ +// +build ignore + package log import ( diff --git a/legacy/log/config_test.go b/legacy/log/config_test.go index 9845974..d885722 100644 --- a/legacy/log/config_test.go +++ b/legacy/log/config_test.go @@ -1,3 +1,5 @@ +// +build ignore + package log import ( diff --git a/legacy/log/doc.go b/legacy/log/doc.go index 54c3c7b..6486c65 100644 --- a/legacy/log/doc.go +++ b/legacy/log/doc.go @@ -1,3 +1,5 @@ +// +build ignore + /* Package log(Deprecated) can filter log by field and support multiple level */ diff --git a/legacy/log/entry.go b/legacy/log/entry.go index 7203334..1b15479 100644 --- a/legacy/log/entry.go +++ b/legacy/log/entry.go @@ -1,3 +1,5 @@ +// +build ignore + package log import ( diff --git a/legacy/log/entry_generated.go b/legacy/log/entry_generated.go index 20c306d..d09832e 100644 --- a/legacy/log/entry_generated.go +++ b/legacy/log/entry_generated.go @@ -1,3 +1,5 @@ +// +build ignore + // Generated by tmpl // https://github.com/benbjohnson/tmpl // diff --git a/legacy/log/entry_test.go b/legacy/log/entry_test.go index 0de073f..99c463c 100644 --- a/legacy/log/entry_test.go +++ b/legacy/log/entry_test.go @@ -1,3 +1,5 @@ +// +build ignore + package log import "testing" diff --git a/legacy/log/filter.go b/legacy/log/filter.go index da31721..63bd85b 100644 --- a/legacy/log/filter.go +++ b/legacy/log/filter.go @@ -1,3 +1,5 @@ +// +build ignore + package log import ( diff --git a/legacy/log/filter_test.go b/legacy/log/filter_test.go index dd1a09b..ce91638 100644 --- a/legacy/log/filter_test.go +++ b/legacy/log/filter_test.go @@ -1,3 +1,5 @@ +// +build ignore + package log import ( diff --git a/legacy/log/formatter.go b/legacy/log/formatter.go index 777f4b4..b9a5e76 100644 --- a/legacy/log/formatter.go +++ b/legacy/log/formatter.go @@ -1,3 +1,5 @@ +// +build ignore + package log import ( diff --git a/legacy/log/formatter_test.go b/legacy/log/formatter_test.go index 7330d54..60bcf63 100644 --- a/legacy/log/formatter_test.go +++ b/legacy/log/formatter_test.go @@ -1 +1,3 @@ +// +build ignore + package log diff --git a/legacy/log/level.go b/legacy/log/level.go index a47fba7..ea83aa4 100644 --- a/legacy/log/level.go +++ b/legacy/log/level.go @@ -1,3 +1,5 @@ +// +build ignore + package log import ( diff --git a/legacy/log/level_test.go b/legacy/log/level_test.go index cfd2176..6c92167 100644 --- a/legacy/log/level_test.go +++ b/legacy/log/level_test.go @@ -1,3 +1,5 @@ +// +build ignore + package log import ( diff --git a/legacy/log/logger.go b/legacy/log/logger.go index 9d18174..1e87697 100644 --- a/legacy/log/logger.go +++ b/legacy/log/logger.go @@ -1,3 +1,5 @@ +// +build ignore + package log import ( diff --git a/legacy/log/logger_test.go b/legacy/log/logger_test.go index 83b42dd..d9800ce 100644 --- a/legacy/log/logger_test.go +++ b/legacy/log/logger_test.go @@ -1,3 +1,5 @@ +// +build ignore + package log import ( diff --git a/legacy/pkg.go b/legacy/pkg.go index 74470c7..d945b1c 100644 --- a/legacy/pkg.go +++ b/legacy/pkg.go @@ -1,2 +1,4 @@ +// +build ignore + // Package legacy contains deprecated gommon packages package legacy diff --git a/legacy/requests/auth.go b/legacy/requests/auth.go index ce9e87a..d99116d 100644 --- a/legacy/requests/auth.go +++ b/legacy/requests/auth.go @@ -1,3 +1,5 @@ +// +build ignore + package requests import ( diff --git a/legacy/requests/auth_test.go b/legacy/requests/auth_test.go index 4e06bed..6623cb4 100644 --- a/legacy/requests/auth_test.go +++ b/legacy/requests/auth_test.go @@ -1,3 +1,5 @@ +// +build ignore + package requests import ( diff --git a/legacy/requests/builder.go b/legacy/requests/builder.go index c186264..3f588f5 100644 --- a/legacy/requests/builder.go +++ b/legacy/requests/builder.go @@ -1,3 +1,5 @@ +// +build ignore + package requests import ( diff --git a/legacy/requests/builder_test.go b/legacy/requests/builder_test.go index 96d4a35..d03a6ec 100644 --- a/legacy/requests/builder_test.go +++ b/legacy/requests/builder_test.go @@ -1,3 +1,5 @@ +// +build ignore + package requests import ( diff --git a/legacy/requests/default.go b/legacy/requests/default.go index a5e8ce9..7e5c82e 100644 --- a/legacy/requests/default.go +++ b/legacy/requests/default.go @@ -1,3 +1,5 @@ +// +build ignore + package requests import ( diff --git a/legacy/requests/pkg.go b/legacy/requests/pkg.go index 9bb1d36..5ce1120 100644 --- a/legacy/requests/pkg.go +++ b/legacy/requests/pkg.go @@ -1,2 +1,4 @@ +// +build ignore + // Package requests is a wrapper around net/http with less public global variables package requests // import "github.com/dyweb/gommon/requests" diff --git a/legacy/requests/requests.go b/legacy/requests/requests.go index 4c76787..f678142 100644 --- a/legacy/requests/requests.go +++ b/legacy/requests/requests.go @@ -1,3 +1,5 @@ +// +build ignore + // Package requests wrap net/http like requests did for python // it is easy to use, but not very efficient package requests diff --git a/legacy/requests/requests_test.go b/legacy/requests/requests_test.go index 42dc915..e69a883 100644 --- a/legacy/requests/requests_test.go +++ b/legacy/requests/requests_test.go @@ -1,3 +1,5 @@ +// +build ignore + package requests import ( diff --git a/legacy/requests/response.go b/legacy/requests/response.go index 69162b9..171053d 100644 --- a/legacy/requests/response.go +++ b/legacy/requests/response.go @@ -1,3 +1,5 @@ +// +build ignore + package requests import ( diff --git a/legacy/runner/command.go b/legacy/runner/command.go index 902f79d..00dec2c 100644 --- a/legacy/runner/command.go +++ b/legacy/runner/command.go @@ -1,3 +1,5 @@ +// +build ignore + package runner import ( diff --git a/legacy/runner/command_test.go b/legacy/runner/command_test.go index 7539f65..b9fbafa 100644 --- a/legacy/runner/command_test.go +++ b/legacy/runner/command_test.go @@ -1,3 +1,5 @@ +// +build ignore + package runner import ( diff --git a/legacy/runner/context.go b/legacy/runner/context.go index 4efd5f7..3f6e137 100644 --- a/legacy/runner/context.go +++ b/legacy/runner/context.go @@ -1,3 +1,5 @@ +// +build ignore + package runner // FIXME: this is never used, and it may not be a good idea to use context, since there is context package diff --git a/legacy/runner/doc.go b/legacy/runner/doc.go index f1e4a1f..98d80cc 100644 --- a/legacy/runner/doc.go +++ b/legacy/runner/doc.go @@ -1,3 +1,5 @@ +// +build ignore + /* Package runner(Deprecated) run commands with some convention */ diff --git a/legacy/runner/pkg.go b/legacy/runner/pkg.go index 75c10db..696b9a9 100644 --- a/legacy/runner/pkg.go +++ b/legacy/runner/pkg.go @@ -1 +1,3 @@ +// +build ignore + package runner From 5ad6c01705efbfa92657523e6d59a2e9cc5dcdf2 Mon Sep 17 00:00:00 2001 From: at15 Date: Sun, 30 Dec 2018 00:30:14 -0800 Subject: [PATCH 30/34] [httputil] Add unix transport without pool --- util/httputil/unix.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/util/httputil/unix.go b/util/httputil/unix.go index eb8dc96..ec749f4 100644 --- a/util/httputil/unix.go +++ b/util/httputil/unix.go @@ -7,6 +7,15 @@ import ( "time" ) +// NewUnPooledUnixTransport uses NewPooledUnixTransport with keep alive and idle connection disabled +func NewUnPooledUnixTransport(socketFile string) *http.Transport { + tr := NewPooledUnixTransport(socketFile) + tr.DisableKeepAlives = true + tr.MaxIdleConnsPerHost = -1 + return tr +} + +// NewPooledUnixTransport creates transport for unix domain socket i.e. /var/run/docker.sock func NewPooledUnixTransport(sockFile string) *http.Transport { return &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { From 88153587295c805a7e6d599479c223fe044189c5 Mon Sep 17 00:00:00 2001 From: at15 Date: Sun, 30 Dec 2018 20:15:40 -0800 Subject: [PATCH 31/34] [build] Add Dockerfile for gommon itself #98 - using go-dev as the base image --- .dockerignore | 6 ++++++ Dockerfile | 32 ++++++++++++++++++++++++++++++++ Makefile | 19 ++++++++++++++----- 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b69ffb8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +# ignore vendor folder because we install it using dep +build +legacy +doc +playground +vendor \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5ea2491 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# This Dockerfile is a demo of using go-dev to build a go binary using multi stage build +# It is based on +# https://docs.docker.com/v17.09/engine/userguide/eng-image/dockerfile_best-practices/#use-multi-stage-builds +FROM dyweb/go-dev:1.11.4 as builder + +LABEL maintainer="contact@dongyue.io" + +ARG PROJECT_ROOT=/go/src/github.com/dyweb/gommon/ + +WORKDIR $PROJECT_ROOT + +# Gopkg.toml and Gopkg.lock lists project dependencies +# These layers will only be re-built when Gopkg files are updated +COPY Gopkg.lock Gopkg.toml $PROJECT_ROOT +RUN dep ensure -v -vendor-only + +# Copy all project and build it +COPY . $PROJECT_ROOT +RUN make install + +# NOTE: use ubuntu instead of alphine +# +# When using alpine I saw standard_init_linux.go:190: exec user process caused "no such file or directory", +# because I didn't compile go with static flag +# https://stackoverflow.com/questions/49535379/binary-compiled-in-prestage-doesnt-work-in-scratch-container +FROM ubuntu:18.04 as runner +LABEL maintainer="contact@dongyue.io" +LABEL github="github.com/dyweb/gommon" +WORKDIR /usr/bin +COPY --from=builder /go/bin/gommon . +ENTRYPOINT ["gommon"] +CMD ["help"] \ No newline at end of file diff --git a/Makefile b/Makefile index 454ad20..60ebdfe 100644 --- a/Makefile +++ b/Makefile @@ -43,10 +43,11 @@ BUILD_COMMIT := $(shell git rev-parse HEAD) BUILD_TIME := $(shell date +%Y-%m-%dT%H:%M:%S%z) CURRENT_USER = $(USER) FLAGS = -X main.version=$(VERSION) -X main.commit=$(BUILD_COMMIT) -X main.buildTime=$(BUILD_TIME) -X main.buildUser=$(CURRENT_USER) +DOCKER_REPO = dyweb/gommon # -- build vars --- .PHONY: install -install: fmt +install: fmt test go install -ldflags "$(FLAGS)" ./cmd/gommon .PHONY: fmt @@ -82,6 +83,9 @@ generate: .PHONY: test-log test-errors test: + go test -cover $(PKGS) + +test-verbose: go test -v -cover $(PKGS) test-cover: @@ -126,12 +130,17 @@ dep-update: # --- dependency management --- # --- docker --- -.PHONY: docker-test +.PHONY: docker-build docker-test + +docker-build: + docker build -t $(DOCKER_REPO):$(VERSION) . + +# TODO: deprecated docker-compose based test docker-test: docker-compose -f scripts/docker-compose.yml run --rm golang1.10 docker-compose -f scripts/docker-compose.yml run --rm golanglatest -.PHONY: docker-remove-all-containers -docker-remove-all-containers: - docker rm $(shell docker ps -a -q) +#.PHONY: docker-remove-all-containers +#docker-remove-all-containers: +# docker rm $(shell docker ps -a -q) # --- docker --- From 96ce3c9185d56fae9e8c95dcad4ecb219a58b939 Mon Sep 17 00:00:00 2001 From: at15 Date: Sun, 30 Dec 2018 21:22:54 -0800 Subject: [PATCH 32/34] [log][cli] Allow disable color - make cli.Handler not exported --- Makefile | 5 ++- build/go-dev/Dockerfile | 1 + legacy/config/pkg.go | 2 +- legacy/requests/pkg.go | 2 +- log/handlers/cli/README.md | 30 +++++++++---- log/handlers/cli/example/main.go | 41 ++++++++++++++++++ log/handlers/cli/handler.go | 71 ++++++++++++++++++++++--------- log/handlers/cli/handler_test.go | 33 ++++++++------ log/handlers/cli/internal_test.go | 21 +++++++++ log/logger_factory.go | 16 ++++--- 10 files changed, 171 insertions(+), 51 deletions(-) create mode 100644 log/handlers/cli/example/main.go create mode 100644 log/handlers/cli/internal_test.go diff --git a/Makefile b/Makefile index 60ebdfe..ccab60a 100644 --- a/Makefile +++ b/Makefile @@ -130,11 +130,14 @@ dep-update: # --- dependency management --- # --- docker --- -.PHONY: docker-build docker-test +.PHONY: docker-build docker-push docker-test docker-build: docker build -t $(DOCKER_REPO):$(VERSION) . +docker-push: + docker push $(DOCKER_REPO):$(VERSION) + # TODO: deprecated docker-compose based test docker-test: docker-compose -f scripts/docker-compose.yml run --rm golang1.10 diff --git a/build/go-dev/Dockerfile b/build/go-dev/Dockerfile index 1e08952..3cfaaa4 100644 --- a/build/go-dev/Dockerfile +++ b/build/go-dev/Dockerfile @@ -47,6 +47,7 @@ RUN \ | tar -vxz -C /usr/local/bin --strip=1 \ && rm /usr/local/bin/README.md /usr/local/bin/LICENSE +# TODO: install dep may have problem when go mod is enabled ... # dep releases are way behind master, so we install from source RUN go get -u -v github.com/golang/dep/cmd/dep \ && go get -u -v golang.org/x/tools/cmd/goimports diff --git a/legacy/config/pkg.go b/legacy/config/pkg.go index bc51532..bdac9bd 100644 --- a/legacy/config/pkg.go +++ b/legacy/config/pkg.go @@ -1,6 +1,6 @@ // +build ignore -// Package config supports go text/template, environment and self defined variables +// Package config(Deprecated) supports go text/template, environment and self defined variables package config import ( diff --git a/legacy/requests/pkg.go b/legacy/requests/pkg.go index 5ce1120..5c5f254 100644 --- a/legacy/requests/pkg.go +++ b/legacy/requests/pkg.go @@ -1,4 +1,4 @@ // +build ignore -// Package requests is a wrapper around net/http with less public global variables +// Package requests(Deprecated) is a wrapper around net/http with less public global variables package requests // import "github.com/dyweb/gommon/requests" diff --git a/log/handlers/cli/README.md b/log/handlers/cli/README.md index e01bef9..842e7d4 100644 --- a/log/handlers/cli/README.md +++ b/log/handlers/cli/README.md @@ -4,27 +4,38 @@ For short running cli and human read ## Usage +See [example/main.go](example/main.go) + +TODO: gommon should allow sync file into markdown + ````go package main import ( "os" "time" - + dlog "github.com/dyweb/gommon/log" "github.com/dyweb/gommon/log/handlers/cli" ) -var log = dlog.NewApplicationLogger() +var log, logReg = dlog.NewApplicationLoggerAndRegistry("example") func main() { - dlog.SetHandlerRecursive(log, cli.New(os.Stderr, true)) + dlog.SetHandler(logReg, cli.New(os.Stderr, true)) + + if len(os.Args) > 1 { + if os.Args[1] == "nocolor" || os.Args[1] == "no" { + dlog.SetHandler(logReg, cli.NewNoColor(os.Stderr)) + } + } + log.Info("hi") log.Infof("open file %s", "foo.yml") - log.InfoF("open", dlog.Fields{ + log.InfoF("open", dlog.Str("file", "foo.yml"), dlog.Int("mode", 0666), - }) + ) log.Warn("I am yellow") func() { defer func() { @@ -32,13 +43,14 @@ func main() { log.Info("recovered", r) } }() - log.Panic("Panic reason") + log.Panic("I just want to panic") }() - dlog.SetLevelRecursive(log, dlog.DebugLevel) + dlog.SetLevel(logReg, dlog.DebugLevel) log.Debug("I will sleep for a while") - time.Sleep(1 * time.Second) + time.Sleep(500 * time.Millisecond) log.Fatal("I am red") } + ```` Result in @@ -54,6 +66,6 @@ DEBU 0000 I will sleep for a while FATA 0001 I am red ```` -It has color (only tested on Linux (Ubuntu)) and can't be disabled +It has color (only tested on Linux (Ubuntu)) and can be disabled when created using no color ![gommon_log_cli_handler](gommon_log_cli_handler.png) \ No newline at end of file diff --git a/log/handlers/cli/example/main.go b/log/handlers/cli/example/main.go new file mode 100644 index 0000000..847b74d --- /dev/null +++ b/log/handlers/cli/example/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "os" + "time" + + dlog "github.com/dyweb/gommon/log" + "github.com/dyweb/gommon/log/handlers/cli" +) + +var log, logReg = dlog.NewApplicationLoggerAndRegistry("example") + +func main() { + dlog.SetHandler(logReg, cli.New(os.Stderr, true)) + + if len(os.Args) > 1 { + if os.Args[1] == "nocolor" || os.Args[1] == "no" { + dlog.SetHandler(logReg, cli.NewNoColor(os.Stderr)) + } + } + + log.Info("hi") + log.Infof("open file %s", "foo.yml") + log.InfoF("open", + dlog.Str("file", "foo.yml"), + dlog.Int("mode", 0666), + ) + log.Warn("I am yellow") + func() { + defer func() { + if r := recover(); r != nil { + log.Info("recovered", r) + } + }() + log.Panic("I just want to panic") + }() + dlog.SetLevel(logReg, dlog.DebugLevel) + log.Debug("I will sleep for a while") + time.Sleep(500 * time.Millisecond) + log.Fatal("I am red") +} diff --git a/log/handlers/cli/handler.go b/log/handlers/cli/handler.go index bba320d..50b2830 100644 --- a/log/handlers/cli/handler.go +++ b/log/handlers/cli/handler.go @@ -1,6 +1,12 @@ -// Package cli writes is same as builtin TextHandler except color and delta time. -// It is used by go.ice as default handler -// TODO: color can't be disabled and we don't detect tty like logrus +// Package cli generates human readable text with color and display time in delta. +// Color and delta can be disabled and output will be same as default handler +// It does NOT escape quote so it is not machine readable +// +// func main() { +// var log, logReg = dlog.NewApplicationLoggerAndRegistry("example") +// dlog.SetHandler(logReg, cli.New(os.Stderr, true)) // with color, delta time +// } +// package cli import ( @@ -14,45 +20,67 @@ import ( ) const ( - defaultTimeStampFormat = time.RFC3339 + DefaultTimeStampFormat = time.RFC3339 ) -var _ log.Handler = (*Handler)(nil) +var _ log.Handler = (*handler)(nil) -type Handler struct { +// handler is not exported since all the fields are not exported +type handler struct { w io.Writer start time.Time delta bool + color bool } -func New(w io.Writer, delta bool) *Handler { - return &Handler{ +// New returns a handler with color on level and field key +func New(w io.Writer, delta bool) log.Handler { + return &handler{ w: w, start: time.Now(), delta: delta, + color: true, } } -func (h *Handler) HandleLog(level log.Level, now time.Time, msg string, source log.Caller, context log.Fields, fields log.Fields) { +// NewNoColor returns a handler without color and use full timestamp +func NewNoColor(w io.Writer) log.Handler { + return &handler{ + w: w, + start: time.Now(), + delta: false, + color: false, + } +} + +func (h *handler) HandleLog(level log.Level, now time.Time, msg string, source log.Caller, context log.Fields, fields log.Fields) { b := make([]byte, 0, 50+len(msg)+len(source.File)+30*len(context)+30*len(fields)) // level - b = append(b, level.ColoredAlignedUpperString()...) + if h.color { + b = append(b, level.ColoredAlignedUpperString()...) + } else { + b = append(b, level.AlignedUpperString()...) + } // time b = append(b, ' ') if h.delta { b = append(b, formatNum(uint(now.Sub(h.start)/time.Second), 4)...) } else { - b = now.AppendFormat(b, defaultTimeStampFormat) + b = now.AppendFormat(b, DefaultTimeStampFormat) } // source if source.Line != 0 { b = append(b, ' ') - b = append(b, color.CyanStart...) + if h.color { + b = append(b, color.CyanStart...) + } last := strings.LastIndex(source.File, "/") b = append(b, source.File[last+1:]...) b = append(b, ':') b = strconv.AppendInt(b, int64(source.Line), 10) - b = append(b, color.End...) + if h.color { + b = append(b, color.End...) + } } // message b = append(b, ' ') @@ -60,18 +88,18 @@ func (h *Handler) HandleLog(level log.Level, now time.Time, msg string, source l // context if len(context) > 0 { b = append(b, ' ') - b = formatFields(b, context) + b = formatFields(b, h.color, context) } // field if len(fields) > 0 { b = append(b, ' ') - b = formatFields(b, fields) + b = formatFields(b, h.color, fields) } b = append(b, '\n') h.w.Write(b) } -func (h *Handler) Flush() { +func (h *handler) Flush() { if s, ok := h.w.(log.Syncer); ok { s.Sync() } @@ -97,12 +125,15 @@ func formatNum(u uint, digits int) []byte { return b } -// it has an extra tailing space, which can be updated inplace to a \n -func formatFields(b []byte, fields log.Fields) []byte { +func formatFields(b []byte, useColor bool, fields log.Fields) []byte { for _, f := range fields { - b = append(b, color.CyanStart...) + if useColor { + b = append(b, color.CyanStart...) + } b = append(b, f.Key...) - b = append(b, color.End...) + if useColor { + b = append(b, color.End...) + } b = append(b, '=') switch f.Type { case log.IntType: diff --git a/log/handlers/cli/handler_test.go b/log/handlers/cli/handler_test.go index 3b03853..20e895c 100644 --- a/log/handlers/cli/handler_test.go +++ b/log/handlers/cli/handler_test.go @@ -1,21 +1,30 @@ -package cli +package cli_test import ( - "fmt" + "bytes" "testing" + "time" - asst "github.com/stretchr/testify/assert" + "github.com/dyweb/gommon/log" + "github.com/dyweb/gommon/log/handlers/cli" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestFmt_FormatNum(t *testing.T) { - fmt.Printf("%04d\n", 2) -} +// TODO: test fields and caller -func Test_FormatNum(t *testing.T) { - assert := asst.New(t) +func TestNew(t *testing.T) { + buf := bytes.Buffer{} + h := cli.New(&buf, true) + h.HandleLog(log.InfoLevel, time.Now(), "hi", log.EmptyCaller(), nil, nil) + assert.Equal(t, "\x1b[34mINFO\x1b[0m 0000 hi\n", buf.String()) +} - assert.Equal("0010", string(formatNum(10, 4))) - assert.Equal("0100", string(formatNum(100, 4))) - assert.Equal("1000", string(formatNum(1000, 4))) - assert.Equal("0000", string(formatNum(10000, 4))) +func TestNewNoColor(t *testing.T) { + buf := bytes.Buffer{} + h := cli.NewNoColor(&buf) + tm, err := time.Parse(cli.DefaultTimeStampFormat, "2018-12-30T21:10:49-08:00") + require.Nil(t, err) + h.HandleLog(log.InfoLevel, tm, "hi", log.EmptyCaller(), nil, nil) + assert.Equal(t, "INFO 2018-12-30T21:10:49-08:00 hi\n", buf.String()) } diff --git a/log/handlers/cli/internal_test.go b/log/handlers/cli/internal_test.go new file mode 100644 index 0000000..3b03853 --- /dev/null +++ b/log/handlers/cli/internal_test.go @@ -0,0 +1,21 @@ +package cli + +import ( + "fmt" + "testing" + + asst "github.com/stretchr/testify/assert" +) + +func TestFmt_FormatNum(t *testing.T) { + fmt.Printf("%04d\n", 2) +} + +func Test_FormatNum(t *testing.T) { + assert := asst.New(t) + + assert.Equal("0010", string(formatNum(10, 4))) + assert.Equal("0100", string(formatNum(100, 4))) + assert.Equal("1000", string(formatNum(1000, 4))) + assert.Equal("0000", string(formatNum(10000, 4))) +} diff --git a/log/logger_factory.go b/log/logger_factory.go index 1779d73..2eba1a0 100644 --- a/log/logger_factory.go +++ b/log/logger_factory.go @@ -1,12 +1,6 @@ package log -// NewTestLogger does not have identity and handler, it is mainly used for benchmark test -func NewTestLogger(level Level) *Logger { - l := &Logger{ - level: level, - } - return l -} +// logger_factory.go creates logger without register them to registry func NewPackageLogger() *Logger { return NewPackageLoggerWithSkip(1) @@ -50,6 +44,14 @@ func NewMethodLogger(structLogger *Logger) *Logger { return newLogger(structLogger, l) } +// NewTestLogger does not have identity and handler, it is mainly used for benchmark test +func NewTestLogger(level Level) *Logger { + l := &Logger{ + level: level, + } + return l +} + func newLogger(parent *Logger, child *Logger) *Logger { if parent != nil { child.h = parent.h From 60464072271f0aa0b80c33804416f32c2e5dfefd Mon Sep 17 00:00:00 2001 From: at15 Date: Sun, 30 Dec 2018 21:36:53 -0800 Subject: [PATCH 33/34] [log][json] Make json.Handler not exported - and export EncodeString since it can be used by other library --- log/handlers/json/README.md | 39 +++++++++++++++++-------------- log/handlers/json/example/main.go | 34 +++++++++++++++++++++++++++ log/handlers/json/handler.go | 28 +++++++++++----------- log/handlers/json/handler_test.go | 4 ++-- 4 files changed, 70 insertions(+), 35 deletions(-) create mode 100644 log/handlers/json/example/main.go diff --git a/log/handlers/json/README.md b/log/handlers/json/README.md index 9e085ec..7bff336 100644 --- a/log/handlers/json/README.md +++ b/log/handlers/json/README.md @@ -4,32 +4,35 @@ For machine ## TODO -- [ ] string are NOT escaped, both message and fields - - i.e. broken JSON like `"msg":"this "specific file"", "file":""there is space.yml""` +- [ ] allow config built in filed names ## Usage +See [example/main.go](example/main.go) + +TODO: gommon should allow sync file into markdown + ````go package main import ( "os" "time" - + dlog "github.com/dyweb/gommon/log" "github.com/dyweb/gommon/log/handlers/json" ) -var log = dlog.NewApplicationLogger() +var log, logReg = dlog.NewApplicationLoggerAndRegistry("example") func main() { - dlog.SetHandlerRecursive(log, json.New(os.Stderr)) + dlog.SetHandler(logReg, json.New(os.Stderr)) log.Info("hi") log.Infof("open file %s", "foo.yml") - log.InfoF("open", dlog.Fields{ + log.InfoF("open", dlog.Str("file", "foo.yml"), dlog.Int("mode", 0666), - }) + ) log.Warn("I am yellow") func() { defer func() { @@ -37,22 +40,22 @@ func main() { log.Info("recovered", r) } }() - log.Panic("Panic reason") + log.Panic("I just want to panic") }() - dlog.SetLevelRecursive(log, dlog.DebugLevel) + dlog.SetLevel(logReg, dlog.DebugLevel) log.Debug("I will sleep for a while") - time.Sleep(1 * time.Second) + time.Sleep(500 * time.Millisecond) log.Fatal("I am red") } ```` ````text -{"l":"info","t":1518210625,"m":"hi"} -{"l":"info","t":1518210625,"m":"open file foo.yml"} -{"l":"info","t":1518210625,"m":"open","file":"foo.yml","mode":438} -{"l":"warn","t":1518210625,"m":"I am yellow"} -{"l":"panic","t":1518210625,"m":"Panic reason"} -{"l":"info","t":1518210625,"m":"recoveredPanic reason"} -{"l":"debug","t":1518210625,"m":"I will sleep for a while"} -{"l":"fatal","t":1518210626,"m":"I am red"} +{"l":"info","t":1546234314,"m":"hi"} +{"l":"info","t":1546234314,"m":"open file foo.yml"} +{"l":"info","t":1546234314,"m":"open","file":"foo.yml","mode":438} +{"l":"warn","t":1546234314,"m":"I am yellow"} +{"l":"panic","t":1546234314,"m":"I just want to panic","s":"main.go:28"} +{"l":"info","t":1546234314,"m":"recoveredI just want to panic"} +{"l":"debug","t":1546234314,"m":"I will sleep for a while"} +{"l":"fatal","t":1546234314,"m":"I am red","s":"main.go:33"} ```` \ No newline at end of file diff --git a/log/handlers/json/example/main.go b/log/handlers/json/example/main.go new file mode 100644 index 0000000..cfd1453 --- /dev/null +++ b/log/handlers/json/example/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "os" + "time" + + dlog "github.com/dyweb/gommon/log" + "github.com/dyweb/gommon/log/handlers/json" +) + +var log, logReg = dlog.NewApplicationLoggerAndRegistry("example") + +func main() { + dlog.SetHandler(logReg, json.New(os.Stderr)) + log.Info("hi") + log.Infof("open file %s", "foo.yml") + log.InfoF("open", + dlog.Str("file", "foo.yml"), + dlog.Int("mode", 0666), + ) + log.Warn("I am yellow") + func() { + defer func() { + if r := recover(); r != nil { + log.Info("recovered", r) + } + }() + log.Panic("I just want to panic") + }() + dlog.SetLevel(logReg, dlog.DebugLevel) + log.Debug("I will sleep for a while") + time.Sleep(500 * time.Millisecond) + log.Fatal("I am red") +} diff --git a/log/handlers/json/handler.go b/log/handlers/json/handler.go index bfbd93c..62555e9 100644 --- a/log/handlers/json/handler.go +++ b/log/handlers/json/handler.go @@ -1,7 +1,5 @@ -/* -Package json writes log in JSON format, it concatenates string directly and does not use encoding/json. -TODO: compare with standard json encoding -*/ +// Package json writes log in JSON format, it escapes string in json based encoding/json, +// It does not use encoding/json directly because all the fields have known type package json import ( @@ -14,19 +12,19 @@ import ( "github.com/dyweb/gommon/log" ) -var _ log.Handler = (*Handler)(nil) +var _ log.Handler = (*handler)(nil) -type Handler struct { +type handler struct { w io.Writer } -func New(w io.Writer) *Handler { - return &Handler{ +func New(w io.Writer) log.Handler { + return &handler{ w: w, } } -func (h *Handler) HandleLog(level log.Level, time time.Time, msg string, source log.Caller, context log.Fields, fields log.Fields) { +func (h *handler) HandleLog(level log.Level, time time.Time, msg string, source log.Caller, context log.Fields, fields log.Fields) { b := make([]byte, 0, 50+len(msg)+len(source.File)+30*len(context)+30*len(fields)) // level b = append(b, `{"l":"`...) @@ -36,7 +34,7 @@ func (h *Handler) HandleLog(level log.Level, time time.Time, msg string, source b = strconv.AppendInt(b, time.Unix(), 10) // message b = append(b, `,"m":`...) - b = encodeString(b, msg) + b = EncodeString(b, msg) // source if source.Line != 0 { b = append(b, `,"s":"`...) @@ -61,7 +59,7 @@ func (h *Handler) HandleLog(level log.Level, time time.Time, msg string, source h.w.Write(b) } -func (h *Handler) Flush() { +func (h *handler) Flush() { if s, ok := h.w.(log.Syncer); ok { s.Sync() } @@ -77,7 +75,7 @@ func formatFields(b []byte, fields log.Fields) []byte { case log.IntType: b = strconv.AppendInt(b, f.Int, 10) case log.StringType: - b = encodeString(b, f.Str) + b = EncodeString(b, f.Str) } b = append(b, ',') } @@ -85,9 +83,9 @@ func formatFields(b []byte, fields log.Fields) []byte { return b } -// encodeString escape character like " \n, it does not handle jsonp or html like standard library does -// it is based on encoding/json/encode.go func (e *encodeState) string(s string, escapeHTML bool) w/ some comment -func encodeString(buf []byte, s string) []byte { +// EncodeString escapes character like " \n, it does not handle jsonp or html like standard library does. +// It is based on encoding/json/encode.go func (e *encodeState) string(s string, escapeHTML bool) +func EncodeString(buf []byte, s string) []byte { buf = append(buf, '"') start := 0 for i := 0; i < len(s); { diff --git a/log/handlers/json/handler_test.go b/log/handlers/json/handler_test.go index 865ca31..3642a85 100644 --- a/log/handlers/json/handler_test.go +++ b/log/handlers/json/handler_test.go @@ -104,13 +104,13 @@ func TestEncodeString(t *testing.T) { // TODO: add assert here for _, s := range strs { var b []byte - b = encodeString(b, s) + b = EncodeString(b, s) t.Log(string(b)) } t.Run("no escape", func(t *testing.T) { var b []byte - b = encodeString(b, "nothing") + b = EncodeString(b, "nothing") t.Log(string(b)) }) } From dabe7e9213f9285eb59b4409b2354a6f815ab8a8 Mon Sep 17 00:00:00 2001 From: at15 Date: Sun, 30 Dec 2018 21:43:57 -0800 Subject: [PATCH 34/34] [test] Remove go1.10 and add goimports --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 96074f4..ab684f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,12 @@ language: go sudo: false go: - - "1.10" - "1.11" - tip install: - go get -u github.com/golang/dep/cmd/dep + - go get -u golang.org/x/tools/cmd/goimports - dep version - dep ensure