You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

231 lines
6.6 KiB

  1. // Copyright 2016 Google LLC
  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. // Package errorreporting is a Google Stackdriver Error Reporting library.
  15. //
  16. // This package is still experimental and subject to change.
  17. //
  18. // See https://cloud.google.com/error-reporting/ for more information.
  19. package errorreporting // import "cloud.google.com/go/errorreporting"
  20. import (
  21. "bytes"
  22. "fmt"
  23. "log"
  24. "net/http"
  25. "runtime"
  26. "time"
  27. api "cloud.google.com/go/errorreporting/apiv1beta1"
  28. "cloud.google.com/go/internal/version"
  29. "github.com/golang/protobuf/ptypes"
  30. gax "github.com/googleapis/gax-go"
  31. "golang.org/x/net/context"
  32. "google.golang.org/api/option"
  33. "google.golang.org/api/support/bundler"
  34. erpb "google.golang.org/genproto/googleapis/devtools/clouderrorreporting/v1beta1"
  35. )
  36. const (
  37. userAgent = `gcloud-golang-errorreporting/20160701`
  38. )
  39. // Config is additional configuration for Client.
  40. type Config struct {
  41. // ServiceName identifies the running program and is included in the error reports.
  42. // Optional.
  43. ServiceName string
  44. // ServiceVersion identifies the version of the running program and is
  45. // included in the error reports.
  46. // Optional.
  47. ServiceVersion string
  48. // OnError is the function to call if any background
  49. // tasks errored. By default, errors are logged.
  50. OnError func(err error)
  51. }
  52. // Entry holds information about the reported error.
  53. type Entry struct {
  54. Error error
  55. Req *http.Request // if error is associated with a request.
  56. Stack []byte // if user does not provide a stack trace, runtime.Stack will be called
  57. }
  58. // Client represents a Google Cloud Error Reporting client.
  59. type Client struct {
  60. projectID string
  61. apiClient client
  62. serviceContext erpb.ServiceContext
  63. bundler *bundler.Bundler
  64. onErrorFn func(err error)
  65. }
  66. var newClient = func(ctx context.Context, opts ...option.ClientOption) (client, error) {
  67. client, err := api.NewReportErrorsClient(ctx, opts...)
  68. if err != nil {
  69. return nil, err
  70. }
  71. client.SetGoogleClientInfo("gccl", version.Repo)
  72. return client, nil
  73. }
  74. // NewClient returns a new error reporting client. Generally you will want
  75. // to create a client on program initialization and use it through the lifetime
  76. // of the process.
  77. func NewClient(ctx context.Context, projectID string, cfg Config, opts ...option.ClientOption) (*Client, error) {
  78. if cfg.ServiceName == "" {
  79. cfg.ServiceName = "goapp"
  80. }
  81. c, err := newClient(ctx, opts...)
  82. if err != nil {
  83. return nil, fmt.Errorf("creating client: %v", err)
  84. }
  85. client := &Client{
  86. apiClient: c,
  87. projectID: "projects/" + projectID,
  88. serviceContext: erpb.ServiceContext{
  89. Service: cfg.ServiceName,
  90. Version: cfg.ServiceVersion,
  91. },
  92. }
  93. bundler := bundler.NewBundler((*erpb.ReportErrorEventRequest)(nil), func(bundle interface{}) {
  94. reqs := bundle.([]*erpb.ReportErrorEventRequest)
  95. for _, req := range reqs {
  96. _, err = client.apiClient.ReportErrorEvent(ctx, req)
  97. if err != nil {
  98. client.onError(fmt.Errorf("failed to upload: %v", err))
  99. }
  100. }
  101. })
  102. // TODO(jbd): Optimize bundler limits.
  103. bundler.DelayThreshold = 2 * time.Second
  104. bundler.BundleCountThreshold = 100
  105. bundler.BundleByteThreshold = 1000
  106. bundler.BundleByteLimit = 1000
  107. bundler.BufferedByteLimit = 10000
  108. client.bundler = bundler
  109. return client, nil
  110. }
  111. func (c *Client) onError(err error) {
  112. if c.onErrorFn != nil {
  113. c.onErrorFn(err)
  114. return
  115. }
  116. log.Println(err)
  117. }
  118. // Close closes any resources held by the client.
  119. // Close should be called when the client is no longer needed.
  120. // It need not be called at program exit.
  121. func (c *Client) Close() error {
  122. return c.apiClient.Close()
  123. }
  124. // Report writes an error report. It doesn't block. Errors in
  125. // writing the error report can be handled via Client.OnError.
  126. func (c *Client) Report(e Entry) {
  127. var stack string
  128. if e.Stack != nil {
  129. stack = string(e.Stack)
  130. }
  131. req := c.makeReportErrorEventRequest(e.Req, e.Error.Error(), stack)
  132. c.bundler.Add(req, 1)
  133. }
  134. // ReportSync writes an error report. It blocks until the entry is written.
  135. func (c *Client) ReportSync(ctx context.Context, e Entry) error {
  136. var stack string
  137. if e.Stack != nil {
  138. stack = string(e.Stack)
  139. }
  140. req := c.makeReportErrorEventRequest(e.Req, e.Error.Error(), stack)
  141. _, err := c.apiClient.ReportErrorEvent(ctx, req)
  142. return err
  143. }
  144. // Flush blocks until all currently buffered error reports are sent.
  145. //
  146. // If any errors occurred since the last call to Flush, or the
  147. // creation of the client if this is the first call, then Flush report the
  148. // error via the (*Client).OnError handler.
  149. func (c *Client) Flush() {
  150. c.bundler.Flush()
  151. }
  152. func (c *Client) makeReportErrorEventRequest(r *http.Request, msg string, stack string) *erpb.ReportErrorEventRequest {
  153. if stack == "" {
  154. // limit the stack trace to 16k.
  155. var buf [16 * 1024]byte
  156. stack = chopStack(buf[0:runtime.Stack(buf[:], false)])
  157. }
  158. message := msg + "\n" + stack
  159. var errorContext *erpb.ErrorContext
  160. if r != nil {
  161. errorContext = &erpb.ErrorContext{
  162. HttpRequest: &erpb.HttpRequestContext{
  163. Method: r.Method,
  164. Url: r.Host + r.RequestURI,
  165. UserAgent: r.UserAgent(),
  166. Referrer: r.Referer(),
  167. RemoteIp: r.RemoteAddr,
  168. },
  169. }
  170. }
  171. return &erpb.ReportErrorEventRequest{
  172. ProjectName: c.projectID,
  173. Event: &erpb.ReportedErrorEvent{
  174. EventTime: ptypes.TimestampNow(),
  175. ServiceContext: &c.serviceContext,
  176. Message: message,
  177. Context: errorContext,
  178. },
  179. }
  180. }
  181. // chopStack trims a stack trace so that the function which panics or calls
  182. // Report is first.
  183. func chopStack(s []byte) string {
  184. f := []byte("cloud.google.com/go/errorreporting.(*Client).Report")
  185. lfFirst := bytes.IndexByte(s, '\n')
  186. if lfFirst == -1 {
  187. return string(s)
  188. }
  189. stack := s[lfFirst:]
  190. panicLine := bytes.Index(stack, f)
  191. if panicLine == -1 {
  192. return string(s)
  193. }
  194. stack = stack[panicLine+1:]
  195. for i := 0; i < 2; i++ {
  196. nextLine := bytes.IndexByte(stack, '\n')
  197. if nextLine == -1 {
  198. return string(s)
  199. }
  200. stack = stack[nextLine+1:]
  201. }
  202. return string(s[:lfFirst+1]) + string(stack)
  203. }
  204. type client interface {
  205. ReportErrorEvent(ctx context.Context, req *erpb.ReportErrorEventRequest, opts ...gax.CallOption) (*erpb.ReportErrorEventResponse, error)
  206. Close() error
  207. }