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.
 
 
 

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