aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/cloud.google.com/go/errors/errors.go
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-06-13 19:23:18 +0200
committerDmitry Vyukov <dvyukov@google.com>2017-06-13 19:26:47 +0200
commit612b82714b3e6660bf702f801ab96aacb3432e1f (patch)
tree04f8586899db96f7fd8e7bc6a010fc10f1e2bb3b /vendor/cloud.google.com/go/errors/errors.go
parentcd8e13f826ff24f5f8e0b8de1b9d3373aaf93d2f (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.go453
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: &timestamp.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)
+}