3
0

Add support for error (panic) reporting through Sentry

This commit is contained in:
Denis Arh 2019-07-03 13:59:46 +02:00
parent 4973638e2a
commit 60ad32e440
40 changed files with 3465 additions and 17 deletions

13
Gopkg.lock generated
View File

@ -76,6 +76,17 @@
pruneopts = "UT"
revision = "b57537c92a6b4155c5219ce0d7d2a9c0cc74b1c4"
[[projects]]
digest = "1:f59f34bb582fbc13885cd0338570bc0f21738fc711065527c36928213de62c1b"
name = "github.com/getsentry/sentry-go"
packages = [
".",
"http",
]
pruneopts = "UT"
revision = "79cad3fec863752ea61059d4565f85b23da2d9d1"
version = "v0.1.1"
[[projects]]
digest = "1:cb53653e4e410ef91f3a83568464ef966e1732ad9a00b2719571f90eae9a15a7"
name = "github.com/go-chi/chi"
@ -565,6 +576,8 @@
"github.com/dgrijalva/jwt-go",
"github.com/disintegration/imaging",
"github.com/edwvee/exiffix",
"github.com/getsentry/sentry-go",
"github.com/getsentry/sentry-go/http",
"github.com/go-chi/chi",
"github.com/go-chi/chi/middleware",
"github.com/go-chi/cors",

View File

