diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2017-06-13 19:23:18 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2017-06-13 19:26:47 +0200 |
| commit | 612b82714b3e6660bf702f801ab96aacb3432e1f (patch) | |
| tree | 04f8586899db96f7fd8e7bc6a010fc10f1e2bb3b /vendor/cloud.google.com/go/errors/errors.go | |
| parent | cd8e13f826ff24f5f8e0b8de1b9d3373aaf93d2f (diff) | |
vendor: vendor dependencies
All dependencies are vendored with:
go get github.com/golang/dep
dep init
If necessary, can be updated with:
dep ensure -update
Fixes #215
Diffstat (limited to 'vendor/cloud.google.com/go/errors/errors.go')
| -rw-r--r-- | vendor/cloud.google.com/go/errors/errors.go | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/vendor/cloud.google.com/go/errors/errors.go b/vendor/cloud.google.com/go/errors/errors.go new file mode 100644 index 000000000..d5e78b8f4 --- /dev/null +++ b/vendor/cloud.google.com/go/errors/errors.go @@ -0,0 +1,453 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package errors is a Google Stackdriver Error Reporting library. +// +// This package is still experimental and subject to change. +// +// See https://cloud.google.com/error-reporting/ for more information. +// +// To initialize a client, use the NewClient function. Generally you will want +// to do this on program initialization. The NewClient function takes as +// arguments a context, the project name, a service name, and a version string. +// The service name and version string identify the running program, and are +// included in error reports. The version string can be left empty. NewClient +// also takes a bool that indicates whether to report errors using Stackdriver +// Logging, which will result in errors appearing in both the logs and the error +// dashboard. This is useful if you are already a user of Stackdriver Logging. +// +// import "cloud.google.com/go/errors" +// ... +// errorsClient, err = errors.NewClient(ctx, projectID, "myservice", "v1.0", true) +// +// The client can recover panics in your program and report them as errors. +// To use this functionality, defer its Catch method, as you would any other +// function for recovering panics. +// +// func foo(ctx context.Context, ...) { +// defer errorsClient.Catch(ctx) +// ... +// } +// +// Catch writes an error report containing the recovered value and a stack trace +// to Stackdriver Error Reporting. +// +// There are various options you can add to the call to Catch that modify how +// panics are handled. +// +// WithMessage and WithMessagef add a custom message after the recovered value, +// using fmt.Sprint and fmt.Sprintf respectively. +// +// defer errorsClient.Catch(ctx, errors.WithMessagef("x=%d", x)) +// +// WithRequest fills in various fields in the error report with information +// about an http.Request that's being handled. +// +// defer errorsClient.Catch(ctx, errors.WithRequest(httpReq)) +// +// By default, after recovering a panic, Catch will panic again with the +// recovered value. You can turn off this behavior with the Repanic option. +// +// defer errorsClient.Catch(ctx, errors.Repanic(false)) +// +// You can also change the default behavior for the client by changing the +// RepanicDefault field. +// +// errorsClient.RepanicDefault = false +// +// It is also possible to write an error report directly without recovering a +// panic, using Report or Reportf. +// +// if err != nil { +// errorsClient.Reportf(ctx, r, "unexpected error %v", err) +// } +// +// If you try to write an error report with a nil client, or if the client +// fails to write the report to the server, the error report is logged using +// log.Println. +package errors // import "cloud.google.com/go/errors" + +import ( + "bytes" + "fmt" + "log" + "net/http" + "runtime" + "strings" + "time" + + api "cloud.google.com/go/errorreporting/apiv1beta1" + "cloud.google.com/go/internal/version" + "cloud.google.com/go/logging" + "github.com/golang/protobuf/ptypes/timestamp" + gax "github.com/googleapis/gax-go" + "golang.org/x/net/context" + "google.golang.org/api/option" + erpb "google.golang.org/genproto/googleapis/devtools/clouderrorreporting/v1beta1" +) + +const ( + userAgent = `gcloud-golang-errorreporting/20160701` +) + +type apiInterface interface { + ReportErrorEvent(ctx context.Context, req *erpb.ReportErrorEventRequest, opts ...gax.CallOption) (*erpb.ReportErrorEventResponse, error) + Close() error +} + +var newApiInterface = func(ctx context.Context, opts ...option.ClientOption) (apiInterface, error) { + client, err := api.NewReportErrorsClient(ctx, opts...) + if err != nil { + return nil, err + } + client.SetGoogleClientInfo("gccl", version.Repo) + return client, nil +} + +type loggerInterface interface { + LogSync(ctx context.Context, e logging.Entry) error + Close() error +} + +type logger struct { + *logging.Logger + c *logging.Client +} + +func (l logger) Close() error { + return l.c.Close() +} + +var newLoggerInterface = func(ctx context.Context, projectID string, opts ...option.ClientOption) (loggerInterface, error) { + lc, err := logging.NewClient(ctx, projectID, opts...) + if err != nil { + return nil, fmt.Errorf("creating Logging client: %v", err) + } + l := lc.Logger("errorreports") + return logger{l, lc}, nil +} + +type sender interface { + send(ctx context.Context, r *http.Request, message string) + close() error +} + +// errorApiSender sends error reports using the Stackdriver Error Reporting API. +type errorApiSender struct { + apiClient apiInterface + projectID string + serviceContext erpb.ServiceContext +} + +// loggingSender sends error reports using the Stackdriver Logging API. +type loggingSender struct { + logger loggerInterface + projectID string + serviceContext map[string]string + client *logging.Client +} + +type Client struct { + sender + // RepanicDefault determines whether Catch will re-panic after recovering a + // panic. This behavior can be overridden for an individual call to Catch using + // the Repanic option. + RepanicDefault bool +} + +func NewClient(ctx context.Context, projectID, serviceName, serviceVersion string, useLogging bool, opts ...option.ClientOption) (*Client, error) { + if useLogging { + l, err := newLoggerInterface(ctx, projectID, opts...) + if err != nil { + return nil, fmt.Errorf("creating Logging client: %v", err) + } + sender := &loggingSender{ + logger: l, + projectID: projectID, + serviceContext: map[string]string{ + "service": serviceName, + }, + } + if serviceVersion != "" { + sender.serviceContext["version"] = serviceVersion + } + c := &Client{ + sender: sender, + RepanicDefault: true, + } + return c, nil + } else { + a, err := newApiInterface(ctx, opts...) + if err != nil { + return nil, fmt.Errorf("creating Error Reporting client: %v", err) + } + c := &Client{ + sender: &errorApiSender{ + apiClient: a, + projectID: "projects/" + projectID, + serviceContext: erpb.ServiceContext{ + Service: serviceName, + Version: serviceVersion, + }, + }, + RepanicDefault: true, + } + return c, nil + } +} + +// Close closes any resources held by the client. +// Close should be called when the client is no longer needed. +// It need not be called at program exit. +func (c *Client) Close() error { + err := c.sender.close() + c.sender = nil + return err +} + +// An Option is an optional argument to Catch. +type Option interface { + isOption() +} + +// PanicFlag returns an Option that can inform Catch that a panic has occurred. +// If *p is true when Catch is called, an error report is made even if recover +// returns nil. This allows Catch to report an error for panic(nil). +// If p is nil, the option is ignored. +// +// Here is an example of how to use PanicFlag: +// +// func foo(ctx context.Context, ...) { +// hasPanicked := true +// defer errorsClient.Catch(ctx, errors.PanicFlag(&hasPanicked)) +// ... +// ... +// // We have reached the end of the function, so we're not panicking. +// hasPanicked = false +// } +func PanicFlag(p *bool) Option { return panicFlag{p} } + +type panicFlag struct { + *bool +} + +func (h panicFlag) isOption() {} + +// Repanic returns an Option that determines whether Catch will re-panic after +// it reports an error. This overrides the default in the client. +func Repanic(r bool) Option { return repanic(r) } + +type repanic bool + +func (r repanic) isOption() {} + +// WithRequest returns an Option that informs Catch or Report of an http.Request +// that is being handled. Information from the Request is included in the error +// report, if one is made. +func WithRequest(r *http.Request) Option { return withRequest{r} } + +type withRequest struct { + *http.Request +} + +func (w withRequest) isOption() {} + +// WithMessage returns an Option that sets a message to be included in the error +// report, if one is made. v is converted to a string with fmt.Sprint. +func WithMessage(v ...interface{}) Option { return message(v) } + +type message []interface{} + +func (m message) isOption() {} + +// WithMessagef returns an Option that sets a message to be included in the error +// report, if one is made. format and v are converted to a string with fmt.Sprintf. +func WithMessagef(format string, v ...interface{}) Option { return messagef{format, v} } + +type messagef struct { + format string + v []interface{} +} + +func (m messagef) isOption() {} + +// Catch tries to recover a panic; if it succeeds, it writes an error report. +// It should be called by deferring it, like any other function for recovering +// panics. +// +// Catch can be called concurrently with other calls to Catch, Report or Reportf. +func (c *Client) Catch(ctx context.Context, opt ...Option) { + panicked := false + for _, o := range opt { + switch o := o.(type) { + case panicFlag: + panicked = panicked || o.bool != nil && *o.bool + } + } + x := recover() + if x == nil && !panicked { + return + } + var ( + r *http.Request + shouldRepanic = true + messages = []string{fmt.Sprint(x)} + ) + if c != nil { + shouldRepanic = c.RepanicDefault + } + for _, o := range opt { + switch o := o.(type) { + case repanic: + shouldRepanic = bool(o) + case withRequest: + r = o.Request + case message: + messages = append(messages, fmt.Sprint(o...)) + case messagef: + messages = append(messages, fmt.Sprintf(o.format, o.v...)) + } + } + c.logInternal(ctx, r, true, strings.Join(messages, " ")) + if shouldRepanic { + panic(x) + } +} + +// Report writes an error report unconditionally, instead of only when a panic +// occurs. +// If r is non-nil, information from the Request is included in the error report. +// +// Report can be called concurrently with other calls to Catch, Report or Reportf. +func (c *Client) Report(ctx context.Context, r *http.Request, v ...interface{}) { + c.logInternal(ctx, r, false, fmt.Sprint(v...)) +} + +// Reportf writes an error report unconditionally, instead of only when a panic +// occurs. +// If r is non-nil, information from the Request is included in the error report. +// +// Reportf can be called concurrently with other calls to Catch, Report or Reportf. +func (c *Client) Reportf(ctx context.Context, r *http.Request, format string, v ...interface{}) { + c.logInternal(ctx, r, false, fmt.Sprintf(format, v...)) +} + +func (c *Client) logInternal(ctx context.Context, r *http.Request, isPanic bool, msg string) { + // limit the stack trace to 16k. + var buf [16384]byte + stack := buf[0:runtime.Stack(buf[:], false)] + message := msg + "\n" + chopStack(stack, isPanic) + if c == nil { + log.Println("Error report used nil client:", message) + return + } + c.send(ctx, r, message) +} + +func (s *loggingSender) send(ctx context.Context, r *http.Request, message string) { + payload := map[string]interface{}{ + "eventTime": time.Now().In(time.UTC).Format(time.RFC3339Nano), + "message": message, + "serviceContext": s.serviceContext, + } + if r != nil { + payload["context"] = map[string]interface{}{ + "httpRequest": map[string]interface{}{ + "method": r.Method, + "url": r.Host + r.RequestURI, + "userAgent": r.UserAgent(), + "referrer": r.Referer(), + "remoteIp": r.RemoteAddr, + }, + } + } + e := logging.Entry{ + Severity: logging.Error, + Payload: payload, + } + err := s.logger.LogSync(ctx, e) + if err != nil { + log.Println("Error writing error report:", err, "report:", payload) + } +} + +func (s *loggingSender) close() error { + return s.client.Close() +} + +func (s *errorApiSender) send(ctx context.Context, r *http.Request, message string) { + time := time.Now() + var errorContext *erpb.ErrorContext + if r != nil { + errorContext = &erpb.ErrorContext{ + HttpRequest: &erpb.HttpRequestContext{ + Method: r.Method, + Url: r.Host + r.RequestURI, + UserAgent: r.UserAgent(), + Referrer: r.Referer(), + RemoteIp: r.RemoteAddr, + }, + } + } + req := erpb.ReportErrorEventRequest{ + ProjectName: s.projectID, + Event: &erpb.ReportedErrorEvent{ + EventTime: ×tamp.Timestamp{ + Seconds: time.Unix(), + Nanos: int32(time.Nanosecond()), + }, + ServiceContext: &s.serviceContext, + Message: message, + Context: errorContext, + }, + } + _, err := s.apiClient.ReportErrorEvent(ctx, &req) + if err != nil { + log.Println("Error writing error report:", err, "report:", message) + } +} + +func (s *errorApiSender) close() error { + return s.apiClient.Close() +} + +// chopStack trims a stack trace so that the function which panics or calls +// Report is first. +func chopStack(s []byte, isPanic bool) string { + var f []byte + if isPanic { + f = []byte("panic(") + } else { + f = []byte("cloud.google.com/go/errors.(*Client).Report") + } + + lfFirst := bytes.IndexByte(s, '\n') + if lfFirst == -1 { + return string(s) + } + stack := s[lfFirst:] + panicLine := bytes.Index(stack, f) + if panicLine == -1 { + return string(s) + } + stack = stack[panicLine+1:] + for i := 0; i < 2; i++ { + nextLine := bytes.IndexByte(stack, '\n') + if nextLine == -1 { + return string(s) + } + stack = stack[nextLine+1:] + } + return string(s[:lfFirst+1]) + string(stack) +} |
