|
- // Copyright 2016 Google LLC
- //
- // 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 errorreporting 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.
- package errorreporting // import "cloud.google.com/go/errorreporting"
-
- import (
- "bytes"
- "fmt"
- "log"
- "net/http"
- "runtime"
- "time"
-
- api "cloud.google.com/go/errorreporting/apiv1beta1"
- "cloud.google.com/go/internal/version"
- "github.com/golang/protobuf/ptypes"
- gax "github.com/googleapis/gax-go"
- "golang.org/x/net/context"
- "google.golang.org/api/option"
- "google.golang.org/api/support/bundler"
- erpb "google.golang.org/genproto/googleapis/devtools/clouderrorreporting/v1beta1"
- )
-
- const (
- userAgent = `gcloud-golang-errorreporting/20160701`
- )
-
- // Config is additional configuration for Client.
- type Config struct {
- // ServiceName identifies the running program and is included in the error reports.
- // Optional.
- ServiceName string
-
- // ServiceVersion identifies the version of the running program and is
- // included in the error reports.
- // Optional.
- ServiceVersion string
-
- // OnError is the function to call if any background
- // tasks errored. By default, errors are logged.
- OnError func(err error)
- }
-
- // Entry holds information about the reported error.
- type Entry struct {
- Error error
- Req *http.Request // if error is associated with a request.
- Stack []byte // if user does not provide a stack trace, runtime.Stack will be called
- }
-
- // Client represents a Google Cloud Error Reporting client.
- type Client struct {
- projectID string
- apiClient client
- serviceContext erpb.ServiceContext
- bundler *bundler.Bundler
-
- onErrorFn func(err error)
- }
-
- var newClient = func(ctx context.Context, opts ...option.ClientOption) (client, error) {
- client, err := api.NewReportErrorsClient(ctx, opts...)
- if err != nil {
- return nil, err
- }
- client.SetGoogleClientInfo("gccl", version.Repo)
- return client, nil
- }
-
- // NewClient returns a new error reporting client. Generally you will want
- // to create a client on program initialization and use it through the lifetime
- // of the process.
- func NewClient(ctx context.Context, projectID string, cfg Config, opts ...option.ClientOption) (*Client, error) {
- if cfg.ServiceName == "" {
- cfg.ServiceName = "goapp"
- }
- c, err := newClient(ctx, opts...)
- if err != nil {
- return nil, fmt.Errorf("creating client: %v", err)
- }
-
- client := &Client{
- apiClient: c,
- projectID: "projects/" + projectID,
- serviceContext: erpb.ServiceContext{
- Service: cfg.ServiceName,
- Version: cfg.ServiceVersion,
- },
- }
- bundler := bundler.NewBundler((*erpb.ReportErrorEventRequest)(nil), func(bundle interface{}) {
- reqs := bundle.([]*erpb.ReportErrorEventRequest)
- for _, req := range reqs {
- _, err = client.apiClient.ReportErrorEvent(ctx, req)
- if err != nil {
- client.onError(fmt.Errorf("failed to upload: %v", err))
- }
- }
- })
- // TODO(jbd): Optimize bundler limits.
- bundler.DelayThreshold = 2 * time.Second
- bundler.BundleCountThreshold = 100
- bundler.BundleByteThreshold = 1000
- bundler.BundleByteLimit = 1000
- bundler.BufferedByteLimit = 10000
- client.bundler = bundler
- return client, nil
- }
-
- func (c *Client) onError(err error) {
- if c.onErrorFn != nil {
- c.onErrorFn(err)
- return
- }
- log.Println(err)
- }
-
- // 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 {
- return c.apiClient.Close()
- }
-
- // Report writes an error report. It doesn't block. Errors in
- // writing the error report can be handled via Client.OnError.
- func (c *Client) Report(e Entry) {
- var stack string
- if e.Stack != nil {
- stack = string(e.Stack)
- }
- req := c.makeReportErrorEventRequest(e.Req, e.Error.Error(), stack)
- c.bundler.Add(req, 1)
- }
-
- // ReportSync writes an error report. It blocks until the entry is written.
- func (c *Client) ReportSync(ctx context.Context, e Entry) error {
- var stack string
- if e.Stack != nil {
- stack = string(e.Stack)
- }
- req := c.makeReportErrorEventRequest(e.Req, e.Error.Error(), stack)
- _, err := c.apiClient.ReportErrorEvent(ctx, req)
- return err
- }
-
- // Flush blocks until all currently buffered error reports are sent.
- //
- // If any errors occurred since the last call to Flush, or the
- // creation of the client if this is the first call, then Flush report the
- // error via the (*Client).OnError handler.
- func (c *Client) Flush() {
- c.bundler.Flush()
- }
-
- func (c *Client) makeReportErrorEventRequest(r *http.Request, msg string, stack string) *erpb.ReportErrorEventRequest {
- if stack == "" {
- // limit the stack trace to 16k.
- var buf [16 * 1024]byte
- stack = chopStack(buf[0:runtime.Stack(buf[:], false)])
- }
- message := msg + "\n" + stack
-
- 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,
- },
- }
- }
- return &erpb.ReportErrorEventRequest{
- ProjectName: c.projectID,
- Event: &erpb.ReportedErrorEvent{
- EventTime: ptypes.TimestampNow(),
- ServiceContext: &c.serviceContext,
- Message: message,
- Context: errorContext,
- },
- }
- }
-
- // chopStack trims a stack trace so that the function which panics or calls
- // Report is first.
- func chopStack(s []byte) string {
- f := []byte("cloud.google.com/go/errorreporting.(*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)
- }
-
- type client interface {
- ReportErrorEvent(ctx context.Context, req *erpb.ReportErrorEventRequest, opts ...gax.CallOption) (*erpb.ReportErrorEventResponse, error)
- Close() error
- }
|