client.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. package sentry
  2. import (
  3. "context"
  4. "crypto/x509"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "log"
  9. "math/rand"
  10. "net/http"
  11. "os"
  12. "reflect"
  13. "sort"
  14. "time"
  15. )
  16. // Logger is an instance of log.Logger that is use to provide debug information about running Sentry Client
  17. // can be enabled by either using `Logger.SetOutput` directly or with `Debug` client option
  18. var Logger = log.New(ioutil.Discard, "[Sentry] ", log.LstdFlags) // nolint: gochecknoglobals
  19. type EventProcessor func(event *Event, hint *EventHint) *Event
  20. type EventModifier interface {
  21. ApplyToEvent(event *Event, hint *EventHint) *Event
  22. }
  23. var globalEventProcessors []EventProcessor // nolint: gochecknoglobals
  24. func AddGlobalEventProcessor(processor EventProcessor) {
  25. globalEventProcessors = append(globalEventProcessors, processor)
  26. }
  27. // Integration allows for registering a functions that modify or discard captured events.
  28. type Integration interface {
  29. Name() string
  30. SetupOnce(client *Client)
  31. }
  32. // ClientOptions that configures a SDK Client
  33. type ClientOptions struct {
  34. // The DSN to use. If the DSN is not set, the client is effectively disabled.
  35. Dsn string
  36. // In debug mode, the debug information is printed to stdout to help you understand what
  37. // sentry is doing.
  38. Debug bool
  39. // Configures whether SDK should generate and attach stacktraces to pure capture message calls.
  40. AttachStacktrace bool
  41. // The sample rate for event submission (0.0 - 1.0, defaults to 1.0).
  42. SampleRate float32
  43. // List of regexp strings that will be used to match against event's message
  44. // and if applicable, caught errors type and value.
  45. // If the match is found, then a whole event will be dropped.
  46. IgnoreErrors []string
  47. // Before send callback.
  48. BeforeSend func(event *Event, hint *EventHint) *Event
  49. // Before breadcrumb add callback.
  50. BeforeBreadcrumb func(breadcrumb *Breadcrumb, hint *BreadcrumbHint) *Breadcrumb
  51. // Integrations to be installed on the current Client, receives default integrations
  52. Integrations func([]Integration) []Integration
  53. // io.Writer implementation that should be used with the `Debug` mode
  54. DebugWriter io.Writer
  55. // The transport to use.
  56. // This is an instance of a struct implementing `Transport` interface.
  57. // Defaults to `httpTransport` from `transport.go`
  58. Transport Transport
  59. // The server name to be reported.
  60. ServerName string
  61. // The release to be sent with events.
  62. Release string
  63. // The dist to be sent with events.
  64. Dist string
  65. // The environment to be sent with events.
  66. Environment string
  67. // Maximum number of breadcrumbs.
  68. MaxBreadcrumbs int
  69. // An optional pointer to `http.Transport` that will be used with a default HTTPTransport.
  70. HTTPTransport *http.Transport
  71. // An optional HTTP proxy to use.
  72. // This will default to the `http_proxy` environment variable.
  73. // or `https_proxy` if that one exists.
  74. HTTPProxy string
  75. // An optional HTTPS proxy to use.
  76. // This will default to the `HTTPS_PROXY` environment variable
  77. // or `http_proxy` if that one exists.
  78. HTTPSProxy string
  79. // An optionsl CaCerts to use.
  80. // Defaults to `gocertifi.CACerts()`.
  81. CaCerts *x509.CertPool
  82. }
  83. // Client is the underlying processor that's used by the main API and `Hub` instances.
  84. type Client struct {
  85. options ClientOptions
  86. dsn *Dsn
  87. eventProcessors []EventProcessor
  88. integrations []Integration
  89. Transport Transport
  90. }
  91. // NewClient creates and returns an instance of `Client` configured using `ClientOptions`.
  92. func NewClient(options ClientOptions) (*Client, error) {
  93. if options.Debug {
  94. debugWriter := options.DebugWriter
  95. if debugWriter == nil {
  96. debugWriter = os.Stdout
  97. }
  98. Logger.SetOutput(debugWriter)
  99. }
  100. if options.Dsn == "" {
  101. options.Dsn = os.Getenv("SENTRY_DSN")
  102. }
  103. if options.Release == "" {
  104. options.Release = os.Getenv("SENTRY_RELEASE")
  105. }
  106. if options.Environment == "" {
  107. options.Environment = os.Getenv("SENTRY_ENVIRONMENT")
  108. }
  109. var dsn *Dsn
  110. if options.Dsn != "" {
  111. var err error
  112. dsn, err = NewDsn(options.Dsn)
  113. if err != nil {
  114. return nil, err
  115. }
  116. }
  117. client := Client{
  118. options: options,
  119. dsn: dsn,
  120. }
  121. client.setupTransport()
  122. client.setupIntegrations()
  123. return &client, nil
  124. }
  125. func (client *Client) setupTransport() {
  126. transport := client.options.Transport
  127. if transport == nil {
  128. if client.options.Dsn == "" {
  129. transport = new(noopTransport)
  130. } else {
  131. transport = NewHTTPTransport()
  132. }
  133. }
  134. transport.Configure(client.options)
  135. client.Transport = transport
  136. }
  137. func (client *Client) setupIntegrations() {
  138. integrations := []Integration{
  139. new(contextifyFramesIntegration),
  140. new(environmentIntegration),
  141. new(modulesIntegration),
  142. new(ignoreErrorsIntegration),
  143. }
  144. if client.options.Integrations != nil {
  145. integrations = client.options.Integrations(integrations)
  146. }
  147. for _, integration := range integrations {
  148. if client.integrationAlreadyInstalled(integration.Name()) {
  149. Logger.Printf("Integration %s is already installed\n", integration.Name())
  150. continue
  151. }
  152. client.integrations = append(client.integrations, integration)
  153. integration.SetupOnce(client)
  154. Logger.Printf("Integration installed: %s\n", integration.Name())
  155. }
  156. }
  157. // AddEventProcessor adds an event processor to the client.
  158. func (client *Client) AddEventProcessor(processor EventProcessor) {
  159. client.eventProcessors = append(client.eventProcessors, processor)
  160. }
  161. // Options return `ClientOptions` for the current `Client`.
  162. func (client Client) Options() ClientOptions {
  163. return client.options
  164. }
  165. // CaptureMessage captures an arbitrary message.
  166. func (client *Client) CaptureMessage(message string, hint *EventHint, scope EventModifier) *EventID {
  167. event := client.eventFromMessage(message, LevelInfo)
  168. return client.CaptureEvent(event, hint, scope)
  169. }
  170. // CaptureException captures an error.
  171. func (client *Client) CaptureException(exception error, hint *EventHint, scope EventModifier) *EventID {
  172. event := client.eventFromException(exception, LevelError)
  173. return client.CaptureEvent(event, hint, scope)
  174. }
  175. // CaptureEvent captures an event on the currently active client if any.
  176. //
  177. // The event must already be assembled. Typically code would instead use
  178. // the utility methods like `CaptureException`. The return value is the
  179. // event ID. In case Sentry is disabled or event was dropped, the return value will be nil.
  180. func (client *Client) CaptureEvent(event *Event, hint *EventHint, scope EventModifier) *EventID {
  181. return client.processEvent(event, hint, scope)
  182. }
  183. // Recover captures a panic.
  184. // Returns `EventID` if successfully, or `nil` if there's no error to recover from.
  185. func (client *Client) Recover(err interface{}, hint *EventHint, scope EventModifier) *EventID {
  186. if err == nil {
  187. err = recover()
  188. }
  189. if err != nil {
  190. if err, ok := err.(error); ok {
  191. event := client.eventFromException(err, LevelFatal)
  192. return client.CaptureEvent(event, hint, scope)
  193. }
  194. if err, ok := err.(string); ok {
  195. event := client.eventFromMessage(err, LevelFatal)
  196. return client.CaptureEvent(event, hint, scope)
  197. }
  198. }
  199. return nil
  200. }
  201. // Recover captures a panic and passes relevant context object.
  202. // Returns `EventID` if successfully, or `nil` if there's no error to recover from.
  203. func (client *Client) RecoverWithContext(
  204. ctx context.Context,
  205. err interface{},
  206. hint *EventHint,
  207. scope EventModifier,
  208. ) *EventID {
  209. if err == nil {
  210. err = recover()
  211. }
  212. if err != nil {
  213. if hint.Context == nil && ctx != nil {
  214. hint.Context = ctx
  215. }
  216. if err, ok := err.(error); ok {
  217. event := client.eventFromException(err, LevelFatal)
  218. return client.CaptureEvent(event, hint, scope)
  219. }
  220. if err, ok := err.(string); ok {
  221. event := client.eventFromMessage(err, LevelFatal)
  222. return client.CaptureEvent(event, hint, scope)
  223. }
  224. }
  225. return nil
  226. }
  227. // Flush notifies when all the buffered events have been sent by returning `true`
  228. // or `false` if timeout was reached. It calls `Flush` method of the configured `Transport`.
  229. func (client *Client) Flush(timeout time.Duration) bool {
  230. return client.Transport.Flush(timeout)
  231. }
  232. func (client *Client) eventFromMessage(message string, level Level) *Event {
  233. event := NewEvent()
  234. event.Level = level
  235. event.Message = message
  236. if client.Options().AttachStacktrace {
  237. event.Threads = []Thread{{
  238. Stacktrace: NewStacktrace(),
  239. Crashed: false,
  240. Current: true,
  241. }}
  242. }
  243. return event
  244. }
  245. func (client *Client) eventFromException(exception error, level Level) *Event {
  246. if exception == nil {
  247. event := NewEvent()
  248. event.Level = level
  249. event.Message = fmt.Sprintf("Called %s with nil value", callerFunctionName())
  250. return event
  251. }
  252. stacktrace := ExtractStacktrace(exception)
  253. if stacktrace == nil {
  254. stacktrace = NewStacktrace()
  255. }
  256. event := NewEvent()
  257. event.Level = level
  258. event.Exception = []Exception{{
  259. Value: exception.Error(),
  260. Type: reflect.TypeOf(exception).String(),
  261. Stacktrace: stacktrace,
  262. }}
  263. return event
  264. }
  265. func (client *Client) processEvent(event *Event, hint *EventHint, scope EventModifier) *EventID {
  266. options := client.Options()
  267. // TODO: Reconsider if its worth going away from default implementation
  268. // of other SDKs. In Go zero value (default) for float32 is 0.0,
  269. // which means that if someone uses ClientOptions{} struct directly
  270. // and we would not check for 0 here, we'd skip all events by default
  271. if options.SampleRate != 0.0 {
  272. randomFloat := rand.New(rand.NewSource(time.Now().UnixNano())).Float32()
  273. if randomFloat > options.SampleRate {
  274. Logger.Println("Event dropped due to SampleRate hit.")
  275. return nil
  276. }
  277. }
  278. if event = client.prepareEvent(event, hint, scope); event == nil {
  279. return nil
  280. }
  281. if options.BeforeSend != nil {
  282. h := &EventHint{}
  283. if hint != nil {
  284. h = hint
  285. }
  286. if event = options.BeforeSend(event, h); event == nil {
  287. Logger.Println("Event dropped due to BeforeSend callback.")
  288. return nil
  289. }
  290. }
  291. client.Transport.SendEvent(event)
  292. return &event.EventID
  293. }
  294. func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventModifier) *Event {
  295. if event.EventID == "" {
  296. event.EventID = EventID(uuid())
  297. }
  298. if event.Timestamp == 0 {
  299. event.Timestamp = time.Now().Unix()
  300. }
  301. if event.Level == "" {
  302. event.Level = LevelInfo
  303. }
  304. if event.ServerName == "" {
  305. if client.Options().ServerName != "" {
  306. event.ServerName = client.Options().ServerName
  307. } else if hostname, err := os.Hostname(); err == nil {
  308. event.ServerName = hostname
  309. }
  310. }
  311. if event.Release == "" && client.Options().Release != "" {
  312. event.Release = client.Options().Release
  313. }
  314. if event.Dist == "" && client.Options().Dist != "" {
  315. event.Dist = client.Options().Dist
  316. }
  317. if event.Environment == "" && client.Options().Environment != "" {
  318. event.Environment = client.Options().Environment
  319. }
  320. event.Platform = "go"
  321. event.Sdk = SdkInfo{
  322. Name: "sentry.go",
  323. Version: Version,
  324. Integrations: client.listIntegrations(),
  325. Packages: []SdkPackage{{
  326. Name: "sentry-go",
  327. Version: Version,
  328. }},
  329. }
  330. event = scope.ApplyToEvent(event, hint)
  331. for _, processor := range client.eventProcessors {
  332. id := event.EventID
  333. event = processor(event, hint)
  334. if event == nil {
  335. Logger.Printf("Event dropped by one of the Client EventProcessors: %s\n", id)
  336. return nil
  337. }
  338. }
  339. for _, processor := range globalEventProcessors {
  340. id := event.EventID
  341. event = processor(event, hint)
  342. if event == nil {
  343. Logger.Printf("Event dropped by one of the Global EventProcessors: %s\n", id)
  344. return nil
  345. }
  346. }
  347. return event
  348. }
  349. func (client Client) listIntegrations() []string {
  350. integrations := make([]string, 0, len(client.integrations))
  351. for _, integration := range client.integrations {
  352. integrations = append(integrations, integration.Name())
  353. }
  354. sort.Strings(integrations)
  355. return integrations
  356. }
  357. func (client Client) integrationAlreadyInstalled(name string) bool {
  358. for _, integration := range client.integrations {
  359. if integration.Name() == name {
  360. return true
  361. }
  362. }
  363. return false
  364. }