123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978 |
- // Package raven implements a client for the Sentry error logging service.
- package raven
- import (
- "bytes"
- "compress/zlib"
- "crypto/rand"
- "crypto/tls"
- "encoding/base64"
- "encoding/hex"
- "encoding/json"
- "errors"
- "fmt"
- "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 (
- userAgent = "raven-go/1.0"
- timestampFormat = `"2006-01-02T15:04:05.00"`
- )
- 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")
- ErrMissingProjectID = errors.New("raven: dsn missing project id")
- ErrInvalidSampleRate = errors.New("raven: sample rate should be between 0 and 1")
- )
- type Severity string
- // http://docs.python.org/2/howto/logging.html#logging-levels
- const (
- DEBUG = Severity("debug")
- INFO = Severity("info")
- WARNING = Severity("warning")
- ERROR = Severity("error")
- FATAL = Severity("fatal")
- )
- type Timestamp time.Time
- func (t Timestamp) MarshalJSON() ([]byte, error) {
- return []byte(time.Time(t).UTC().Format(timestampFormat)), nil
- }
- func (timestamp *Timestamp) UnmarshalJSON(data []byte) error {
- t, err := time.Parse(timestampFormat, string(data))
- if err != nil {
- return err
- }
- *timestamp = Timestamp(t)
- 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 {
- // The Sentry class name. Example: sentry.interfaces.Stacktrace
- Class() string
- }
- type Culpriter interface {
- Culprit() string
- }
- type Transport interface {
- Send(url, authHeader string, packet *Packet) error
- }
- type Extra map[string]interface{}
- type outgoingPacket struct {
- packet *Packet
- ch chan error
- }
- type Tag struct {
- Key string
- Value string
- }
- type Tags []Tag
- func (tag *Tag) MarshalJSON() ([]byte, error) {
- return json.Marshal([2]string{tag.Key, tag.Value})
- }
- func (t *Tag) UnmarshalJSON(data []byte) error {
- var tag [2]string
- if err := json.Unmarshal(data, &tag); err != nil {
- return err
- }
- *t = Tag{tag[0], tag[1]}
- return nil
- }
- func (t *Tags) UnmarshalJSON(data []byte) error {
- var tags []Tag
- switch data[0] {
- case '[':
- // Unmarshal into []Tag
- if err := json.Unmarshal(data, &tags); err != nil {
- return err
- }
- case '{':
- // Unmarshal into map[string]string
- tagMap := make(map[string]string)
- if err := json.Unmarshal(data, &tagMap); err != nil {
- return err
- }
- // Convert to []Tag
- for k, v := range tagMap {
- tags = append(tags, Tag{k, v})
- }
- default:
- return ErrUnableToUnmarshalJSON
- }
- *t = tags
- return nil
- }
- // https://docs.getsentry.com/hosted/clientdev/#building-the-json-packet
- type Packet struct {
- // Required
- Message string `json:"message"`
- // Required, set automatically by Client.Send/Report via Packet.Init if blank
- EventID string `json:"event_id"`
- Project string `json:"project"`
- Timestamp Timestamp `json:"timestamp"`
- Level Severity `json:"level"`
- 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 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 := Extra{}
- setExtraDefaults(extra)
- return &Packet{
- Message: message,
- Interfaces: interfaces,
- Extra: extra,
- }
- }
- // 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 {
- if packet.Project == "" {
- packet.Project = project
- }
- if packet.EventID == "" {
- var err error
- packet.EventID, err = uuid()
- if err != nil {
- return err
- }
- }
- if time.Time(packet.Timestamp).IsZero() {
- packet.Timestamp = Timestamp(time.Now())
- }
- if packet.Level == "" {
- packet.Level = ERROR
- }
- if packet.Logger == "" {
- packet.Logger = "root"
- }
- if packet.ServerName == "" {
- packet.ServerName = hostname
- }
- if packet.Platform == "" {
- packet.Platform = "go"
- }
- if packet.Culprit == "" {
- for _, inter := range packet.Interfaces {
- if c, ok := inter.(Culpriter); ok {
- packet.Culprit = c.Culprit()
- if packet.Culprit != "" {
- break
- }
- }
- }
- }
- return nil
- }
- func (packet *Packet) AddTags(tags map[string]string) {
- for k, v := range tags {
- packet.Tags = append(packet.Tags, Tag{k, v})
- }
- }
- func uuid() (string, error) {
- id := make([]byte, 16)
- _, err := io.ReadFull(rand.Reader, id)
- if err != nil {
- return "", err
- }
- 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), nil
- }
- func (packet *Packet) JSON() ([]byte, error) {
- packetJSON, err := json.Marshal(packet)
- if err != nil {
- return nil, err
- }
- interfaces := make(map[string]Interface, len(packet.Interfaces))
- for _, inter := range packet.Interfaces {
- if inter != nil {
- interfaces[inter.Class()] = inter
- }
- }
- if len(interfaces) > 0 {
- interfaceJSON, err := json.Marshal(interfaces)
- if err != nil {
- return nil, err
- }
- packetJSON[len(packetJSON)-1] = ','
- packetJSON = append(packetJSON, interfaceJSON[1:]...)
- }
- return packetJSON, nil
- }
- type context struct {
- user *User
- http *Http
- 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) {
- if c.tags == nil {
- c.tags = make(map[string]string)
- }
- for k, v := range t {
- c.tags[k] = v
- }
- }
- func (c *context) clear() {
- c.user = nil
- c.http = nil
- c.tags = nil
- }
- // Return a list of interfaces to be used in appending with the rest
- func (c *context) interfaces() []Interface {
- len, i := 0, 0
- if c.user != nil {
- len++
- }
- if c.http != nil {
- len++
- }
- interfaces := make([]Interface, len)
- if c.user != nil {
- interfaces[i] = c.user
- i++
- }
- if c.http != nil {
- interfaces[i] = c.http
- i++
- }
- return interfaces
- }
- // The maximum number of packets that will be buffered waiting to be delivered.
- // Packets will be dropped if the buffer is full. Used by NewClient.
- var MaxQueueBuffer = 100
- func newTransport() Transport {
- t := &HTTPTransport{}
- rootCAs, err := gocertifi.CACerts()
- if err != nil {
- log.Println("raven: failed to load root TLS certificates:", err)
- } else {
- t.Client = &http.Client{
- Transport: &http.Transport{
- Proxy: http.ProxyFromEnvironment,
- TLSClientConfig: &tls.Config{RootCAs: rootCAs},
- },
- }
- }
- return t
- }
- func newClient(tags map[string]string) *Client {
- client := &Client{
- 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
- }
- // New constructs a new Sentry client instance
- func New(dsn string) (*Client, error) {
- client := newClient(nil)
- return client, client.SetDSN(dsn)
- }
- // NewWithTags constructs a new Sentry client instance with default tags.
- func NewWithTags(dsn string, tags map[string]string) (*Client, error) {
- client := newClient(tags)
- return client, client.SetDSN(dsn)
- }
- // NewClient constructs a Sentry client and spawns a background goroutine to
- // handle packets sent by Client.Report.
- //
- // Deprecated: use New and NewWithTags instead
- func NewClient(dsn string, tags map[string]string) (*Client, error) {
- client := newClient(tags)
- return client, client.SetDSN(dsn)
- }
- // Client encapsulates a connection to a Sentry server. It must be initialized
- // by calling NewClient. Modification of fields concurrently with Send or after
- // calling Report for the first time is not thread-safe.
- type Client struct {
- Tags map[string]string
- Transport Transport
- // DropHandler is called when a packet is dropped because the buffer is full.
- DropHandler func(*Packet)
- // Context that will get appending to all packets
- context *context
- 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
- // all messages have been transported before exiting the process.
- wg sync.WaitGroup
- // A Once to track only starting up the background worker once
- start sync.Once
- }
- // 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 {
- if dsn == "" {
- return nil
- }
- client.mu.Lock()
- defer client.mu.Unlock()
- uri, err := url.Parse(dsn)
- if err != nil {
- return err
- }
- if uri.User == nil {
- return ErrMissingUser
- }
- publicKey := uri.User.Username()
- secretKey, hasSecretKey := uri.User.Password()
- uri.User = nil
- if idx := strings.LastIndex(uri.Path, "/"); idx != -1 {
- client.projectID = uri.Path[idx+1:]
- uri.Path = uri.Path[:idx+1] + "api/" + client.projectID + "/store/"
- }
- if client.projectID == "" {
- return ErrMissingProjectID
- }
- client.url = uri.String()
- 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
- }
- // Sets the DSN for the default *Client instance
- func SetDSN(dsn string) error { return DefaultClient.SetDSN(dsn) }
- // SetRelease sets the "release" tag.
- func (client *Client) SetRelease(release string) {
- client.mu.Lock()
- defer client.mu.Unlock()
- client.release = release
- }
- // SetEnvironment sets the "environment" tag.
- func (client *Client) SetEnvironment(environment string) {
- client.mu.Lock()
- defer client.mu.Unlock()
- 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 {
- client.mu.RLock()
- url, authHeader := client.url, client.authHeader
- client.mu.RUnlock()
- outgoingPacket.ch <- client.Transport.Send(url, authHeader, outgoingPacket.packet)
- client.wg.Done()
- }
- }
- // Capture asynchronously delivers a packet to the Sentry server. It is a no-op
- // 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
- }
- // Keep track of all running Captures so that we can wait for them all to finish
- // *Must* call client.wg.Done() on any path that indicates that an event was
- // finished being acted upon, whether success or failure
- client.wg.Add(1)
- // Merge capture tags and client tags
- packet.AddTags(captureTags)
- packet.AddTags(client.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
- client.wg.Done()
- return
- }
- if packet.Release == "" {
- packet.Release = release
- }
- if packet.Environment == "" {
- packet.Environment = environment
- }
- outgoingPacket := &outgoingPacket{packet, ch}
- // Lazily start background worker until we
- // do our first write into the queue.
- client.start.Do(func() {
- go client.worker()
- })
- select {
- case client.queue <- outgoingPacket:
- default:
- // Send would block, drop the packet
- if client.DropHandler != nil {
- client.DropHandler(packet)
- }
- ch <- ErrPacketDropped
- client.wg.Done()
- }
- return packet.EventID, ch
- }
- // Capture asynchronously delivers a packet to the Sentry server with the default *Client.
- // It is a no-op when client is nil. A channel is provided if it is important to check for a
- // send's success.
- func Capture(packet *Packet, captureTags map[string]string) (eventID string, ch chan error) {
- return DefaultClient.Capture(packet, captureTags)
- }
- // CaptureMessage formats and delivers a string message to the Sentry server.
- func (client *Client) CaptureMessage(message string, tags map[string]string, interfaces ...Interface) string {
- if client == nil {
- return ""
- }
- if client.shouldExcludeErr(message) {
- return ""
- }
- packet := NewPacket(message, append(append(interfaces, client.context.interfaces()...), &Message{message, nil})...)
- eventID, _ := client.Capture(packet, tags)
- return eventID
- }
- // CaptureMessage formats and delivers a string message to the Sentry server with the default *Client
- func CaptureMessage(message string, tags map[string]string, interfaces ...Interface) string {
- return DefaultClient.CaptureMessage(message, tags, interfaces...)
- }
- // CaptureMessageAndWait is identical to CaptureMessage except it blocks and waits for the message to be sent.
- func (client *Client) CaptureMessageAndWait(message string, tags map[string]string, interfaces ...Interface) string {
- if client == nil {
- return ""
- }
- if client.shouldExcludeErr(message) {
- return ""
- }
- packet := NewPacket(message, append(append(interfaces, client.context.interfaces()...), &Message{message, nil})...)
- eventID, ch := client.Capture(packet, tags)
- if eventID != "" {
- <-ch
- }
- return eventID
- }
- // CaptureMessageAndWait is identical to CaptureMessage except it blocks and waits for the message to be sent.
- func CaptureMessageAndWait(message string, tags map[string]string, interfaces ...Interface) string {
- return DefaultClient.CaptureMessageAndWait(message, tags, interfaces...)
- }
- // CaptureErrors formats and delivers an error to the Sentry server.
- // Adds a stacktrace to the packet, excluding the call to this method.
- func (client *Client) CaptureError(err error, tags map[string]string, interfaces ...Interface) string {
- if client == nil {
- return ""
- }
- 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
- }
- // CaptureErrors formats and delivers an error to the Sentry server using the default *Client.
- // Adds a stacktrace to the packet, excluding the call to this method.
- func CaptureError(err error, tags map[string]string, interfaces ...Interface) string {
- return DefaultClient.CaptureError(err, tags, interfaces...)
- }
- // CaptureErrorAndWait is identical to CaptureError, except it blocks and assures that the event was sent
- func (client *Client) CaptureErrorAndWait(err error, tags map[string]string, interfaces ...Interface) string {
- if client == 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, ch := client.Capture(packet, tags)
- if eventID != "" {
- <-ch
- }
- return eventID
- }
- // CaptureErrorAndWait is identical to CaptureError, except it blocks and assures that the event was sent
- func CaptureErrorAndWait(err error, tags map[string]string, interfaces ...Interface) string {
- return DefaultClient.CaptureErrorAndWait(err, tags, interfaces...)
- }
- // CapturePanic calls f and then recovers and reports a panic to the Sentry server if it occurs.
- // If an error is captured, both the error and the reported Sentry error ID are returned.
- func (client *Client) CapturePanic(f func(), tags map[string]string, interfaces ...Interface) (err interface{}, errorID string) {
- // Note: This doesn't need to check for client, because we still want to go through the defer/recover path
- // Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the
- // *Packet just to be thrown away, this should not be the normal case. Could be refactored to
- // be completely noop though if we cared.
- defer func() {
- var packet *Packet
- err = recover()
- switch rval := err.(type) {
- 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)))...)
- }
- errorID, _ = client.Capture(packet, tags)
- }()
- f()
- return
- }
- // CapturePanic calls f and then recovers and reports a panic to the Sentry server if it occurs.
- // If an error is captured, both the error and the reported Sentry error ID are returned.
- func CapturePanic(f func(), tags map[string]string, interfaces ...Interface) (interface{}, string) {
- return DefaultClient.CapturePanic(f, tags, interfaces...)
- }
- // CapturePanicAndWait is identical to CaptureError, except it blocks and assures that the event was sent
- func (client *Client) CapturePanicAndWait(f func(), tags map[string]string, interfaces ...Interface) (err interface{}, errorID string) {
- // Note: This doesn't need to check for client, because we still want to go through the defer/recover path
- // Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the
- // *Packet just to be thrown away, this should not be the normal case. Could be refactored to
- // be completely noop though if we cared.
- defer func() {
- var packet *Packet
- err = recover()
- switch rval := err.(type) {
- 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)
- if errorID != "" {
- <-ch
- }
- }()
- f()
- return
- }
- // CapturePanicAndWait is identical to CaptureError, except it blocks and assures that the event was sent
- func CapturePanicAndWait(f func(), tags map[string]string, interfaces ...Interface) (interface{}, string) {
- return DefaultClient.CapturePanicAndWait(f, tags, interfaces...)
- }
- func (client *Client) Close() {
- close(client.queue)
- }
- func Close() { DefaultClient.Close() }
- // Wait blocks and waits for all events to finish being sent to Sentry server
- func (client *Client) Wait() {
- client.wg.Wait()
- }
- // Wait blocks and waits for all events to finish being sent to Sentry server
- func Wait() { DefaultClient.Wait() }
- func (client *Client) URL() string {
- client.mu.RLock()
- defer client.mu.RUnlock()
- return client.url
- }
- func URL() string { return DefaultClient.URL() }
- func (client *Client) ProjectID() string {
- client.mu.RLock()
- defer client.mu.RUnlock()
- return client.projectID
- }
- func ProjectID() string { return DefaultClient.ProjectID() }
- func (client *Client) Release() string {
- client.mu.RLock()
- defer client.mu.RUnlock()
- return client.release
- }
- func Release() string { return DefaultClient.Release() }
- func IncludePaths() []string { return DefaultClient.IncludePaths() }
- func (client *Client) IncludePaths() []string {
- client.mu.RLock()
- defer client.mu.RUnlock()
- return client.includePaths
- }
- func SetIncludePaths(p []string) { DefaultClient.SetIncludePaths(p) }
- func (client *Client) SetIncludePaths(p []string) {
- client.mu.Lock()
- defer client.mu.Unlock()
- client.includePaths = p
- }
- 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) }
- func SetTagsContext(t map[string]string) { DefaultClient.SetTagsContext(t) }
- func ClearContext() { DefaultClient.ClearContext() }
- // HTTPTransport is the default transport, delivering packets to Sentry via the
- // HTTP API.
- type HTTPTransport struct {
- *http.Client
- }
- func (t *HTTPTransport) Send(url, authHeader string, packet *Packet) error {
- if url == "" {
- return nil
- }
- body, contentType, err := serializedPacket(packet)
- if err != nil {
- return fmt.Errorf("error serializing packet: %v", err)
- }
- req, err := http.NewRequest("POST", url, body)
- if err != nil {
- return fmt.Errorf("can't create new request: %v", err)
- }
- req.Header.Set("X-Sentry-Auth", authHeader)
- req.Header.Set("User-Agent", userAgent)
- req.Header.Set("Content-Type", contentType)
- res, err := t.Do(req)
- if err != nil {
- return err
- }
- io.Copy(ioutil.Discard, res.Body)
- res.Body.Close()
- if res.StatusCode != 200 {
- return fmt.Errorf("raven: got http status %d - x-sentry-error: %s", res.StatusCode, res.Header.Get("X-Sentry-Error"))
- }
- return nil
- }
- func serializedPacket(packet *Packet) (io.Reader, string, error) {
- packetJSON, err := packet.JSON()
- if err != nil {
- return nil, "", fmt.Errorf("error marshaling packet %+v to JSON: %v", packet, err)
- }
- // Only deflate/base64 the packet if it is bigger than 1KB, as there is
- // overhead.
- if len(packetJSON) > 1000 {
- buf := &bytes.Buffer{}
- b64 := base64.NewEncoder(base64.StdEncoding, buf)
- deflate, _ := zlib.NewWriterLevel(b64, zlib.BestCompression)
- deflate.Write(packetJSON)
- deflate.Close()
- b64.Close()
- return buf, "application/octet-stream", nil
- }
- return bytes.NewReader(packetJSON), "application/json", nil
- }
- var hostname string
- func init() {
- hostname, _ = os.Hostname()
- }
|