google-cloud-go: errorreporting: Stack incompatible with pkg/errors

Client

Error Reporting

Describe Your Environment

Go 1.10.1 on Ubuntu 18.04 (local development machine)

Use Case

type StackTracer interface {
	StackTrace() errors.StackTrace
}

var (
	projectID       = flag.String("projectID", "", "")
	credentialsFile = flag.String("credentialsFile", "", "")
)

func main() {
        flag.Parse()

	client, err := errorreporting.NewClient(ctx, *projectID, errorreporting.Config{
		ServiceName:    "test",
		OnError: func(err error) {
			log.Printf("Failed to report error: %+v", err)
		},
	}, option.WithCredentialsFile(*credentialsFile))
	if err != nil {
		panic(errors.Wrap(err, "failed to initialize error reporting client"))
	}

        // given a pkg/errors error
	testErr := errors.New("hello there")

        // report error without stack trace
	err = errorReportingClient.ReportSync(ctx, errorreporting.Entry{
		Error: testErr,
	})
	if err != nil {
		panic(errors.Wrap(err, "failed to log error"))
	}

        // report error with stack trace
	err = client.ReportSync(ctx, errorreporting.Entry{
		Error: testErr,
		Stack: []byte(fmt.Sprintf("%v", testErr.(StackTracer).StackTrace())),
	})
	if err != nil {
		panic(errors.Wrap(err, "failed to log error with stack trace"))
	}
}

Expected Behavior

Error gets reported successfully.

Actual Behavior

panic: failed to log error with stack trace: rpc error: code = InvalidArgument desc = ReportedErrorEvent.context must contain a location unless `message` contain an exception or stacktrace.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 19 (8 by maintainers)

Most upvoted comments

We hacked together a formatter that Stackdriver seems happy with. Some frame parts (routine id, routine status, func args) are either hard-coded or omitted, but the important parts work. Note that var ErrorReportingClient *errorreporting.Client is initialized in another file in this package.

package microservice

import (
	"bytes"
	"fmt"
	"runtime"
	"strings"

	"cloud.google.com/go/errorreporting"
	"github.com/pkg/errors"
)

type causer interface {
	Cause() error
}

type stackTracer interface {
	StackTrace() errors.StackTrace
}

// ReportError logs the given errorreporting entry to Stackdriver
func ReportError(entry errorreporting.Entry) {
	if ErrorReportingClient != nil {
		if len(entry.Stack) == 0 {
			// attempt to add a formatted stack based on the error
			entry.Stack = FormatStack(entry.Error)
		}
		ErrorReportingClient.Report(entry)
	}
}

// FormatStack is a best attempt at recreating the output of runtime.Stack
// Stackdriver currently only supports the stack output from runtime.Stack
// Tracking this issue for pkg/errors support: https://github.com/googleapis/google-cloud-go/issues/1084
func FormatStack(err error) (buffer []byte) {
	if err == nil {
		return
	}

	// find the inner most error with a stack
	inner := err
	for inner != nil {
		if cause, ok := inner.(causer); ok {
			inner = cause.Cause()
			if _, ok := inner.(stackTracer); ok {
				err = inner
			}
		} else {
			break
		}
	}

	if stackTrace, ok := err.(stackTracer); ok {
		buf := bytes.Buffer{}
		// routine id and state aren't available in pure go, so we hard-coded these
		buf.WriteString(fmt.Sprintf("%s\ngoroutine 1 [running]:\n", err.Error()))

		// format each frame of the stack to match runtime.Stack's format
		var lines []string
		for _, frame := range stackTrace.StackTrace() {
			pc := uintptr(frame) - 1
			fn := runtime.FuncForPC(pc)
			if fn != nil {
				file, line := fn.FileLine(pc)
				lines = append(lines, fmt.Sprintf("%s()\n\t%s:%d +%#x", fn.Name(), file, line, fn.Entry()))
			}
		}
		buf.WriteString(strings.Join(lines, "\n"))

		buffer = buf.Bytes()
	}

	return
}