diff --git a/m/m.go b/m/m.go index 8ac5c7e..cd4d4ae 100644 --- a/m/m.go +++ b/m/m.go @@ -6,7 +6,6 @@ package m import ( "context" - "log" "os" "os/signal" @@ -43,9 +42,9 @@ func ServiceContext() context.Context { ctx, logLevelStr := mcfg.WithString(ctx, "log-level", "info", "Maximum log level which will be printed.") ctx = mrun.WithStartHook(ctx, func(context.Context) error { logLevel := mlog.LevelFromString(*logLevelStr) - log.Printf("setting log level to %v", logLevel) if logLevel == nil { - return merr.New(ctx, "invalid log level", "log-level", *logLevelStr) + ctx := mctx.Annotate(ctx, "log-level", *logLevelStr) + return merr.New("invalid log level", ctx) } logger.SetMaxLevel(logLevel) return nil diff --git a/mcfg/cli.go b/mcfg/cli.go index 807734a..766a8c7 100644 --- a/mcfg/cli.go +++ b/mcfg/cli.go @@ -90,7 +90,8 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) { break } if !pvOk { - return nil, merr.New(context.Background(), "unexpected config parameter", "param", arg) + ctx := mctx.Annotate(context.Background(), "param", arg) + return nil, merr.New("unexpected config parameter", ctx) } } @@ -121,7 +122,8 @@ func (cli SourceCLI) Parse(params []Param) ([]ParamValue, error) { pvStrValOk = false } if pvOk && !pvStrValOk { - return nil, merr.New(p.Context, "param expected a value", "param", key) + ctx := mctx.Annotate(p.Context, "param", key) + return nil, merr.New("param expected a value", ctx) } return pvs, nil } diff --git a/mcfg/env.go b/mcfg/env.go index 73dd92c..35cb970 100644 --- a/mcfg/env.go +++ b/mcfg/env.go @@ -59,7 +59,8 @@ func (env SourceEnv) Parse(params []Param) ([]ParamValue, error) { for _, kv := range kvs { split := strings.SplitN(kv, "=", 2) if len(split) != 2 { - return nil, merr.New(context.Background(), "malformed environment key/value pair", "kv", kv) + ctx := mctx.Annotate(context.Background(), "kv", kv) + return nil, merr.New("malformed environment key/value pair", ctx) } k, v := split[0], split[1] if p, ok := pM[k]; ok { diff --git a/mcfg/mcfg.go b/mcfg/mcfg.go index ff7321f..1c0ef5a 100644 --- a/mcfg/mcfg.go +++ b/mcfg/mcfg.go @@ -110,8 +110,9 @@ func populate(params []Param, src Source) error { if !p.Required { continue } else if _, ok := pvM[hash]; !ok { - return merr.New(p.Context, "required parameter is not set", + ctx := mctx.Annotate(p.Context, "param", paramFullName(mctx.Path(p.Context), p.Name)) + return merr.New("required parameter is not set", ctx) } } @@ -133,7 +134,7 @@ func populate(params []Param, src Source) error { // of the Params which were provided by the respective Source. // // Source may be nil to indicate that no configuration is provided. Only default -// values will be used, and if any paramaters are required this will error. +// values will be used, and if any parameters are required this will error. func Populate(ctx context.Context, src Source) error { return populate(collectParams(ctx), src) } diff --git a/mcrypto/pair.go b/mcrypto/pair.go index dd2e40c..5a8e7ac 100644 --- a/mcrypto/pair.go +++ b/mcrypto/pair.go @@ -14,6 +14,7 @@ import ( "math/big" "time" + "github.com/mediocregopher/mediocre-go-lib/mctx" "github.com/mediocregopher/mediocre-go-lib/merr" ) @@ -59,7 +60,8 @@ func (pk PublicKey) verify(s Signature, r io.Reader) error { return err } if err := rsa.VerifyPSS(&pk.PublicKey, crypto.SHA256, h.Sum(nil), s.sig, nil); err != nil { - return merr.Wrap(context.Background(), ErrInvalidSig, "sig", s) + ctx := mctx.Annotate(context.Background(), "sig", s) + return merr.Wrap(ErrInvalidSig, ctx) } return nil } @@ -89,12 +91,14 @@ func (pk *PublicKey) UnmarshalText(b []byte) error { str := string(b) strEnc, ok := stripPrefix(str, pubKeyV0) if !ok || len(strEnc) <= hex.EncodedLen(8) { - return merr.Wrap(context.Background(), errMalformedPublicKey, "pubKeyStr", str) + ctx := mctx.Annotate(context.Background(), "pubKeyStr", str) + return merr.Wrap(errMalformedPublicKey, ctx) } b, err := hex.DecodeString(strEnc) if err != nil { - return merr.Wrap(context.Background(), err, "pubKeyStr", str) + ctx := mctx.Annotate(context.Background(), "pubKeyStr", str) + return merr.Wrap(err, ctx) } pk.E = int(binary.BigEndian.Uint64(b)) @@ -185,17 +189,17 @@ func (pk *PrivateKey) UnmarshalText(b []byte) error { str := string(b) strEnc, ok := stripPrefix(str, privKeyV0) if !ok { - return merr.Wrap(context.Background(), errMalformedPrivateKey) + return merr.Wrap(errMalformedPrivateKey) } b, err := hex.DecodeString(strEnc) if err != nil { - return merr.Wrap(context.Background(), errMalformedPrivateKey) + return merr.Wrap(errMalformedPrivateKey) } e, n := binary.Uvarint(b) if n <= 0 { - return merr.Wrap(context.Background(), errMalformedPrivateKey) + return merr.Wrap(errMalformedPrivateKey) } pk.PublicKey.E = int(e) b = b[n:] @@ -206,7 +210,7 @@ func (pk *PrivateKey) UnmarshalText(b []byte) error { } l, n := binary.Uvarint(b) if n <= 0 { - err = merr.Wrap(context.Background(), errMalformedPrivateKey) + err = merr.Wrap(errMalformedPrivateKey) } b = b[n:] i := new(big.Int) diff --git a/mcrypto/secret.go b/mcrypto/secret.go index 76e3736..ba9bd12 100644 --- a/mcrypto/secret.go +++ b/mcrypto/secret.go @@ -1,7 +1,6 @@ package mcrypto import ( - "context" "crypto/hmac" "crypto/rand" "crypto/sha256" @@ -77,9 +76,9 @@ func (s Secret) sign(r io.Reader) (Signature, error) { func (s Secret) verify(sig Signature, r io.Reader) error { sigB, err := s.signRaw(r, uint8(len(sig.sig)), sig.salt, sig.t) if err != nil { - return merr.Wrap(context.Background(), err, "sig", sig) + return merr.Wrap(err) } else if !hmac.Equal(sigB, sig.sig) { - return merr.Wrap(context.Background(), ErrInvalidSig, "sig", sig) + return merr.Wrap(ErrInvalidSig) } return nil } diff --git a/mcrypto/sig.go b/mcrypto/sig.go index 8d0bdf9..adfb613 100644 --- a/mcrypto/sig.go +++ b/mcrypto/sig.go @@ -2,7 +2,6 @@ package mcrypto import ( "bytes" - "context" "encoding/binary" "encoding/hex" "encoding/json" @@ -67,12 +66,12 @@ func (s *Signature) UnmarshalText(b []byte) error { str := string(b) strEnc, ok := stripPrefix(str, sigV0) if !ok || len(strEnc) < hex.EncodedLen(10) { - return merr.Wrap(context.Background(), errMalformedSig, "sigStr", str) + return merr.Wrap(errMalformedSig) } b, err := hex.DecodeString(strEnc) if err != nil { - return merr.Wrap(context.Background(), err, "sigStr", str) + return merr.Wrap(err) } unixNano, b := int64(binary.BigEndian.Uint64(b[:8])), b[8:] @@ -82,7 +81,7 @@ func (s *Signature) UnmarshalText(b []byte) error { if err != nil { return nil } else if len(b) < 1+int(b[0]) { - err = merr.Wrap(context.Background(), errMalformedSig, "sigStr", str) + err = merr.Wrap(errMalformedSig) return nil } out := b[1 : 1+b[0]] diff --git a/mcrypto/uuid.go b/mcrypto/uuid.go index ebe3a94..e319bde 100644 --- a/mcrypto/uuid.go +++ b/mcrypto/uuid.go @@ -10,6 +10,7 @@ import ( "errors" "time" + "github.com/mediocregopher/mediocre-go-lib/mctx" "github.com/mediocregopher/mediocre-go-lib/merr" ) @@ -69,11 +70,13 @@ func (u *UUID) UnmarshalText(b []byte) error { str := string(b) strEnc, ok := stripPrefix(str, uuidV0) if !ok || len(strEnc) != hex.EncodedLen(16) { - return merr.Wrap(context.Background(), errMalformedUUID, "uuidStr", str) + ctx := mctx.Annotate(context.Background(), "uuidStr", str) + return merr.Wrap(errMalformedUUID, ctx) } b, err := hex.DecodeString(strEnc) if err != nil { - return merr.Wrap(context.Background(), err, "uuidStr", str) + ctx := mctx.Annotate(context.Background(), "uuidStr", str) + return merr.Wrap(errMalformedUUID, ctx) } u.b = b return nil diff --git a/mctx/annotate.go b/mctx/annotate.go index 8cad9df..1547565 100644 --- a/mctx/annotate.go +++ b/mctx/annotate.go @@ -278,9 +278,14 @@ func mergeAnnotations(ctxA, ctxB context.Context) context.Context { // // NOTE this will panic if no Contexts are passed in. func MergeAnnotations(ctxs ...context.Context) context.Context { - ctxA := ctxs[0] - for _, ctxB := range ctxs[1:] { - ctxA = mergeAnnotations(ctxA, ctxB) - } - return ctxA + return MergeAnnotationsInto(ctxs[0], ctxs[1:]...) +} + +// MergeAnnotationsInto is a convenience function which works like +// MergeAnnotations. +func MergeAnnotationsInto(ctx context.Context, ctxs ...context.Context) context.Context { + for _, ctxB := range ctxs { + ctx = mergeAnnotations(ctx, ctxB) + } + return ctx } diff --git a/mdb/mbigquery/bigquery.go b/mdb/mbigquery/bigquery.go index 64ad3b4..4acabb6 100644 --- a/mdb/mbigquery/bigquery.go +++ b/mdb/mbigquery/bigquery.go @@ -66,7 +66,7 @@ func WithBigQuery(parent context.Context, gce *mdb.GCE) (context.Context, *BigQu mlog.Info("connecting to bigquery", bq.ctx) var err error bq.Client, err = bigquery.NewClient(innerCtx, bq.gce.Project, bq.gce.ClientOptions()...) - return merr.Wrap(bq.ctx, err) + return merr.Wrap(err, bq.ctx) }) ctx = mrun.WithStopHook(ctx, func(context.Context) error { return bq.Client.Close() @@ -100,12 +100,12 @@ func (bq *BigQuery) Table( mlog.Debug("creating/grabbing table", bq.ctx) schema, err := bigquery.InferSchema(schemaObj) if err != nil { - return nil, nil, merr.Wrap(ctx, err) + return nil, nil, merr.Wrap(err, ctx) } ds := bq.Dataset(dataset) if err := ds.Create(ctx, nil); err != nil && !isErrAlreadyExists(err) { - return nil, nil, merr.Wrap(ctx, err) + return nil, nil, merr.Wrap(err, ctx) } table := ds.Table(tableName) @@ -114,7 +114,7 @@ func (bq *BigQuery) Table( Schema: schema, } if err := table.Create(ctx, meta); err != nil && !isErrAlreadyExists(err) { - return nil, nil, merr.Wrap(ctx, err) + return nil, nil, merr.Wrap(err, ctx) } uploader := table.Uploader() diff --git a/mdb/mbigtable/bigtable.go b/mdb/mbigtable/bigtable.go index c858ca1..5e5c3cc 100644 --- a/mdb/mbigtable/bigtable.go +++ b/mdb/mbigtable/bigtable.go @@ -74,7 +74,7 @@ func WithBigTable(parent context.Context, gce *mdb.GCE, defaultInstance string) bt.gce.Project, bt.Instance, bt.gce.ClientOptions()..., ) - return merr.Wrap(bt.ctx, err) + return merr.Wrap(err, bt.ctx) }) ctx = mrun.WithStopHook(ctx, func(context.Context) error { return bt.Client.Close() @@ -95,14 +95,14 @@ func (bt *Bigtable) EnsureTable(ctx context.Context, name string, colFams ...str mlog.Debug("creating admin client", ctx) adminClient, err := bigtable.NewAdminClient(ctx, bt.gce.Project, bt.Instance) if err != nil { - return merr.Wrap(ctx, err) + return merr.Wrap(err, ctx) } defer adminClient.Close() mlog.Debug("creating bigtable table (if needed)", ctx) err = adminClient.CreateTable(ctx, name) if err != nil && !isErrAlreadyExists(err) { - return merr.Wrap(ctx, err) + return merr.Wrap(err, ctx) } for _, colFam := range colFams { @@ -110,7 +110,7 @@ func (bt *Bigtable) EnsureTable(ctx context.Context, name string, colFams ...str mlog.Debug("creating bigtable column family (if needed)", ctx) err := adminClient.CreateColumnFamily(ctx, name, colFam) if err != nil && !isErrAlreadyExists(err) { - return merr.Wrap(ctx, err) + return merr.Wrap(err, ctx) } } diff --git a/mdb/mdatastore/datastore.go b/mdb/mdatastore/datastore.go index 1a74e9f..902efd7 100644 --- a/mdb/mdatastore/datastore.go +++ b/mdb/mdatastore/datastore.go @@ -44,7 +44,7 @@ func WithDatastore(parent context.Context, gce *mdb.GCE) (context.Context, *Data mlog.Info("connecting to datastore", ds.ctx) var err error ds.Client, err = datastore.NewClient(innerCtx, ds.gce.Project, ds.gce.ClientOptions()...) - return merr.Wrap(ds.ctx, err) + return merr.Wrap(err, ds.ctx) }) ctx = mrun.WithStopHook(ctx, func(context.Context) error { return ds.Client.Close() diff --git a/mdb/mpubsub/pubsub.go b/mdb/mpubsub/pubsub.go index c63fefc..fa8efb8 100644 --- a/mdb/mpubsub/pubsub.go +++ b/mdb/mpubsub/pubsub.go @@ -62,7 +62,7 @@ func WithPubSub(parent context.Context, gce *mdb.GCE) (context.Context, *PubSub) mlog.Info("connecting to pubsub", ps.ctx) var err error ps.Client, err = pubsub.NewClient(innerCtx, ps.gce.Project, ps.gce.ClientOptions()...) - return merr.Wrap(ps.ctx, err) + return merr.Wrap(err, ps.ctx) }) ctx = mrun.WithStopHook(ctx, func(context.Context) error { return ps.Client.Close() @@ -93,14 +93,14 @@ func (ps *PubSub) Topic(ctx context.Context, name string, create bool) (*Topic, if isErrAlreadyExists(err) { t.topic = ps.Client.Topic(name) } else if err != nil { - return nil, merr.Wrap(ctx, merr.Wrap(t.ctx, err)) + return nil, merr.Wrap(err, t.ctx, ctx) } } else { t.topic = ps.Client.Topic(name) if exists, err := t.topic.Exists(t.ctx); err != nil { - return nil, merr.Wrap(ctx, merr.Wrap(t.ctx, err)) + return nil, merr.Wrap(err, t.ctx, ctx) } else if !exists { - return nil, merr.Wrap(ctx, merr.New(t.ctx, "topic dne")) + return nil, merr.New("topic dne", t.ctx, ctx) } } return t, nil @@ -110,7 +110,7 @@ func (ps *PubSub) Topic(ctx context.Context, name string, create bool) (*Topic, func (t *Topic) Publish(ctx context.Context, data []byte) error { _, err := t.topic.Publish(ctx, &Message{Data: data}).Get(ctx) if err != nil { - return merr.Wrap(ctx, merr.Wrap(t.ctx, err)) + return merr.Wrap(err, t.ctx, ctx) } return nil } @@ -144,14 +144,14 @@ func (t *Topic) Subscription(ctx context.Context, name string, create bool) (*Su if isErrAlreadyExists(err) { s.sub = t.ps.Subscription(name) } else if err != nil { - return nil, merr.Wrap(ctx, merr.Wrap(s.ctx, err)) + return nil, merr.Wrap(err, s.ctx, ctx) } } else { s.sub = t.ps.Subscription(name) if exists, err := s.sub.Exists(ctx); err != nil { - return nil, merr.Wrap(ctx, merr.Wrap(s.ctx, err)) + return nil, merr.Wrap(err, s.ctx, ctx) } else if !exists { - return nil, merr.Wrap(ctx, merr.New(s.ctx, "sub dne")) + return nil, merr.New("sub dne", s.ctx, ctx) } } return s, nil @@ -350,7 +350,7 @@ func (s *Subscription) BatchConsume( case ret := <-retCh: return ret, nil case <-ctx.Done(): - return false, merr.Wrap(ctx, merr.New(s.ctx, "reading from batch grouping process timed out")) + return false, merr.New("reading from batch grouping process timed out", s.ctx, ctx) } }, opts) diff --git a/mdb/msql/sql.go b/mdb/msql/sql.go index d23cbf8..d7accf0 100644 --- a/mdb/msql/sql.go +++ b/mdb/msql/sql.go @@ -46,7 +46,7 @@ func WithMySQL(parent context.Context, defaultDB string) (context.Context, *SQL) mlog.Info("connecting to mysql server", sql.ctx) var err error sql.DB, err = sqlx.ConnectContext(innerCtx, "mysql", dsn) - return merr.Wrap(sql.ctx, err) + return merr.Wrap(err, sql.ctx) }) ctx = mrun.WithStopHook(ctx, func(innerCtx context.Context) error { mlog.Info("closing connection to sql server", sql.ctx) diff --git a/merr/merr.go b/merr/merr.go index 9d59282..e11bf8d 100644 --- a/merr/merr.go +++ b/merr/merr.go @@ -57,7 +57,7 @@ func wrap(e error, cp bool) *err { er2 := &err{ err: er.err, - attr: make(map[interface{}]interface{}, len(er.attr)), + attr: make(map[interface{}]interface{}, len(er.attr)+1), } for k, v := range er.attr { er2.attr[k] = v @@ -102,47 +102,47 @@ func Value(e error, k interface{}) interface{} { // WrapSkip is like Wrap but also allows for skipping extra stack frames when // embedding the stack into the error. -func WrapSkip(ctx context.Context, e error, skip int, kvs ...interface{}) error { +func WrapSkip(e error, skip int, ctxs ...context.Context) error { + if e == nil { + return nil + } + er := wrap(e, true) if _, ok := getStack(er); !ok { setStack(er, skip+1) } - if prevCtx, _ := er.attr[attrKeyCtx].(context.Context); prevCtx != nil { - ctx = mctx.MergeAnnotations(prevCtx, ctx) - } - - if len(kvs) > 0 { - ctx = mctx.Annotate(ctx, kvs...) + ctx, _ := er.attr[attrKeyCtx].(context.Context) + if ctx != nil { + ctx = mctx.MergeAnnotationsInto(ctx, ctxs...) + } else if len(ctxs) > 0 { + ctx = mctx.MergeAnnotations(ctxs...) } er.attr[attrKeyCtx] = ctx return er } -// Wrap takes in an error and returns one which wraps it in merr's inner type, -// embedding the given Context (which can later be retrieved by Ctx) at the same -// time. -// -// For convenience, extra annotation information can be passed in here as well -// via the kvs argument. See mctx.Annotate for more information. +// Wrap returns a copy of the given error wrapped in merr's inner type. It will +// perform an mctx.MergeAnnotations on the given Contexts to create a new +// Context, and embed that in the returned error. If the given error already has +// an embedded Context then ctxs will be merged into that. // // This function automatically embeds stack information into the error as it's // being stored, using WithStack, unless the error already has stack information // in it. -func Wrap(ctx context.Context, e error, kvs ...interface{}) error { - return WrapSkip(ctx, e, 1, kvs...) +// +// Wrapping nil returns nil. +func Wrap(e error, ctx ...context.Context) error { + return WrapSkip(e, 1, ctx...) } // New is a shortcut for: -// merr.Wrap(ctx, errors.New(str), kvs...) -func New(ctx context.Context, str string, kvs ...interface{}) error { - return WrapSkip(ctx, errors.New(str), 1, kvs...) +// merr.Wrap(errors.New(str), ctxs...) +func New(str string, ctxs ...context.Context) error { + return WrapSkip(errors.New(str), 1, ctxs...) } -// TODO it would be more convenient in a lot of cases if New and Wrap took in a -// list of Contexts. - type annotateKey string func ctx(e error) context.Context { diff --git a/merr/merr_test.go b/merr/merr_test.go index 84f9abb..b64d315 100644 --- a/merr/merr_test.go +++ b/merr/merr_test.go @@ -10,24 +10,41 @@ import ( ) func TestError(t *T) { - e := New(context.Background(), "foo", + massert.Fatal(t, massert.Nil(Wrap(nil))) + + ctx := mctx.Annotate(context.Background(), "a", "aaa aaa\n", "c", "ccc\nccc\n", - "d\t", "weird key but ok", - ) - exp := `foo + "d\t", "weird key but ok") + + { + e := New("foo", ctx) + exp := `foo * a: aaa aaa * c: ccc ccc * d: weird key but ok - * errLoc: merr/merr_test.go:13` - massert.Fatal(t, massert.Equal(exp, e.Error())) + * errLoc: merr/merr_test.go:19` + massert.Fatal(t, massert.Equal(exp, e.Error())) + } + + { + e := Wrap(errors.New("foo"), ctx) + exp := `foo + * a: aaa aaa + * c: + ccc + ccc + * d: weird key but ok + * errLoc: merr/merr_test.go:31` + massert.Fatal(t, massert.Equal(exp, e.Error())) + } } func TestBase(t *T) { errFoo, errBar := errors.New("foo"), errors.New("bar") - erFoo := Wrap(context.Background(), errFoo) + erFoo := Wrap(errFoo) massert.Fatal(t, massert.All( massert.Nil(Base(nil)), massert.Equal(errFoo, Base(erFoo)), @@ -45,7 +62,7 @@ func TestValue(t *T) { massert.Nil(Value(nil, "foo")), )) - e1 := New(context.Background(), "foo") + e1 := New("foo") e1 = WithValue(e1, "a", "A") e2 := WithValue(errors.New("bar"), "a", "A") massert.Fatal(t, massert.All( @@ -62,7 +79,7 @@ func TestValue(t *T) { } func mkErr(ctx context.Context, err error) error { - return Wrap(ctx, err) // it's important that this is line 65 + return Wrap(err, ctx) } func TestCtx(t *T) { @@ -72,14 +89,14 @@ func TestCtx(t *T) { // use mkErr so that it's easy to test that the stack info isn't overwritten // when Wrap is called with ctxB. e := mkErr(ctxA, errors.New("hello")) - e = Wrap(ctxB, e) + e = Wrap(e, ctxB) err := massert.Equal(map[string]string{ "0": "ZERO", "1": "ONE", "2": "TWO", "err": "hello", - "errLoc": "merr/merr_test.go:65", + "errLoc": "merr/merr_test.go:80", }, mctx.Annotations(Context(e)).StringMap()).Assert() if err != nil { t.Fatal(err) diff --git a/merr/stack_test.go b/merr/stack_test.go index 5a27138..e17ac03 100644 --- a/merr/stack_test.go +++ b/merr/stack_test.go @@ -1,7 +1,6 @@ package merr import ( - "context" "strings" . "testing" @@ -9,7 +8,7 @@ import ( ) func TestStack(t *T) { - foo := New(context.Background(), "test") + foo := New("test") fooStack, ok := Stack(foo) massert.Fatal(t, massert.Equal(true, ok))