@ -6,6 +6,7 @@ import (
)
func main() {
cfg := system.Configure()
cmd := cfg.MakeCLI(cli.Context())
cli.HandleError(cmd.Execute())

View File

@ -4,6 +4,7 @@ import (
"context"
"time"
sentry "github.com/getsentry/sentry-go"
"github.com/pkg/errors"
"github.com/titpetric/factory"
"go.uber.org/zap"
@ -34,6 +35,8 @@ func TryToConnect(ctx context.Context, log *zap.Logger, name, dsn, profiler stri
zap.Duration("timeout", timeout))
go func() {
defer sentry.Recover()
var (
try = 0
)

View File

@ -5,6 +5,7 @@ import (
"sync"
"time"
sentry "github.com/getsentry/sentry-go"
"github.com/pkg/errors"
"go.uber.org/zap"
@ -111,9 +112,10 @@ func (svc *service) Grant(ctx context.Context, wl Whitelist, rules ...*Rule) (er
// Watches for changes
func (svc service) Watch(ctx context.Context) {
go func() {
defer sentry.Recover()
var ticker = time.NewTicker(watchInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
sentry "github.com/getsentry/sentry-go"
"github.com/titpetric/factory"
"github.com/cortezaproject/corteza-server/internal/payload"
@ -43,6 +44,8 @@ func EventQueue(origin uint64) *eventQueue {
// @todo: retire this function, use Events().Push(ctx, item) directly.
func (eq *eventQueue) store(ctx context.Context, qp repository.EventsRepository) {
go func() {
defer sentry.Recover()
for {
select {
case <-ctx.Done():

View File

@ -4,6 +4,7 @@ import (
"context"
"net/http"
sentry "github.com/getsentry/sentry-go"
"github.com/go-chi/chi"
"go.uber.org/zap"
@ -27,6 +28,8 @@ func Init(ctx context.Context, config *Config) *Websocket {
events := repository.Events()
go func() {
defer sentry.Recover()
for {
if err := eq.feedSessions(ctx, events, store); err != nil {
if err == context.Canceled {

View File

@ -5,6 +5,7 @@ import (
"sync"
"time"
sentry "github.com/getsentry/sentry-go"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
"go.uber.org/zap"
@ -110,6 +111,8 @@ func (sess *Session) connected() (err error) {
// Create a heartbeat every minute for this user
go func() {
defer sentry.Recover()
t := time.NewTicker(time.Second * 60)
for {
select {

View File

@ -73,7 +73,6 @@ func LogResponse(next http.Handler) http.Handler {
}()
next.ServeHTTP(wrapped, req)
})
}

View File

@ -2,23 +2,47 @@ package api
import (
"net/http"
"os"
"runtime/debug"
"github.com/go-chi/chi/middleware"
"go.uber.org/zap"
sentryhttp "github.com/getsentry/sentry-go/http"
)
func Base() []func(http.Handler) http.Handler {
func Base(log *zap.Logger) []func(http.Handler) http.Handler {
return []func(http.Handler) http.Handler{
handleCORS,
middleware.RealIP,
middleware.RequestID,
contextLogger(log),
}
}
func Logging(log *zap.Logger) []func(http.Handler) http.Handler {
return []func(http.Handler) http.Handler{
contextLogger(log),
LogRequest,
LogResponse,
}
func Sentry() func(http.Handler) http.Handler {
return sentryhttp.New(sentryhttp.Options{
Repanic: true,
}).Handle
}
// HandlePanic sends 500 error when panic occurs inside the request call
func HandlePanic(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
defer func() {
if err := recover(); err != nil {
w.WriteHeader(500)
if _, has := os.LookupEnv("DEBUG_DUMP_STACK_IN_RESPONSE"); has {
// Provide nice call stack on endpoint when
// we crash
w.Write(debug.Stack())
}
return
}
}()
next.ServeHTTP(w, req)
})
}

View File

@ -86,12 +86,28 @@ func (s Server) Serve(ctx context.Context) {
router := chi.NewRouter()
router.Use(Base()...)
// Base middleware, CORS, RealIP, RequestID, context-logger
router.Use(Base(s.log)...)
if s.httpOpt.Logging {
router.Use(Logging(s.log)...)
// Logging request if enabled
if s.httpOpt.LogRequest {
router.Use(LogRequest)
}
// Logging response if enabled
if s.httpOpt.LogResponse {
router.Use(LogResponse)
}
// Handle panic (sets 500 Server error headers)
router.Use(HandlePanic)
// Reports error to Sentry if enabled
if s.httpOpt.EnablePanicReporting {
router.Use(Sentry())
}
// Metrics tracking middleware
if s.httpOpt.EnableMetrics {
router.Use(Middleware(s.httpOpt.MetricsServiceLabel))
}

View File

@ -5,6 +5,7 @@ import (
"os"
"time"
"github.com/getsentry/sentry-go"
"go.uber.org/zap"
"github.com/cortezaproject/corteza-server/internal/auth"
@ -28,6 +29,29 @@ func InitGeneralServices(logOpt *options.LogOpt, smtpOpt *options.SMTPOpt, jwtOp
)
}
func InitSentry(sentryOpt *options.SentryOpt) error {
if sentryOpt.DSN == "" {
return nil
}
return sentry.Init(sentry.ClientOptions{
Dsn: sentryOpt.DSN,
Debug: sentryOpt.Debug,
AttachStacktrace: sentryOpt.AttachStacktrace,
SampleRate: sentryOpt.SampleRate,
MaxBreadcrumbs: sentryOpt.MaxBreadcrumbs,
IgnoreErrors: nil,
BeforeSend: nil,
BeforeBreadcrumb: nil,
Integrations: nil,
Transport: nil,
ServerName: sentryOpt.ServerName,
Release: sentryOpt.Release,
Dist: sentryOpt.Dist,
Environment: sentryOpt.Environment,
})
}
func HandleError(err error) {
if err == nil {
return

View File

@ -50,6 +50,11 @@ func fill(opt interface{}, pfix string) {
continue
}
if f.Kind() == reflect.Float32 {
v.FieldByName(t.Name).SetFloat(float64(EnvFloat32(pfix, tag, float32(f.Float()))))
continue
}
panic("unsupported type/kind for field " + t.Name)
}
@ -94,6 +99,17 @@ func EnvInt(pfix, key string, def int) int {
return def
}
func EnvFloat32(pfix, key string, def float32) float32 {
for _, key = range makeEnvKeys(pfix, key) {
if val, has := os.LookupEnv(key); has {
if i, err := cast.ToFloat32E(val); err == nil {
return i
}
}
}
return def
}
func EnvDuration(pfix, key string, def time.Duration) time.Duration {
for _, key = range makeEnvKeys(pfix, key) {
if val, has := os.LookupEnv(key); has {

View File

@ -6,9 +6,10 @@ import (
type (
HTTPOpt struct {
Addr string `env:"HTTP_ADDR"`
Logging bool `env:"HTTP_LOG_REQUESTS"`
Tracing bool `env:"HTTP_ERROR_TRACING"`
Addr string `env:"HTTP_ADDR"`
LogRequest bool `env:"HTTP_LOG_REQUEST"`
LogResponse bool `env:"HTTP_LOG_RESPONSE"`
Tracing bool `env:"HTTP_ERROR_TRACING"`
EnableVersionRoute bool `env:"HTTP_ENABLE_VERSION_ROUTE"`
EnableDebugRoute bool `env:"HTTP_ENABLE_DEBUG_ROUTE"`
@ -17,13 +18,16 @@ type (
MetricsServiceLabel string `env:"HTTP_METRICS_NAME"`
MetricsUsername string `env:"HTTP_METRICS_USERNAME"`
MetricsPassword string `env:"HTTP_METRICS_PASSWORD"`
EnablePanicReporting bool `env:"HTTP_REPORT_PANIC"`
}
)
func HTTP(pfix string) (o *HTTPOpt) {
o = &HTTPOpt{
Addr: ":80",
Logging: true,
LogRequest: false,
LogResponse: false,
Tracing: false,
EnableVersionRoute: true,
EnableDebugRoute: false,
@ -31,6 +35,9 @@ func HTTP(pfix string) (o *HTTPOpt) {
MetricsServiceLabel: "corteza",
MetricsUsername: "metrics",
// Reports panics to Sentry throught HTTP middleware
EnablePanicReporting: true,
// Setting metrics password to random string to prevent security accidents...
MetricsPassword: string(rand.Bytes(5)),
}

34
pkg/cli/options/sentry.go Normal file
View File

@ -0,0 +1,34 @@
package options
import (
"github.com/cortezaproject/corteza-server/internal/version"
)
type (
SentryOpt struct {
DSN string `env:"SENTRY_DSN"`
Debug bool `env:"SENTRY_DEBUG"`
AttachStacktrace bool `env:"SENTRY_ATTACH_STACKTRACE"`
SampleRate float32 `env:"SENTRY_SAMPLE_RATE"`
MaxBreadcrumbs int `env:"SENTRY_MAX_BREADCRUMBS"`
ServerName string `env:"SENTRY_SERVERNAME"`
Release string `env:"SENTRY_RELEASE"`
Dist string `env:"SENTRY_DIST"`
Environment string `env:"SENTRY_ENVIRONMENT"`
}
)
func Sentry(pfix string) (o *SentryOpt) {
o = &SentryOpt{
AttachStacktrace: true,
MaxBreadcrumbs: 0,
Release: version.Version,
}
fill(o, pfix)
return
}

View File

@ -3,6 +3,7 @@ package cli
import (
"context"
"github.com/getsentry/sentry-go"
"github.com/go-chi/chi"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -48,6 +49,7 @@ type (
HttpClientOpt *options.HttpClientOpt
DbOpt *options.DBOpt
ProvisionOpt *options.ProvisionOpt
SentryOpt *options.SentryOpt
// DB Connection name, defaults to ServiceName
DatabaseName string
@ -179,6 +181,7 @@ func (c *Config) Init() {
c.HttpClientOpt = options.HttpClient(c.EnvPrefix)
c.DbOpt = options.DB(c.ServiceName)
c.ProvisionOpt = options.Provision(c.ServiceName)
c.SentryOpt = options.Sentry(c.EnvPrefix)
if c.RootCommandDBSetup == nil {
c.RootCommandDBSetup = Runners{func(ctx context.Context, cmd *cobra.Command, c *Config) (err error) {
@ -213,6 +216,11 @@ func (c *Config) MakeCLI(ctx context.Context) (cmd *cobra.Command) {
Use: c.RootCommandName,
TraverseChildren: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
if err = InitSentry(c.SentryOpt); err != nil {
c.Log.Error("could not initialize Sentry", zap.Error(err))
}
defer sentry.Recover()
InitGeneralServices(c.LogOpt, c.SmtpOpt, c.JwtOpt, c.HttpClientOpt)
err = c.RootCommandDBSetup.Run(ctx, cmd, c)
@ -232,6 +240,7 @@ func (c *Config) MakeCLI(ctx context.Context) (cmd *cobra.Command) {
}
serveApiCmd := c.ApiServer.Command(ctx, c.ApiServerCommandName, c.EnvPrefix, func(ctx context.Context) (err error) {
defer sentry.Recover()
return c.ApiServerPreRun.Run(ctx, cmd, c)
})

View File

@ -49,7 +49,6 @@ func Configure() *cli.Config {
ApiServerPreRun: cli.Runners{
func(ctx context.Context, cmd *cobra.Command, c *cli.Config) error {
external.Init(service.DefaultIntSettings)
go service.Watchers(ctx)
return nil
},

12
vendor/github.com/getsentry/sentry-go/.craft.yml generated vendored Normal file
View File

@ -0,0 +1,12 @@
github:
owner: getsentry
repo: sentry-go
preReleaseCommand: bash scripts/craft-pre-release.sh
changelogPolicy: simple
targets:
- name: github
tagPrefix: v
- name: registry
type: sdk
config:
canonical: "github:getsentry/sentry-go"

6
vendor/github.com/getsentry/sentry-go/.gitignore generated vendored Normal file
View File

@ -0,0 +1,6 @@
coverage.txt
# Just my personal way of tracking stuff — Kamil
FIXME.md
TODO.md
!NOTES.md

9
vendor/github.com/getsentry/sentry-go/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,9 @@
linters:
enable-all: true
disable:
- megacheck
- stylecheck
run:
skip-dirs:
- echo
- example/echo

28
vendor/github.com/getsentry/sentry-go/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,28 @@
sudo: false
language: go
go:
- 1.10.x
- 1.11.x
- 1.12.x
env:
- GO111MODULE=on
before_install:
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.15.0
script:
- golangci-lint run
- go build
- go test
notifications:
webhooks:
urls:
- https://zeus.ci/hooks/befe9810-9285-11e9-b01a-0a580a281808/public/provider/travis/webhook
on_success: always
on_failure: always
on_start: always
on_cancel: always
on_error: always

64
vendor/github.com/getsentry/sentry-go/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,64 @@
# Changelog
## v0.1.1
- fix: Check for initialized Client in AddBreadcrumbs (#20)
- build: Bump version when releasing with Craft (#19)
## v0.1.0
- First stable release! \o/
## v0.0.1-beta.5
- feat: **[breaking]** Add `NewHTTPTransport` and `NewHTTPSyncTransport` which accepts all transport options
- feat: New `HTTPSyncTransport` that blocks after each call
- feat: New `Echo` integration
- ref: **[breaking]** Remove `BufferSize` option from `ClientOptions` and move it to `HTTPTransport` instead
- ref: Export default `HTTPTransport`
- ref: Export `net/http` integration handler
- ref: Set `Request` instantly in the package handlers, not in `recoverWithSentry` so it can be accessed later on
- ci: Add craft config
## v0.0.1-beta.4
- feat: `IgnoreErrors` client option and corresponding integration
- ref: Reworked `net/http` integration, wrote better example and complete readme
- ref: Reworked `Gin` integration, wrote better example and complete readme
- ref: Reworked `Iris` integration, wrote better example and complete readme
- ref: Reworked `Negroni` integration, wrote better example and complete readme
- ref: Reworked `Martini` integration, wrote better example and complete readme
- ref: Remove `Handle()` from frameworks handlers and return it directly from New
## v0.0.1-beta.3
- feat: `Iris` framework support with `sentryiris` package
- feat: `Gin` framework support with `sentrygin` package
- feat: `Martini` framework support with `sentrymartini` package
- feat: `Negroni` framework support with `sentrynegroni` package
- feat: Add `Hub.Clone()` for easier frameworks integration
- feat: Return `EventID` from `Recovery` methods
- feat: Add `NewScope` and `NewEvent` functions and use them in the whole codebase
- feat: Add `AddEventProcessor` to the `Client`
- fix: Operate on requests body copy instead of the original
- ref: Try to read source files from the root directory, based on the filename as well, to make it work on AWS Lambda
- ref: Remove `gocertifi` dependence and document how to provide your own certificates
- ref: **[breaking]** Remove `Decorate` and `DecorateFunc` methods in favor of `sentryhttp` package
- ref: **[breaking]** Allow for integrations to live on the client, by passing client instance in `SetupOnce` method
- ref: **[breaking]** Remove `GetIntegration` from the `Hub`
- ref: **[breaking]** Remove `GlobalEventProcessors` getter from the public API
## v0.0.1-beta.2
- feat: Add `AttachStacktrace` client option to include stacktrace for messages
- feat: Add `BufferSize` client option to configure transport buffer size
- feat: Add `SetRequest` method on a `Scope` to control `Request` context data
- feat: Add `FromHTTPRequest` for `Request` type for easier extraction
- ref: Extract `Request` information more accurately
- fix: Attach `ServerName`, `Release`, `Dist`, `Environment` options to the event
- fix: Don't log events dropped due to full transport buffer as sent
- fix: Don't panic and create an appropriate event when called `CaptureException` or `Recover` with `nil` value
## v0.0.1-beta
- Initial release

41
vendor/github.com/getsentry/sentry-go/CONTRIBUTION.md generated vendored Normal file
View File

@ -0,0 +1,41 @@
## Testing
```bash
$ go test
```
### Watch mode
Use: https://github.com/cespare/reflex
```bash
$ reflex -g '*.go' -d "none" -- sh -c 'printf "\n"; go test'
```
### With data race detection
```bash
$ go test -race
```
### Coverage
```bash
$ go test -race -coverprofile=coverage.txt -covermode=atomic && go tool cover -html coverage.txt
```
## Linting
```bash
$ golangci-lint run
```
## Release
1. Update changelog with new version in `vX.X.X` format title and list of changes
2. Commit with `release: X.X.X` commit message and push to `master`
3. Let `craft` do the rest
```bash
$ craft prepare X.X.X
$ craft publish X.X.X --skip-status-check
```

9
vendor/github.com/getsentry/sentry-go/LICENSE generated vendored Normal file
View File

@ -0,0 +1,9 @@
Copyright (c) 2019 Sentry (https://sentry.io) and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

392
vendor/github.com/getsentry/sentry-go/MIGRATION.md generated vendored Normal file
View File

@ -0,0 +1,392 @@
# `raven-go` to `sentry-go` Migration Guide
## Installation
raven-go
```go
go get github.com/getsentry/raven-go
```
sentry-go
```go
go get github.com/getsentry/sentry-go@v0.0.1
```
## Configuration
raven-go
```go
import "github.com/getsentry/raven-go"
func main() {
raven.SetDSN("https://16427b2f210046b585ee51fd8a1ac54f@sentry.io/1")
}
```
sentry-go
```go
import (
"fmt"
"github.com/getsentry/sentry-go"
)
func main() {
err := sentry.Init(sentry.ClientOptions{
Dsn: "https://16427b2f210046b585ee51fd8a1ac54f@sentry.io/1",
})
if err != nil {
fmt.Printf("Sentry initialization failed: %v\n", err)
}
}
```
raven-go
```go
SetDSN()
SetDefaultLoggerName()
SetDebug()
SetEnvironment()
SetRelease()
SetSampleRate()
SetIgnoreErrors()
SetIncludePaths()
```
sentry-go
```go
sentry.Init(sentry.ClientOptions{
Dsn: "https://16427b2f210046b585ee51fd8a1ac54f@sentry.io/1",
DebugWriter: os.Stderr,
Debug: true,
Environment: "environment",
Release: "release",
SampleRate: 0.5,
// IgnoreErrors: TBD,
// IncludePaths: TBD
})
```
Available options: see [Configuration](https://docs.sentry.io/platforms/go/config/) section.
### Providing SSL Certificates
By default, TLS uses the host's root CA set. If you don't have `ca-certificates` (which should be your go-to way of fixing the issue of missing ceritificates) and want to use `gocertifi` instead, you can provide pre-loaded cert files as one of the options to the `sentry.Init` call:
```go
package main
import (
"log"
"github.com/certifi/gocertifi"
"github.com/getsentry/sentry-go"
)
sentryClientOptions := sentry.ClientOptions{
Dsn: "https://16427b2f210046b585ee51fd8a1ac54f@sentry.io/1",
}
rootCAs, err := gocertifi.CACerts()
if err != nil {
log.Println("Coudnt load CA Certificates: %v\n", err)
} else {
sentryClientOptions.CaCerts = rootCAs
}
sentry.Init(sentryClientOptions)
```
## Usage
### Capturing Errors
raven-go
```go
f, err := os.Open("filename.ext")
if err != nil {
raven.CaptureError(err, nil)
}
```
sentry-go
```go
f, err := os.Open("filename.ext")
if err != nil {
sentry.CaptureException(err)
}
```
### Capturing Panics
raven-go
```go
raven.CapturePanic(func() {
// do all of the scary things here
}, nil)
```
sentry-go
```go
func() {
defer sentry.Recover()
// do all of the scary things here
}()
```
### Capturing Messages
raven-go
```go
raven.CaptureMessage("Something bad happened and I would like to know about that")
```
sentry-go
```go
sentry.CaptureMessage("Something bad happened and I would like to know about that")
```
### Capturing Events
raven-go
```go
packet := &raven.Packet{
Message: "Hand-crafted event",
Extra: &raven.Extra{
"runtime.Version": runtime.Version(),
"runtime.NumCPU": runtime.NumCPU(),
},
}
raven.Capture(packet)
```
sentry-go
```go
event := &sentry.NewEvent()
event.Message = "Hand-crafted event"
event.Extra["runtime.Version"] = runtime.Version()
event.Extra["runtime.NumCPU"] = runtime.NumCPU()
sentry.CaptureEvent(event)
```
### Additional Data
See Context section.
### Event Sampling
raven-go
```go
raven.SetSampleRate(0.25)
```
sentry-go
```go
sentry.Init(sentry.ClientOptions{
SampleRate: 0.25,
})
```
### Awaiting the response (not recommended)
```go
raven.CaptureMessageAndWait("Something bad happened and I would like to know about that")
```
sentry-go
```go
sentry.CaptureMessage("Something bad happened and I would like to know about that")
if sentry.Flush(time.Second * 2) {
// event delivered
} else {
// timeout reached
}
```
## Context
### Per-event
raven-go
```go
raven.CaptureError(err, map[string]string{"browser": "Firefox"}, &raven.Http{
Method: "GET",
URL: "https://example.com/raven-go"
})
```
sentry-go
```go
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetTag("browser", "Firefox")
scope.SetContext("Request", map[string]string{
"Method": "GET",
"URL": "https://example.com/raven-go",
})
sentry.CaptureException(err)
})
```
### Globally
#### SetHttpContext
raven-go
```go
raven.SetHttpContext(&raven.Http{
Method: "GET",
URL: "https://example.com/raven-go",
})
```
sentry-go
```go
sentry.ConfigureScope(func(scope *sentry.Scope) {
scope.SetContext("Request", map[string]string{
"Method": "GET",
"URL": "https://example.com/raven-go",
})
})
```
#### SetTagsContext
raven-go
```go
t := map[string]string{"day": "Friday", "sport": "Weightlifting"}
raven.SetTagsContext(map[string]string{"day": "Friday", "sport": "Weightlifting"})
```
sentry-go
```go
sentry.ConfigureScope(func(scope *sentry.Scope) {
scope.SetTags(map[string]string{"day": "Friday", "sport": "Weightlifting"})
})
```
#### SetUserContext
raven-go
```go
raven.SetUserContext(&raven.User{
ID: "1337",
Username: "kamilogorek",
Email: "kamil@sentry.io",
IP: "127.0.0.1",
})
```
sentry-go
```go
sentry.ConfigureScope(func(scope *sentry.Scope) {
scope.SetUser(sentry.User{
ID: "1337",
Username: "kamilogorek",
Email: "kamil@sentry.io",
IPAddress: "127.0.0.1",
})
})
```
#### ClearContext
raven-go
```go
raven.ClearContext()
```
sentry-go
```go
sentry.ConfigureScope(func(scope *sentry.Scope) {
scope.Clear()
})
```
#### WrapWithExtra
raven-go
```go
path := "filename.ext"
f, err := os.Open(path)
if err != nil {
err = raven.WrapWithExtra(err, map[string]string{"path": path, "cwd": os.Getwd()}
raven.CaptureError(err, nil)
}
```
sentry-go
```go
// use `sentry.WithScope`, see "Context / Per-event Section"
path := "filename.ext"
f, err := os.Open(path)
if err != nil {
sentry.WithScope(func(scope *sentry.Scope) {
sentry.SetExtras(map[string]interface{}{"path": path, "cwd": os.Getwd())
sentry.CaptureException(err)
})
}
```
## Integrations
### net/http
raven-go
```go
mux := http.NewServeMux
http.Handle("/", raven.Recoverer(mux))
// or
func root(w http.ResponseWriter, r *http.Request) {}
http.HandleFunc("/", raven.RecoveryHandler(root))
```
sentry-go
```go
sentryHandler := sentryhttp.New(sentryhttp.Options{
Repanic: false,
WaitForDelivery: true,
})
mux := http.NewServeMux
http.Handle("/", sentryHandler.Handle(mux))
// or
func root(w http.ResponseWriter, r *http.Request) {}
http.HandleFunc("/", sentryHandler.HandleFunc(root))
```

107
vendor/github.com/getsentry/sentry-go/README.md generated vendored Normal file
View File

@ -0,0 +1,107 @@
<p align="center">
<a href="https://sentry.io" target="_blank" align="center">
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
</a>
<br />
</p>
# Official Sentry SDK for Go
[![Build Status](https://travis-ci.com/getsentry/sentry-go.svg?branch=master)](https://travis-ci.com/getsentry/sentry-go)
[![Go Report Card](https://goreportcard.com/badge/github.com/getsentry/sentry-go)](https://goreportcard.com/report/github.com/getsentry/sentry-go)
`sentry-go` provides a Sentry client implementation for the Go programming language. This is the next line of the Go SDK for [Sentry](https://sentry.io/), intended to replace the `raven-go` package.
> Looking for the old `raven-go` SDK documentation? See the Legacy client section [here](https://docs.sentry.io/clients/go/).
> If you want to start using sentry-go instead, check out the [migration guide](https://docs.sentry.io/platforms/go/migration/).
## Requirements
We verify this package against N-2 recent versions of Go compiler. As of June 2019, those versions are:
* 1.10
* 1.11
* 1.12
## Installation
`sentry-go` can be installed like any other Go library through `go get`:
```bash
$ go get github.com/getsentry/sentry-go
```
Or, if you are already using Go Modules, specify a version number as well:
```bash
$ go get github.com/getsentry/sentry-go@v0.1.0
```
## Configuration
To use `sentry-go`, youll need to import the `sentry-go` package and initialize it with the client options that will include your DSN. If you specify the `SENTRY_DSN` environment variable, you can omit this value from options and it will be picked up automatically for you. The release and environment can also be specified in the environment variables `SENTRY_RELEASE` and `SENTRY_ENVIRONMENT` respectively.
More on this in [Configuration](https://docs.sentry.io/platforms/go/config/) section.
## Usage
By default, Sentry Go SDK uses asynchronous transport, which in the code example below requires an explicit awaiting for event delivery to be finished using `sentry.Flush` method. It is necessary, because otherwise the program would not wait for the async HTTP calls to return a response, and exit the process immediately when it reached the end of the `main` function. It would not be required inside a running goroutine or if you would use `HTTPSyncTransport`, which you can read about in `Transports` section.
```go
package main
import (
"fmt"
"os"
"time"
"github.com/getsentry/sentry-go"
)
func main() {
err := sentry.Init(sentry.ClientOptions{
Dsn: "___DSN___",
})
if err != nil {
fmt.Printf("Sentry initialization failed: %v\n", err)
}
f, err := os.Open("filename.ext")
if err != nil {
sentry.CaptureException(err)
sentry.Flush(time.Second * 5)
}
}
```
For more detailed information about how to get the most out of `sentry-go` there is additional documentation available:
- [Configuration](https://docs.sentry.io/platforms/go/config)
- [Error Reporting](https://docs.sentry.io/error-reporting/quickstart?platform=go)
- [Enriching Error Data](https://docs.sentry.io/enriching-error-data/context?platform=go)
- [Transports](https://docs.sentry.io/platforms/go/transports)
- [Integrations](https://docs.sentry.io/platforms/go/integrations)
- [net/http](https://docs.sentry.io/platforms/go/http)
- [echo](https://docs.sentry.io/platforms/go/echo)
- [gin](https://docs.sentry.io/platforms/go/gin)
- [iris](https://docs.sentry.io/platforms/go/iris)
- [martini](https://docs.sentry.io/platforms/go/martini)
- [negroni](https://docs.sentry.io/platforms/go/negroni)
## Resources:
- [Bug Tracker](https://github.com/getsentry/sentry-go/issues)
- [GitHub Project](https://github.com/getsentry/sentry-go)
- [Godocs](https://godoc.org/github.com/getsentry/sentry-go)
- [@getsentry](https://twitter.com/getsentry) on Twitter for updates
## License
Licensed under the BSD license, see `LICENSE`
## Community
Want to join our Sentry's `community-golang` channel, get involved and help us improve the SDK?
Do not hesistate to shoot me up an email at [kamil@sentry.io](mailto:kamil@sentry.io) for Slack invite!

425
vendor/github.com/getsentry/sentry-go/client.go generated vendored Normal file
View File

@ -0,0 +1,425 @@
package sentry
import (
"context"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"reflect"
"sort"
"time"
)
// Logger is an instance of log.Logger that is use to provide debug information about running Sentry Client
// can be enabled by either using `Logger.SetOutput` directly or with `Debug` client option
var Logger = log.New(ioutil.Discard, "[Sentry] ", log.LstdFlags) // nolint: gochecknoglobals
type EventProcessor func(event *Event, hint *EventHint) *Event
type EventModifier interface {
ApplyToEvent(event *Event, hint *EventHint) *Event
}
var globalEventProcessors []EventProcessor // nolint: gochecknoglobals
func AddGlobalEventProcessor(processor EventProcessor) {
globalEventProcessors = append(globalEventProcessors, processor)
}
// Integration allows for registering a functions that modify or discard captured events.
type Integration interface {
Name() string
SetupOnce(client *Client)
}
// ClientOptions that configures a SDK Client
type ClientOptions struct {
// The DSN to use. If the DSN is not set, the client is effectively disabled.
Dsn string
// In debug mode, the debug information is printed to stdout to help you understand what
// sentry is doing.
Debug bool
// Configures whether SDK should generate and attach stacktraces to pure capture message calls.
AttachStacktrace bool
// The sample rate for event submission (0.0 - 1.0, defaults to 1.0).
SampleRate float32
// List of regexp strings that will be used to match against event's message
// and if applicable, caught errors type and value.
// If the match is found, then a whole event will be dropped.
IgnoreErrors []string
// Before send callback.
BeforeSend func(event *Event, hint *EventHint) *Event
// Before breadcrumb add callback.
BeforeBreadcrumb func(breadcrumb *Breadcrumb, hint *BreadcrumbHint) *Breadcrumb
// Integrations to be installed on the current Client, receives default integrations
Integrations func([]Integration) []Integration
// io.Writer implementation that should be used with the `Debug` mode
DebugWriter io.Writer
// The transport to use.
// This is an instance of a struct implementing `Transport` interface.
// Defaults to `httpTransport` from `transport.go`
Transport Transport
// The server name to be reported.
ServerName string
// The release to be sent with events.
Release string
// The dist to be sent with events.
Dist string
// The environment to be sent with events.
Environment string
// Maximum number of breadcrumbs.
MaxBreadcrumbs int
// An optional pointer to `http.Transport` that will be used with a default HTTPTransport.
HTTPTransport *http.Transport
// An optional HTTP proxy to use.
// This will default to the `http_proxy` environment variable.
// or `https_proxy` if that one exists.
HTTPProxy string
// An optional HTTPS proxy to use.
// This will default to the `HTTPS_PROXY` environment variable
// or `http_proxy` if that one exists.
HTTPSProxy string
// An optionsl CaCerts to use.
// Defaults to `gocertifi.CACerts()`.
CaCerts *x509.CertPool
}
// Client is the underlying processor that's used by the main API and `Hub` instances.
type Client struct {
options ClientOptions
dsn *Dsn
eventProcessors []EventProcessor
integrations []Integration
Transport Transport
}
// NewClient creates and returns an instance of `Client` configured using `ClientOptions`.
func NewClient(options ClientOptions) (*Client, error) {
if options.Debug {
debugWriter := options.DebugWriter
if debugWriter == nil {
debugWriter = os.Stdout
}
Logger.SetOutput(debugWriter)
}
if options.Dsn == "" {
options.Dsn = os.Getenv("SENTRY_DSN")
}
if options.Release == "" {
options.Release = os.Getenv("SENTRY_RELEASE")
}
if options.Environment == "" {
options.Environment = os.Getenv("SENTRY_ENVIRONMENT")
}
dsn, err := NewDsn(options.Dsn)
if err != nil {
return nil, err
}
if dsn == nil {
Logger.Println("Sentry client initialized with an empty DSN")
}
client := Client{
options: options,
dsn: dsn,
}
client.setupTransport()
client.setupIntegrations()
return &client, nil
}
func (client *Client) setupTransport() {
transport := client.options.Transport
if transport == nil {
transport = NewHTTPTransport()
}
transport.Configure(client.options)
client.Transport = transport
}
func (client *Client) setupIntegrations() {
integrations := []Integration{
new(environmentIntegration),
new(modulesIntegration),
new(ignoreErrorsIntegration),
}
if client.options.Integrations != nil {
integrations = client.options.Integrations(integrations)
}
for _, integration := range integrations {
if client.integrationAlreadyInstalled(integration.Name()) {
Logger.Printf("Integration %s is already installed\n", integration.Name())
continue
}
client.integrations = append(client.integrations, integration)
integration.SetupOnce(client)
Logger.Printf("Integration installed: %s\n", integration.Name())
}
}
// AddEventProcessor adds an event processor to the client.
func (client *Client) AddEventProcessor(processor EventProcessor) {
client.eventProcessors = append(client.eventProcessors, processor)
}
// Options return `ClientOptions` for the current `Client`.
func (client Client) Options() ClientOptions {
return client.options
}
// CaptureMessage captures an arbitrary message.
func (client *Client) CaptureMessage(message string, hint *EventHint, scope EventModifier) *EventID {
event := client.eventFromMessage(message, LevelInfo)
return client.CaptureEvent(event, hint, scope)
}
// CaptureException captures an error.
func (client *Client) CaptureException(exception error, hint *EventHint, scope EventModifier) *EventID {
event := client.eventFromException(exception, LevelError)
return client.CaptureEvent(event, hint, scope)
}
// CaptureEvent captures an event on the currently active client if any.
//
// The event must already be assembled. Typically code would instead use
// the utility methods like `CaptureException`. The return value is the
// event ID. In case Sentry is disabled or event was dropped, the return value will be nil.
func (client *Client) CaptureEvent(event *Event, hint *EventHint, scope EventModifier) *EventID {
return client.processEvent(event, hint, scope)
}
// Recover captures a panic.
// Returns `EventID` if successfully, or `nil` if there's no error to recover from.
func (client *Client) Recover(err interface{}, hint *EventHint, scope EventModifier) *EventID {
if err == nil {
err = recover()
}
if err != nil {
if err, ok := err.(error); ok {
event := client.eventFromException(err, LevelFatal)
return client.CaptureEvent(event, hint, scope)
}
if err, ok := err.(string); ok {
event := client.eventFromMessage(err, LevelFatal)
return client.CaptureEvent(event, hint, scope)
}
}
return nil
}
// Recover captures a panic and passes relevant context object.
// Returns `EventID` if successfully, or `nil` if there's no error to recover from.
func (client *Client) RecoverWithContext(
ctx context.Context,
err interface{},
hint *EventHint,
scope EventModifier,
) *EventID {
if err == nil {
err = recover()
}
if err != nil {
if hint.Context == nil && ctx != nil {
hint.Context = ctx
}
if err, ok := err.(error); ok {
event := client.eventFromException(err, LevelFatal)
return client.CaptureEvent(event, hint, scope)
}
if err, ok := err.(string); ok {
event := client.eventFromMessage(err, LevelFatal)
return client.CaptureEvent(event, hint, scope)
}
}
return nil
}
// Flush notifies when all the buffered events have been sent by returning `true`
// or `false` if timeout was reached. It calls `Flush` method of the configured `Transport`.
func (client *Client) Flush(timeout time.Duration) bool {
return client.Transport.Flush(timeout)
}
func (client *Client) eventFromMessage(message string, level Level) *Event {
event := NewEvent()
event.Level = level
event.Message = message
if client.Options().AttachStacktrace {
event.Threads = []Thread{{
Stacktrace: NewStacktrace(),
Crashed: false,
Current: true,
}}
}
return event
}
func (client *Client) eventFromException(exception error, level Level) *Event {
if exception == nil {
event := NewEvent()
event.Level = level
event.Message = fmt.Sprintf("Called %s with nil value", callerFunctionName())
return event
}
stacktrace := ExtractStacktrace(exception)
if stacktrace == nil {
stacktrace = NewStacktrace()
}
event := NewEvent()
event.Level = level
event.Exception = []Exception{{
Value: exception.Error(),
Type: reflect.TypeOf(exception).String(),
Stacktrace: stacktrace,
}}
return event
}
func (client *Client) processEvent(event *Event, hint *EventHint, scope EventModifier) *EventID {
options := client.Options()
// TODO: Reconsider if its worth going away from default implementation
// of other SDKs. In Go zero value (default) for float32 is 0.0,
// which means that if someone uses ClientOptions{} struct directly
// and we would not check for 0 here, we'd skip all events by default
if options.SampleRate != 0.0 {
randomFloat := rand.New(rand.NewSource(time.Now().UnixNano())).Float32()
if randomFloat > options.SampleRate {
Logger.Println("Event dropped due to SampleRate hit")
return nil
}
}
if event = client.prepareEvent(event, hint, scope); event == nil {
return nil
}
if options.BeforeSend != nil {
h := &EventHint{}
if hint != nil {
h = hint
}
if event = options.BeforeSend(event, h); event == nil {
Logger.Println("Event dropped due to BeforeSend callback")
return nil
}
}
client.Transport.SendEvent(event)
return &event.EventID
}
func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventModifier) *Event {
if event.EventID == "" {
event.EventID = EventID(uuid())
}
if event.Timestamp == 0 {
event.Timestamp = time.Now().Unix()
}
if event.Level == "" {
event.Level = LevelInfo
}
if event.ServerName == "" {
if client.Options().ServerName != "" {
event.ServerName = client.Options().ServerName
} else if hostname, err := os.Hostname(); err == nil {
event.ServerName = hostname
}
}
if event.Release == "" && client.Options().Release != "" {
event.Release = client.Options().Release
}
if event.Dist == "" && client.Options().Dist != "" {
event.Dist = client.Options().Dist
}
if event.Environment == "" && client.Options().Environment != "" {
event.Environment = client.Options().Environment
}
event.Platform = "go"
event.Sdk = SdkInfo{
Name: "sentry.go",
Version: Version,
Integrations: client.listIntegrations(),
Packages: []SdkPackage{{
Name: "sentry-go",
Version: Version,
}},
}
event = scope.ApplyToEvent(event, hint)
for _, processor := range client.eventProcessors {
id := event.EventID
event = processor(event, hint)
if event == nil {
Logger.Printf("Event dropped by one of the Client EventProcessors: %s\n", id)
return nil
}
}
for _, processor := range globalEventProcessors {
id := event.EventID
event = processor(event, hint)
if event == nil {
Logger.Printf("Event dropped by one of the Global EventProcessors: %s\n", id)
return nil
}
}
return event
}
func (client Client) listIntegrations() []string {
integrations := make([]string, 0, len(client.integrations))
for _, integration := range client.integrations {
integrations = append(integrations, integration.Name())
}
sort.Strings(integrations)
return integrations
}
func (client Client) integrationAlreadyInstalled(name string) bool {
for _, integration := range client.integrations {
if integration.Name() == name {
return true
}
}
return false
}

185
vendor/github.com/getsentry/sentry-go/dsn.go generated vendored Normal file
View File

@ -0,0 +1,185 @@
package sentry
import (
"encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
"time"
)
type scheme string
const (
schemeHTTP scheme = "http"
schemeHTTPS scheme = "https"
)
func (scheme scheme) defaultPort() int {
switch scheme {
case schemeHTTPS:
return 443
case schemeHTTP:
return 80
default:
return 80
}
}
type DsnParseError struct {
Message string
}
func (e DsnParseError) Error() string {
return "[Sentry] DsnParseError: " + e.Message
}
// Dsn is used as the remote address source to client transport.
type Dsn struct {
scheme scheme
publicKey string
secretKey string
host string
port int
path string
projectID int
}
// NewDsn creates an instance od `Dsn` by parsing provided url in a `string` format.
// If Dsn is not set the client is effectively disabled.
func NewDsn(rawURL string) (*Dsn, error) {
// Parse
parsedURL, err := url.Parse(rawURL)
if err != nil {
return nil, &DsnParseError{"invalid url"}
}
// Scheme
var scheme scheme
switch parsedURL.Scheme {
case "http":
scheme = schemeHTTP
case "https":
scheme = schemeHTTPS
default:
return nil, &DsnParseError{"invalid scheme"}
}
// PublicKey
publicKey := parsedURL.User.Username()
if publicKey == "" {
return nil, &DsnParseError{"empty username"}
}
// SecretKey
var secretKey string
if parsedSecretKey, ok := parsedURL.User.Password(); ok {
secretKey = parsedSecretKey
}
// Host
host := parsedURL.Hostname()
if host == "" {
return nil, &DsnParseError{"empty host"}
}
// Port
var port int
if parsedURL.Port() != "" {
parsedPort, err := strconv.Atoi(parsedURL.Port())
if err != nil {
return nil, &DsnParseError{"invalid port"}
}
port = parsedPort
} else {
port = scheme.defaultPort()
}
// ProjectID
if len(parsedURL.Path) == 0 || parsedURL.Path == "/" {
return nil, &DsnParseError{"empty project id"}
}
pathSegments := strings.Split(parsedURL.Path[1:], "/")
projectID, err := strconv.Atoi(pathSegments[len(pathSegments)-1])
if err != nil {
return nil, &DsnParseError{"invalid project id"}
}
// Path
var path string
if len(pathSegments) > 1 {
path = "/" + strings.Join(pathSegments[0:len(pathSegments)-1], "/")
}
return &Dsn{
scheme: scheme,
publicKey: publicKey,
secretKey: secretKey,
host: host,
port: port,
path: path,
projectID: projectID,
}, nil
}
// String formats Dsn struct into a valid string url
func (dsn Dsn) String() string {
var url string
url += fmt.Sprintf("%s://%s", dsn.scheme, dsn.publicKey)
if dsn.secretKey != "" {
url += fmt.Sprintf(":%s", dsn.secretKey)
}
url += fmt.Sprintf("@%s", dsn.host)
if dsn.port != dsn.scheme.defaultPort() {
url += fmt.Sprintf(":%d", dsn.port)
}
if dsn.path != "" {
url += dsn.path
}
url += fmt.Sprintf("/%d", dsn.projectID)
return url
}
// StoreAPIURL returns assembled url to be used in the transport.
// It points to configures Sentry instance.
func (dsn Dsn) StoreAPIURL() *url.URL {
var rawURL string
rawURL += fmt.Sprintf("%s://%s", dsn.scheme, dsn.host)
if dsn.port != dsn.scheme.defaultPort() {
rawURL += fmt.Sprintf(":%d", dsn.port)
}
rawURL += fmt.Sprintf("/api/%d/store/", dsn.projectID)
parsedURL, _ := url.Parse(rawURL)
return parsedURL
}
// RequestHeaders returns all the necessary headers that have to be used in the transport.
func (dsn Dsn) RequestHeaders() map[string]string {
auth := fmt.Sprintf("Sentry sentry_version=%d, sentry_timestamp=%d, "+
"sentry_client=sentry.go/%s, sentry_key=%s", 7, time.Now().Unix(), Version, dsn.publicKey)
if dsn.secretKey != "" {
auth = fmt.Sprintf("%s, sentry_secret=%s", auth, dsn.secretKey)
}
return map[string]string{
"Content-Type": "application/json",
"X-Sentry-Auth": auth,
}
}
func (dsn Dsn) MarshalJSON() ([]byte, error) {
return json.Marshal(dsn.String())
}
func (dsn *Dsn) UnmarshalJSON(data []byte) error {
var str string
_ = json.Unmarshal(data, &str)
newDsn, err := NewDsn(str)
if err != nil {
return err
}
*dsn = *newDsn
return nil
}

7
vendor/github.com/getsentry/sentry-go/go.mod generated vendored Normal file
View File

@ -0,0 +1,7 @@
module github.com/getsentry/sentry-go
require (
github.com/go-errors/errors v1.0.1
github.com/pingcap/errors v0.11.1
github.com/pkg/errors v0.8.1
)

6
vendor/github.com/getsentry/sentry-go/go.sum generated vendored Normal file
View File

@ -0,0 +1,6 @@
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/pingcap/errors v0.11.1 h1:BXFZ6MdDd2U1uJUa2sRAWTmm+nieEzuyYM0R4aUTcC8=
github.com/pingcap/errors v0.11.1/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

135
vendor/github.com/getsentry/sentry-go/http/README.md generated vendored Normal file
View File

@ -0,0 +1,135 @@
<p align="center">
<a href="https://sentry.io" target="_blank" align="center">
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
</a>
<br />
</p>
# Official Sentry net/http Handler for Sentry-go SDK
**Godoc:** https://godoc.org/github.com/getsentry/sentry-go/http
**Example:** https://github.com/getsentry/sentry-go/tree/master/example/http
## Installation
```sh
go get github.com/getsentry/sentry-go/http
```
```go
import (
"fmt"
"net/http"
"github.com/getsentry/sentry-go"
sentryhttp "github.com/getsentry/sentry-go/http"
)
// To initialize Sentry's handler, you need to initialize Sentry itself beforehand
if err := sentry.Init(sentry.ClientOptions{
Dsn: "your-public-dsn",
}); err != nil {
fmt.Printf("Sentry initialization failed: %v\n", err)
}
// Create an instance of sentryhttp
sentryHandler := sentryhttp.New(sentryhttp.Options{})
// Once it's done, you can setup routes and attach the handler as one of your middleware
http.Handle("/", sentryHandler.Handle(&handler{}))
http.HandleFunc("/foo", sentryHandler.HandleFunc(func(rw http.ResponseWriter, r *http.Request) {
panic("y tho")
}))
fmt.Println("Listening and serving HTTP on :3000")
// And run it
if err := http.ListenAndServe(":3000", nil); err != nil {
panic(err)
}
```
## Configuration
`sentryhttp` accepts a struct of `Options` that allows you to configure how the handler will behave.
Currently it respects 3 options:
```go
// Whether Sentry should repanic after recovery, in most cases it should be set to true,
// and you should gracefully handle http responses.
Repanic bool
// Whether you want to block the request before moving forward with the response.
// Useful, when you want to restart the process after it panics.
WaitForDelivery bool
// Timeout for the event delivery requests.
Timeout time.Duration
```
## Usage
`sentryhttp` attaches an instance of `*sentry.Hub` (https://godoc.org/github.com/getsentry/sentry-go#Hub) to the request's context, which makes it available throughout the rest of the request's lifetime.
You can access it by using the `sentry.GetHubFromContext()` method on the request itself in any of your proceeding middleware and routes.
And it should be used instead of the global `sentry.CaptureMessage`, `sentry.CaptureException`, or any other calls, as it keeps the separation of data between the requests.
**Keep in mind that `*sentry.Hub` won't be available in middleware attached before to `sentryhttp`!**
```go
type handler struct{}
func (h *handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
if hub := sentry.GetHubFromContext(r.Context()); hub != nil {
hub.WithScope(func(scope *sentry.Scope) {
scope.SetExtra("unwantedQuery", "someQueryDataMaybe")
hub.CaptureMessage("User provided unwanted query string, but we recovered just fine")
})
}
rw.WriteHeader(http.StatusOK)
}
func enhanceSentryEvent(handler http.HandlerFunc) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
if hub := sentry.GetHubFromContext(r.Context()); hub != nil {
hub.Scope().SetTag("someRandomTag", "maybeYouNeedIt")
}
handler(rw, r)
}
}
// Later in the code
sentryHandler := sentryhttp.New(sentryhttp.Options{
Repanic: true,
})
http.Handle("/", sentryHandler.Handle(&handler{}))
http.HandleFunc("/foo", sentryHandler.HandleFunc(
enhanceSentryEvent(func(rw http.ResponseWriter, r *http.Request) {
panic("y tho")
}),
))
fmt.Println("Listening and serving HTTP on :3000")
if err := http.ListenAndServe(":3000", nil); err != nil {
panic(err)
}
```
### Accessing Request in `BeforeSend` callback
```go
sentry.Init(sentry.ClientOptions{
Dsn: "your-public-dsn",
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
if hint.Context != nil {
if req, ok := hint.Context.Value(sentry.RequestContextKey).(*http.Request); ok {
// You have access to the original Request here
}
}
return event
},
})
```

View File

@ -0,0 +1,90 @@
package sentryhttp
import (
"context"
"net/http"
"time"
"github.com/getsentry/sentry-go"
)
type Handler struct {
repanic bool
waitForDelivery bool
timeout time.Duration
}
type Options struct {
// Repanic configures whether Sentry should repanic after recovery, in most cases it should be set to true,
// as iris.Default includes it's own Recovery middleware what handles http responses.
Repanic bool
// WaitForDelivery configures whether you want to block the request before moving forward with the response.
// Because Iris's default `Recovery` handler doesn't restart the application,
// it's safe to either skip this option or set it to `false`.
WaitForDelivery bool
// Timeout for the event delivery requests.
Timeout time.Duration
}
// New returns a struct that provides Handle and HandleFunc methods
// that satisfy http.Handler and http.HandlerFunc interfaces.
func New(options Options) *Handler {
handler := Handler{
repanic: false,
timeout: time.Second * 2,
waitForDelivery: false,
}
if options.Repanic {
handler.repanic = true
}
if options.WaitForDelivery {
handler.waitForDelivery = true
}
return &handler
}
// Handle wraps http.Handler and recovers from caught panics.
func (h *Handler) Handle(handler http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
hub := sentry.CurrentHub().Clone()
hub.Scope().SetRequest(sentry.Request{}.FromHTTPRequest(r))
ctx := sentry.SetHubOnContext(
r.Context(),
hub,
)
defer h.recoverWithSentry(hub, r)
handler.ServeHTTP(rw, r.WithContext(ctx))
})
}
// HandleFunc wraps http.HandleFunc and recovers from caught panics.
func (h *Handler) HandleFunc(handler http.HandlerFunc) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
hub := sentry.CurrentHub().Clone()
hub.Scope().SetRequest(sentry.Request{}.FromHTTPRequest(r))
ctx := sentry.SetHubOnContext(
r.Context(),
hub,
)
defer h.recoverWithSentry(hub, r)
handler(rw, r.WithContext(ctx))
}
}
func (h *Handler) recoverWithSentry(hub *sentry.Hub, r *http.Request) {
if err := recover(); err != nil {
eventID := hub.RecoverWithContext(
context.WithValue(r.Context(), sentry.RequestContextKey, r),
err,
)
if eventID != nil && h.waitForDelivery {
hub.Flush(h.timeout)
}
if h.repanic {
panic(err)
}
}
}

277
vendor/github.com/getsentry/sentry-go/hub.go generated vendored Normal file
View File

@ -0,0 +1,277 @@
package sentry
import (
"context"
"time"
)
type contextKey int
// HubContextKey is a context key used to store Hub on any context.Context type
const HubContextKey = contextKey(1)
// RequestContextKey is a context key used to store http.Request on the context passed to RecoverWithContext
const RequestContextKey = contextKey(2)
// Default maximum number of breadcrumbs added to an event. Can be overwritten `maxBreadcrumbs` option.
const defaultMaxBreadcrumbs = 30
// Absolute maximum number of breadcrumbs added to an event.
// The `maxBreadcrumbs` option cannot be higher than this value.
const maxBreadcrumbs = 100
// Initial instance of the Hub that has no `Client` bound and an empty `Scope`
var currentHub = NewHub(nil, NewScope()) // nolint: gochecknoglobals
// Hub is the central object that can manages scopes and clients.
//
// This can be used to capture events and manage the scope.
// The default hub that is available automatically.
//
// In most situations developers do not need to interface the hub. Instead
// toplevel convenience functions are exposed that will automatically dispatch
// to global (`CurrentHub`) hub. In some situations this might not be
// possible in which case it might become necessary to manually work with the
// hub. This is for instance the case when working with async code.
type Hub struct {
stack *stack
lastEventID EventID
}
type layer struct {
client *Client
scope *Scope
}
type stack []*layer
// NewHub returns an instance of a `Hub` with provided `Client` and `Scope` bound.
func NewHub(client *Client, scope *Scope) *Hub {
hub := Hub{
stack: &stack{{
client: client,
scope: scope,
}},
}
return &hub
}
// CurrentHub returns an instance of previously initialized `Hub` stored in the global namespace.
func CurrentHub() *Hub {
return currentHub
}
// LastEventID returns an ID of last captured event for the current `Hub`.
func (hub *Hub) LastEventID() EventID {
return hub.lastEventID
}
func (hub *Hub) stackTop() *layer {
stack := hub.stack
if stack == nil || len(*stack) == 0 {
return nil
}
return (*stack)[len(*stack)-1]
}
// Clone returns a copy of the current Hub with top-most scope and client copied over.
func (hub *Hub) Clone() *Hub {
return NewHub(hub.Client(), hub.Scope().Clone())
}
// Scope returns top-level `Scope` of the current `Hub` or `nil` if no `Scope` is bound.
func (hub *Hub) Scope() *Scope {
top := hub.stackTop()
if top == nil {
return nil
}
return top.scope
}
// Scope returns top-level `Client` of the current `Hub` or `nil` if no `Client` is bound.
func (hub *Hub) Client() *Client {
top := hub.stackTop()
if top == nil {
return nil
}
return top.client
}
// PushScope pushes a new scope for the current `Hub` and reuses previously bound `Client`.
func (hub *Hub) PushScope() *Scope {
scope := hub.Scope().Clone()
*hub.stack = append(*hub.stack, &layer{
client: hub.Client(),
scope: scope,
})
return scope
}
// PushScope pops the most recent scope for the current `Hub`.
func (hub *Hub) PopScope() {
stack := *hub.stack
if len(stack) == 0 {
return
}
*hub.stack = stack[0 : len(stack)-1]
}
// BindClient binds a new `Client` for the current `Hub`.
func (hub *Hub) BindClient(client *Client) {
hub.stackTop().client = client
}
// WithScope temporarily pushes a scope for a single call.
//
// A shorthand for:
// PushScope()
// f(scope)
// PopScope()
func (hub *Hub) WithScope(f func(scope *Scope)) {
scope := hub.PushScope()
defer hub.PopScope()
f(scope)
}
// ConfigureScope invokes a function that can modify the current scope.
//
// The function is passed a mutable reference to the `Scope` so that modifications
// can be performed.
func (hub *Hub) ConfigureScope(f func(scope *Scope)) {
f(hub.Scope())
}
// CaptureEvent calls the method of a same name on currently bound `Client` instance
// passing it a top-level `Scope`.
// Returns `EventID` if successfully, or `nil` if there's no `Scope` or `Client` available.
func (hub *Hub) CaptureEvent(event *Event) *EventID {
client, scope := hub.Client(), hub.Scope()
if client == nil || scope == nil {
return nil
}
return client.CaptureEvent(event, nil, scope)
}
// CaptureMessage calls the method of a same name on currently bound `Client` instance
// passing it a top-level `Scope`.
// Returns `EventID` if successfully, or `nil` if there's no `Scope` or `Client` available.
func (hub *Hub) CaptureMessage(message string) *EventID {
client, scope := hub.Client(), hub.Scope()
if client == nil || scope == nil {
return nil
}
return client.CaptureMessage(message, nil, scope)
}
// CaptureException calls the method of a same name on currently bound `Client` instance
// passing it a top-level `Scope`.
// Returns `EventID` if successfully, or `nil` if there's no `Scope` or `Client` available.
func (hub *Hub) CaptureException(exception error) *EventID {
client, scope := hub.Client(), hub.Scope()
if client == nil || scope == nil {
return nil
}
return client.CaptureException(exception, &EventHint{OriginalException: exception}, scope)
}
// AddBreadcrumb records a new breadcrumb.
//
// The total number of breadcrumbs that can be recorded are limited by the
// configuration on the client.
func (hub *Hub) AddBreadcrumb(breadcrumb *Breadcrumb, hint *BreadcrumbHint) {
client := hub.Client()
// If there's no client, just store it on the scope straight away
if client == nil {
hub.Scope().AddBreadcrumb(breadcrumb, maxBreadcrumbs)
return
}
options := client.Options()
max := defaultMaxBreadcrumbs
if options.MaxBreadcrumbs != 0 {
max = options.MaxBreadcrumbs
}
if max < 0 {
return
}
if options.BeforeBreadcrumb != nil {
h := &BreadcrumbHint{}
if hint != nil {
h = hint
}
if breadcrumb = options.BeforeBreadcrumb(breadcrumb, h); breadcrumb == nil {
Logger.Println("breadcrumb dropped due to BeforeBreadcrumb callback")
return
}
}
if max > maxBreadcrumbs {
max = maxBreadcrumbs
}
hub.Scope().AddBreadcrumb(breadcrumb, max)
}
// Recover calls the method of a same name on currently bound `Client` instance
// passing it a top-level `Scope`.
// Returns `EventID` if successfully, or `nil` if there's no `Scope` or `Client` available.
func (hub *Hub) Recover(err interface{}) *EventID {
if err == nil {
err = recover()
}
client, scope := hub.Client(), hub.Scope()
if client == nil || scope == nil {
return nil
}
return client.Recover(err, &EventHint{RecoveredException: err}, scope)
}
// RecoverWithContext calls the method of a same name on currently bound `Client` instance
// passing it a top-level `Scope`.
// Returns `EventID` if successfully, or `nil` if there's no `Scope` or `Client` available.
func (hub *Hub) RecoverWithContext(ctx context.Context, err interface{}) *EventID {
if err == nil {
err = recover()
}
client, scope := hub.Client(), hub.Scope()
if client == nil || scope == nil {
return nil
}
return client.RecoverWithContext(ctx, err, &EventHint{RecoveredException: err}, scope)
}
// Flush calls the method of a same name on currently bound `Client` instance.
func (hub *Hub) Flush(timeout time.Duration) bool {
client := hub.Client()
if client == nil {
return false
}
return client.Flush(timeout)
}
// HasHubOnContext checks whether `Hub` instance is bound to a given `Context` struct.
func HasHubOnContext(ctx context.Context) bool {
_, ok := ctx.Value(HubContextKey).(*Hub)
return ok
}
// GetHubFromContext tries to retrieve `Hub` instance from the given `Context` struct
// or return `nil` if one is not found.
func GetHubFromContext(ctx context.Context) *Hub {
if hub, ok := ctx.Value(HubContextKey).(*Hub); ok {
return hub
}
return nil
}
// SetHubOnContext stores given `Hub` instance on the `Context` struct and returns a new `Context`.
func SetHubOnContext(ctx context.Context, hub *Hub) context.Context {
return context.WithValue(ctx, HubContextKey, hub)
}

262
vendor/github.com/getsentry/sentry-go/integrations.go generated vendored Normal file
View File

@ -0,0 +1,262 @@
package sentry
import (
"bufio"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"regexp"
"runtime"
"strings"
)
// ================================
// Modules Integration
// ================================
type modulesIntegration struct{}
var _modulesCache map[string]string // nolint: gochecknoglobals
func (mi *modulesIntegration) Name() string {
return "Modules"
}
func (mi *modulesIntegration) SetupOnce(client *Client) {
client.AddEventProcessor(mi.processor)
}
func (mi *modulesIntegration) processor(event *Event, hint *EventHint) *Event {
if event.Modules == nil {
event.Modules = extractModules()
}
return event
}
func extractModules() map[string]string {
if _modulesCache != nil {
return _modulesCache
}
extractedModules, err := getModules()
if err != nil {
Logger.Printf("ModuleIntegration wasn't able to extract modules: %v\n", err)
return nil
}
_modulesCache = extractedModules
return extractedModules
}
func getModules() (map[string]string, error) {
if fileExists("go.mod") {
return getModulesFromMod()
}
if fileExists("vendor") {
// Priority given to vendor created by modules
if fileExists("vendor/modules.txt") {
return getModulesFromVendorTxt()
}
if fileExists("vendor/vendor.json") {
return getModulesFromVendorJSON()
}
}
return nil, fmt.Errorf("module integration failed")
}
func getModulesFromMod() (map[string]string, error) {
modules := make(map[string]string)
file, err := os.Open("go.mod")
if err != nil {
return nil, fmt.Errorf("unable to open mod file")
}
defer file.Close()
areModulesPresent := false
scanner := bufio.NewScanner(file)
for scanner.Scan() {
splits := strings.Split(scanner.Text(), " ")
if splits[0] == "require" {
areModulesPresent = true
// Mod file has only 1 dependency
if len(splits) > 2 {
modules[strings.TrimSpace(splits[1])] = splits[2]
return modules, nil
}
} else if areModulesPresent && splits[0] != ")" {
modules[strings.TrimSpace(splits[0])] = splits[1]
}
}
if scannerErr := scanner.Err(); scannerErr != nil {
return nil, scannerErr
}
return modules, nil
}
func getModulesFromVendorTxt() (map[string]string, error) {
modules := make(map[string]string)
file, err := os.Open("vendor/modules.txt")
if err != nil {
return nil, fmt.Errorf("unable to open vendor/modules.txt")
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
splits := strings.Split(scanner.Text(), " ")
if splits[0] == "#" {
modules[splits[1]] = splits[2]
}
}
if scannerErr := scanner.Err(); scannerErr != nil {
return nil, scannerErr
}
return modules, nil
}
func getModulesFromVendorJSON() (map[string]string, error) {
modules := make(map[string]string)
file, err := ioutil.ReadFile("vendor/vendor.json")
if err != nil {
return nil, fmt.Errorf("unable to open vendor/vendor.json")
}
var vendor map[string]interface{}
if unmarshalErr := json.Unmarshal(file, &vendor); unmarshalErr != nil {
return nil, unmarshalErr
}
packages := vendor["package"].([]interface{})
// To avoid iterative dependencies, TODO: Change of default value
lastPath := "\n"
for _, value := range packages {
path := value.(map[string]interface{})["path"].(string)
if !strings.Contains(path, lastPath) {
// No versions are available through vendor.json
modules[path] = ""
lastPath = path
}
}
return modules, nil
}
// ================================
// Environment Integration
// ================================
type environmentIntegration struct{}
func (ei *environmentIntegration) Name() string {
return "Environment"
}
func (ei *environmentIntegration) SetupOnce(client *Client) {
client.AddEventProcessor(ei.processor)
}
func (ei *environmentIntegration) processor(event *Event, hint *EventHint) *Event {
if event.Contexts == nil {
event.Contexts = make(map[string]interface{})
}
event.Contexts["device"] = map[string]interface{}{
"arch": runtime.GOARCH,
"num_cpu": runtime.NumCPU(),
}
event.Contexts["os"] = map[string]interface{}{
"name": runtime.GOOS,
}
event.Contexts["runtime"] = map[string]interface{}{
"name": "go",
"version": runtime.Version(),
}
return event
}
// ================================
// Ignore Errors Integration
// ================================
type ignoreErrorsIntegration struct {
ignoreErrors []*regexp.Regexp
}
func (iei *ignoreErrorsIntegration) Name() string {
return "IgnoreErrors"
}
func (iei *ignoreErrorsIntegration) SetupOnce(client *Client) {
iei.ignoreErrors = transformStringsIntoRegexps(client.Options().IgnoreErrors)
client.AddEventProcessor(iei.processor)
}
func (iei *ignoreErrorsIntegration) processor(event *Event, hint *EventHint) *Event {
suspects := getIgnoreErrorsSuspects(event)
for _, suspect := range suspects {
for _, pattern := range iei.ignoreErrors {
if pattern.Match([]byte(suspect)) {
Logger.Printf("Event dropped due to being matched by `IgnoreErrors` option."+
"| Value matched: %s | Filter used: %s", suspect, pattern)
return nil
}
}
}
return event
}
func transformStringsIntoRegexps(strings []string) []*regexp.Regexp {
var exprs []*regexp.Regexp
for _, s := range strings {
r, err := regexp.Compile(s)
if err == nil {
exprs = append(exprs, r)
}
}
return exprs
}
func getIgnoreErrorsSuspects(event *Event) []string {
suspects := []string{}
if event.Message != "" {
suspects = append(suspects, event.Message)
}
for _, ex := range event.Exception {
suspects = append(suspects, ex.Type)
suspects = append(suspects, ex.Value)
}
return suspects
}

180
vendor/github.com/getsentry/sentry-go/interfaces.go generated vendored Normal file
View File

@ -0,0 +1,180 @@
package sentry
import (
"context"
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
)
// Protocol Docs (kinda)
// https://github.com/getsentry/rust-sentry-types/blob/master/src/protocol/v7.rs
// Level marks the severity of the event
type Level string
const (
LevelDebug Level = "debug"
LevelInfo Level = "info"
LevelWarning Level = "warning"
LevelError Level = "error"
LevelFatal Level = "fatal"
)
// https://docs.sentry.io/development/sdk-dev/interfaces/sdk/
type SdkInfo struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
Integrations []string `json:"integrations,omitempty"`
Packages []SdkPackage `json:"packages,omitempty"`
}
type SdkPackage struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
}
// TODO: This type could be more useful, as map of interface{} is too generic
// and requires a lot of type assertions in beforeBreadcrumb calls
// plus it could just be `map[string]interface{}` then
type BreadcrumbHint map[string]interface{}
// https://docs.sentry.io/development/sdk-dev/interfaces/breadcrumbs/
type Breadcrumb struct {
Category string `json:"category,omitempty"`
Data map[string]interface{} `json:"data,omitempty"`
Level Level `json:"level,omitempty"`
Message string `json:"message,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Type string `json:"type,omitempty"`
}
// https://docs.sentry.io/development/sdk-dev/interfaces/user/
type User struct {
Email string `json:"email,omitempty"`
ID string `json:"id,omitempty"`
IPAddress string `json:"ip_address,omitempty"`
Username string `json:"username,omitempty"`
}
// https://docs.sentry.io/development/sdk-dev/interfaces/http/
type Request struct {
URL string `json:"url,omitempty"`
Method string `json:"method,omitempty"`
Data string `json:"data,omitempty"`
QueryString string `json:"query_string,omitempty"`
Cookies string `json:"cookies,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Env map[string]string `json:"env,omitempty"`
}
func (r Request) FromHTTPRequest(request *http.Request) Request {
// Method
r.Method = request.Method
// URL
protocol := schemeHTTP
if request.TLS != nil || request.Header.Get("X-Forwarded-Proto") == "https" {
protocol = schemeHTTPS
}
r.URL = fmt.Sprintf("%s://%s%s", protocol, request.Host, request.URL.Path)
// Headers
headers := make(map[string]string, len(request.Header))
for k, v := range request.Header {
headers[k] = strings.Join(v, ",")
}
headers["Host"] = request.Host
r.Headers = headers
// Cookies
r.Cookies = request.Header.Get("Cookie")
// Env
if addr, port, err := net.SplitHostPort(request.RemoteAddr); err == nil {
r.Env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port}
}
// QueryString
r.QueryString = request.URL.RawQuery
// Body
if request.GetBody != nil {
if bodyCopy, err := request.GetBody(); err == nil && bodyCopy != nil {
body, err := ioutil.ReadAll(bodyCopy)
if err == nil {
r.Data = string(body)
}
}
}
return r
}
// https://docs.sentry.io/development/sdk-dev/interfaces/exception/
type Exception struct {
Type string `json:"type,omitempty"`
Value string `json:"value,omitempty"`
Module string `json:"module,omitempty"`
Stacktrace *Stacktrace `json:"stacktrace,omitempty"`
RawStacktrace *Stacktrace `json:"raw_stacktrace,omitempty"`
}
type EventID string
// https://docs.sentry.io/development/sdk-dev/attributes/
type Event struct {
Breadcrumbs []*Breadcrumb `json:"breadcrumbs,omitempty"`
Contexts map[string]interface{} `json:"contexts,omitempty"`
Dist string `json:"dist,omitempty"`
Environment string `json:"environment,omitempty"`
EventID EventID `json:"event_id,omitempty"`
Extra map[string]interface{} `json:"extra,omitempty"`
Fingerprint []string `json:"fingerprint,omitempty"`
Level Level `json:"level,omitempty"`
Message string `json:"message,omitempty"`
Platform string `json:"platform,omitempty"`
Release string `json:"release,omitempty"`
Sdk SdkInfo `json:"sdk,omitempty"`
ServerName string `json:"server_name,omitempty"`
Threads []Thread `json:"threads,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
Transaction string `json:"transaction,omitempty"`
User User `json:"user,omitempty"`
Logger string `json:"logger,omitempty"`
Modules map[string]string `json:"modules,omitempty"`
Request Request `json:"request,omitempty"`
Exception []Exception `json:"exception,omitempty"`
}
func NewEvent() *Event {
event := Event{
Contexts: make(map[string]interface{}),
Extra: make(map[string]interface{}),
Tags: make(map[string]string),
Modules: make(map[string]string),
}
return &event
}
type Thread struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Stacktrace *Stacktrace `json:"stacktrace,omitempty"`
RawStacktrace *Stacktrace `json:"raw_stacktrace,omitempty"`
Crashed bool `json:"crashed,omitempty"`
Current bool `json:"current,omitempty"`
}
type EventHint struct {
Data interface{}
EventID string
OriginalException error
RecoveredException interface{}
Context context.Context
Request *http.Request
Response *http.Response
}

238
vendor/github.com/getsentry/sentry-go/scope.go generated vendored Normal file
View File

@ -0,0 +1,238 @@
package sentry
import (
"reflect"
"time"
)
// Scope holds contextual data for the current scope.
//
// The scope is an object that can cloned efficiently and stores data that
// is locally relevant to an event. For instance the scope will hold recorded
// breadcrumbs and similar information.
//
// The scope can be interacted with in two ways:
//
// 1. the scope is routinely updated with information by functions such as
// `AddBreadcrumb` which will modify the currently top-most scope.
// 2. the topmost scope can also be configured through the `ConfigureScope`
// method.
//
// Note that the scope can only be modified but not inspected.
// Only the client can use the scope to extract information currently.
type Scope struct {
breadcrumbs []*Breadcrumb
user User
tags map[string]string
contexts map[string]interface{}
extra map[string]interface{}
fingerprint []string
level Level
request Request
eventProcessors []EventProcessor
}
func NewScope() *Scope {
scope := Scope{
breadcrumbs: make([]*Breadcrumb, 0),
tags: make(map[string]string),
contexts: make(map[string]interface{}),
extra: make(map[string]interface{}),
fingerprint: make([]string, 0),
}
return &scope
}
// AddBreadcrumb adds new breadcrumb to the current scope
// and optionaly throws the old one if limit is reached.
func (scope *Scope) AddBreadcrumb(breadcrumb *Breadcrumb, limit int) {
if breadcrumb.Timestamp == 0 {
breadcrumb.Timestamp = time.Now().Unix()
}
breadcrumbs := append(scope.breadcrumbs, breadcrumb)
if len(breadcrumbs) > limit {
scope.breadcrumbs = breadcrumbs[1 : limit+1]
} else {
scope.breadcrumbs = breadcrumbs
}
}
// ClearBreadcrumbs clears all breadcrumbs from the current scope.
func (scope *Scope) ClearBreadcrumbs() {
scope.breadcrumbs = []*Breadcrumb{}
}
// SetUser sets new user for the current scope.
func (scope *Scope) SetUser(user User) {
scope.user = user
}
// SetRequest sets new user for the current scope.
func (scope *Scope) SetRequest(request Request) {
scope.request = request
}
// SetTag adds a tag to the current scope.
func (scope *Scope) SetTag(key, value string) {
scope.tags[key] = value
}
// SetTags assigns multiple tags to the current scope.
func (scope *Scope) SetTags(tags map[string]string) {
for k, v := range tags {
scope.tags[k] = v
}
}
// RemoveTag removes a tag from the current scope.
func (scope *Scope) RemoveTag(key string) {
delete(scope.tags, key)
}
// SetContext adds a context to the current scope.
func (scope *Scope) SetContext(key string, value interface{}) {
scope.contexts[key] = value
}
// SetContexts assigns multiple contexts to the current scope.
func (scope *Scope) SetContexts(contexts map[string]interface{}) {
for k, v := range contexts {
scope.contexts[k] = v
}
}
// RemoveContext removes a context from the current scope.
func (scope *Scope) RemoveContext(key string) {
delete(scope.contexts, key)
}
// SetExtra adds an extra to the current scope.
func (scope *Scope) SetExtra(key string, value interface{}) {
scope.extra[key] = value
}
// SetExtras assigns multiple extras to the current scope.
func (scope *Scope) SetExtras(extra map[string]interface{}) {
for k, v := range extra {
scope.extra[k] = v
}
}
// RemoveExtra removes a extra from the current scope.
func (scope *Scope) RemoveExtra(key string) {
delete(scope.extra, key)
}
// SetFingerprint sets new fingerprint for the current scope.
func (scope *Scope) SetFingerprint(fingerprint []string) {
scope.fingerprint = fingerprint
}
// SetLevel sets new level for the current scope.
func (scope *Scope) SetLevel(level Level) {
scope.level = level
}
// Clone returns a copy of the current scope with all data copied over.
func (scope *Scope) Clone() *Scope {
clone := NewScope()
clone.user = scope.user
clone.breadcrumbs = make([]*Breadcrumb, len(scope.breadcrumbs))
copy(clone.breadcrumbs, scope.breadcrumbs)
for key, value := range scope.tags {
clone.tags[key] = value
}
for key, value := range scope.contexts {
clone.contexts[key] = value
}
for key, value := range scope.extra {
clone.extra[key] = value
}
clone.fingerprint = make([]string, len(scope.fingerprint))
copy(clone.fingerprint, scope.fingerprint)
clone.level = scope.level
clone.request = scope.request
return clone
}
// Clear removed the data from the current scope.
func (scope *Scope) Clear() {
*scope = Scope{}
}
// AddEventProcessor adds an event processor to the current scope.
func (scope *Scope) AddEventProcessor(processor EventProcessor) {
scope.eventProcessors = append(scope.eventProcessors, processor)
}
// ApplyToEvent takes the data from the current scope and attaches it to the event.
func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint) *Event {
if len(scope.breadcrumbs) > 0 {
if event.Breadcrumbs == nil {
event.Breadcrumbs = []*Breadcrumb{}
}
event.Breadcrumbs = append(event.Breadcrumbs, scope.breadcrumbs...)
}
if len(scope.tags) > 0 {
if event.Tags == nil {
event.Tags = make(map[string]string)
}
for key, value := range scope.tags {
event.Tags[key] = value
}
}
if len(scope.contexts) > 0 {
if event.Contexts == nil {
event.Contexts = make(map[string]interface{})
}
for key, value := range scope.contexts {
event.Contexts[key] = value
}
}
if len(scope.extra) > 0 {
if event.Extra == nil {
event.Extra = make(map[string]interface{})
}
for key, value := range scope.extra {
event.Extra[key] = value
}
}
if (reflect.DeepEqual(event.User, User{})) {
event.User = scope.user
}
if (event.Fingerprint == nil || len(event.Fingerprint) == 0) &&
len(scope.fingerprint) > 0 {
event.Fingerprint = make([]string, len(scope.fingerprint))
copy(event.Fingerprint, scope.fingerprint)
}
if scope.level != "" {
event.Level = scope.level
}
if (reflect.DeepEqual(event.Request, Request{})) {
event.Request = scope.request
}
for _, processor := range scope.eventProcessors {
id := event.EventID
event = processor(event, hint)
if event == nil {
Logger.Printf("Event dropped by one of the Scope EventProcessors: %s\n", id)
return nil
}
}
return event
}

122
vendor/github.com/getsentry/sentry-go/sentry.go generated vendored Normal file
View File

@ -0,0 +1,122 @@
package sentry
import (
"context"
"time"
)
// Version Sentry-Go SDK Version
const Version = "0.1.1"
// Init initializes whole SDK by creating new `Client` and binding it to the current `Hub`
func Init(options ClientOptions) error {
hub := CurrentHub()
client, err := NewClient(options)
if err != nil {
return err
}
hub.BindClient(client)
return nil
}
// AddBreadcrumb records a new breadcrumb.
//
// The total number of breadcrumbs that can be recorded are limited by the
// configuration on the client.
func AddBreadcrumb(breadcrumb *Breadcrumb) {
hub := CurrentHub()
hub.AddBreadcrumb(breadcrumb, nil)
}
// CaptureMessage captures an arbitrary message.
func CaptureMessage(message string) *EventID {
hub := CurrentHub()
return hub.CaptureMessage(message)
}
// CaptureException captures an error.
func CaptureException(exception error) *EventID {
hub := CurrentHub()
return hub.CaptureException(exception)
}
// CaptureEvent captures an event on the currently active client if any.
//
// The event must already be assembled. Typically code would instead use
// the utility methods like `CaptureException`. The return value is the
// event ID. In case Sentry is disabled or event was dropped, the return value will be nil.
func CaptureEvent(event *Event) *EventID {
hub := CurrentHub()
return hub.CaptureEvent(event)
}
// Recover captures a panic.
func Recover() *EventID {
if err := recover(); err != nil {
hub := CurrentHub()
return hub.Recover(err)
}
return nil
}
// Recover captures a panic and passes relevant context object.
func RecoverWithContext(ctx context.Context) *EventID {
if err := recover(); err != nil {
var hub *Hub
if HasHubOnContext(ctx) {
hub = GetHubFromContext(ctx)
} else {
hub = CurrentHub()
}
return hub.RecoverWithContext(ctx, err)
}
return nil
}
// WithScope temporarily pushes a scope for a single call.
//
// This function takes one argument, a callback that executes
// in the context of that scope.
//
// This is useful when extra data should be send with a single capture call
// for instance a different level or tags
func WithScope(f func(scope *Scope)) {
hub := CurrentHub()
hub.WithScope(f)
}
// ConfigureScope invokes a function that can modify the current scope.
//
// The function is passed a mutable reference to the `Scope` so that modifications
// can be performed.
func ConfigureScope(f func(scope *Scope)) {
hub := CurrentHub()
hub.ConfigureScope(f)
}
// PushScope pushes a new scope.
func PushScope() {
hub := CurrentHub()
hub.PushScope()
}
// PopScope pushes a new scope.
func PopScope() {
hub := CurrentHub()
hub.PopScope()
}
// Flush notifies when all the buffered events have been sent by returning `true`
// or `false` if timeout was reached.
func Flush(timeout time.Duration) bool {
hub := CurrentHub()
return hub.Flush(timeout)
}
// LastEventID returns an ID of last captured event.
func LastEventID() EventID {
hub := CurrentHub()
return hub.LastEventID()
}

69
vendor/github.com/getsentry/sentry-go/sourcereader.go generated vendored Normal file
View File

@ -0,0 +1,69 @@
package sentry
import (
"bytes"
"io/ioutil"
"sync"
)
type sourceReader struct {
mu sync.Mutex
cache map[string][][]byte
}
func newSourceReader() sourceReader {
return sourceReader{
cache: make(map[string][][]byte),
}
}
func (sr *sourceReader) readContextLines(filename string, line, context int) ([][]byte, int) {
sr.mu.Lock()
defer sr.mu.Unlock()
lines, ok := sr.cache[filename]
if !ok {
data, err := ioutil.ReadFile(filename)
if err != nil {
sr.cache[filename] = nil
return nil, 0
}
lines = bytes.Split(data, []byte{'\n'})
sr.cache[filename] = lines
}
return sr.calculateContextLines(lines, line, context)
}
// `initial` points to a line that's the `context_line` itself in relation to returned slice
func (sr *sourceReader) calculateContextLines(lines [][]byte, line, context int) ([][]byte, int) {
// Stacktrace lines are 1-indexed, slices are 0-indexed
line--
initial := context
if lines == nil || line >= len(lines) || line < 0 {
return nil, 0
}
if context < 0 {
context = 0
initial = 0
}
start := line - context
if start < 0 {
initial += start
start = 0
}
end := line + context + 1
if end > len(lines) {
end = len(lines)
}
return lines[start:end], initial
}

300
vendor/github.com/getsentry/sentry-go/stacktrace.go generated vendored Normal file
View File

@ -0,0 +1,300 @@
package sentry
import (
"path/filepath"
"reflect"
"regexp"
"runtime"
"strings"
)
const unknown string = "unknown"
const contextLines int = 5
// The module download is split into two parts: downloading the go.mod and downloading the actual code.
// If you have dependencies only needed for tests, then they will show up in your go.mod,
// and go get will download their go.mods, but it will not download their code.
// The test-only dependencies get downloaded only when you need it, such as the first time you run go test.
//
// https://github.com/golang/go/issues/26913#issuecomment-411976222
// Stacktrace holds information about the frames of the stack.
type Stacktrace struct {
Frames []Frame `json:"frames,omitempty"`
FramesOmitted []uint `json:"frames_omitted,omitempty"`
}
// NewStacktrace creates a stacktrace using `runtime.Callers`.
func NewStacktrace() *Stacktrace {
pcs := make([]uintptr, 100)
n := runtime.Callers(1, pcs)
if n == 0 {
return nil
}
frames := extractFrames(pcs[:n])
frames = filterFrames(frames)
frames = contextifyFrames(frames)
stacktrace := Stacktrace{
Frames: frames,
}
return &stacktrace
}
// ExtractStacktrace creates a new `Stacktrace` based on the given `error` object.
// TODO: Make it configurable so that anyone can provide their own implementation?
// Use of reflection allows us to not have a hard dependency on any given package, so we don't have to import it
func ExtractStacktrace(err error) *Stacktrace {
method := extractReflectedStacktraceMethod(err)
if !method.IsValid() {
return nil
}
pcs := extractPcs(method)
if len(pcs) == 0 {
return nil
}
frames := extractFrames(pcs)
frames = filterFrames(frames)
frames = contextifyFrames(frames)
stacktrace := Stacktrace{
Frames: frames,
}
return &stacktrace
}
func extractReflectedStacktraceMethod(err error) reflect.Value {
var method reflect.Value
// https://github.com/pingcap/errors
methodGetStackTracer := reflect.ValueOf(err).MethodByName("GetStackTracer")
// https://github.com/pkg/errors
methodStackTrace := reflect.ValueOf(err).MethodByName("StackTrace")
// https://github.com/go-errors/errors
methodStackFrames := reflect.ValueOf(err).MethodByName("StackFrames")
if methodGetStackTracer.IsValid() {
stacktracer := methodGetStackTracer.Call(make([]reflect.Value, 0))[0]
stacktracerStackTrace := reflect.ValueOf(stacktracer).MethodByName("StackTrace")
if stacktracerStackTrace.IsValid() {
method = stacktracerStackTrace
}
}
if methodStackTrace.IsValid() {
method = methodStackTrace
}
if methodStackFrames.IsValid() {
method = methodStackFrames
}
return method
}
func extractPcs(method reflect.Value) []uintptr {
var pcs []uintptr
stacktrace := method.Call(make([]reflect.Value, 0))[0]
if stacktrace.Kind() != reflect.Slice {
return nil
}
for i := 0; i < stacktrace.Len(); i++ {
pc := stacktrace.Index(i)
if pc.Kind() == reflect.Uintptr {
pcs = append(pcs, uintptr(pc.Uint()))
continue
}
if pc.Kind() == reflect.Struct {
field := pc.FieldByName("ProgramCounter")
if field.IsValid() && field.Kind() == reflect.Uintptr {
pcs = append(pcs, uintptr(field.Uint()))
continue
}
}
}
return pcs
}
// https://docs.sentry.io/development/sdk-dev/interfaces/stacktrace/
type Frame struct {
Function string `json:"function,omitempty"`
Symbol string `json:"symbol,omitempty"`
Module string `json:"module,omitempty"`
Package string `json:"package,omitempty"`
Filename string `json:"filename,omitempty"`
AbsPath string `json:"abs_path,omitempty"`
Lineno int `json:"lineno,omitempty"`
Colno int `json:"colno,omitempty"`
PreContext []string `json:"pre_context,omitempty"`
ContextLine string `json:"context_line,omitempty"`
PostContext []string `json:"post_context,omitempty"`
InApp bool `json:"in_app,omitempty"`
Vars map[string]interface{} `json:"vars,omitempty"`
}
// NewFrame assembles a stacktrace frame out of `runtime.Frame`.
func NewFrame(f runtime.Frame) Frame {
abspath := f.File
filename := f.File
function := f.Function
var module string
if filename != "" {
filename = extractFilename(filename)
} else {
filename = unknown
}
if abspath == "" {
abspath = unknown
}
if function != "" {
module, function = deconstructFunctionName(function)
}
frame := Frame{
AbsPath: abspath,
Filename: filename,
Lineno: f.Line,
Module: module,
Function: function,
}
frame.InApp = isInAppFrame(frame)
return frame
}
func extractFrames(pcs []uintptr) []Frame {
var frames []Frame
callersFrames := runtime.CallersFrames(pcs)
for {
callerFrame, more := callersFrames.Next()
frames = append([]Frame{
NewFrame(callerFrame),
}, frames...)
if !more {
break
}
}
return frames
}
func filterFrames(frames []Frame) []Frame {
isTestFileRegexp := regexp.MustCompile(`getsentry/sentry-go/.+_test.go`)
isExampleFileRegexp := regexp.MustCompile(`getsentry/sentry-go/example/`)
filteredFrames := make([]Frame, 0, len(frames))
for _, frame := range frames {
// go runtime frames
if frame.Module == "runtime" || frame.Module == "testing" {
continue
}
// sentry internal frames
isTestFile := isTestFileRegexp.MatchString(frame.AbsPath)
isExampleFile := isExampleFileRegexp.MatchString(frame.AbsPath)
if strings.Contains(frame.AbsPath, "github.com/getsentry/sentry-go") &&
!isTestFile &&
!isExampleFile {
continue
}
filteredFrames = append(filteredFrames, frame)
}
return filteredFrames
}
var sr = newSourceReader() // nolint: gochecknoglobals
func contextifyFrames(frames []Frame) []Frame {
contextifiedFrames := make([]Frame, 0, len(frames))
for _, frame := range frames {
var path string
// If we are not able to read the source code from either absolute or relative path (root dir only)
// Skip this part and return the original frame
switch {
case fileExists(frame.AbsPath):
path = frame.AbsPath
case fileExists(frame.Filename):
path = frame.Filename
default:
contextifiedFrames = append(contextifiedFrames, frame)
continue
}
lines, initial := sr.readContextLines(path, frame.Lineno, contextLines)
for i, line := range lines {
switch {
case i < initial:
frame.PreContext = append(frame.PreContext, string(line))
case i == initial:
frame.ContextLine = string(line)
default:
frame.PostContext = append(frame.PostContext, string(line))
}
}
contextifiedFrames = append(contextifiedFrames, frame)
}
return contextifiedFrames
}
func extractFilename(path string) string {
_, file := filepath.Split(path)
return file
}
func isInAppFrame(frame Frame) bool {
if frame.Module == "main" {
return true
}
if !strings.Contains(frame.Module, "vendor") && !strings.Contains(frame.Module, "third_party") {
return true
}
return false
}
// Transform `runtime/debug.*T·ptrmethod` into `{ module: runtime/debug, function: *T.ptrmethod }`
func deconstructFunctionName(name string) (module string, function string) {
if idx := strings.LastIndex(name, "."); idx != -1 {
module = name[:idx]
function = name[idx+1:]
}
function = strings.Replace(function, "·", ".", -1)
return module, function
}
func callerFunctionName() string {
pcs := make([]uintptr, 1)
runtime.Callers(3, pcs)
callersFrames := runtime.CallersFrames(pcs)
callerFrame, _ := callersFrames.Next()
_, function := deconstructFunctionName(callerFrame.Function)
return function
}

294
vendor/github.com/getsentry/sentry-go/transport.go generated vendored Normal file
View File

@ -0,0 +1,294 @@
package sentry
import (
"bytes"
"crypto/tls"
"encoding/json"
"net/http"
"net/url"
"strconv"
"sync"
"time"
)
const defaultBufferSize = 30
const defaultRetryAfter = time.Second * 60
const defaultTimeout = time.Second * 30
// Transport is used by the `Client` to deliver events to remote server.
type Transport interface {
Flush(timeout time.Duration) bool
Configure(options ClientOptions)
SendEvent(event *Event)
}
func getProxyConfig(options ClientOptions) func(*http.Request) (*url.URL, error) {
if options.HTTPSProxy != "" {
return func(_ *http.Request) (*url.URL, error) {
return url.Parse(options.HTTPSProxy)
}
} else if options.HTTPProxy != "" {
return func(_ *http.Request) (*url.URL, error) {
return url.Parse(options.HTTPProxy)
}
}
return http.ProxyFromEnvironment
}
func getTLSConfig(options ClientOptions) *tls.Config {
if options.CaCerts != nil {
return &tls.Config{
RootCAs: options.CaCerts,
}
}
return nil
}
func retryAfter(now time.Time, r *http.Response) time.Duration {
retryAfterHeader := r.Header["Retry-After"]
if retryAfterHeader == nil {
return defaultRetryAfter
}
if date, err := time.Parse(time.RFC1123, retryAfterHeader[0]); err == nil {
return date.Sub(now)
}
if seconds, err := strconv.Atoi(retryAfterHeader[0]); err == nil {
return time.Second * time.Duration(seconds)
}
return defaultRetryAfter
}
// ================================
// HTTPTransport
// ================================
// HTTPTransport is a default implementation of `Transport` interface used by `Client`.
type HTTPTransport struct {
dsn *Dsn
client *http.Client
transport *http.Transport
buffer chan *http.Request
disabledUntil time.Time
wg sync.WaitGroup
start sync.Once
// Size of the transport buffer. Defaults to 30.
BufferSize int
// HTTP Client request timeout. Defaults to 30 seconds.
Timeout time.Duration
}
// NewHTTPTransport returns a new pre-configured instance of HTTPTransport
func NewHTTPTransport() *HTTPTransport {
transport := HTTPTransport{
BufferSize: defaultBufferSize,
Timeout: defaultTimeout,
}
return &transport
}
// Configure is called by the `Client` itself, providing it it's own `ClientOptions`.
func (t *HTTPTransport) Configure(options ClientOptions) {
dsn, err := NewDsn(options.Dsn)
if err != nil {
Logger.Printf("%v\n", err)
return
}
t.dsn = dsn
t.buffer = make(chan *http.Request, t.BufferSize)
if options.HTTPTransport != nil {
t.transport = options.HTTPTransport
} else {
t.transport = &http.Transport{
Proxy: getProxyConfig(options),
TLSClientConfig: getTLSConfig(options),
}
}
t.client = &http.Client{
Transport: t.transport,
Timeout: t.Timeout,
}
t.start.Do(func() {
go t.worker()
})
}
// SendEvent assembles a new packet out of `Event` and sends it to remote server.
func (t *HTTPTransport) SendEvent(event *Event) {
if t.dsn == nil || time.Now().Before(t.disabledUntil) {
return
}
body, _ := json.Marshal(event)
request, _ := http.NewRequest(
http.MethodPost,
t.dsn.StoreAPIURL().String(),
bytes.NewBuffer(body),
)
for headerKey, headerValue := range t.dsn.RequestHeaders() {
request.Header.Set(headerKey, headerValue)
}
select {
case t.buffer <- request:
Logger.Printf(
"Sending %s event [%s] to %s project: %d\n",
event.Level,
event.EventID,
t.dsn.host,
t.dsn.projectID,
)
t.wg.Add(1)
default:
Logger.Println("Event dropped due to transport buffer being full")
// worker would block, drop the packet
}
}
// Flush notifies when all the buffered events have been sent by returning `true`
// or `false` if timeout was reached.
func (t *HTTPTransport) Flush(timeout time.Duration) bool {
c := make(chan struct{})
go func() {
t.wg.Wait()
close(c)
}()
select {
case <-c:
Logger.Println("Buffer flushed successfully")
return true
case <-time.After(timeout):
Logger.Println("Buffer flushing reached the timeout")
return false
}
}
func (t *HTTPTransport) worker() {
for request := range t.buffer {
if time.Now().Before(t.disabledUntil) {
t.wg.Done()
continue
}
response, err := t.client.Do(request)
if err != nil {
Logger.Printf("There was an issue with sending an event: %v", err)
}
if response != nil && response.StatusCode == http.StatusTooManyRequests {
t.disabledUntil = time.Now().Add(retryAfter(time.Now(), response))
Logger.Printf("Too many requests, backing off till: %s\n", t.disabledUntil)
}
t.wg.Done()
}
}
// ================================
// HTTPSyncTransport
// ================================
// HTTPSyncTransport is an implementation of `Transport` interface which blocks after each captured event.
type HTTPSyncTransport struct {
dsn *Dsn
client *http.Client
transport *http.Transport
disabledUntil time.Time
// HTTP Client request timeout. Defaults to 30 seconds.
Timeout time.Duration
}
// NewHTTPSyncTransport returns a new pre-configured instance of HTTPSyncTransport
func NewHTTPSyncTransport() *HTTPSyncTransport {
transport := HTTPSyncTransport{
Timeout: defaultTimeout,
}
return &transport
}
// Configure is called by the `Client` itself, providing it it's own `ClientOptions`.
func (t *HTTPSyncTransport) Configure(options ClientOptions) {
dsn, err := NewDsn(options.Dsn)
if err != nil {
Logger.Printf("%v\n", err)
return
}
t.dsn = dsn
if options.HTTPTransport != nil {
t.transport = options.HTTPTransport
} else {
t.transport = &http.Transport{
Proxy: getProxyConfig(options),
TLSClientConfig: getTLSConfig(options),
}
}
t.client = &http.Client{
Transport: t.transport,
Timeout: t.Timeout,
}
}
// SendEvent assembles a new packet out of `Event` and sends it to remote server.
func (t *HTTPSyncTransport) SendEvent(event *Event) {
if t.dsn == nil || time.Now().Before(t.disabledUntil) {
return
}
body, _ := json.Marshal(event)
request, _ := http.NewRequest(
http.MethodPost,
t.dsn.StoreAPIURL().String(),
bytes.NewBuffer(body),
)
for headerKey, headerValue := range t.dsn.RequestHeaders() {
request.Header.Set(headerKey, headerValue)
}
Logger.Printf(
"Sending %s event [%s] to %s project: %d\n",
event.Level,
event.EventID,
t.dsn.host,
t.dsn.projectID,
)
response, err := t.client.Do(request)
if err != nil {
Logger.Printf("There was an issue with sending an event: %v", err)
}
if response != nil && response.StatusCode == http.StatusTooManyRequests {
t.disabledUntil = time.Now().Add(retryAfter(time.Now(), response))
Logger.Printf("Too many requests, backing off till: %s\n", t.disabledUntil)
}
}
// Flush notifies when all the buffered events have been sent by returning `true`
// or `false` if timeout was reached. No-op for HTTPSyncTransport.
func (t *HTTPSyncTransport) Flush(_ time.Duration) bool {
return true
}

34
vendor/github.com/getsentry/sentry-go/util.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
package sentry
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
)
func uuid() string {
id := make([]byte, 16)
_, _ = io.ReadFull(rand.Reader, id)
id[6] &= 0x0F // clear version
id[6] |= 0x40 // set version to 4 (random uuid)
id[8] &= 0x3F // clear variant
id[8] |= 0x80 // set to IETF variant
return hex.EncodeToString(id)
}
func fileExists(fileName string) bool {
if _, err := os.Stat(fileName); err != nil {
return false
}
return true
}
// nolint: deadcode, unused
func prettyPrint(data interface{}) {
dbg, _ := json.MarshalIndent(data, "", " ")
fmt.Println(string(dbg))
}