mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-09-20 14:34:14 +00:00
use go 1.16, GODEBUG=x509ignoreCN=0, upgrade some old deps, replace travis with actions
This commit is contained in:
+3
-3
@@ -1,5 +1,5 @@
|
||||
*.test
|
||||
*.out
|
||||
example/example
|
||||
|
||||
docs/_build
|
||||
docs/doctrees
|
||||
/xunit.xml
|
||||
/coverage.xml
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "docs/_sentryext"]
|
||||
path = docs/_sentryext
|
||||
url = https://github.com/getsentry/sentry-doc-support
|
||||
|
||||
+26
-8
@@ -1,23 +1,41 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- "1.2"
|
||||
- "1.3"
|
||||
- "1.4"
|
||||
- "1.5"
|
||||
- "1.6"
|
||||
- "1.7"
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go install -race std
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/tebeka/go2xunit
|
||||
- go get github.com/t-yuki/gocover-cobertura
|
||||
- go get -v ./...
|
||||
|
||||
script:
|
||||
- go test -v -race ./...
|
||||
- go test -v -cover ./...
|
||||
- go test -v -race ./... | tee gotest.out
|
||||
- $GOPATH/bin/go2xunit -fail -input gotest.out -output xunit.xml
|
||||
- go test -v -coverprofile=coverage.txt -covermode count .
|
||||
- $GOPATH/bin/gocover-cobertura < coverage.txt > coverage.xml
|
||||
|
||||
after_script:
|
||||
- npm install -g @zeus-ci/cli
|
||||
- zeus upload -t "application/x-cobertura+xml" coverage.xml
|
||||
- zeus upload -t "application/x-xunit+xml" xunit.xml
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://zeus.ci/hooks/cd949996-d30a-11e8-ba53-0a580a28042d/public/provider/travis/webhook
|
||||
on_success: always
|
||||
on_failure: always
|
||||
on_start: always
|
||||
on_cancel: always
|
||||
on_error: always
|
||||
|
||||
+8
-2
@@ -1,6 +1,10 @@
|
||||
# raven [](https://travis-ci.org/getsentry/raven-go)
|
||||
# raven
|
||||
|
||||
raven is a Go client for the [Sentry](https://github.com/getsentry/sentry)
|
||||
[](https://travis-ci.org/getsentry/raven-go)
|
||||
[](https://goreportcard.com/report/github.com/getsentry/raven-go)
|
||||
[](https://godoc.org/github.com/getsentry/raven-go)
|
||||
|
||||
raven is the official Go SDK for the [Sentry](https://github.com/getsentry/sentry)
|
||||
event/error logging system.
|
||||
|
||||
- [**API Documentation**](https://godoc.org/github.com/getsentry/raven-go)
|
||||
@@ -11,3 +15,5 @@ event/error logging system.
|
||||
```text
|
||||
go get github.com/getsentry/raven-go
|
||||
```
|
||||
|
||||
Note: Go 1.7 and newer are supported.
|
||||
|
||||
+230
-52
@@ -14,15 +14,18 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
mrand "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/certifi/gocertifi"
|
||||
pkgErrors "github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,8 +37,8 @@ var (
|
||||
ErrPacketDropped = errors.New("raven: packet dropped")
|
||||
ErrUnableToUnmarshalJSON = errors.New("raven: unable to unmarshal JSON")
|
||||
ErrMissingUser = errors.New("raven: dsn missing public key and/or password")
|
||||
ErrMissingPrivateKey = errors.New("raven: dsn missing private key")
|
||||
ErrMissingProjectID = errors.New("raven: dsn missing project id")
|
||||
ErrInvalidSampleRate = errors.New("raven: sample rate should be between 0 and 1")
|
||||
)
|
||||
|
||||
type Severity string
|
||||
@@ -65,6 +68,11 @@ func (timestamp *Timestamp) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (timestamp Timestamp) Format(format string) string {
|
||||
t := time.Time(timestamp)
|
||||
return t.Format(format)
|
||||
}
|
||||
|
||||
// An Interface is a Sentry interface that will be serialized as JSON.
|
||||
// It must implement json.Marshaler or use json struct tags.
|
||||
type Interface interface {
|
||||
@@ -80,6 +88,8 @@ type Transport interface {
|
||||
Send(url, authHeader string, packet *Packet) error
|
||||
}
|
||||
|
||||
type Extra map[string]interface{}
|
||||
|
||||
type outgoingPacket struct {
|
||||
packet *Packet
|
||||
ch chan error
|
||||
@@ -146,27 +156,23 @@ type Packet struct {
|
||||
Logger string `json:"logger"`
|
||||
|
||||
// Optional
|
||||
Platform string `json:"platform,omitempty"`
|
||||
Culprit string `json:"culprit,omitempty"`
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
Release string `json:"release,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
Tags Tags `json:"tags,omitempty"`
|
||||
Modules map[string]string `json:"modules,omitempty"`
|
||||
Fingerprint []string `json:"fingerprint,omitempty"`
|
||||
Extra map[string]interface{} `json:"extra,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
Culprit string `json:"culprit,omitempty"`
|
||||
ServerName string `json:"server_name,omitempty"`
|
||||
Release string `json:"release,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
Tags Tags `json:"tags,omitempty"`
|
||||
Modules map[string]string `json:"modules,omitempty"`
|
||||
Fingerprint []string `json:"fingerprint,omitempty"`
|
||||
Extra Extra `json:"extra,omitempty"`
|
||||
|
||||
Interfaces []Interface `json:"-"`
|
||||
}
|
||||
|
||||
// NewPacket constructs a packet with the specified message and interfaces.
|
||||
func NewPacket(message string, interfaces ...Interface) *Packet {
|
||||
extra := map[string]interface{}{
|
||||
"runtime.Version": runtime.Version(),
|
||||
"runtime.NumCPU": runtime.NumCPU(),
|
||||
"runtime.GOMAXPROCS": runtime.GOMAXPROCS(0), // 0 just returns the current value
|
||||
"runtime.NumGoroutine": runtime.NumGoroutine(),
|
||||
}
|
||||
extra := Extra{}
|
||||
setExtraDefaults(extra)
|
||||
return &Packet{
|
||||
Message: message,
|
||||
Interfaces: interfaces,
|
||||
@@ -174,6 +180,28 @@ func NewPacket(message string, interfaces ...Interface) *Packet {
|
||||
}
|
||||
}
|
||||
|
||||
// NewPacketWithExtra constructs a packet with the specified message, extra information, and interfaces.
|
||||
func NewPacketWithExtra(message string, extra Extra, interfaces ...Interface) *Packet {
|
||||
if extra == nil {
|
||||
extra = Extra{}
|
||||
}
|
||||
setExtraDefaults(extra)
|
||||
|
||||
return &Packet{
|
||||
Message: message,
|
||||
Interfaces: interfaces,
|
||||
Extra: extra,
|
||||
}
|
||||
}
|
||||
|
||||
func setExtraDefaults(extra Extra) Extra {
|
||||
extra["runtime.Version"] = runtime.Version()
|
||||
extra["runtime.NumCPU"] = runtime.NumCPU()
|
||||
extra["runtime.GOMAXPROCS"] = runtime.GOMAXPROCS(0) // 0 just returns the current value
|
||||
extra["runtime.NumGoroutine"] = runtime.NumGoroutine()
|
||||
return extra
|
||||
}
|
||||
|
||||
// Init initializes required fields in a packet. It is typically called by
|
||||
// Client.Send/Report automatically.
|
||||
func (packet *Packet) Init(project string) error {
|
||||
@@ -267,9 +295,9 @@ type context struct {
|
||||
tags map[string]string
|
||||
}
|
||||
|
||||
func (c *context) SetUser(u *User) { c.user = u }
|
||||
func (c *context) SetHttp(h *Http) { c.http = h }
|
||||
func (c *context) SetTags(t map[string]string) {
|
||||
func (c *context) setUser(u *User) { c.user = u }
|
||||
func (c *context) setHttp(h *Http) { c.http = h }
|
||||
func (c *context) setTags(t map[string]string) {
|
||||
if c.tags == nil {
|
||||
c.tags = make(map[string]string)
|
||||
}
|
||||
@@ -277,7 +305,7 @@ func (c *context) SetTags(t map[string]string) {
|
||||
c.tags[k] = v
|
||||
}
|
||||
}
|
||||
func (c *context) Clear() {
|
||||
func (c *context) clear() {
|
||||
c.user = nil
|
||||
c.http = nil
|
||||
c.tags = nil
|
||||
@@ -316,6 +344,7 @@ func newTransport() Transport {
|
||||
} else {
|
||||
t.Client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{RootCAs: rootCAs},
|
||||
},
|
||||
}
|
||||
@@ -325,12 +354,15 @@ func newTransport() Transport {
|
||||
|
||||
func newClient(tags map[string]string) *Client {
|
||||
client := &Client{
|
||||
Transport: newTransport(),
|
||||
Tags: tags,
|
||||
context: &context{},
|
||||
queue: make(chan *outgoingPacket, MaxQueueBuffer),
|
||||
Transport: newTransport(),
|
||||
Tags: tags,
|
||||
context: &context{},
|
||||
sampleRate: 1.0,
|
||||
queue: make(chan *outgoingPacket, MaxQueueBuffer),
|
||||
}
|
||||
client.SetDSN(os.Getenv("SENTRY_DSN"))
|
||||
client.SetRelease(os.Getenv("SENTRY_RELEASE"))
|
||||
client.SetEnvironment(os.Getenv("SENTRY_ENVIRONMENT"))
|
||||
return client
|
||||
}
|
||||
|
||||
@@ -369,14 +401,20 @@ type Client struct {
|
||||
// Context that will get appending to all packets
|
||||
context *context
|
||||
|
||||
mu sync.RWMutex
|
||||
url string
|
||||
projectID string
|
||||
authHeader string
|
||||
release string
|
||||
environment string
|
||||
includePaths []string
|
||||
queue chan *outgoingPacket
|
||||
mu sync.RWMutex
|
||||
url string
|
||||
projectID string
|
||||
authHeader string
|
||||
release string
|
||||
environment string
|
||||
sampleRate float32
|
||||
|
||||
// default logger name (leave empty for 'root')
|
||||
defaultLoggerName string
|
||||
|
||||
includePaths []string
|
||||
ignoreErrorsRegexp *regexp.Regexp
|
||||
queue chan *outgoingPacket
|
||||
|
||||
// A WaitGroup to keep track of all currently in-progress captures
|
||||
// This is intended to be used with Client.Wait() to assure that
|
||||
@@ -390,6 +428,29 @@ type Client struct {
|
||||
// Initialize a default *Client instance
|
||||
var DefaultClient = newClient(nil)
|
||||
|
||||
func (c *Client) SetIgnoreErrors(errs []string) error {
|
||||
joinedRegexp := strings.Join(errs, "|")
|
||||
r, err := regexp.Compile(joinedRegexp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile regexp %q for %q: %v", joinedRegexp, errs, err)
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.ignoreErrorsRegexp = r
|
||||
c.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) shouldExcludeErr(errStr string) bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.ignoreErrorsRegexp != nil && c.ignoreErrorsRegexp.MatchString(errStr)
|
||||
}
|
||||
|
||||
func SetIgnoreErrors(errs ...string) error {
|
||||
return DefaultClient.SetIgnoreErrors(errs)
|
||||
}
|
||||
|
||||
// SetDSN updates a client with a new DSN. It safe to call after and
|
||||
// concurrently with calls to Report and Send.
|
||||
func (client *Client) SetDSN(dsn string) error {
|
||||
@@ -409,10 +470,7 @@ func (client *Client) SetDSN(dsn string) error {
|
||||
return ErrMissingUser
|
||||
}
|
||||
publicKey := uri.User.Username()
|
||||
secretKey, ok := uri.User.Password()
|
||||
if !ok {
|
||||
return ErrMissingPrivateKey
|
||||
}
|
||||
secretKey, hasSecretKey := uri.User.Password()
|
||||
uri.User = nil
|
||||
|
||||
if idx := strings.LastIndex(uri.Path, "/"); idx != -1 {
|
||||
@@ -425,7 +483,11 @@ func (client *Client) SetDSN(dsn string) error {
|
||||
|
||||
client.url = uri.String()
|
||||
|
||||
client.authHeader = fmt.Sprintf("Sentry sentry_version=4, sentry_key=%s, sentry_secret=%s", publicKey, secretKey)
|
||||
if hasSecretKey {
|
||||
client.authHeader = fmt.Sprintf("Sentry sentry_version=4, sentry_key=%s, sentry_secret=%s", publicKey, secretKey)
|
||||
} else {
|
||||
client.authHeader = fmt.Sprintf("Sentry sentry_version=4, sentry_key=%s", publicKey)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -447,12 +509,39 @@ func (client *Client) SetEnvironment(environment string) {
|
||||
client.environment = environment
|
||||
}
|
||||
|
||||
// SetDefaultLoggerName sets the default logger name.
|
||||
func (client *Client) SetDefaultLoggerName(name string) {
|
||||
client.mu.Lock()
|
||||
defer client.mu.Unlock()
|
||||
client.defaultLoggerName = name
|
||||
}
|
||||
|
||||
// SetSampleRate sets how much sampling we want on client side
|
||||
func (client *Client) SetSampleRate(rate float32) error {
|
||||
client.mu.Lock()
|
||||
defer client.mu.Unlock()
|
||||
|
||||
if rate < 0 || rate > 1 {
|
||||
return ErrInvalidSampleRate
|
||||
}
|
||||
client.sampleRate = rate
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRelease sets the "release" tag on the default *Client
|
||||
func SetRelease(release string) { DefaultClient.SetRelease(release) }
|
||||
|
||||
// SetEnvironment sets the "environment" tag on the default *Client
|
||||
func SetEnvironment(environment string) { DefaultClient.SetEnvironment(environment) }
|
||||
|
||||
// SetDefaultLoggerName sets the "defaultLoggerName" on the default *Client
|
||||
func SetDefaultLoggerName(name string) {
|
||||
DefaultClient.SetDefaultLoggerName(name)
|
||||
}
|
||||
|
||||
// SetSampleRate sets the "sample rate" on the degault *Client
|
||||
func SetSampleRate(rate float32) error { return DefaultClient.SetSampleRate(rate) }
|
||||
|
||||
func (client *Client) worker() {
|
||||
for outgoingPacket := range client.queue {
|
||||
|
||||
@@ -469,7 +558,24 @@ func (client *Client) worker() {
|
||||
// when client is nil. A channel is provided if it is important to check for a
|
||||
// send's success.
|
||||
func (client *Client) Capture(packet *Packet, captureTags map[string]string) (eventID string, ch chan error) {
|
||||
ch = make(chan error, 1)
|
||||
|
||||
if client == nil {
|
||||
// return a chan that always returns nil when the caller receives from it
|
||||
close(ch)
|
||||
return
|
||||
}
|
||||
|
||||
if client.sampleRate < 1.0 && mrand.Float32() > client.sampleRate {
|
||||
return
|
||||
}
|
||||
|
||||
if packet == nil {
|
||||
close(ch)
|
||||
return
|
||||
}
|
||||
|
||||
if client.shouldExcludeErr(packet.Message) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -478,20 +584,24 @@ func (client *Client) Capture(packet *Packet, captureTags map[string]string) (ev
|
||||
// finished being acted upon, whether success or failure
|
||||
client.wg.Add(1)
|
||||
|
||||
ch = make(chan error, 1)
|
||||
|
||||
// Merge capture tags and client tags
|
||||
packet.AddTags(captureTags)
|
||||
packet.AddTags(client.Tags)
|
||||
packet.AddTags(client.context.tags)
|
||||
|
||||
// Initialize any required packet fields
|
||||
client.mu.RLock()
|
||||
packet.AddTags(client.context.tags)
|
||||
projectID := client.projectID
|
||||
release := client.release
|
||||
environment := client.environment
|
||||
defaultLoggerName := client.defaultLoggerName
|
||||
client.mu.RUnlock()
|
||||
|
||||
// set the global logger name on the packet if we must
|
||||
if packet.Logger == "" && defaultLoggerName != "" {
|
||||
packet.Logger = defaultLoggerName
|
||||
}
|
||||
|
||||
err := packet.Init(projectID)
|
||||
if err != nil {
|
||||
ch <- err
|
||||
@@ -499,8 +609,13 @@ func (client *Client) Capture(packet *Packet, captureTags map[string]string) (ev
|
||||
return
|
||||
}
|
||||
|
||||
packet.Release = release
|
||||
packet.Environment = environment
|
||||
if packet.Release == "" {
|
||||
packet.Release = release
|
||||
}
|
||||
|
||||
if packet.Environment == "" {
|
||||
packet.Environment = environment
|
||||
}
|
||||
|
||||
outgoingPacket := &outgoingPacket{packet, ch}
|
||||
|
||||
@@ -537,6 +652,10 @@ func (client *Client) CaptureMessage(message string, tags map[string]string, int
|
||||
return ""
|
||||
}
|
||||
|
||||
if client.shouldExcludeErr(message) {
|
||||
return ""
|
||||
}
|
||||
|
||||
packet := NewPacket(message, append(append(interfaces, client.context.interfaces()...), &Message{message, nil})...)
|
||||
eventID, _ := client.Capture(packet, tags)
|
||||
|
||||
@@ -554,9 +673,15 @@ func (client *Client) CaptureMessageAndWait(message string, tags map[string]stri
|
||||
return ""
|
||||
}
|
||||
|
||||
if client.shouldExcludeErr(message) {
|
||||
return ""
|
||||
}
|
||||
|
||||
packet := NewPacket(message, append(append(interfaces, client.context.interfaces()...), &Message{message, nil})...)
|
||||
eventID, ch := client.Capture(packet, tags)
|
||||
<-ch
|
||||
if eventID != "" {
|
||||
<-ch
|
||||
}
|
||||
|
||||
return eventID
|
||||
}
|
||||
@@ -573,7 +698,18 @@ func (client *Client) CaptureError(err error, tags map[string]string, interfaces
|
||||
return ""
|
||||
}
|
||||
|
||||
packet := NewPacket(err.Error(), append(append(interfaces, client.context.interfaces()...), NewException(err, NewStacktrace(1, 3, client.includePaths)))...)
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if client.shouldExcludeErr(err.Error()) {
|
||||
return ""
|
||||
}
|
||||
|
||||
extra := extractExtra(err)
|
||||
cause := pkgErrors.Cause(err)
|
||||
|
||||
packet := NewPacketWithExtra(err.Error(), extra, append(append(interfaces, client.context.interfaces()...), NewException(cause, GetOrNewStacktrace(cause, 1, 3, client.includePaths)))...)
|
||||
eventID, _ := client.Capture(packet, tags)
|
||||
|
||||
return eventID
|
||||
@@ -591,9 +727,18 @@ func (client *Client) CaptureErrorAndWait(err error, tags map[string]string, int
|
||||
return ""
|
||||
}
|
||||
|
||||
packet := NewPacket(err.Error(), append(append(interfaces, client.context.interfaces()...), NewException(err, NewStacktrace(1, 3, client.includePaths)))...)
|
||||
if client.shouldExcludeErr(err.Error()) {
|
||||
return ""
|
||||
}
|
||||
|
||||
extra := extractExtra(err)
|
||||
cause := pkgErrors.Cause(err)
|
||||
|
||||
packet := NewPacketWithExtra(err.Error(), extra, append(append(interfaces, client.context.interfaces()...), NewException(cause, GetOrNewStacktrace(cause, 1, 3, client.includePaths)))...)
|
||||
eventID, ch := client.Capture(packet, tags)
|
||||
<-ch
|
||||
if eventID != "" {
|
||||
<-ch
|
||||
}
|
||||
|
||||
return eventID
|
||||
}
|
||||
@@ -617,9 +762,15 @@ func (client *Client) CapturePanic(f func(), tags map[string]string, interfaces
|
||||
case nil:
|
||||
return
|
||||
case error:
|
||||
if client.shouldExcludeErr(rval.Error()) {
|
||||
return
|
||||
}
|
||||
packet = NewPacket(rval.Error(), append(append(interfaces, client.context.interfaces()...), NewException(rval, NewStacktrace(2, 3, client.includePaths)))...)
|
||||
default:
|
||||
rvalStr := fmt.Sprint(rval)
|
||||
if client.shouldExcludeErr(rvalStr) {
|
||||
return
|
||||
}
|
||||
packet = NewPacket(rvalStr, append(append(interfaces, client.context.interfaces()...), NewException(errors.New(rvalStr), NewStacktrace(2, 3, client.includePaths)))...)
|
||||
}
|
||||
|
||||
@@ -649,15 +800,23 @@ func (client *Client) CapturePanicAndWait(f func(), tags map[string]string, inte
|
||||
case nil:
|
||||
return
|
||||
case error:
|
||||
if client.shouldExcludeErr(rval.Error()) {
|
||||
return
|
||||
}
|
||||
packet = NewPacket(rval.Error(), append(append(interfaces, client.context.interfaces()...), NewException(rval, NewStacktrace(2, 3, client.includePaths)))...)
|
||||
default:
|
||||
rvalStr := fmt.Sprint(rval)
|
||||
if client.shouldExcludeErr(rvalStr) {
|
||||
return
|
||||
}
|
||||
packet = NewPacket(rvalStr, append(append(interfaces, client.context.interfaces()...), NewException(errors.New(rvalStr), NewStacktrace(2, 3, client.includePaths)))...)
|
||||
}
|
||||
|
||||
var ch chan error
|
||||
errorID, ch = client.Capture(packet, tags)
|
||||
<-ch
|
||||
if errorID != "" {
|
||||
<-ch
|
||||
}
|
||||
}()
|
||||
|
||||
f()
|
||||
@@ -728,10 +887,29 @@ func (client *Client) SetIncludePaths(p []string) {
|
||||
client.includePaths = p
|
||||
}
|
||||
|
||||
func (c *Client) SetUserContext(u *User) { c.context.SetUser(u) }
|
||||
func (c *Client) SetHttpContext(h *Http) { c.context.SetHttp(h) }
|
||||
func (c *Client) SetTagsContext(t map[string]string) { c.context.SetTags(t) }
|
||||
func (c *Client) ClearContext() { c.context.Clear() }
|
||||
func (c *Client) SetUserContext(u *User) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.context.setUser(u)
|
||||
}
|
||||
|
||||
func (c *Client) SetHttpContext(h *Http) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.context.setHttp(h)
|
||||
}
|
||||
|
||||
func (c *Client) SetTagsContext(t map[string]string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.context.setTags(t)
|
||||
}
|
||||
|
||||
func (c *Client) ClearContext() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.context.clear()
|
||||
}
|
||||
|
||||
func SetUserContext(u *User) { DefaultClient.SetUserContext(u) }
|
||||
func SetHttpContext(h *Http) { DefaultClient.SetHttpContext(h) }
|
||||
@@ -767,7 +945,7 @@ func (t *HTTPTransport) Send(url, authHeader string, packet *Packet) error {
|
||||
io.Copy(ioutil.Discard, res.Body)
|
||||
res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("raven: got http status %d", res.StatusCode)
|
||||
return fmt.Errorf("raven: got http status %d - x-sentry-error: %s", res.StatusCode, res.Header.Get("X-Sentry-Error"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package raven
|
||||
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
type errWrappedWithExtra struct {
|
||||
err error
|
||||
extraInfo map[string]interface{}
|
||||
}
|
||||
|
||||
func (ewx *errWrappedWithExtra) Error() string {
|
||||
return ewx.err.Error()
|
||||
}
|
||||
|
||||
func (ewx *errWrappedWithExtra) Cause() error {
|
||||
return ewx.err
|
||||
}
|
||||
|
||||
func (ewx *errWrappedWithExtra) ExtraInfo() Extra {
|
||||
return ewx.extraInfo
|
||||
}
|
||||
|
||||
// Adds extra data to an error before reporting to Sentry
|
||||
func WrapWithExtra(err error, extraInfo map[string]interface{}) error {
|
||||
return &errWrappedWithExtra{
|
||||
err: err,
|
||||
extraInfo: extraInfo,
|
||||
}
|
||||
}
|
||||
|
||||
type ErrWithExtra interface {
|
||||
Error() string
|
||||
Cause() error
|
||||
ExtraInfo() Extra
|
||||
}
|
||||
|
||||
// Iteratively fetches all the Extra data added to an error,
|
||||
// and it's underlying errors. Extra data defined first is
|
||||
// respected, and is not overridden when extracting.
|
||||
func extractExtra(err error) Extra {
|
||||
extra := Extra{}
|
||||
|
||||
currentErr := err
|
||||
for currentErr != nil {
|
||||
if errWithExtra, ok := currentErr.(ErrWithExtra); ok {
|
||||
for k, v := range errWithExtra.ExtraInfo() {
|
||||
extra[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if errWithCause, ok := currentErr.(causer); ok {
|
||||
currentErr = errWithCause.Cause()
|
||||
} else {
|
||||
currentErr = nil
|
||||
}
|
||||
}
|
||||
|
||||
return extra
|
||||
}
|
||||
+9
@@ -39,3 +39,12 @@ func (e *Exception) Culprit() string {
|
||||
}
|
||||
return e.Stacktrace.Culprit()
|
||||
}
|
||||
|
||||
// Exceptions allows for chained errors
|
||||
// https://docs.sentry.io/clientdev/interfaces/exception/
|
||||
type Exceptions struct {
|
||||
// Required
|
||||
Values []*Exception `json:"values"`
|
||||
}
|
||||
|
||||
func (es Exceptions) Class() string { return "exception" }
|
||||
|
||||
+19
-4
@@ -28,6 +28,7 @@ func NewHttp(req *http.Request) *Http {
|
||||
for k, v := range req.Header {
|
||||
h.Headers[k] = strings.Join(v, ",")
|
||||
}
|
||||
h.Headers["Host"] = req.Host
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -68,17 +69,31 @@ func (h *Http) Class() string { return "request" }
|
||||
// ...
|
||||
// }))
|
||||
func RecoveryHandler(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
return Recoverer(http.HandlerFunc(handler)).ServeHTTP
|
||||
}
|
||||
|
||||
// Recovery handler to wrap the stdlib net/http Mux.
|
||||
// Example:
|
||||
// mux := http.NewServeMux
|
||||
// ...
|
||||
// http.Handle("/", raven.Recoverer(mux))
|
||||
func Recoverer(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if rval := recover(); rval != nil {
|
||||
debug.PrintStack()
|
||||
rvalStr := fmt.Sprint(rval)
|
||||
packet := NewPacket(rvalStr, NewException(errors.New(rvalStr), NewStacktrace(2, 3, nil)), NewHttp(r))
|
||||
var packet *Packet
|
||||
if err, ok := rval.(error); ok {
|
||||
packet = NewPacket(rvalStr, NewException(errors.New(rvalStr), GetOrNewStacktrace(err, 2, 3, nil)), NewHttp(r))
|
||||
} else {
|
||||
packet = NewPacket(rvalStr, NewException(errors.New(rvalStr), NewStacktrace(2, 3, nil)), NewHttp(r))
|
||||
}
|
||||
Capture(packet, nil)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
|
||||
handler(w, r)
|
||||
}
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
+88
-24
@@ -13,6 +13,8 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// https://docs.getsentry.com/hosted/clientdev/interfaces/#failure-interfaces
|
||||
@@ -49,6 +51,37 @@ type StacktraceFrame struct {
|
||||
InApp bool `json:"in_app"`
|
||||
}
|
||||
|
||||
// Try to get stacktrace from err as an interface of github.com/pkg/errors, or else NewStacktrace()
|
||||
func GetOrNewStacktrace(err error, skip int, context int, appPackagePrefixes []string) *Stacktrace {
|
||||
stacktracer, errHasStacktrace := err.(interface {
|
||||
StackTrace() errors.StackTrace
|
||||
})
|
||||
if errHasStacktrace {
|
||||
var frames []*StacktraceFrame
|
||||
for _, f := range stacktracer.StackTrace() {
|
||||
pc := uintptr(f) - 1
|
||||
fn := runtime.FuncForPC(pc)
|
||||
var fName string
|
||||
var file string
|
||||
var line int
|
||||
if fn != nil {
|
||||
file, line = fn.FileLine(pc)
|
||||
fName = fn.Name()
|
||||
} else {
|
||||
file = "unknown"
|
||||
fName = "unknown"
|
||||
}
|
||||
frame := NewStacktraceFrame(pc, fName, file, line, context, appPackagePrefixes)
|
||||
if frame != nil {
|
||||
frames = append([]*StacktraceFrame{frame}, frames...)
|
||||
}
|
||||
}
|
||||
return &Stacktrace{Frames: frames}
|
||||
} else {
|
||||
return NewStacktrace(skip+1, context, appPackagePrefixes)
|
||||
}
|
||||
}
|
||||
|
||||
// Intialize and populate a new stacktrace, skipping skip frames.
|
||||
//
|
||||
// context is the number of surrounding lines that should be included for context.
|
||||
@@ -59,14 +92,27 @@ type StacktraceFrame struct {
|
||||
// be considered "in app".
|
||||
func NewStacktrace(skip int, context int, appPackagePrefixes []string) *Stacktrace {
|
||||
var frames []*StacktraceFrame
|
||||
for i := 1 + skip; ; i++ {
|
||||
pc, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
|
||||
callerPcs := make([]uintptr, 100)
|
||||
numCallers := runtime.Callers(skip+2, callerPcs)
|
||||
|
||||
// If there are no callers, the entire stacktrace is nil
|
||||
if numCallers == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
callersFrames := runtime.CallersFrames(callerPcs)
|
||||
|
||||
for {
|
||||
fr, more := callersFrames.Next()
|
||||
if fr.Func != nil {
|
||||
frame := NewStacktraceFrame(fr.PC, fr.Function, fr.File, fr.Line, context, appPackagePrefixes)
|
||||
if frame != nil {
|
||||
frames = append(frames, frame)
|
||||
}
|
||||
}
|
||||
frame := NewStacktraceFrame(pc, file, line, context, appPackagePrefixes)
|
||||
if frame != nil {
|
||||
frames = append(frames, frame)
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
// If there are no frames, the entire stacktrace is nil
|
||||
@@ -92,9 +138,9 @@ func NewStacktrace(skip int, context int, appPackagePrefixes []string) *Stacktra
|
||||
//
|
||||
// appPackagePrefixes is a list of prefixes used to check whether a package should
|
||||
// be considered "in app".
|
||||
func NewStacktraceFrame(pc uintptr, file string, line, context int, appPackagePrefixes []string) *StacktraceFrame {
|
||||
func NewStacktraceFrame(pc uintptr, fName, file string, line, context int, appPackagePrefixes []string) *StacktraceFrame {
|
||||
frame := &StacktraceFrame{AbsolutePath: file, Filename: trimPath(file), Lineno: line, InApp: false}
|
||||
frame.Module, frame.Function = functionName(pc)
|
||||
frame.Module, frame.Function = functionName(fName)
|
||||
|
||||
// `runtime.goexit` is effectively a placeholder that comes from
|
||||
// runtime/asm_amd64.s and is meaningless.
|
||||
@@ -113,7 +159,7 @@ func NewStacktraceFrame(pc uintptr, file string, line, context int, appPackagePr
|
||||
}
|
||||
|
||||
if context > 0 {
|
||||
contextLines, lineIdx := fileContext(file, line, context)
|
||||
contextLines, lineIdx := sourceCodeLoader.Load(file, line, context)
|
||||
if len(contextLines) > 0 {
|
||||
for i, line := range contextLines {
|
||||
switch {
|
||||
@@ -127,7 +173,7 @@ func NewStacktraceFrame(pc uintptr, file string, line, context int, appPackagePr
|
||||
}
|
||||
}
|
||||
} else if context == -1 {
|
||||
contextLine, _ := fileContext(file, line, 0)
|
||||
contextLine, _ := sourceCodeLoader.Load(file, line, 0)
|
||||
if len(contextLine) > 0 {
|
||||
frame.ContextLine = string(contextLine[0])
|
||||
}
|
||||
@@ -136,12 +182,8 @@ func NewStacktraceFrame(pc uintptr, file string, line, context int, appPackagePr
|
||||
}
|
||||
|
||||
// Retrieve the name of the package and function containing the PC.
|
||||
func functionName(pc uintptr) (pack string, name string) {
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
name = fn.Name()
|
||||
func functionName(fName string) (pack string, name string) {
|
||||
name = fName
|
||||
// We get this:
|
||||
// runtime/debug.*T·ptrmethod
|
||||
// and want this:
|
||||
@@ -155,21 +197,43 @@ func functionName(pc uintptr) (pack string, name string) {
|
||||
return
|
||||
}
|
||||
|
||||
var fileCacheLock sync.Mutex
|
||||
var fileCache = make(map[string][][]byte)
|
||||
type SourceCodeLoader interface {
|
||||
Load(filename string, line, context int) ([][]byte, int)
|
||||
}
|
||||
|
||||
func fileContext(filename string, line, context int) ([][]byte, int) {
|
||||
fileCacheLock.Lock()
|
||||
defer fileCacheLock.Unlock()
|
||||
lines, ok := fileCache[filename]
|
||||
var sourceCodeLoader SourceCodeLoader = &fsLoader{cache: make(map[string][][]byte)}
|
||||
|
||||
func SetSourceCodeLoader(loader SourceCodeLoader) {
|
||||
sourceCodeLoader = loader
|
||||
}
|
||||
|
||||
type fsLoader struct {
|
||||
mu sync.Mutex
|
||||
cache map[string][][]byte
|
||||
}
|
||||
|
||||
func (fs *fsLoader) Load(filename string, line, context int) ([][]byte, int) {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
lines, ok := fs.cache[filename]
|
||||
if !ok {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
// cache errors as nil slice: code below handles it correctly
|
||||
// otherwise when missing the source or running as a different user, we try
|
||||
// reading the file on each error which is unnecessary
|
||||
fs.cache[filename] = nil
|
||||
return nil, 0
|
||||
}
|
||||
lines = bytes.Split(data, []byte{'\n'})
|
||||
fileCache[filename] = lines
|
||||
fs.cache[filename] = lines
|
||||
}
|
||||
|
||||
if lines == nil {
|
||||
// cached error from ReadFile: return no lines
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
line-- // stack trace lines are 1-indexed
|
||||
start := line - context
|
||||
var idx int
|
||||
|
||||
Reference in New Issue
Block a user