go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/errors/annotate.go (about)

     1  // Copyright 2016 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package errors
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"path/filepath"
    24  	"runtime"
    25  	"sort"
    26  	"strings"
    27  
    28  	"go.chromium.org/luci/common/data/stringset"
    29  	"go.chromium.org/luci/common/data/text/indented"
    30  	"go.chromium.org/luci/common/iotools"
    31  	"go.chromium.org/luci/common/logging"
    32  	"go.chromium.org/luci/common/runtime/goroutine"
    33  )
    34  
    35  const (
    36  	// renderedStackDumpFormat is used as the prefix format when renderedStack is dumped to string.
    37  	renderedStackDumpFormat = "goroutine %d:\n"
    38  	// RenderedStackDumpRe is coupled with renderedStackDumpFormat to indicate its regex expression.
    39  	RenderedStackDumpRe = `goroutine \d+:\n`
    40  )
    41  
    42  type stack struct {
    43  	id     goroutine.ID
    44  	frames []uintptr
    45  }
    46  
    47  func (s *stack) findPointOfDivergence(other *stack) int {
    48  	// TODO(iannucci): can we optimize this search routine to not overly penalize
    49  	// tail-recursive functions? Searching 'up' from both stacks doesn't work in
    50  	// the face of recursion because there will be multiple ambiguous stack
    51  	// frames. The algorithm below is correct, but it potentially collects and
    52  	// then throws away a bunch of data (e.g. if we could walk up from the current
    53  	// stack and then find the intersection in the captured stack, we could stop
    54  	// walking at that point instead of walking all the way back from the root on
    55  	// every Annotate call.
    56  	if s.id != other.id {
    57  		panic(fmt.Errorf(
    58  			"finding intersection between unrelated stacks: %d v %d", s.id, other.id))
    59  	}
    60  
    61  	myIdx := len(s.frames) - 1
    62  	oIdx := len(other.frames) - 1
    63  
    64  	for s.frames[myIdx] == other.frames[oIdx] {
    65  		myIdx--
    66  		oIdx--
    67  	}
    68  
    69  	return myIdx
    70  }
    71  
    72  // stackContexter is the interface that an error may implement if it has data
    73  // associated with a specific stack frame.
    74  type stackContexter interface {
    75  	stackContext() stackContext
    76  }
    77  
    78  // stackFrameInfo holds a stack and an index into that stack for association
    79  // with stackContexts.
    80  type stackFrameInfo struct {
    81  	frameIdx int
    82  	forStack *stack
    83  }
    84  
    85  // stackContext represents the annotation data associated with an error, or an
    86  // annotation of an error.
    87  type stackContext struct {
    88  	frameInfo stackFrameInfo
    89  	// publicly-facing reason, and will show up in the Error() string.
    90  	reason string
    91  
    92  	// used for printing tracebacks, but will not show up in the Error() string.
    93  	internalReason string
    94  
    95  	// tags are any data associated with this frame.
    96  	tags map[TagKey]any
    97  }
    98  
    99  // renderPublic renders the public error.Error()-style string for this frame,
   100  // combining this frame's Reason with the inner error.
   101  func (s *stackContext) renderPublic(inner error) string {
   102  	switch {
   103  	case inner == nil:
   104  		return s.reason
   105  	case s.reason == "":
   106  		return inner.Error()
   107  	}
   108  	return fmt.Sprintf("%s: %s", s.reason, inner.Error())
   109  }
   110  
   111  // render renders the frame as a single entry in a stack trace. This looks like:
   112  //
   113  //	reason: "The literal content of the reason field: %(key2)d"
   114  //	internal reason: I am an internal reason formatted with key1: value
   115  func (s *stackContext) render() lines {
   116  	siz := len(s.tags)
   117  	if s.internalReason != "" {
   118  		siz++
   119  	}
   120  	if s.reason != "" {
   121  		siz++
   122  	}
   123  	if siz == 0 {
   124  		return nil
   125  	}
   126  
   127  	ret := make(lines, 0, siz)
   128  	if s.reason != "" {
   129  		ret = append(ret, fmt.Sprintf("reason: %s", s.reason))
   130  	}
   131  	if s.internalReason != "" {
   132  		ret = append(ret, fmt.Sprintf("internal reason: %s", s.internalReason))
   133  	}
   134  	keys := make(tagKeySlice, 0, len(s.tags))
   135  	for key := range s.tags {
   136  		keys = append(keys, key)
   137  	}
   138  	sort.Sort(keys)
   139  	for _, key := range keys {
   140  		if key != nil {
   141  			ret = append(ret, fmt.Sprintf("tag[%q]: %#v", key.description, s.tags[key]))
   142  		} else {
   143  			ret = append(ret, fmt.Sprintf("tag[nil]: %#v", s.tags[key]))
   144  		}
   145  	}
   146  
   147  	return ret
   148  }
   149  
   150  type terminalStackError struct {
   151  	error
   152  	finfo stackFrameInfo
   153  	tags  map[TagKey]any
   154  }
   155  
   156  var _ interface {
   157  	error
   158  	stackContexter
   159  } = (*terminalStackError)(nil)
   160  
   161  func (e *terminalStackError) stackContext() stackContext {
   162  	return stackContext{frameInfo: e.finfo, tags: e.tags}
   163  }
   164  
   165  type annotatedError struct {
   166  	inner error
   167  	ctx   stackContext
   168  }
   169  
   170  var _ interface {
   171  	error
   172  	stackContexter
   173  	Wrapped
   174  } = (*annotatedError)(nil)
   175  
   176  func (e *annotatedError) Error() string              { return e.ctx.renderPublic(e.inner) }
   177  func (e *annotatedError) stackContext() stackContext { return e.ctx }
   178  func (e *annotatedError) Unwrap() error              { return e.inner }
   179  
   180  // Annotator is a builder for annotating errors. Obtain one by calling Annotate
   181  // on an existing error or using Reason.
   182  //
   183  // See the example test for Annotate to see how this is meant to be used.
   184  type Annotator struct {
   185  	inner error
   186  	ctx   stackContext
   187  }
   188  
   189  // InternalReason adds a stack-trace-only internal reason string (for humans) to
   190  // this error.
   191  //
   192  // The text here will only be visible when using `errors.Log` or
   193  // `errors.RenderStack`, not when calling the .Error() method of the resulting
   194  // error.
   195  //
   196  // The `reason` string is formatted with `args` and may contain Sprintf-style
   197  // formatting directives.
   198  func (a *Annotator) InternalReason(reason string, args ...any) *Annotator {
   199  	if a == nil {
   200  		return a
   201  	}
   202  	a.ctx.internalReason = fmt.Sprintf(reason, args...)
   203  	return a
   204  }
   205  
   206  // Tag adds a tag with an optional value to this error.
   207  //
   208  // `value` is a unary optional argument, and must be a simple type (i.e. has
   209  // a reflect.Kind which is a base data type like bool, string, or int).
   210  func (a *Annotator) Tag(tags ...TagValueGenerator) *Annotator {
   211  	if a == nil {
   212  		return a
   213  	}
   214  	tagMap := make(map[TagKey]any, len(tags))
   215  	for _, t := range tags {
   216  		v := t.GenerateErrorTagValue()
   217  		tagMap[v.Key] = v.Value
   218  	}
   219  	if len(tagMap) > 0 {
   220  		if a.ctx.tags == nil {
   221  			a.ctx.tags = tagMap
   222  		} else {
   223  			for k, v := range tagMap {
   224  				a.ctx.tags[k] = v
   225  			}
   226  		}
   227  	}
   228  	return a
   229  }
   230  
   231  // Err returns the finalized annotated error.
   232  //
   233  //go:noinline
   234  func (a *Annotator) Err() error {
   235  	if a == nil {
   236  		return nil
   237  	}
   238  	return (*annotatedError)(a)
   239  }
   240  
   241  // maxLogEntrySize limits log messages produced by Log.
   242  //
   243  // It is modified in tests of this package.
   244  var maxLogEntrySize = 64 * 1024
   245  
   246  // Log logs the full error. If this is an Annotated error, it will log the full
   247  // stack information as well.
   248  //
   249  // This is a shortcut for logging the output of RenderStack(err).
   250  //
   251  // If resulting log message is large, splits it into log entries of at most
   252  // 64KiB.
   253  func Log(ctx context.Context, err error, excludePkgs ...string) {
   254  	log := logging.Get(ctx)
   255  	r := renderStack(err)
   256  	buf := strings.Builder{}
   257  	_, _ = r.dumpTo(&buf, excludePkgs...) // no errors can happen, only panics.
   258  	s := strings.TrimSuffix(buf.String(), "\n")
   259  
   260  	preamble := ""
   261  	for len(s) > 0 {
   262  		chunk := s
   263  		if maxLen := maxLogEntrySize - len(preamble); maxLen < len(chunk) {
   264  			// Make chunk end with '\n' and be smaller than maxLen.
   265  			chunk = strings.TrimRightFunc(chunk[:maxLen], func(r rune) bool { return r != '\n' })
   266  			if len(chunk) == 0 {
   267  				// Chunk is 1 very long line, can't be nice about splitting it.
   268  				chunk = s[:maxLen]
   269  			}
   270  		}
   271  		s = s[len(chunk):]
   272  		chunk = strings.TrimSuffix(chunk, "\n")
   273  		log.Errorf("%s%s", preamble, chunk)
   274  		preamble = "(continuation of error log)\n"
   275  	}
   276  }
   277  
   278  // lines is just a list of printable lines.
   279  //
   280  // It's a type because it's most frequently used as []lines, and [][]string
   281  // doesn't read well.
   282  type lines []string
   283  
   284  // renderedFrame represents a single, rendered stack frame.
   285  type renderedFrame struct {
   286  	pkg      string
   287  	file     string
   288  	lineNum  int
   289  	funcName string
   290  
   291  	// wrappers is any frame-info-less errors.Wrapped that were encountered when
   292  	// rendering that didn't have any associated frame info: this is the closest
   293  	// frame to where they were added to the error.
   294  	wrappers []lines
   295  
   296  	// annotations is any Annotate context associated directly with this Frame.
   297  	annotations []lines
   298  }
   299  
   300  var nlSlice = []byte{'\n'}
   301  
   302  // dumpWrappersTo formats the wrappers portion of this renderedFrame.
   303  func (r *renderedFrame) dumpWrappersTo(w io.Writer, from, to int) (n int, err error) {
   304  	return iotools.WriteTracker(w, func(rawWriter io.Writer) error {
   305  		w := &indented.Writer{Writer: rawWriter, UseSpaces: true}
   306  		fmt.Fprintf(w, "From frame %d to %d, the following wrappers were found:\n", from, to)
   307  		for i, wrp := range r.wrappers {
   308  			if i != 0 {
   309  				w.Write(nlSlice)
   310  			}
   311  			w.Level = 2
   312  			for i, line := range wrp {
   313  				if i == 0 {
   314  					fmt.Fprintf(w, "%s\n", line)
   315  					w.Level += 2
   316  				} else {
   317  					fmt.Fprintf(w, "%s\n", line)
   318  				}
   319  			}
   320  		}
   321  		return nil
   322  	})
   323  }
   324  
   325  // dumpTo formats the Header and annotations for this renderedFrame.
   326  func (r *renderedFrame) dumpTo(w io.Writer, idx int) (n int, err error) {
   327  	return iotools.WriteTracker(w, func(rawWriter io.Writer) error {
   328  		w := &indented.Writer{Writer: rawWriter, UseSpaces: true}
   329  
   330  		fmt.Fprintf(w, "#%d %s/%s:%d - %s()\n", idx, r.pkg, r.file,
   331  			r.lineNum, r.funcName)
   332  		w.Level += 2
   333  		switch len(r.annotations) {
   334  		case 0:
   335  			// pass
   336  		case 1:
   337  			for _, line := range r.annotations[0] {
   338  				fmt.Fprintf(w, "%s\n", line)
   339  			}
   340  		default:
   341  			for i, ann := range r.annotations {
   342  				fmt.Fprintf(w, "annotation #%d:\n", i)
   343  				w.Level += 2
   344  				for _, line := range ann {
   345  					fmt.Fprintf(w, "%s\n", line)
   346  				}
   347  				w.Level -= 2
   348  			}
   349  		}
   350  		return nil
   351  	})
   352  }
   353  
   354  // renderedStack is a single rendered stack from one goroutine.
   355  type renderedStack struct {
   356  	goID   goroutine.ID
   357  	frames []*renderedFrame
   358  }
   359  
   360  // dumpTo formats the full stack.
   361  func (r *renderedStack) dumpTo(w io.Writer, excludePkgs ...string) (n int, err error) {
   362  	excludeSet := stringset.NewFromSlice(excludePkgs...)
   363  
   364  	return iotools.WriteTracker(w, func(w io.Writer) error {
   365  		fmt.Fprintf(w, renderedStackDumpFormat, r.goID)
   366  
   367  		lastIdx := 0
   368  		needNL := false
   369  		skipCount := 0
   370  		skipPkg := ""
   371  		flushSkips := func(extra string) {
   372  			if skipCount != 0 {
   373  				if needNL {
   374  					w.Write(nlSlice)
   375  					needNL = false
   376  				}
   377  				fmt.Fprintf(w, "... skipped %d frames in pkg %q...\n%s", skipCount, skipPkg, extra)
   378  				skipCount = 0
   379  				skipPkg = ""
   380  			}
   381  		}
   382  		for i, f := range r.frames {
   383  			if needNL {
   384  				w.Write(nlSlice)
   385  				needNL = false
   386  			}
   387  			pkg := dropVersionSuffix(f.pkg)
   388  			if excludeSet.Has(pkg) {
   389  				if skipPkg == pkg {
   390  					skipCount++
   391  				} else {
   392  					flushSkips("")
   393  					skipCount++
   394  					skipPkg = pkg
   395  				}
   396  				continue
   397  			}
   398  			flushSkips("\n")
   399  			if len(f.wrappers) > 0 {
   400  				f.dumpWrappersTo(w, lastIdx, i)
   401  				w.Write(nlSlice)
   402  			}
   403  			if len(f.annotations) > 0 {
   404  				lastIdx = i
   405  				needNL = true
   406  			}
   407  			f.dumpTo(w, i)
   408  		}
   409  		flushSkips("")
   410  
   411  		return nil
   412  	})
   413  }
   414  
   415  // dropVersionSuffix takes "a/b@zzz" and returns "a/b".
   416  func dropVersionSuffix(pkg string) string {
   417  	if i := strings.LastIndexAny(pkg, "@"); i != -1 {
   418  		return pkg[:i]
   419  	}
   420  	return pkg
   421  }
   422  
   423  // renderedError is a series of RenderedStacks, one for each goroutine that the
   424  // error was annotated on.
   425  type renderedError struct {
   426  	// if originalError is set and `stacks` is empty, this was just a plain-old Go
   427  	// error without any stack context.
   428  	originalError string
   429  	stacks        []*renderedStack
   430  }
   431  
   432  // toLines renders a full-information stack trace as a series of lines.
   433  func (r *renderedError) toLines(excludePkgs ...string) lines {
   434  	if r.originalError != "" && len(r.stacks) == 0 {
   435  		return []string{r.originalError}
   436  	}
   437  
   438  	buf := bytes.Buffer{}
   439  	r.dumpTo(&buf, excludePkgs...)
   440  	return strings.Split(strings.TrimSuffix(buf.String(), "\n"), "\n")
   441  }
   442  
   443  // dumpTo writes the full-information stack trace to the writer.
   444  func (r *renderedError) dumpTo(w io.Writer, excludePkgs ...string) (n int, err error) {
   445  	if r.originalError != "" && len(r.stacks) == 0 {
   446  		return w.Write([]byte(r.originalError))
   447  	}
   448  
   449  	return iotools.WriteTracker(w, func(w io.Writer) error {
   450  		if r.originalError != "" {
   451  			fmt.Fprintf(w, "original error: %s\n\n", r.originalError)
   452  		}
   453  
   454  		for i := len(r.stacks) - 1; i >= 0; i-- {
   455  			if i != len(r.stacks)-1 {
   456  				w.Write(nlSlice)
   457  			}
   458  			r.stacks[i].dumpTo(w, excludePkgs...)
   459  		}
   460  		return nil
   461  	})
   462  }
   463  
   464  func frameHeaderDetails(frm uintptr) (pkg, filename, funcName string, lineno int) {
   465  	// this `frm--` is to get the correct line/function information, since the
   466  	// Frame is actually the `return` pc. See runtime.Callers.
   467  	frm--
   468  
   469  	fn := runtime.FuncForPC(frm)
   470  	file, lineno := fn.FileLine(frm)
   471  
   472  	var dirpath string
   473  	dirpath, filename = filepath.Split(file)
   474  	pkgTopLevelName := filepath.Base(dirpath)
   475  
   476  	fnName := fn.Name()
   477  	lastSlash := strings.LastIndex(fnName, "/")
   478  	if lastSlash == -1 {
   479  		funcName = fnName
   480  		pkg = pkgTopLevelName
   481  	} else {
   482  		funcName = fnName[lastSlash+1:]
   483  		pkg = fmt.Sprintf("%s/%s", fnName[:lastSlash], pkgTopLevelName)
   484  	}
   485  	return
   486  }
   487  
   488  // RenderStack renders the error to a list of lines.
   489  func RenderStack(err error, excludePkgs ...string) []string {
   490  	return renderStack(err).toLines(excludePkgs...)
   491  }
   492  
   493  func renderStack(err error) *renderedError {
   494  	ret := &renderedError{}
   495  
   496  	lastAnnotatedFrame := 0
   497  	var wrappers []lines
   498  	getCurFrame := func(fi *stackFrameInfo) *renderedFrame {
   499  		if len(ret.stacks) == 0 || ret.stacks[len(ret.stacks)-1].goID != fi.forStack.id {
   500  			lastAnnotatedFrame = len(fi.forStack.frames) - 1
   501  			toAdd := &renderedStack{
   502  				goID:   fi.forStack.id,
   503  				frames: make([]*renderedFrame, len(fi.forStack.frames)),
   504  			}
   505  			for i, frm := range fi.forStack.frames {
   506  				pkgPath, filename, functionName, line := frameHeaderDetails(frm)
   507  				toAdd.frames[i] = &renderedFrame{
   508  					pkg: pkgPath, file: filename, lineNum: line, funcName: functionName}
   509  			}
   510  			ret.stacks = append(ret.stacks, toAdd)
   511  		}
   512  		curStack := ret.stacks[len(ret.stacks)-1]
   513  
   514  		if fi.frameIdx < lastAnnotatedFrame {
   515  			lastAnnotatedFrame = fi.frameIdx
   516  			frm := curStack.frames[lastAnnotatedFrame]
   517  			frm.wrappers = wrappers
   518  			wrappers = nil
   519  			return frm
   520  		}
   521  		return curStack.frames[lastAnnotatedFrame]
   522  	}
   523  
   524  	// originalError will hold a pointer in the error tree of the last candidate
   525  	// error we encountered.
   526  	originalError := err
   527  
   528  	for err != nil {
   529  		// we attempt to walk the error; we expect to see a mix of stackContexters and
   530  		// non-stackContexters (i.e. regular Go errors, which may wrap other errors,
   531  		// possibly even stackContexter errors). When we hit a stackContexter, we need
   532  		// to select the error it contains as the next originalError, so we use this
   533  		// boolean to keep track.
   534  		needNewOriginalError := false
   535  
   536  		if sc, ok := err.(stackContexter); ok {
   537  			needNewOriginalError = true
   538  			ctx := sc.stackContext()
   539  			if stk := ctx.frameInfo.forStack; stk != nil {
   540  				frm := getCurFrame(&ctx.frameInfo)
   541  				if rendered := ctx.render(); len(rendered) > 0 {
   542  					frm.annotations = append(frm.annotations, rendered)
   543  				}
   544  			} else {
   545  				wrappers = append(wrappers, ctx.render())
   546  			}
   547  		} else {
   548  			wrappers = append(wrappers, lines{fmt.Sprintf("unknown wrapper %T", err)})
   549  		}
   550  
   551  		var inner error
   552  		switch x := err.(type) {
   553  		case MultiError:
   554  			// TODO(riannucci): it's kinda dumb that we have to walk the MultiError
   555  			// twice (once in its stackContext method, and again here).
   556  			inner = x.First()
   557  		case Wrapped:
   558  			inner = x.Unwrap()
   559  		}
   560  
   561  		if inner != nil {
   562  			err = inner
   563  			if needNewOriginalError {
   564  				originalError = err
   565  				needNewOriginalError = false
   566  			}
   567  			continue
   568  		} else {
   569  			// At this point we hit the bottom of the stack.
   570  			// We need to render the error with originalError in order to account for
   571  			// non-LUCI wrapped errors (e.g. `fmt.Errorf("something: %w", err)`).
   572  			ret.originalError = originalError.Error()
   573  			break
   574  		}
   575  	}
   576  
   577  	return ret
   578  }
   579  
   580  // Annotate captures the current stack frame and returns a new annotatable
   581  // error, attaching the publicly readable `reason` format string to the error.
   582  // You can add additional metadata to this error with the 'InternalReason' and
   583  // 'Tag' methods, and then obtain a real `error` with the Err() function.
   584  //
   585  // If this is passed nil, it will return a no-op Annotator whose .Err() function
   586  // will also return nil.
   587  //
   588  // The original error may be recovered by using Wrapped.Unwrap on the
   589  // returned error.
   590  //
   591  // Rendering the derived error with Error() will render a summary version of all
   592  // the public `reason`s as well as the initial underlying error's Error() text.
   593  // It is intended that the initial underlying error and all annotated reasons
   594  // only contain user-visible information, so that the accumulated error may be
   595  // returned to the user without worrying about leakage.
   596  //
   597  // You should assume that end-users (including unauthenticated end users) may
   598  // see the text in the `reason` field here. To only attach an internal reason,
   599  // leave the `reason` argument blank and don't pass any additional formatting
   600  // arguments.
   601  //
   602  // The `reason` string is formatted with `args` and may contain Sprintf-style
   603  // formatting directives.
   604  func Annotate(err error, reason string, args ...any) *Annotator {
   605  	if err == nil {
   606  		return nil
   607  	}
   608  	return &Annotator{err, stackContext{
   609  		frameInfo: stackFrameInfoForError(1, err),
   610  		reason:    fmt.Sprintf(reason, args...),
   611  	}}
   612  }
   613  
   614  // Reason builds a new Annotator starting with reason. This allows you to use
   615  // all the formatting directives you would normally use with Annotate, in case
   616  // your originating error needs tags or an internal reason.
   617  //
   618  //	errors.Reason("something bad: %d", value).Tag(transient.Tag).Err()
   619  //
   620  // Prefer this form to errors.New(fmt.Sprintf("...")) or fmt.Errorf("...")
   621  func Reason(reason string, args ...any) *Annotator {
   622  	currentStack := captureStack(1)
   623  	frameInfo := stackFrameInfo{0, currentStack}
   624  	return (&Annotator{nil, stackContext{
   625  		frameInfo: frameInfo,
   626  		reason:    fmt.Sprintf(reason, args...),
   627  	}})
   628  }
   629  
   630  // New is an API-compatible version of the standard errors.New function. Unlike
   631  // the stdlib errors.New, this will capture the current stack information at the
   632  // place this error was created.
   633  func New(msg string, tags ...TagValueGenerator) error {
   634  	tse := &terminalStackError{
   635  		errors.New(msg), stackFrameInfo{forStack: captureStack(1)}, nil}
   636  	if len(tags) > 0 {
   637  		tse.tags = make(map[TagKey]any, len(tags))
   638  		for _, t := range tags {
   639  			v := t.GenerateErrorTagValue()
   640  			tse.tags[v.Key] = v.Value
   641  		}
   642  	}
   643  	return tse
   644  }
   645  
   646  func captureStack(skip int) *stack {
   647  	fullStk := stack{goroutine.CurID(), nil}
   648  	stk := make([]uintptr, 16)
   649  	offset := skip + 2
   650  	for n := len(stk); n == len(stk); {
   651  		n = runtime.Callers(offset, stk)
   652  		offset += n
   653  		fullStk.frames = append(fullStk.frames, stk[:n]...)
   654  	}
   655  	return &fullStk
   656  }
   657  
   658  func getCapturedStack(err error) (ret *stack) {
   659  	Walk(err, func(err error) bool {
   660  		if sc, ok := err.(stackContexter); ok {
   661  			ret = sc.stackContext().frameInfo.forStack
   662  			return false
   663  		}
   664  		return true
   665  	})
   666  	return
   667  }
   668  
   669  // stackFrameInfoForError returns a stackFrameInfo suitable for use to implement
   670  // the stackContexter interface.
   671  //
   672  // It skips the provided number of frames when collecting the current trace
   673  // (which should be equal to the number of functions between your error library
   674  // and the user's code).
   675  //
   676  // The returned stackFrameInfo will find the appropriate frame in the error's
   677  // existing stack information (if the error was created with errors.New), or
   678  // include the current stack if it was not.
   679  func stackFrameInfoForError(skip int, err error) stackFrameInfo {
   680  	currentStack := captureStack(skip + 1)
   681  	currentlyCapturedStack := getCapturedStack(err)
   682  	if currentlyCapturedStack == nil || currentStack.id != currentlyCapturedStack.id {
   683  		// This is the very first annotation on this error OR
   684  		// We switched goroutines.
   685  		return stackFrameInfo{forStack: currentStack}
   686  	}
   687  	return stackFrameInfo{
   688  		frameIdx: currentlyCapturedStack.findPointOfDivergence(currentStack),
   689  		forStack: currentlyCapturedStack,
   690  	}
   691  }